Switch Handler to an interface.
continuous-integration/drone/push Build is passing Details

HandlerFunc is much better as a function returning a Handler, rather
than a newtype for the function type itself. This way there is no
confusion creating a type-inferenced variable with HandlerFunc(func(...
and then using a HandlerFunc where a Handler is expected. Much better to
only have one public type.
This commit is contained in:
tjpcc 2023-02-15 16:44:29 -07:00
parent bc96af40db
commit 46ad450327
26 changed files with 140 additions and 106 deletions

View File

@ -13,7 +13,7 @@ Gus is carefully structured as composable building blocks. The top-level package
* a "Server" interface type
* a "Handler" abstraction
* a "Middleware" abstraction
* some useful Handler wrappers: filtering, falling through a list of handlers
* some useful Handler wrappers: a router, request filtering, falling through a list of handlers
## Protocols
@ -39,6 +39,16 @@ The gus/logging package provides everything you need to get a good basic start t
* A request-logging middleware with common diagnostics (time, duration, url, status codes, response body lengths)
* A simple constructor of useful default loggers at various levels. They output colorful logfmt lines to stdout.
## Routing
The router in the gus package supports slash-delimited path pattern strings. In the segments of these patterns:
* A "/:wildcard/" segment matches anything in that position, and captures the value as a route parameter. Or if the paramter name is omitted like "/:/", it matches anything in a single segment without capturing a paramter.
* A "/*remainder" segment is only allowed at the end and matches the rest of the path, capturing it into the paramter name. Or again, omitting a parameter name like "/*" simple matches any path suffix.
* Any other segment in the pattern must match the corresponding segment of a request exactly.
Router also supports maintaining a list of middlewares at the router level, mounting sub-routers under a pattern, looking up the matching handler for any request, and of course acting as a Handler itself.
## gus/contrib/*
This is where useful building blocks themselves start to come in. Sub-packages of contrib include Handler and Middleware implementations which accomplish the things your servers actually need to do.

View File

@ -14,7 +14,7 @@ Gus is carefully structured as composable building blocks. The top-level package
* a "Server" interface type
* a "Handler" abstraction
* a "Middleware" abstraction
* some useful Handler wrappers: filtering, falling through a list of handlers
* some useful Handler wrappers: a router, request filtering, falling through a list of handlers
## Protocols
@ -42,6 +42,16 @@ The gus/logging package provides everything you need to get a good basic start t
* A request-logging middleware with common diagnostics (time, duration, url, status codes, response body lengths)
* A simple constructor of useful default loggers at various levels. They output colorful logfmt lines to stdout.
## Routing
The router in the gus package supports slash-delimited path pattern strings. In the segments of these patterns:
* A "/:wildcard/" segment matches anything in that position, and captures the value as a route parameter. Or if the paramter name is omitted like "/:/", it matches anything in a single segment without capturing a paramter.
* A "/*remainder" segment is only allowed at the end and matches the rest of the path, capturing it into the paramter name. Or again, omitting a parameter name like "/*" simple matches any path suffix.
* Any other segment in the pattern must match the corresponding segment of a request exactly.
Router also supports maintaining a list of middlewares at the router level, mounting sub-routers under a pattern, looking up the matching handler for any request, and of course acting as a Handler itself.
## gus/contrib/*
This is where useful building blocks themselves start to come in. Sub-packages of contrib include Handler and Middleware implementations which accomplish the things your servers actually need to do.

View File

@ -17,7 +17,7 @@ import (
// the URI path.
func GeminiCGIDirectory(pathRoot, fsRoot string) gus.Handler {
fsRoot = strings.TrimRight(fsRoot, "/")
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, pathRoot) {
return nil
}
@ -43,5 +43,5 @@ func GeminiCGIDirectory(pathRoot, fsRoot string) gus.Handler {
return gemini.Failure(err)
}
return response
}
})
}

View File

@ -17,7 +17,7 @@ import (
// the URI path.
func GopherCGIDirectory(pathRoot, fsRoot string) gus.Handler {
fsRoot = strings.TrimRight(fsRoot, "/")
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, pathRoot) {
return nil
}
@ -41,5 +41,5 @@ func GopherCGIDirectory(pathRoot, fsRoot string) gus.Handler {
}
return gopher.File(0, stdout)
}
})
}

View File

@ -47,7 +47,7 @@ func TestDirectoryDefault(t *testing.T) {
require.Nil(t, err)
request := &gus.Request{URL: u}
response := handler(context.Background(), request)
response := handler.Handle(context.Background(), request)
if response == nil {
assert.Equal(t, test.status, gemini.StatusNotFound)
@ -108,7 +108,7 @@ func TestDirectoryListing(t *testing.T) {
require.Nil(t, err)
request := &gus.Request{URL: u}
response := handler(context.Background(), request)
response := handler.Handle(context.Background(), request)
if response == nil {
assert.Equal(t, test.status, gemini.StatusNotFound)

View File

@ -58,7 +58,7 @@ func TestFileHandler(t *testing.T) {
require.Nil(t, err)
request := &gus.Request{URL: u}
response := handler(context.Background(), request)
response := handler.Handle(context.Background(), request)
if response == nil {
assert.Equal(t, test.status, gemini.StatusNotFound)

View File

@ -14,7 +14,7 @@ import (
//
// It only serves responses for paths which do not correspond to directories on disk.
func GeminiFileHandler(fileSystem fs.FS) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
filepath, file, err := ResolveFile(request, fileSystem)
if err != nil {
return gemini.Failure(err)
@ -25,7 +25,7 @@ func GeminiFileHandler(fileSystem fs.FS) gus.Handler {
}
return gemini.Success(mediaType(filepath), file)
}
})
}
// GeminiDirectoryDefault serves up default files for directory path requests.
@ -42,7 +42,7 @@ func GeminiFileHandler(fileSystem fs.FS) gus.Handler {
// It requires that files from the provided fs.FS implement fs.ReadDirFile. If they
// don't, it will produce nil responses for any directory paths.
func GeminiDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
dirpath, dir, response := handleDirGemini(request, fileSystem)
if response != nil {
return response
@ -61,7 +61,7 @@ func GeminiDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
}
return gemini.Success(mediaType(filepath), file)
}
})
}
// GeminiDirectoryListing produces a listing of the contents of any requested directories.
@ -78,7 +78,7 @@ func GeminiDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
// The template may be nil, in which case DefaultGeminiDirectoryList is used instead. The
// template is then processed with RenderDirectoryListing.
func GeminiDirectoryListing(fileSystem fs.FS, template *template.Template) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
dirpath, dir, response := handleDirGemini(request, fileSystem)
if response != nil {
return response
@ -97,7 +97,7 @@ func GeminiDirectoryListing(fileSystem fs.FS, template *template.Template) gus.H
}
return gemini.Success("text/gemini", body)
}
})
}
// DefaultGeminiDirectoryList is a template which renders a reasonable gemtext dir list.

View File

@ -16,7 +16,7 @@ import (
//
// It only serves responses for paths which correspond to files, not directories.
func GopherFileHandler(fileSystem fs.FS) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
filepath, file, err := ResolveFile(request, fileSystem)
if err != nil {
return gopher.Error(err).Response()
@ -27,7 +27,7 @@ func GopherFileHandler(fileSystem fs.FS) gus.Handler {
}
return gopher.File(GuessGopherItemType(filepath), file)
}
})
}
// GopherDirectoryDefault serves up default files for directory path requests.
@ -40,7 +40,7 @@ func GopherFileHandler(fileSystem fs.FS) gus.Handler {
// It requires that files from the provided fs.FS implement fs.ReadDirFile. If
// they don't, it will produce nil responses for all directory paths.
func GopherDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
dirpath, dir, err := ResolveDirectory(request, fileSystem)
if err != nil {
return gopher.Error(err).Response()
@ -59,7 +59,7 @@ func GopherDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
}
return gopher.File(gopher.MenuType, file)
}
})
}
// GopherDirectoryListing produces a listing of the contents of any requested directories.
@ -72,7 +72,7 @@ func GopherDirectoryDefault(fileSystem fs.FS, filenames ...string) gus.Handler {
// A template may be nil, in which case DefaultGopherDirectoryList is used instead. The
// template is then processed with RenderDirectoryListing.
func GopherDirectoryListing(fileSystem fs.FS, tpl *template.Template) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
dirpath, dir, err := ResolveDirectory(request, fileSystem)
if err != nil {
return gopher.Error(err).Response()
@ -91,7 +91,7 @@ func GopherDirectoryListing(fileSystem fs.FS, tpl *template.Template) gus.Handle
}
return gopher.File(gopher.MenuType, body)
}
})
}
// GopherTemplateFunctions is a map for templates providing useful functions for gophermaps.

View File

@ -19,14 +19,14 @@ import (
// "users/", "domain.com/~jim/index.gmi" maps to "domain.com/users/jim/index.gmi".
func ReplaceTilde(replacement string) gus.Middleware {
return func(inner gus.Handler) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
if len(request.Path) > 1 && request.Path[0] == '/' && request.Path[1] == '~' {
request = cloneRequest(request)
request.Path = "/" + replacement + request.Path[2:]
}
return inner(ctx, request)
}
return inner.Handle(ctx, request)
})
}
}

View File

@ -43,12 +43,12 @@ func TestReplaceTilde(t *testing.T) {
replacer := sharedhost.ReplaceTilde(test.replacement)
request := &gus.Request{URL: u}
handler := replacer(func(_ context.Context, request *gus.Request) *gus.Response {
handler := replacer(gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
assert.Equal(t, test.replacedPath, request.Path)
return &gus.Response{}
})
}))
handler(context.Background(), request)
handler.Handle(context.Background(), request)
// original request was unmodified
assert.Equal(t, originalPath, request.Path)

View File

@ -24,7 +24,7 @@ func TestIdentify(t *testing.T) {
server, client, clientCert := setup(t,
"testdata/server.crt", "testdata/server.key",
"testdata/client1.crt", "testdata/client1.key",
func(_ context.Context, request *gus.Request) *gus.Response {
gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
invoked = true
ident := tlsauth.Identity(request)
@ -33,7 +33,7 @@ func TestIdentify(t *testing.T) {
}
return nil
},
}),
)
leafCert, err := x509.ParseCertificate(clientCert.Certificate[0])
require.Nil(t, err)
@ -51,15 +51,15 @@ func TestRequiredAuth(t *testing.T) {
invoked1 := false
invoked2 := false
handler1 := func(_ context.Context, request *gus.Request) *gus.Response {
handler1 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
invoked1 = true
return gemini.Success("", &bytes.Buffer{})
}
})
handler2 := func(_ context.Context, request *gus.Request) *gus.Response {
handler2 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
invoked2 = true
return gemini.Success("", &bytes.Buffer{})
}
})
authMiddleware := gus.Filter(tlsauth.RequiredAuth(tlsauth.Allow), nil)
@ -94,19 +94,19 @@ func TestOptionalAuth(t *testing.T) {
invoked1 := false
invoked2 := false
handler1 := func(_ context.Context, request *gus.Request) *gus.Response {
handler1 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, "/one") {
return nil
}
invoked1 = true
return gemini.Success("", &bytes.Buffer{})
}
})
handler2 := func(_ context.Context, request *gus.Request) *gus.Response {
handler2 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
invoked2 = true
return gemini.Success("", &bytes.Buffer{})
}
})
mw := gus.Filter(tlsauth.OptionalAuth(tlsauth.Reject), nil)
handler := gus.FallthroughHandler(mw(handler1), mw(handler2))

View File

@ -14,7 +14,7 @@ import (
// not pass the approver it will be rejected with "62 certificate invalid".
func GeminiAuth(approver Approver) gus.Middleware {
return func(inner gus.Handler) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
identity := Identity(request)
if identity == nil {
return geminiMissingCert(ctx, request)
@ -23,8 +23,8 @@ func GeminiAuth(approver Approver) gus.Middleware {
return geminiCertNotAuthorized(ctx, request)
}
return inner(ctx, request)
}
return inner.Handle(ctx, request)
})
}
}
@ -35,14 +35,14 @@ func GeminiAuth(approver Approver) gus.Middleware {
// certificate, but it fails the approval.
func GeminiOptionalAuth(approver Approver) gus.Middleware {
return func(inner gus.Handler) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
identity := Identity(request)
if identity != nil && !approver(identity) {
return geminiCertNotAuthorized(ctx, request)
}
return inner(ctx, request)
}
return inner.Handle(ctx, request)
})
}
}

View File

@ -14,30 +14,30 @@ import (
)
func TestGeminiAuth(t *testing.T) {
handler1 := func(_ context.Context, request *gus.Request) *gus.Response {
handler1 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, "/one") {
return nil
}
return gemini.Success("", &bytes.Buffer{})
}
handler2 := func(_ context.Context, request *gus.Request) *gus.Response {
})
handler2 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, "/two") {
return nil
}
return gemini.Success("", &bytes.Buffer{})
}
handler3 := func(_ context.Context, request *gus.Request) *gus.Response {
})
handler3 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, "/three") {
return nil
}
return gemini.Success("", &bytes.Buffer{})
}
handler4 := func(_ context.Context, request *gus.Request) *gus.Response {
})
handler4 := gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
return gemini.Success("", &bytes.Buffer{})
}
})
handler := gus.FallthroughHandler(
tlsauth.GeminiAuth(tlsauth.Allow)(handler1),
@ -74,12 +74,12 @@ func TestGeminiAuth(t *testing.T) {
func TestGeminiOptionalAuth(t *testing.T) {
pathHandler := func(path string) gus.Handler {
return func(_ context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(_ context.Context, request *gus.Request) *gus.Response {
if !strings.HasPrefix(request.Path, path) {
return nil
}
return gemini.Success("", &bytes.Buffer{})
}
})
}
handler := gus.FallthroughHandler(

View File

@ -36,7 +36,7 @@ func main() {
server.Serve()
}
func cowsayHandler(ctx context.Context, req *gus.Request) *gus.Response {
var cowsayHandler = gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
// prompt for a query if there is none already
if req.RawQuery == "" {
return gemini.Input("enter a phrase")
@ -82,7 +82,7 @@ func cowsayHandler(ctx context.Context, req *gus.Request) *gus.Response {
bytes.NewBufferString("\n```\n=> . again"),
)
return gemini.Success("text/gemini", out)
}
})
func envConfig() (string, string) {
certfile, ok := os.LookupEnv("SERVER_CERTIFICATE")

View File

@ -54,11 +54,11 @@ func envConfig() (string, string) {
return certfile, keyfile
}
func inspectHandler(ctx context.Context, req *gus.Request) *gus.Response {
var inspectHandler = gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
// build and return a ```-wrapped description of the connection TLS state
body := "```\n" + displayTLSState(req.TLSState) + "\n```"
return gemini.Success("text/gemini", bytes.NewBufferString(body))
}
})
func displayTLSState(state *tls.ConnectionState) string {
builder := &strings.Builder{}

View File

@ -58,7 +58,7 @@ func (fs *fingerServer) handleConn(conn net.Conn) {
_, _ = fmt.Fprint(conn, "Error handling request.\r\n")
}
}()
response := fs.handler(fs.Ctx, request)
response := fs.handler.Handle(fs.Ctx, request)
if response == nil {
response = Error("No result found.")
}

View File

@ -14,7 +14,7 @@ var ListingDenied = errors.New("Finger online user list denied.")
// SystemFinger handles finger requests by invoking the finger(1) command-line utility.
func SystemFinger(allowListings bool) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
fingerPath, err := exec.LookPath("finger")
if err != nil {
_ = request.Server.LogError(
@ -44,5 +44,5 @@ func SystemFinger(allowListings bool) gus.Handler {
return Error(err.Error())
}
return Success(outbuf)
}
})
}

View File

@ -20,9 +20,9 @@ func TestRoundTrip(t *testing.T) {
tlsConf, err := gemini.FileTLS("./testdata/server.crt", "./testdata/server.key")
require.Nil(t, err)
handler := func(ctx context.Context, req *gus.Request) *gus.Response {
handler := gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
return gemini.Success("text/gemini", bytes.NewBufferString("you've found my page"))
}
})
server, err := gemini.NewServer(context.Background(), "localhost", "tcp", "127.0.0.1:0", handler, nil, tlsConf)
require.Nil(t, err)
@ -54,7 +54,7 @@ func TestTitanRequest(t *testing.T) {
require.Nil(t, err)
invoked := false
handler := func(ctx context.Context, request *gus.Request) *gus.Response {
handler := gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
invoked = true
body := ctx.Value(gemini.TitanRequestBody)
@ -67,7 +67,7 @@ func TestTitanRequest(t *testing.T) {
assert.Equal(t, "the request body\n", string(bodyBytes))
return gemini.Success("", nil)
}
})
server, err := gemini.NewServer(context.Background(), "localhost", "tcp", "127.0.0.1:0", handler, nil, tlsConf)
require.Nil(t, err)

View File

@ -94,7 +94,7 @@ func (s *server) handleConn(conn net.Conn) {
_, _ = io.Copy(conn, NewResponseReader(Failure(err)))
}
}()
response = s.handler(ctx, request)
response = s.handler.Handle(ctx, request)
if response == nil {
response = NotFound("Resource does not exist.")
}
@ -127,12 +127,12 @@ func sizeParam(path string) (int, error) {
// Filtered requests will be turned away with a 53 response "proxy request refused".
func GeminiOnly(allowTitan bool) gus.Middleware {
return func(inner gus.Handler) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
if request.Scheme == "gemini" || (allowTitan && request.Scheme == "titan") {
return inner(ctx, request)
return inner.Handle(ctx, request)
}
return RefuseProxy("Non-gemini protocol requests are not supported.")
}
})
}
}

View File

@ -61,7 +61,7 @@ func (gs *gopherServer) handleConn(conn net.Conn) {
_, _ = io.Copy(conn, rdr)
}
}()
response = gs.handler(gs.Ctx, request)
response = gs.handler.Handle(gs.Ctx, request)
if response == nil {
response = Error(errors.New("Resource does not exist.")).Response()
}

View File

@ -2,11 +2,25 @@ package gus
import "context"
// Handler is a function which can turn a request into a response.
// Handler is a type which can turn a request into a response.
//
// A Handler can return a nil response, in which case the Server is expected
// Handle may return a nil response, in which case the Server is expected
// to build the protocol-appropriate "Not Found" response.
type Handler func(context.Context, *Request) *Response
type Handler interface {
Handle(context.Context, *Request) *Response
}
type handlerFunc func(context.Context, *Request) *Response
// HandlerFunc is a wrapper to allow using a function as a Handler.
func HandlerFunc(f func(context.Context, *Request) *Response) Handler {
return handlerFunc(f)
}
// Handle implements Handler.
func (f handlerFunc) Handle(ctx context.Context, request *Request) *Response {
return f(ctx, request)
}
// Middleware is a handler decorator.
//
@ -19,14 +33,14 @@ type Middleware func(Handler) Handler
// The returned handler will invoke each of the passed-in handlers in order,
// stopping when it receives a non-nil response.
func FallthroughHandler(handlers ...Handler) Handler {
return func(ctx context.Context, request *Request) *Response {
return HandlerFunc(func(ctx context.Context, request *Request) *Response {
for _, handler := range handlers {
if response := handler(ctx, request); response != nil {
if response := handler.Handle(ctx, request); response != nil {
return response
}
}
return nil
}
})
}
// Filter builds a middleware which only calls the wrapped Handler under a condition.
@ -39,14 +53,14 @@ func Filter(
failure Handler,
) Middleware {
return func(success Handler) Handler {
return func(ctx context.Context, request *Request) *Response {
return HandlerFunc(func(ctx context.Context, request *Request) *Response {
if condition(ctx, request) {
return success(ctx, request)
return success.Handle(ctx, request)
}
if failure == nil {
return nil
}
return failure(ctx, request)
}
return failure.Handle(ctx, request)
})
}
}

View File

@ -13,19 +13,19 @@ import (
)
func TestFallthrough(t *testing.T) {
h1 := func(ctx context.Context, req *gus.Request) *gus.Response {
h1 := gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
if req.Path == "/one" {
return gemini.Success("text/gemini", bytes.NewBufferString("one"))
}
return nil
}
})
h2 := func(ctx context.Context, req *gus.Request) *gus.Response {
h2 := gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
if req.Path == "/two" {
return gemini.Success("text/gemini", bytes.NewBufferString("two"))
}
return nil
}
})
fth := gus.FallthroughHandler(h1, h2)
@ -34,7 +34,7 @@ func TestFallthrough(t *testing.T) {
t.Fatalf("url.Parse: %s", err.Error())
}
resp := fth(context.Background(), &gus.Request{URL: u})
resp := fth.Handle(context.Background(), &gus.Request{URL: u})
if resp.Status != gemini.StatusSuccess {
t.Errorf("expected status %d, got %d", gemini.StatusSuccess, resp.Status)
@ -57,7 +57,7 @@ func TestFallthrough(t *testing.T) {
t.Fatalf("url.Parse: %s", err.Error())
}
resp = fth(context.Background(), &gus.Request{URL: u})
resp = fth.Handle(context.Background(), &gus.Request{URL: u})
if resp.Status != gemini.StatusSuccess {
t.Errorf("expected status %d, got %d", gemini.StatusSuccess, resp.Status)
@ -80,7 +80,7 @@ func TestFallthrough(t *testing.T) {
t.Fatalf("url.Parse: %s", err.Error())
}
resp = fth(context.Background(), &gus.Request{URL: u})
resp = fth.Handle(context.Background(), &gus.Request{URL: u})
if resp != nil {
t.Errorf("expected nil, got %+v", resp)
@ -91,9 +91,9 @@ func TestFilter(t *testing.T) {
pred := func(ctx context.Context, req *gus.Request) bool {
return strings.HasPrefix(req.Path, "/allow")
}
base := func(ctx context.Context, req *gus.Request) *gus.Response {
base := gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
return gemini.Success("text/gemini", bytes.NewBufferString("allowed!"))
}
})
handler := gus.Filter(pred, nil)(base)
u, err := url.Parse("gemini://test.local/allow/please")
@ -101,7 +101,7 @@ func TestFilter(t *testing.T) {
t.Fatalf("url.Parse: %s", err.Error())
}
resp := handler(context.Background(), &gus.Request{URL: u})
resp := handler.Handle(context.Background(), &gus.Request{URL: u})
if resp.Status != gemini.StatusSuccess {
t.Errorf("expected status %d, got %d", gemini.StatusSuccess, resp.Status)
}
@ -111,7 +111,7 @@ func TestFilter(t *testing.T) {
t.Fatalf("url.Parse: %s", err.Error())
}
resp = handler(context.Background(), &gus.Request{URL: u})
resp = handler.Handle(context.Background(), &gus.Request{URL: u})
if resp != nil {
t.Errorf("expected nil, got %+v", resp)
}

View File

@ -11,14 +11,14 @@ import (
func LogRequests(logger Logger) gus.Middleware {
return func(inner gus.Handler) gus.Handler {
return func(ctx context.Context, request *gus.Request) *gus.Response {
response := inner(ctx, request)
return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
response := inner.Handle(ctx, request)
if response != nil {
response.Body = loggingBody(logger, request, response)
}
return response
}
})
}
}

View File

@ -14,11 +14,11 @@ import (
func TestLogRequests(t *testing.T) {
logger := logRecorder{}
handler := logging.LogRequests(&logger)(func(_ context.Context, _ *gus.Request) *gus.Response {
handler := logging.LogRequests(&logger)(gus.HandlerFunc(func(_ context.Context, _ *gus.Request) *gus.Response {
return &gus.Response{}
})
}))
response := handler(context.Background(), &gus.Request{})
response := handler.Handle(context.Background(), &gus.Request{})
_, err := io.ReadAll(response.Body)
assert.Nil(t, err)

View File

@ -50,10 +50,10 @@ func (r Router) Handler(ctx context.Context, request *Request) *Response {
return nil
}
return handler(context.WithValue(ctx, routeParamsKey, params), request)
return handler.Handle(context.WithValue(ctx, routeParamsKey, params), request)
}
// Match returns the matched handler and captured path parameters, or nils.
// Match returns the matched handler and captured path parameters, or (nil, nil).
//
// The returned handlers will be wrapped with any middleware attached to the router.
func (r Router) Match(request *Request) (Handler, map[string]string) {

View File

@ -13,24 +13,24 @@ import (
"tildegit.org/tjp/gus/gemini"
)
func h1(_ context.Context, _ *gus.Request) *gus.Response {
var h1 = gus.HandlerFunc(func(_ context.Context, _ *gus.Request) *gus.Response {
return gemini.Success("", &bytes.Buffer{})
}
})
func mw1(h gus.Handler) gus.Handler {
return func(ctx context.Context, req *gus.Request) *gus.Response {
resp := h(ctx, req)
return gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
resp := h.Handle(ctx, req)
resp.Body = io.MultiReader(resp.Body, bytes.NewBufferString("\nmiddleware 1"))
return resp
}
})
}
func mw2(h gus.Handler) gus.Handler {
return func(ctx context.Context, req *gus.Request) *gus.Response {
resp := h(ctx, req)
return gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
resp := h.Handle(ctx, req)
resp.Body = io.MultiReader(resp.Body, bytes.NewBufferString("\nmiddleware 2"))
return resp
}
})
}
func TestRouterUse(t *testing.T) {