lots more documentation comments
continuous-integration/drone/push Build is passing Details

This commit is contained in:
tjpcc 2023-01-11 10:36:56 -07:00
parent 197d8e4cb0
commit e183f9cd23
7 changed files with 60 additions and 5 deletions

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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()

View File

@ -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 {