diff options
| author | adnano <[email protected]> | 2020-09-21 15:48:42 -0400 |
|---|---|---|
| committer | adnano <[email protected]> | 2020-09-21 15:49:09 -0400 |
| commit | 257b8076756fa25e3b07d6d85e76874d1fe33268 (patch) | |
| tree | 5cf003942047b7a4d25e8a16bdca3e856f600bd4 /server.go | |
| download | go-gemini-257b8076756fa25e3b07d6d85e76874d1fe33268.tar.xz go-gemini-257b8076756fa25e3b07d6d85e76874d1fe33268.zip | |
Initial commit
Diffstat (limited to 'server.go')
| -rw-r--r-- | server.go | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/server.go b/server.go new file mode 100644 index 0000000..24c6a15 --- /dev/null +++ b/server.go @@ -0,0 +1,185 @@ +package gemini + +import ( + "bufio" + "crypto/tls" + "io" + "net" + "net/url" + "strconv" + "strings" +) + +// Status codes. +const ( + StatusInput = 10 + StatusSensitiveInput = 11 + StatusSuccess = 20 + StatusRedirectTemporary = 30 + StatusRedirectPermanent = 31 + StatusTemporaryFailure = 40 + StatusServerUnavailable = 41 + StatusCGIError = 42 + StatusProxyError = 43 + StatusSlowDown = 44 + StatusPermanentFailure = 50 + StatusNotFound = 51 + StatusGone = 52 + StatusProxyRequestRefused = 53 + StatusBadRequest = 59 + StatusClientCertificateRequired = 60 + StatusCertificateNotAuthorised = 61 + StatusCertificateNotValid = 62 +) + +// Status code categories. +const ( + StatusClassInput = 1 + StatusClassSuccess = 2 + StatusClassRedirect = 3 + StatusClassTemporaryFailure = 4 + StatusClassPermanentFailure = 5 + StatusClassClientCertificateRequired = 6 +) + +// Response is a Gemini response. +type Response struct { + Status int + Meta string + Body []byte +} + +// Write writes the Response to the provided io.Writer. +func (r *Response) Write(w io.Writer) { + header := strconv.Itoa(r.Status) + " " + r.Meta + "\r\n" + w.Write([]byte(header)) + + // Only write response body on success + if r.Status/10 == StatusClassSuccess { + w.Write(r.Body) + } +} + +// Server is a Gemini server. +type Server struct { + Addr string + TLSConfig *tls.Config + Handler Handler +} + +// ListenAndServer listens on the given address and serves. +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() + + tlsListener := tls.NewListener(ln, s.TLSConfig) + return s.Serve(tlsListener) +} + +// Serve listens for requests on the provided listener. +func (s *Server) Serve(ln net.Listener) error { + for { + rw, err := ln.Accept() + if err != nil { + return err + } + + req, err := readLine(rw) + if err != nil { + continue + } + url, err := url.Parse(req) + if err != nil { + continue + } + resp := s.Handler.Serve(url) + resp.Write(rw) + rw.Close() + } +} + +// Handler handles a url with a response. +type Handler interface { + // Serve accepts a url, as that is the only information that the client + // provides in a request. + Serve(*url.URL) *Response +} + +// Mux 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 Mux struct { + entries []muxEntry +} + +type muxEntry struct { + scheme string + host string + path string + handler Handler +} + +func (m *Mux) match(url *url.URL) Handler { + for _, e := range m.entries { + if (e.scheme == "" || url.Scheme == e.scheme) && + (e.host == "" || url.Host == e.host) && + strings.HasPrefix(url.Path, e.path) { + return e.handler + } + } + return nil +} + +func (m *Mux) Handle(pattern string, handler Handler) { + url, err := url.Parse(pattern) + if err != nil { + panic(err) + } + m.entries = append(m.entries, muxEntry{ + url.Scheme, + url.Host, + url.Path, + handler, + }) +} + +func (m *Mux) HandleFunc(pattern string, handlerFunc func(url *url.URL) *Response) { + handler := HandlerFunc(handlerFunc) + m.Handle(pattern, handler) +} + +func (m *Mux) Serve(url *url.URL) *Response { + h := m.match(url) + if h == nil { + return &Response{ + Status: StatusNotFound, + Meta: "Not found", + } + } + return h.Serve(url) +} + +type HandlerFunc func(url *url.URL) *Response + +func (f HandlerFunc) Serve(url *url.URL) *Response { + return f(url) +} + +// readLine reads a line. +func readLine(r io.Reader) (string, error) { + scanner := bufio.NewScanner(r) + scanner.Scan() + if err := scanner.Err(); err != nil { + return "", err + } + return scanner.Text(), nil +} |