Switch Handler to an interface.
continuous-integration/drone/push Build is passing
Details
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:
parent
bc96af40db
commit
46ad450327
12
README.gmi
12
README.gmi
|
@ -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.
|
||||
|
|
12
README.md
12
README.md
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
34
handler.go
34
handler.go
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Reference in New Issue