summaryrefslogtreecommitdiff
path: root/gemini
diff options
context:
space:
mode:
Diffstat (limited to 'gemini')
-rw-r--r--gemini/client.go7
-rw-r--r--gemini/handler.go29
-rw-r--r--gemini/request.go16
-rw-r--r--gemini/response.go2
-rw-r--r--gemini/serve.go6
-rw-r--r--gemini/tls.go3
6 files changed, 59 insertions, 4 deletions
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 {