diff --git a/fs.go b/fs.go index 4ef515c..2ee6a10 100644 --- a/fs.go +++ b/fs.go @@ -33,7 +33,7 @@ type fileServer struct { fs.FS } -func (fs fileServer) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { +func (fs fileServer) ServeGemini(ctx context.Context, w *ResponseWriter, r *Request) { serveFile(w, r, fs, path.Clean(r.URL.Path), true) } @@ -43,11 +43,11 @@ func (fs fileServer) ServeGemini(ctx context.Context, w ResponseWriter, r *Reque // // 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) { +func ServeContent(w *ResponseWriter, r *Request, name string, content io.Reader) { serveContent(w, name, content) } -func serveContent(w ResponseWriter, name string, content io.Reader) { +func serveContent(w *ResponseWriter, name string, content io.Reader) { // Detect mimetype from file extension ext := path.Ext(name) mimetype := mime.TypeByExtension(ext) @@ -75,7 +75,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(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 @@ -102,7 +102,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(w *ResponseWriter, r *Request, fsys fs.FS, name string, redirect bool) { const indexPage = "/index.gmi" // Redirect .../index.gmi to .../ @@ -177,7 +177,7 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b serveContent(w, name, f) } -func dirList(w ResponseWriter, f fs.File) { +func dirList(w *ResponseWriter, f fs.File) { var entries []fs.DirEntry var err error d, ok := f.(fs.ReadDirFile) diff --git a/handler.go b/handler.go index c8cd13f..757566a 100644 --- a/handler.go +++ b/handler.go @@ -19,23 +19,23 @@ import ( // // Handlers should not modify the provided Request. type Handler interface { - ServeGemini(context.Context, 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(context.Context, ResponseWriter, *Request) +type HandlerFunc func(context.Context, *ResponseWriter, *Request) // ServeGemini calls f(ctx, w, r). -func (f HandlerFunc) ServeGemini(ctx context.Context, w ResponseWriter, r *Request) { +func (f HandlerFunc) ServeGemini(ctx context.Context, w *ResponseWriter, r *Request) { f(ctx, w, r) } // StatusHandler returns a request handler that responds to each request // with the provided status code and meta. func StatusHandler(status Status, meta string) Handler { - return HandlerFunc(func(ctx context.Context, w ResponseWriter, r *Request) { + return HandlerFunc(func(ctx context.Context, w *ResponseWriter, r *Request) { w.WriteHeader(status, meta) }) } @@ -56,7 +56,7 @@ func StripPrefix(prefix string, h Handler) Handler { if prefix == "" { return h } - return HandlerFunc(func(ctx context.Context, 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)) { diff --git a/mux.go b/mux.go index e4ad726..c3d575b 100644 --- a/mux.go +++ b/mux.go @@ -212,7 +212,7 @@ 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(ctx context.Context, w ResponseWriter, r *Request) { +func (mux *ServeMux) ServeGemini(ctx context.Context, w *ResponseWriter, r *Request) { h := mux.Handler(r) h.ServeGemini(ctx, w, r) } diff --git a/mux_test.go b/mux_test.go index 858c1ba..0ee7b82 100644 --- a/mux_test.go +++ b/mux_test.go @@ -8,7 +8,7 @@ import ( type nopHandler struct{} -func (*nopHandler) ServeGemini(context.Context, ResponseWriter, *Request) {} +func (*nopHandler) ServeGemini(context.Context, *ResponseWriter, *Request) {} func TestServeMuxMatch(t *testing.T) { type Match struct { diff --git a/response.go b/response.go index d169e17..8c7febc 100644 --- a/response.go +++ b/response.go @@ -157,44 +157,7 @@ func (r *Response) TLS() *tls.ConnectionState { // // A ResponseWriter may not be used after the Handler.ServeGemini method // has returned. -type ResponseWriter interface { - // SetMediaType sets the media type that will be sent by Write for a - // successful response. If no media type is set, a default of - // "text/gemini; charset=utf-8" will be used. - // - // Setting the media type after a call to Write or WriteHeader has - // no effect. - SetMediaType(string) - - // Write writes the data to the connection as part of a Gemini response. - // - // If WriteHeader has not yet been called, Write calls WriteHeader with - // StatusSuccess and the media type set in SetMediaType before writing the data. - // If no media type was set, Write uses a default media type of - // "text/gemini; charset=utf-8". - Write([]byte) (int, error) - - // WriteHeader sends a Gemini response header with the provided - // status code and meta. - // - // If WriteHeader is not called explicitly, the first call to Write - // will trigger an implicit call to WriteHeader with a successful - // status code and the media type set in SetMediaType. - // - // The provided code must be a valid Gemini status code. - // The provided meta must not be longer than 1024 bytes. - // Only one header may be written. - WriteHeader(status Status, meta string) - - // Flush sends any buffered data to the client. - Flush() error - - // Close closes the connection. - // Any blocked Read or Write operations will be unblocked and return errors. - Close() error -} - -type responseWriter struct { +type ResponseWriter struct { b *bufio.Writer closer io.Closer mediatype string @@ -203,22 +166,30 @@ type responseWriter struct { } // NewResponseWriter returns a ResponseWriter that uses the provided io.WriteCloser. -func NewResponseWriter(wc io.WriteCloser) ResponseWriter { - return newResponseWriter(wc) -} - -func newResponseWriter(wc io.WriteCloser) *responseWriter { - return &responseWriter{ +func NewResponseWriter(wc io.WriteCloser) *ResponseWriter { + return &ResponseWriter{ b: bufio.NewWriter(wc), closer: wc, } } -func (w *responseWriter) SetMediaType(mediatype string) { +// SetMediaType sets the media type that will be sent by Write for a +// successful response. If no media type is set, a default of +// "text/gemini; charset=utf-8" will be used. +// +// Setting the media type after a call to Write or WriteHeader has +// no effect. +func (w *ResponseWriter) SetMediaType(mediatype string) { w.mediatype = mediatype } -func (w *responseWriter) Write(b []byte) (int, error) { +// Write writes the data to the connection as part of a Gemini response. +// +// If WriteHeader has not yet been called, Write calls WriteHeader with +// StatusSuccess and the media type set in SetMediaType before writing the data. +// If no media type was set, Write uses a default media type of +// "text/gemini; charset=utf-8". +func (w *ResponseWriter) Write(b []byte) (int, error) { if !w.wroteHeader { meta := w.mediatype if meta == "" { @@ -233,7 +204,17 @@ func (w *responseWriter) Write(b []byte) (int, error) { return w.b.Write(b) } -func (w *responseWriter) WriteHeader(status Status, meta string) { +// WriteHeader sends a Gemini response header with the provided +// status code and meta. +// +// If WriteHeader is not called explicitly, the first call to Write +// will trigger an implicit call to WriteHeader with a successful +// status code and the media type set in SetMediaType. +// +// The provided code must be a valid Gemini status code. +// The provided meta must not be longer than 1024 bytes. +// Only one header may be written. +func (w *ResponseWriter) WriteHeader(status Status, meta string) { if w.wroteHeader { return } @@ -249,7 +230,8 @@ func (w *responseWriter) WriteHeader(status Status, meta string) { w.wroteHeader = true } -func (w *responseWriter) Flush() error { +// Flush sends any buffered data to the client. +func (w *ResponseWriter) Flush() error { if !w.wroteHeader { w.WriteHeader(StatusTemporaryFailure, "Temporary failure") } @@ -257,6 +239,8 @@ func (w *responseWriter) Flush() error { return w.b.Flush() } -func (w *responseWriter) Close() error { +// Close closes the connection. +// Any blocked Write operations will be unblocked and return errors. +func (w *ResponseWriter) Close() error { return w.closer.Close() } diff --git a/server.go b/server.go index fb8f287..961203b 100644 --- a/server.go +++ b/server.go @@ -359,7 +359,7 @@ func (srv *Server) serveConn(ctx context.Context, conn net.Conn) error { rc: conn, } - w := newResponseWriter(cw) + w := NewResponseWriter(cw) req, err := ReadRequest(r) if err != nil { diff --git a/timeout.go b/timeout.go index 20b270c..17c78e4 100644 --- a/timeout.go +++ b/timeout.go @@ -1,3 +1,5 @@ +// +build ignore + package gemini import (