diff --git a/fs.go b/fs.go index 52a4f77..4942ccf 100644 --- a/fs.go +++ b/fs.go @@ -1,6 +1,7 @@ package gemini import ( + "context" "fmt" "io" "io/fs" @@ -31,8 +32,8 @@ type fileServer struct { fs.FS } -func (fs fileServer) ServeGemini(w ResponseWriter, r *Request) { - serveFile(w, r, fs, path.Clean(r.URL.Path), true) +func (fs fileServer) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { + serveFile(ctx, w, r, fs, path.Clean(r.URL.Path), true) } // ServeContent replies to the request using the content in the @@ -41,11 +42,11 @@ func (fs fileServer) ServeGemini(w ResponseWriter, r *Request) { // // ServeContent tries to deduce the type from name's file extension. // The name is otherwise unused; it is never sent in the response. -func ServeContent(w ResponseWriter, r *Request, name string, content io.Reader) { - serveContent(w, name, content) +func ServeContent(ctx context.Context, w ResponseWriter, r *Request, name string, content io.Reader) { + serveContent(ctx, w, name, content) } -func serveContent(w ResponseWriter, name string, content io.Reader) { +func serveContent(ctx context.Context, w ResponseWriter, name string, content io.Reader) { // Detect mimetype from file extension ext := path.Ext(name) mimetype := mime.TypeByExtension(ext) @@ -73,7 +74,7 @@ func serveContent(w ResponseWriter, name string, content io.Reader) { // Outside of those two special cases, ServeFile does not use r.URL.Path for // selecting the file or directory to serve; only the file or directory // provided in the name argument is used. -func ServeFile(w ResponseWriter, r *Request, fsys fs.FS, name string) { +func ServeFile(ctx context.Context, w ResponseWriter, r *Request, fsys fs.FS, name string) { if containsDotDot(r.URL.Path) { // Too many programs use r.URL.Path to construct the argument to // serveFile. Reject the request under the assumption that happened @@ -83,7 +84,7 @@ func ServeFile(w ResponseWriter, r *Request, fsys fs.FS, name string) { w.WriteHeader(StatusBadRequest, "invalid URL path") return } - serveFile(w, r, fsys, name, false) + serveFile(ctx, w, r, fsys, name, false) } func containsDotDot(v string) bool { @@ -100,7 +101,7 @@ func containsDotDot(v string) bool { func isSlashRune(r rune) bool { return r == '/' || r == '\\' } -func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect bool) { +func serveFile(ctx context.Context, w ResponseWriter, r *Request, fsys fs.FS, name string, redirect bool) { const indexPage = "/index.gmi" // Redirect .../index.gmi to .../ @@ -172,7 +173,7 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b return } - serveContent(w, name, f) + serveContent(ctx, w, name, f) } func dirList(w ResponseWriter, f fs.File) { diff --git a/handler.go b/handler.go index 1b553ec..a85ba3c 100644 --- a/handler.go +++ b/handler.go @@ -1,6 +1,7 @@ package gemini import ( + "context" "net/url" "strings" ) @@ -21,17 +22,17 @@ import ( // response but the server doesn't log an error, panic with the value // ErrAbortHandler. type Handler interface { - ServeGemini(ResponseWriter, *Request) + ServeGemini(context.Context, ResponseWriter, *Request) } // The HandlerFunc type is an adapter to allow the use of ordinary functions // as Gemini handlers. If f is a function with the appropriate signature, // HandlerFunc(f) is a Handler that calls f. -type HandlerFunc func(ResponseWriter, *Request) +type HandlerFunc func(context.Context, ResponseWriter, *Request) -// ServeGemini calls f(w, r). -func (f HandlerFunc) ServeGemini(w ResponseWriter, r *Request) { - f(w, r) +// ServeGemini calls f(ctx, w, r). +func (f HandlerFunc) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { + f(ctx, w, r) } // RedirectHandler returns a request handler that redirects each request it @@ -48,12 +49,12 @@ type redirectHandler struct { url string } -func (h *redirectHandler) ServeGemini(w ResponseWriter, r *Request) { +func (h *redirectHandler) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { w.WriteHeader(h.code, h.url) } // NotFound replies to the request with a Gemini 51 not found error. -func NotFound(w ResponseWriter, r *Request) { +func NotFound(ctx context.Context, w ResponseWriter, r *Request) { w.WriteHeader(StatusNotFound, "Not found") } @@ -73,7 +74,7 @@ func StripPrefix(prefix string, h Handler) Handler { if prefix == "" { return h } - return HandlerFunc(func(w ResponseWriter, r *Request) { + return HandlerFunc(func(ctx context.Context, w ResponseWriter, r *Request) { p := strings.TrimPrefix(r.URL.Path, prefix) rp := strings.TrimPrefix(r.URL.RawPath, prefix) if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) { @@ -83,9 +84,9 @@ func StripPrefix(prefix string, h Handler) Handler { *r2.URL = *r.URL r2.URL.Path = p r2.URL.RawPath = rp - h.ServeGemini(w, r2) + h.ServeGemini(ctx, w, r2) } else { - NotFound(w, r) + NotFound(ctx, w, r) } }) } diff --git a/mux.go b/mux.go index 3eec7f5..1d45d47 100644 --- a/mux.go +++ b/mux.go @@ -1,6 +1,7 @@ package gemini import ( + "context" "net" "net/url" "path" @@ -211,9 +212,9 @@ func (mux *ServeMux) Handler(r *Request) Handler { // ServeGemini dispatches the request to the handler whose // pattern most closely matches the request URL. -func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request) { +func (mux *ServeMux) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { h := mux.Handler(r) - h.ServeGemini(w, r) + h.ServeGemini(ctx, w, r) } // Handle registers the handler for the given pattern. @@ -293,7 +294,7 @@ func appendSorted(es []muxEntry, e muxEntry) []muxEntry { } // HandleFunc registers the handler function for the given pattern. -func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { +func (mux *ServeMux) HandleFunc(pattern string, handler func(context.Context, ResponseWriter, *Request)) { if handler == nil { panic("gemini: nil handler") } diff --git a/mux_test.go b/mux_test.go index f8dd988..858c1ba 100644 --- a/mux_test.go +++ b/mux_test.go @@ -1,13 +1,14 @@ package gemini import ( + "context" "net/url" "testing" ) type nopHandler struct{} -func (*nopHandler) ServeGemini(ResponseWriter, *Request) {} +func (*nopHandler) ServeGemini(context.Context, ResponseWriter, *Request) {} func TestServeMuxMatch(t *testing.T) { type Match struct { diff --git a/server.go b/server.go index def224a..c6c33f1 100644 --- a/server.go +++ b/server.go @@ -303,7 +303,9 @@ func (srv *Server) respond(conn net.Conn) { return } - h.ServeGemini(w, req) + // TODO: Allow configuring the server context + ctx := context.Background() + h.ServeGemini(ctx, w, req) w.Flush() } diff --git a/timeout.go b/timeout.go index 14b7860..0a48d70 100644 --- a/timeout.go +++ b/timeout.go @@ -27,7 +27,7 @@ type timeoutHandler struct { dt time.Duration } -func (t *timeoutHandler) ServeGemini(w ResponseWriter, r *Request) { +func (t *timeoutHandler) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { ctx, cancel := context.WithTimeout(context.TODO(), t.dt) defer cancel() @@ -40,7 +40,7 @@ func (t *timeoutHandler) ServeGemini(w ResponseWriter, r *Request) { panicChan <- p } }() - t.h.ServeGemini(tw, r) + t.h.ServeGemini(ctx, tw, r) close(done) }()