From da77deba78c8a7447b4a38324d2422a5df293b26 Mon Sep 17 00:00:00 2001 From: clsr Date: Fri, 18 Aug 2017 13:46:10 +0200 Subject: Initial commit --- server.go | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 server.go (limited to 'server.go') diff --git a/server.go b/server.go new file mode 100644 index 0000000..3413923 --- /dev/null +++ b/server.go @@ -0,0 +1,257 @@ +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 with errors. + Validate bool + + sock net.Conn +} + +// NewServer creates a new Server with default access and errors logs and sets +// the listen address 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) + req.Body = io.LimitReader(req.Body, req.Length()) + if err != nil { + if e, ok := err.(Error); ok { + resp, _ := NewResponse(IntentError, nil) + resp.SetParam("reason", e.CNPError()) + resp.Write(conn) + return + } + panic(err) + } + + 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() +} -- cgit