diff --git a/gemini.go b/gemini.go index 1adf284..941969d 100644 --- a/gemini.go +++ b/gemini.go @@ -6,13 +6,9 @@ import ( "crypto/tls" "errors" "io/ioutil" - "log" "net" "net/url" - "sort" "strconv" - "strings" - "time" ) // Status codes. @@ -238,255 +234,3 @@ func Do(req *Request) (*Response, error) { TLS: conn.ConnectionState(), }, nil } - -// Server is a Gemini server. -type Server struct { - // Addr specifies the address that the server should listen on. - // If Addr is empty, the server will listen on the address ":1965". - Addr string - - // Certificate provides a TLS certificate for use by the server. - // Using a self-signed certificate is recommended. - Certificate tls.Certificate - - // Handler specifies the Handler for requests. - // If Handler is not set, the server will error. - Handler Handler -} - -// ListenAndServe listens for requests at the server's configured address. -func (s *Server) ListenAndServe() error { - addr := s.Addr - - if addr == "" { - addr = ":1965" - } - - ln, err := net.Listen("tcp", addr) - if err != nil { - return err - } - defer ln.Close() - - config := &tls.Config{ - InsecureSkipVerify: true, - Certificates: []tls.Certificate{s.Certificate}, - ClientAuth: tls.RequestClientCert, - } - tlsListener := tls.NewListener(ln, config) - return s.Serve(tlsListener) -} - -// Serve listens for requests on the provided listener. -func (s *Server) Serve(l net.Listener) error { - var tempDelay time.Duration // how long to sleep on accept failure - - for { - rw, err := l.Accept() - if err != nil { - // If this is a temporary error, sleep - if ne, ok := err.(net.Error); ok && ne.Temporary() { - if tempDelay == 0 { - tempDelay = 5 * time.Millisecond - } else { - tempDelay *= 2 - } - if max := 1 * time.Second; tempDelay > max { - tempDelay = max - } - log.Printf("gemini: Accept error: %v; retrying in %v", err, tempDelay) - time.Sleep(tempDelay) - continue - } - - // Otherwise, return the error - return err - } - - tempDelay = 0 - go s.respond(rw) - } -} - -// ResponseWriter is used by a Gemini handler to construct a Gemini response. -type ResponseWriter struct { - w *bufio.Writer - bodyAllowed bool -} - -func newResponseWriter(conn net.Conn) *ResponseWriter { - return &ResponseWriter{ - w: bufio.NewWriter(conn), - } -} - -// WriteHeader writes the response header. -// -// Meta contains more information related to the response status. -// For successful responses, Meta should contain the mimetype of the response. -// For failure responses, Meta should contain a short description of the failure. -// Meta should not be longer than 1024 bytes. -func (r *ResponseWriter) WriteHeader(status int, meta string) { - r.w.WriteString(strconv.Itoa(status)) - r.w.WriteByte(' ') - r.w.WriteString(meta) - r.w.Write(crlf) - - // Only allow body to be written on successful status codes. - if status/10 == StatusClassSuccess { - r.bodyAllowed = true - } -} - -// Write writes the response body. -// If the response status does not allow for a response body, Write returns -// ErrBodyNotAllowed. -// WriteHeader must be called before Write. -func (r *ResponseWriter) Write(b []byte) (int, error) { - if !r.bodyAllowed { - return 0, ErrBodyNotAllowed - } - return r.w.Write(b) -} - -// respond responds to a connection. -func (s *Server) respond(conn net.Conn) { - r := bufio.NewReader(conn) - rw := newResponseWriter(conn) - // Read requested URL - rawurl, err := r.ReadString('\r') - if err != nil { - return - } - // Read terminating line feed - if b, err := r.ReadByte(); err != nil { - return - } else if b != '\n' { - rw.WriteHeader(StatusBadRequest, "Bad request") - } - // Trim carriage return - rawurl = rawurl[:len(rawurl)-1] - // Ensure URL is valid - if len(rawurl) > 1024 { - rw.WriteHeader(StatusBadRequest, "Requested URL exceeds 1024 bytes") - } else if url, err := url.Parse(rawurl); err != nil || url.User != nil { - // Note that we return an error status if User is specified in the URL - rw.WriteHeader(StatusBadRequest, "Requested URL is invalid") - } else { - // Gather information about the request - req := &Request{ - URL: url, - RemoteAddr: conn.RemoteAddr(), - TLS: conn.(*tls.Conn).ConnectionState(), - } - s.Handler.Serve(rw, req) - } - rw.w.Flush() - conn.Close() -} - -// A Handler responds to a Gemini request. -type Handler interface { - // Serve accepts a Request and constructs a Response. - Serve(*ResponseWriter, *Request) -} - -// ServeMux is a Gemini request multiplexer. -// It matches the URL of each incoming request against a list of registered -// patterns and calls the handler for the pattern that most closesly matches -// the URL. -type ServeMux struct { - entries []muxEntry -} - -type muxEntry struct { - u *url.URL - handler Handler -} - -func (m *ServeMux) match(url *url.URL) Handler { - for _, e := range m.entries { - if (e.u.Scheme == "" || url.Scheme == e.u.Scheme) && - (e.u.Host == "" || url.Host == e.u.Host) && - strings.HasPrefix(url.Path, e.u.Path) { - return e.handler - } - } - return nil -} - -// Handle registers a Handler for the given pattern. -func (m *ServeMux) Handle(pattern string, handler Handler) { - url, err := url.Parse(pattern) - if err != nil { - panic(err) - } - e := muxEntry{ - url, - handler, - } - m.entries = appendSorted(m.entries, e) -} - -// HandleFunc registers a HandlerFunc for the given pattern. -func (m *ServeMux) HandleFunc(pattern string, handlerFunc func(*ResponseWriter, *Request)) { - handler := HandlerFunc(handlerFunc) - m.Handle(pattern, handler) -} - -// Serve responds to the request with the appropriate handler. -func (m *ServeMux) Serve(rw *ResponseWriter, req *Request) { - h := m.match(req.URL) - if h == nil { - rw.WriteHeader(StatusNotFound, "Not found") - return - } - h.Serve(rw, req) -} - -// appendSorted appends the entry e in the proper place in entries. -func appendSorted(es []muxEntry, e muxEntry) []muxEntry { - n := len(es) - // sort by length - i := sort.Search(n, func(i int) bool { - // Sort entries by length. - // - Entries with a scheme take preference over entries without. - // - Entries with a host take preference over entries without. - // - Longer paths take preference over shorter paths. - // - // Long version: - // if es[i].scheme != "" { - // if e.scheme == "" { - // return false - // } - // return len(es[i].scheme) < len(e.scheme) - // } - // if es[i].host != "" { - // if e.host == "" { - // return false - // } - // return len(es[i].host) < len(e.host) - // } - // return len(es[i].path) < len(e.path) - - // Condensed version: - return (es[i].u.Scheme == "" || (e.u.Scheme != "" && len(es[i].u.Scheme) < len(e.u.Scheme))) && - (es[i].u.Host == "" || (e.u.Host != "" && len(es[i].u.Host) < len(e.u.Host))) && - len(es[i].u.Path) < len(e.u.Path) - }) - if i == n { - return append(es, e) - } - // we now know that i points at where we want to insert - es = append(es, muxEntry{}) // try to grow the slice in place, any entry works. - copy(es[i+1:], es[i:]) // Move shorter entries down - es[i] = e - return es -} - -// A wrapper around a bare function that implements Handler. -type HandlerFunc func(*ResponseWriter, *Request) - -func (f HandlerFunc) Serve(rw *ResponseWriter, req *Request) { - f(rw, req) -} diff --git a/server.go b/server.go new file mode 100644 index 0000000..cb3782a --- /dev/null +++ b/server.go @@ -0,0 +1,265 @@ +package gemini + +import ( + "bufio" + "crypto/tls" + "log" + "net" + "net/url" + "sort" + "strconv" + "strings" + "time" +) + +// Server is a Gemini server. +type Server struct { + // Addr specifies the address that the server should listen on. + // If Addr is empty, the server will listen on the address ":1965". + Addr string + + // Certificate provides a TLS certificate for use by the server. + // Using a self-signed certificate is recommended. + Certificate tls.Certificate + + // Handler specifies the Handler for requests. + // If Handler is not set, the server will error. + Handler Handler +} + +// ListenAndServe listens for requests at the server's configured address. +func (s *Server) ListenAndServe() error { + addr := s.Addr + + if addr == "" { + addr = ":1965" + } + + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + defer ln.Close() + + config := &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{s.Certificate}, + ClientAuth: tls.RequestClientCert, + } + tlsListener := tls.NewListener(ln, config) + return s.Serve(tlsListener) +} + +// Serve listens for requests on the provided listener. +func (s *Server) Serve(l net.Listener) error { + var tempDelay time.Duration // how long to sleep on accept failure + + for { + rw, err := l.Accept() + if err != nil { + // If this is a temporary error, sleep + if ne, ok := err.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + log.Printf("gemini: Accept error: %v; retrying in %v", err, tempDelay) + time.Sleep(tempDelay) + continue + } + + // Otherwise, return the error + return err + } + + tempDelay = 0 + go s.respond(rw) + } +} + +// ResponseWriter is used by a Gemini handler to construct a Gemini response. +type ResponseWriter struct { + w *bufio.Writer + bodyAllowed bool +} + +func newResponseWriter(conn net.Conn) *ResponseWriter { + return &ResponseWriter{ + w: bufio.NewWriter(conn), + } +} + +// WriteHeader writes the response header. +// +// Meta contains more information related to the response status. +// For successful responses, Meta should contain the mimetype of the response. +// For failure responses, Meta should contain a short description of the failure. +// Meta should not be longer than 1024 bytes. +func (r *ResponseWriter) WriteHeader(status int, meta string) { + r.w.WriteString(strconv.Itoa(status)) + r.w.WriteByte(' ') + r.w.WriteString(meta) + r.w.Write(crlf) + + // Only allow body to be written on successful status codes. + if status/10 == StatusClassSuccess { + r.bodyAllowed = true + } +} + +// Write writes the response body. +// If the response status does not allow for a response body, Write returns +// ErrBodyNotAllowed. +// WriteHeader must be called before Write. +func (r *ResponseWriter) Write(b []byte) (int, error) { + if !r.bodyAllowed { + return 0, ErrBodyNotAllowed + } + return r.w.Write(b) +} + +// respond responds to a connection. +func (s *Server) respond(conn net.Conn) { + r := bufio.NewReader(conn) + rw := newResponseWriter(conn) + // Read requested URL + rawurl, err := r.ReadString('\r') + if err != nil { + return + } + // Read terminating line feed + if b, err := r.ReadByte(); err != nil { + return + } else if b != '\n' { + rw.WriteHeader(StatusBadRequest, "Bad request") + } + // Trim carriage return + rawurl = rawurl[:len(rawurl)-1] + // Ensure URL is valid + if len(rawurl) > 1024 { + rw.WriteHeader(StatusBadRequest, "Requested URL exceeds 1024 bytes") + } else if url, err := url.Parse(rawurl); err != nil || url.User != nil { + // Note that we return an error status if User is specified in the URL + rw.WriteHeader(StatusBadRequest, "Requested URL is invalid") + } else { + // Gather information about the request + req := &Request{ + URL: url, + RemoteAddr: conn.RemoteAddr(), + TLS: conn.(*tls.Conn).ConnectionState(), + } + s.Handler.Serve(rw, req) + } + rw.w.Flush() + conn.Close() +} + +// A Handler responds to a Gemini request. +type Handler interface { + // Serve accepts a Request and constructs a Response. + Serve(*ResponseWriter, *Request) +} + +// ServeMux is a Gemini request multiplexer. +// It matches the URL of each incoming request against a list of registered +// patterns and calls the handler for the pattern that most closesly matches +// the URL. +type ServeMux struct { + entries []muxEntry +} + +type muxEntry struct { + u *url.URL + handler Handler +} + +func (m *ServeMux) match(url *url.URL) Handler { + for _, e := range m.entries { + if (e.u.Scheme == "" || url.Scheme == e.u.Scheme) && + (e.u.Host == "" || url.Host == e.u.Host) && + strings.HasPrefix(url.Path, e.u.Path) { + return e.handler + } + } + return nil +} + +// Handle registers a Handler for the given pattern. +func (m *ServeMux) Handle(pattern string, handler Handler) { + url, err := url.Parse(pattern) + if err != nil { + panic(err) + } + e := muxEntry{ + url, + handler, + } + m.entries = appendSorted(m.entries, e) +} + +// HandleFunc registers a HandlerFunc for the given pattern. +func (m *ServeMux) HandleFunc(pattern string, handlerFunc func(*ResponseWriter, *Request)) { + handler := HandlerFunc(handlerFunc) + m.Handle(pattern, handler) +} + +// Serve responds to the request with the appropriate handler. +func (m *ServeMux) Serve(rw *ResponseWriter, req *Request) { + h := m.match(req.URL) + if h == nil { + rw.WriteHeader(StatusNotFound, "Not found") + return + } + h.Serve(rw, req) +} + +// appendSorted appends the entry e in the proper place in entries. +func appendSorted(es []muxEntry, e muxEntry) []muxEntry { + n := len(es) + // sort by length + i := sort.Search(n, func(i int) bool { + // Sort entries by length. + // - Entries with a scheme take preference over entries without. + // - Entries with a host take preference over entries without. + // - Longer paths take preference over shorter paths. + // + // Long version: + // if es[i].scheme != "" { + // if e.scheme == "" { + // return false + // } + // return len(es[i].scheme) < len(e.scheme) + // } + // if es[i].host != "" { + // if e.host == "" { + // return false + // } + // return len(es[i].host) < len(e.host) + // } + // return len(es[i].path) < len(e.path) + + // Condensed version: + return (es[i].u.Scheme == "" || (e.u.Scheme != "" && len(es[i].u.Scheme) < len(e.u.Scheme))) && + (es[i].u.Host == "" || (e.u.Host != "" && len(es[i].u.Host) < len(e.u.Host))) && + len(es[i].u.Path) < len(e.u.Path) + }) + if i == n { + return append(es, e) + } + // we now know that i points at where we want to insert + es = append(es, muxEntry{}) // try to grow the slice in place, any entry works. + copy(es[i+1:], es[i:]) // Move shorter entries down + es[i] = e + return es +} + +// A wrapper around a bare function that implements Handler. +type HandlerFunc func(*ResponseWriter, *Request) + +func (f HandlerFunc) Serve(rw *ResponseWriter, req *Request) { + f(rw, req) +}