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