Redesign ResponseWriter interface
This commit is contained in:
		
							parent
							
								
									8543eca416
								
							
						
					
					
						commit
						110c2de6de
					
				
							
								
								
									
										18
									
								
								fs.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								fs.go
									
									
									
									
									
								
							@ -49,7 +49,7 @@ func serveContent(w ResponseWriter, name string, content io.Reader) {
 | 
			
		||||
	// Detect mimetype from file extension
 | 
			
		||||
	ext := path.Ext(name)
 | 
			
		||||
	mimetype := mime.TypeByExtension(ext)
 | 
			
		||||
	w.Meta(mimetype)
 | 
			
		||||
	w.MediaType(mimetype)
 | 
			
		||||
	io.Copy(w, content)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -80,7 +80,7 @@ func ServeFile(w ResponseWriter, r *Request, fsys fs.FS, name string) {
 | 
			
		||||
		// here and ".." may not be wanted.
 | 
			
		||||
		// Note that name might not contain "..", for example if code (still
 | 
			
		||||
		// incorrectly) used filepath.Join(myDir, r.URL.Path).
 | 
			
		||||
		w.Header(StatusBadRequest, "invalid URL path")
 | 
			
		||||
		w.WriteHeader(StatusBadRequest, "invalid URL path")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	serveFile(w, r, fsys, name, false)
 | 
			
		||||
@ -105,7 +105,7 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
 | 
			
		||||
 | 
			
		||||
	// Redirect .../index.gmi to .../
 | 
			
		||||
	if strings.HasSuffix(r.URL.Path, indexPage) {
 | 
			
		||||
		w.Header(StatusPermanentRedirect, "./")
 | 
			
		||||
		w.WriteHeader(StatusPermanentRedirect, "./")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -117,14 +117,14 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
 | 
			
		||||
 | 
			
		||||
	f, err := fsys.Open(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		w.Status(StatusNotFound)
 | 
			
		||||
		w.WriteHeader(StatusNotFound, "Not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	stat, err := f.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		w.Status(StatusTemporaryFailure)
 | 
			
		||||
		w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -134,13 +134,13 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
 | 
			
		||||
		if stat.IsDir() {
 | 
			
		||||
			// Add trailing slash
 | 
			
		||||
			if url[len(url)-1] != '/' {
 | 
			
		||||
				w.Header(StatusPermanentRedirect, path.Base(url)+"/")
 | 
			
		||||
				w.WriteHeader(StatusPermanentRedirect, path.Base(url)+"/")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// Remove trailing slash
 | 
			
		||||
			if url[len(url)-1] == '/' {
 | 
			
		||||
				w.Header(StatusPermanentRedirect, "../"+path.Base(url))
 | 
			
		||||
				w.WriteHeader(StatusPermanentRedirect, "../"+path.Base(url))
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@ -150,7 +150,7 @@ func serveFile(w ResponseWriter, r *Request, fsys fs.FS, name string, redirect b
 | 
			
		||||
		// Redirect if the directory name doesn't end in a slash
 | 
			
		||||
		url := r.URL.Path
 | 
			
		||||
		if url[len(url)-1] != '/' {
 | 
			
		||||
			w.Header(StatusRedirect, path.Base(url)+"/")
 | 
			
		||||
			w.WriteHeader(StatusRedirect, path.Base(url)+"/")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -183,7 +183,7 @@ func dirList(w ResponseWriter, f fs.File) {
 | 
			
		||||
		entries, err = d.ReadDir(-1)
 | 
			
		||||
	}
 | 
			
		||||
	if !ok || err != nil {
 | 
			
		||||
		w.Header(StatusTemporaryFailure, "Error reading directory")
 | 
			
		||||
		w.WriteHeader(StatusTemporaryFailure, "Error reading directory")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								mux.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								mux.go
									
									
									
									
									
								
							@ -138,14 +138,14 @@ func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request) {
 | 
			
		||||
	// If the given path is /tree and its handler is not registered,
 | 
			
		||||
	// redirect for /tree/.
 | 
			
		||||
	if u, ok := mux.redirectToPathSlash(path, r.URL); ok {
 | 
			
		||||
		w.Header(StatusRedirect, u.String())
 | 
			
		||||
		w.WriteHeader(StatusRedirect, u.String())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if path != r.URL.Path {
 | 
			
		||||
		u := *r.URL
 | 
			
		||||
		u.Path = path
 | 
			
		||||
		w.Header(StatusRedirect, u.String())
 | 
			
		||||
		w.WriteHeader(StatusRedirect, u.String())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -154,7 +154,7 @@ func (mux *ServeMux) ServeGemini(w ResponseWriter, r *Request) {
 | 
			
		||||
 | 
			
		||||
	resp := mux.match(path)
 | 
			
		||||
	if resp == nil {
 | 
			
		||||
		w.Status(StatusNotFound)
 | 
			
		||||
		w.WriteHeader(StatusNotFound, "Not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.ServeGemini(w, r)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										96
									
								
								response.go
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								response.go
									
									
									
									
									
								
							@ -7,6 +7,9 @@ import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// The default media type for responses.
 | 
			
		||||
const defaultMediaType = "text/gemini; charset=utf-8"
 | 
			
		||||
 | 
			
		||||
// Response represents the response from a Gemini request.
 | 
			
		||||
//
 | 
			
		||||
// The Client returns Responses from servers once the response
 | 
			
		||||
@ -73,9 +76,9 @@ func ReadResponse(rc io.ReadCloser) (*Response, error) {
 | 
			
		||||
	if len(meta) > 1024 {
 | 
			
		||||
		return nil, ErrInvalidResponse
 | 
			
		||||
	}
 | 
			
		||||
	// Default mime type of text/gemini; charset=utf-8
 | 
			
		||||
	if StatusClass(status) == StatusSuccess && meta == "" {
 | 
			
		||||
		meta = "text/gemini; charset=utf-8"
 | 
			
		||||
		// Use default media type
 | 
			
		||||
		meta = defaultMediaType
 | 
			
		||||
	}
 | 
			
		||||
	resp.Meta = meta
 | 
			
		||||
 | 
			
		||||
@ -132,30 +135,39 @@ func (b *readCloserBody) Read(p []byte) (n int, err error) {
 | 
			
		||||
	return b.ReadCloser.Read(p)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A ResponseWriter interface is used by a Gemini handler
 | 
			
		||||
// to construct a Gemini response.
 | 
			
		||||
// A ResponseWriter interface is used by a Gemini handler to construct
 | 
			
		||||
// a Gemini response.
 | 
			
		||||
//
 | 
			
		||||
// A ResponseWriter may not be used after the Handler.ServeGemini method
 | 
			
		||||
// has returned.
 | 
			
		||||
type ResponseWriter interface {
 | 
			
		||||
	// Header sets the response header.
 | 
			
		||||
	Header(status int, meta string)
 | 
			
		||||
 | 
			
		||||
	// Status sets the response status code.
 | 
			
		||||
	// It also sets the response meta to StatusText(status).
 | 
			
		||||
	Status(status int)
 | 
			
		||||
 | 
			
		||||
	// Meta sets the response meta.
 | 
			
		||||
	// MediaType 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.
 | 
			
		||||
	//
 | 
			
		||||
	// For successful responses, meta should contain the media type of the response.
 | 
			
		||||
	// For failure responses, meta should contain a short description of the failure.
 | 
			
		||||
	// The response meta should not be greater than 1024 bytes.
 | 
			
		||||
	Meta(meta string)
 | 
			
		||||
	// Setting the media type after a call to Write or WriteHeader has
 | 
			
		||||
	// no effect.
 | 
			
		||||
	MediaType(string)
 | 
			
		||||
 | 
			
		||||
	// Write writes data to the connection as part of the response body.
 | 
			
		||||
	// If the response status does not allow for a response body, Write returns
 | 
			
		||||
	// ErrBodyNotAllowed.
 | 
			
		||||
	// Write writes the data to the connection as part of a Gemini response.
 | 
			
		||||
	//
 | 
			
		||||
	// Write writes the response header if it has not already been written.
 | 
			
		||||
	// It writes a successful status code if one is not set.
 | 
			
		||||
	// If WriteHeader has not yet been called, Write calls WriteHeader with
 | 
			
		||||
	// StatusSuccess and the media type set in MediaType 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 MediaType.
 | 
			
		||||
	//
 | 
			
		||||
	// 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(statusCode int, meta string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The Flusher interface is implemented by ResponseWriters that allow a
 | 
			
		||||
@ -171,8 +183,7 @@ type Flusher interface {
 | 
			
		||||
 | 
			
		||||
type responseWriter struct {
 | 
			
		||||
	b           *bufio.Writer
 | 
			
		||||
	status      int
 | 
			
		||||
	meta        string
 | 
			
		||||
	mediatype   string
 | 
			
		||||
	wroteHeader bool
 | 
			
		||||
	bodyAllowed bool
 | 
			
		||||
}
 | 
			
		||||
@ -188,23 +199,18 @@ func newResponseWriter(w io.Writer) *responseWriter {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *responseWriter) Header(status int, meta string) {
 | 
			
		||||
	w.status = status
 | 
			
		||||
	w.meta = meta
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *responseWriter) Status(status int) {
 | 
			
		||||
	w.status = status
 | 
			
		||||
	w.meta = StatusText(status)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *responseWriter) Meta(meta string) {
 | 
			
		||||
	w.meta = meta
 | 
			
		||||
func (w *responseWriter) MediaType(mediatype string) {
 | 
			
		||||
	w.mediatype = mediatype
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *responseWriter) Write(b []byte) (int, error) {
 | 
			
		||||
	if !w.wroteHeader {
 | 
			
		||||
		w.writeHeader(StatusSuccess)
 | 
			
		||||
		meta := w.mediatype
 | 
			
		||||
		if meta == "" {
 | 
			
		||||
			// Use default media type
 | 
			
		||||
			meta = defaultMediaType
 | 
			
		||||
		}
 | 
			
		||||
		w.WriteHeader(StatusSuccess, meta)
 | 
			
		||||
	}
 | 
			
		||||
	if !w.bodyAllowed {
 | 
			
		||||
		return 0, ErrBodyNotAllowed
 | 
			
		||||
@ -212,22 +218,12 @@ func (w *responseWriter) Write(b []byte) (int, error) {
 | 
			
		||||
	return w.b.Write(b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (w *responseWriter) writeHeader(defaultStatus int) {
 | 
			
		||||
	status := w.status
 | 
			
		||||
	if status == 0 {
 | 
			
		||||
		status = defaultStatus
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	meta := w.meta
 | 
			
		||||
	if StatusClass(status) == StatusSuccess {
 | 
			
		||||
func (w *responseWriter) WriteHeader(statusCode int, meta string) {
 | 
			
		||||
	if StatusClass(statusCode) == StatusSuccess {
 | 
			
		||||
		w.bodyAllowed = true
 | 
			
		||||
 | 
			
		||||
		if meta == "" {
 | 
			
		||||
			meta = "text/gemini"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.b.WriteString(strconv.Itoa(status))
 | 
			
		||||
	w.b.WriteString(strconv.Itoa(statusCode))
 | 
			
		||||
	w.b.WriteByte(' ')
 | 
			
		||||
	w.b.WriteString(meta)
 | 
			
		||||
	w.b.Write(crlf)
 | 
			
		||||
@ -236,7 +232,7 @@ func (w *responseWriter) writeHeader(defaultStatus int) {
 | 
			
		||||
 | 
			
		||||
func (w *responseWriter) Flush() error {
 | 
			
		||||
	if !w.wroteHeader {
 | 
			
		||||
		w.writeHeader(StatusTemporaryFailure)
 | 
			
		||||
		w.WriteHeader(StatusTemporaryFailure, "Temporary failure")
 | 
			
		||||
	}
 | 
			
		||||
	// Write errors from writeHeader will be returned here.
 | 
			
		||||
	return w.b.Flush()
 | 
			
		||||
 | 
			
		||||
@ -380,7 +380,7 @@ func (srv *Server) respond(conn net.Conn) {
 | 
			
		||||
 | 
			
		||||
	req, err := ReadRequest(conn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		w.Status(StatusBadRequest)
 | 
			
		||||
		w.WriteHeader(StatusBadRequest, "Bad request")
 | 
			
		||||
		w.Flush()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@ -396,7 +396,7 @@ func (srv *Server) respond(conn net.Conn) {
 | 
			
		||||
 | 
			
		||||
	h := srv.handler(req)
 | 
			
		||||
	if h == nil {
 | 
			
		||||
		w.Status(StatusNotFound)
 | 
			
		||||
		w.WriteHeader(StatusNotFound, "Not found")
 | 
			
		||||
		w.Flush()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user