diff options
| -rw-r--r-- | examples/fileserver/main.go | 2 | ||||
| -rw-r--r-- | gemini/client.go | 7 | ||||
| -rw-r--r-- | gemini/handler.go | 29 | ||||
| -rw-r--r-- | gemini/request.go | 16 | ||||
| -rw-r--r-- | gemini/response.go | 2 | ||||
| -rw-r--r-- | gemini/serve.go | 6 | ||||
| -rw-r--r-- | gemini/tls.go | 3 | 
7 files changed, 60 insertions, 5 deletions
diff --git a/examples/fileserver/main.go b/examples/fileserver/main.go index d7f628b..b38ae76 100644 --- a/examples/fileserver/main.go +++ b/examples/fileserver/main.go @@ -23,7 +23,7 @@ func main() {  	// build the request handler  	fileSystem := os.DirFS(".")  	// Fallthrough tries each handler in succession until it gets something other than "51 Not Found" -	handler := gemini.Fallthrough( +	handler := gemini.FallthroughHandler(  		// first see if they're fetching a directory and we have <dir>/index.gmi  		fs.DirectoryDefault(fileSystem, "index.gmi"),  		// next (still if they requested a directory) build a directory listing response diff --git a/gemini/client.go b/gemini/client.go index 53c8b71..aca4576 100644 --- a/gemini/client.go +++ b/gemini/client.go @@ -28,6 +28,9 @@ func NewClient(tlsConf *tls.Config) Client {  //  // It also populates the TLSState and RemoteAddr fields on the request - the only field  // it needs populated beforehand is the URL. +// +// This method will not automatically follow redirects or cache permanent failures or +// redirects.  func (client Client) RoundTrip(request *Request) (*Response, error) {  	if request.Scheme != "gemini" && request.Scheme != "" {  		return nil, errors.New("non-gemini protocols not supported") @@ -57,8 +60,8 @@ func (client Client) RoundTrip(request *Request) (*Response, error) {  		return nil, err  	} -	// read and store the request body in full or we may miss doing so before the -	// connection gets closed. +	// read and store the request body in full or we may miss doing so before +	// closing the connection  	bodybuf, err := io.ReadAll(response.Body)  	if err != nil {  		return nil, err diff --git a/gemini/handler.go b/gemini/handler.go index ded77a5..0f48e62 100644 --- a/gemini/handler.go +++ b/gemini/handler.go @@ -6,7 +6,7 @@ import "context"  //  // A Handler MUST NOT return a nil response. Errors should be returned in the form  // of error responses (4x, 5x, 6x response status). If the Handler should not be -// responsible for the requested resource it can return a "51 Not Found" response. +// responsible for the requested resource it can return a 51 response.  type Handler func(context.Context, *Request) *Response  // Middleware is a handle decorator. @@ -15,7 +15,11 @@ type Handler func(context.Context, *Request) *Response  // transform the request or response in some way.  type Middleware func(Handler) Handler -func Fallthrough(handlers ...Handler) Handler { +// FallthroughHandler builds a handler which tries multiple child handlers. +// +// The returned handler will invoke each of the passed child handlers in order, +// stopping when it receives a response with status other than 51. +func FallthroughHandler(handlers ...Handler) Handler {  	return func(ctx context.Context, req *Request) *Response {  		for _, handler := range handlers {  			response := handler(ctx, req) @@ -27,3 +31,24 @@ func Fallthrough(handlers ...Handler) Handler {  		return NotFound("Resource does not exist.")  	}  } + +// Filter wraps a handler with a predicate which determines whether to run the handler. +// +// When the predicate function returns false, the Filter returns the provided failure +// response. The failure argument may be nil, in which case a "51 Resource does not exist." +// response will be used. +func Filter( +	predicate func(context.Context, *Request) bool, +	handler Handler, +	failure *Response, +) Handler { +	if failure == nil { +		failure = NotFound("Resource does not exist.") +	} +	return func(ctx context.Context, req *Request) *Response { +		if !predicate(ctx, req) { +			return failure +		} +		return handler(ctx, req) +	} +} diff --git a/gemini/request.go b/gemini/request.go index 43ee69b..933281b 100644 --- a/gemini/request.go +++ b/gemini/request.go @@ -14,10 +14,26 @@ var InvalidRequestLineEnding = errors.New("invalid request line ending")  // Request represents a request over the gemini protocol.  type Request struct { +	// URL is the specific URL being fetched by the request.  	*url.URL +	// Server is the server which received the request. +	// +	// This is only populated in gemini servers. +	// It is unused on the client end.  	Server     *Server + +	// RemoteAddr is the address of the other side of the connection. +	// +	// This will be the server address for clients, or the connecting +	// client's address in servers. +	// +	// Be aware though that proxies (and reverse proxies) can confuse this.  	RemoteAddr net.Addr + +	// TLSState contains information about the TLS encryption over the connection. +	// +	// This includes peer certificates and version information.  	TLSState   *tls.ConnectionState  } diff --git a/gemini/response.go b/gemini/response.go index 1fa64cf..5b5ced4 100644 --- a/gemini/response.go +++ b/gemini/response.go @@ -114,6 +114,8 @@ type Response struct {  	Meta string  	// Body is the response body, if any. +	// +	// It is not guaranteed to be readable more than once.  	Body io.Reader  	reader io.Reader diff --git a/gemini/serve.go b/gemini/serve.go index abf127e..f9a8a1c 100644 --- a/gemini/serve.go +++ b/gemini/serve.go @@ -8,6 +8,7 @@ import (  	"sync"  ) +// Server listens on a network and serves the gemini protocol.  type Server struct {  	ctx      context.Context  	network  string @@ -18,6 +19,7 @@ type Server struct {  	handler  Handler  } +// NewServer builds a server.  func NewServer(  	ctx context.Context,  	tlsConfig *tls.Config, @@ -46,6 +48,10 @@ func NewServer(  //  // This function will allocate resources which are not cleaned up until  // Close() is called. +// +// It will respect cancellation of the context the server was created with, +// but be aware that Close() must still be called in that case to avoid +// dangling goroutines.  func (s *Server) Serve() error {  	s.wg.Add(1)  	defer s.wg.Done() diff --git a/gemini/tls.go b/gemini/tls.go index 3cdf93b..5b35fb6 100644 --- a/gemini/tls.go +++ b/gemini/tls.go @@ -2,6 +2,9 @@ package gemini  import "crypto/tls" +// FileTLS builds a TLS configuration from paths to a certificate and key file. +// +// It sets parameters on the configuration to make it suitable for use with gemini.  func FileTLS(certfile string, keyfile string) (*tls.Config, error) {  	cert, err := tls.LoadX509KeyPair(certfile, keyfile)  	if err != nil {  | 
