summaryrefslogtreecommitdiffstats
path: root/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'server.go')
-rw-r--r--server.go257
1 files changed, 257 insertions, 0 deletions
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()
+}