package cnp import ( "bytes" "errors" "io" "log" "net" "os" "strconv" "strings" "time" ) // TODO: make more modular and extensible like net/http // Server represents a CNP server. type Server struct { // LogAccess is called for every finished response if it's non-nil. LogAccess func(resp ResponseWriter, req *Request, respIntent string, respBytes int64) // LogError is called when an error happens if it's non-nil. LogError func(err interface{}) // AccessLog is used to log finished responses if LogAccess is nil. AccessLogger *log.Logger // ErrorLogger is used to log errors if LogError is nil. ErrorLogger *log.Logger // Address is the host:port that the server listens on. Address string // Handler is used to handle received requests. Handler Handler // Validate enables request parameter value validation; invalid requests // are responded to with errors. Validate bool sock net.Conn } // NewServer creates a new Server with default access and errors loggers, // validation enabled and the listen address set to "localhost". func NewServer() *Server { return &Server{ AccessLogger: log.New(os.Stdout, "", 0), ErrorLogger: log.New(os.Stderr, "error: ", log.LstdFlags), Validate: true, Address: "localhost", } } // ListenAndServe uses net.Listen to listen on TCP srv.Address for new // requests, then calls srv.Serve. func (srv *Server) ListenAndServe() error { addr := srv.Address if strings.LastIndexByte(addr, ':') <= strings.LastIndexByte(addr, ']') || strings.Count(addr, ":") > 1 && !strings.HasPrefix(addr, "[") { // missing/default port addr = net.JoinHostPort(addr, strconv.Itoa(DefaultPort)) } l, err := net.Listen("tcp", addr) if err != nil { return err } return srv.Serve(l) } // Serve listens on l for new connections and dispatches HandleConn goroutines. func (srv *Server) Serve(l net.Listener) error { defer l.Close() for { conn, err := l.Accept() if err != nil { return err } go srv.HandleConn(conn) } } func (srv *Server) sendError(conn net.Conn, req *Request, err Error) { er, _ := NewResponse(IntentError, []byte(err.Error()+"\n")) er.SetParam("reason", err.CNPError()) er.SetParam("type", "text/plain") var buf bytes.Buffer er.Write(&buf) er.SetLength(int64(buf.Len())) l, e2 := io.Copy(conn, &buf) if e2 != nil { srv.logError(e2) } srv.logAccess(&responseWriter{addr: conn.RemoteAddr()}, req, er.Intent(), l) } // HandleConn reads a CNP request from conn and runs a handler to respond. func (srv *Server) HandleConn(conn net.Conn) { var rw *responseWriter var req *Request defer func() { /*_, err := io.Copy(ioutil.Discard, req.Body) if err != nil { srv.ErrorLog.Print(err) }*/ if rw != nil && rw.headerWritten { srv.logAccess(rw, req, rw.resp.Header.Intent, rw.n) } if rec := recover(); rec != nil { srv.logError(rec) if err, ok := rec.(Error); ok && rw != nil && !rw.headerWritten { srv.sendError(conn, req, err) } } if req != nil { req.Close() } }() req, err := ParseRequest(conn) if err != nil { if e, ok := err.(Error); ok { resp, _ := NewResponse(IntentError, nil) resp.SetParam("reason", e.CNPError()) resp.Write(conn) return } panic(err) } req.Body = io.LimitReader(req.Body, req.Length()) if srv.Validate { err = req.Validate() if err != nil { srv.sendError(conn, req, err.(Error)) return } } if srv.Handler != nil { resp, _ := NewResponse(IntentOK, nil) rw = &responseWriter{ w: conn, resp: resp, addr: conn.RemoteAddr(), } srv.Handler.ServeCNP(rw, req) if !rw.headerWritten { rw.WriteHeader() } } } func (srv *Server) logAccess(resp ResponseWriter, req *Request, respIntent string, respBytes int64) { if srv.LogAccess != nil { srv.LogAccess(resp, req, respIntent, respBytes) } else if srv.AccessLogger != nil { srv.AccessLogger.Printf("%s - - %s %q %s %d", resp.RemoteAddr(), time.Now().Format("[02/Jan/2006:03:04:05 -0700]"), req.Header.Version()+" "+string(Escape(req.Host()))+string(Escape(req.Path())), respIntent, respBytes, ) } } func (srv *Server) logError(err interface{}) { if srv.LogError != nil { srv.LogError(err) } else if srv.ErrorLogger != nil { srv.ErrorLogger.Println(err) } } // Handler handles CNP requests accepted by the server. type Handler interface { // ServeCNP responds to a CNP request. // // This function must be safe for concurrent use. ServeCNP(resp ResponseWriter, req *Request) } // HandlerFunc allows using raw functions as handlers. type HandlerFunc func(resp ResponseWriter, req *Request) // ServeCNP calls h(resp, req). func (h HandlerFunc) ServeCNP(resp ResponseWriter, req *Request) { h(resp, req) } // ResponseWriter is used by a CNP server to write responses to CNP requests. type ResponseWriter interface { // Response returns a Response object whose header will be written to the // socket. The body is nil and should be ignored. Response() *Response // RemoteAddr returns the network address of the client. RemoteAddr() net.Addr // WriteHeader sends a CNP header with an intent. WriteHeader() error // Write sends data in the CNP response body. // // If WriteHeader has not been called yet, it also calls WriteHeader("ok"). Write(data []byte) (int, error) } type responseWriter struct { w io.Writer addr net.Addr resp *Response srv *Server n int64 headerWritten bool } func (r *responseWriter) Response() *Response { return r.resp } func (r *responseWriter) RemoteAddr() net.Addr { return r.addr } func (r *responseWriter) WriteHeader() error { if r.headerWritten { err := errors.New("multiple WriteHeader calls") r.srv.logError(err) return err } r.headerWritten = true return r.resp.Header.Write(r) } func (r *responseWriter) Write(data []byte) (int, error) { if !r.headerWritten { r.WriteHeader() } n, err := r.w.Write(data) r.n += int64(n) return n, err } // ListenAndServe creates a new Server with a listen address and a handler and // calls its ListenAndServe method. func ListenAndServe(addr string, handler Handler) error { srv := NewServer() srv.Address = addr srv.Handler = handler return srv.ListenAndServe() }