diff --git a/Benchmarks_test.go b/Benchmarks_test.go new file mode 100644 index 0000000..3ce6969 --- /dev/null +++ b/Benchmarks_test.go @@ -0,0 +1,38 @@ +package server_test + +import ( + "net/http/httptest" + "testing" + + "git.akyoto.dev/go/server" +) + +func BenchmarkHello(b *testing.B) { + request := httptest.NewRequest("GET", "/", nil) + response := &NullResponse{} + s := server.New() + + s.Get("/", func(ctx server.Context) error { + return ctx.String("Hello") + }) + + for range b.N { + s.ServeHTTP(response, request) + } +} + +func BenchmarkGitHub(b *testing.B) { + request := httptest.NewRequest("GET", "/repos/:owner/:repo", nil) + response := &NullResponse{} + s := server.New() + + for _, route := range loadRoutes("testdata/github.txt") { + s.Router().Add(route.Method, route.Path, func(server.Context) error { + return nil + }) + } + + for range b.N { + s.ServeHTTP(response, request) + } +} diff --git a/Handler.go b/Handler.go new file mode 100644 index 0000000..4030ee7 --- /dev/null +++ b/Handler.go @@ -0,0 +1,4 @@ +package server + +// Handler is a function that deals with the given request/response context. +type Handler func(Context) error diff --git a/NullResponse_test.go b/NullResponse_test.go new file mode 100644 index 0000000..6b7b942 --- /dev/null +++ b/NullResponse_test.go @@ -0,0 +1,11 @@ +package server_test + +import "net/http" + +// NullResponse implements the http.ResponseWriter interface with +// empty methods to better understand memory usage in benchmarks. +type NullResponse struct{} + +func (r *NullResponse) Header() http.Header { return nil } +func (r *NullResponse) Write([]byte) (int, error) { return 0, nil } +func (r *NullResponse) WriteHeader(int) {} diff --git a/README.md b/README.md index 701f3a4..ac4d2bf 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ coverage: 100.0% of statements ## Benchmarks ``` -coming soon +BenchmarkHello-12 33502272 33.64 ns/op 0 B/op 0 allocs/op +BenchmarkGitHub-12 17698947 65.50 ns/op 0 B/op 0 allocs/op ``` ## License diff --git a/Route_test.go b/Route_test.go new file mode 100644 index 0000000..8ca46de --- /dev/null +++ b/Route_test.go @@ -0,0 +1,46 @@ +package server_test + +import ( + "bufio" + "os" + "strings" +) + +// Route represents a single line in the test data. +type Route struct { + Method string + Path string +} + +// loadRoutes loads all routes from a text file. +func loadRoutes(filePath string) []Route { + var routes []Route + f, err := os.Open(filePath) + + if err != nil { + panic(err) + } + + defer f.Close() + reader := bufio.NewReader(f) + + for { + line, err := reader.ReadString('\n') + + if line != "" { + line = strings.TrimSpace(line) + parts := strings.Split(line, " ") + + routes = append(routes, Route{ + Method: parts[0], + Path: parts[1], + }) + } + + if err != nil { + break + } + } + + return routes +} diff --git a/Server.go b/Server.go index 90ce281..3758672 100644 --- a/Server.go +++ b/Server.go @@ -8,9 +8,6 @@ import ( "git.akyoto.dev/go/router" ) -// Handler is a function that deals with the given request/response context. -type Handler func(Context) error - // Server represents a single web service. type Server struct { router *router.Router[Handler] @@ -43,15 +40,21 @@ func (server *Server) Put(path string, handler Handler) { server.router.Add(http.MethodPut, path, handler) } +// Router returns the router used by the server. +func (server *Server) Router() *router.Router[Handler] { + return server.router +} + // ServeHTTP responds to the given request. func (server *Server) ServeHTTP(response http.ResponseWriter, request *http.Request) { ctx := newContext(request, response) + defer contextPool.Put(ctx) + handler := server.router.LookupNoAlloc(request.Method, request.URL.Path, ctx.addParameter) if handler == nil { response.WriteHeader(http.StatusNotFound) response.(io.StringWriter).WriteString(http.StatusText(http.StatusNotFound)) - contextPool.Put(ctx) return } @@ -61,6 +64,4 @@ func (server *Server) ServeHTTP(response http.ResponseWriter, request *http.Requ response.(io.StringWriter).WriteString(err.Error()) log.Println(request.URL, err) } - - contextPool.Put(ctx) } diff --git a/Server_test.go b/Server_test.go index 1bf355e..557291a 100644 --- a/Server_test.go +++ b/Server_test.go @@ -121,7 +121,7 @@ func TestRouter(t *testing.T) { func TestPanic(t *testing.T) { s := server.New() - s.Get("/panic", func(ctx server.Context) error { + s.Router().Add(http.MethodGet, "/panic", func(ctx server.Context) error { panic("Something unbelievable happened") }) diff --git a/examples/hello/main.go b/examples/hello/main.go new file mode 100644 index 0000000..6ac7313 --- /dev/null +++ b/examples/hello/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "net/http" + + "git.akyoto.dev/go/server" +) + +func main() { + s := server.New() + + s.Get("/", func(ctx server.Context) error { + return ctx.String("Hello") + }) + + http.ListenAndServe(":8080", s) +} diff --git a/go.mod b/go.mod index 1b5293b..402d15c 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.22 require ( git.akyoto.dev/go/assert v0.1.3 - git.akyoto.dev/go/router v0.1.1 + git.akyoto.dev/go/router v0.1.2 ) diff --git a/go.sum b/go.sum index 2b79a30..a127934 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM= -git.akyoto.dev/go/router v0.1.1 h1:6fHjzv59MKMhO2DsM90mkI5hy5PrcjV4WeD1Vk1xXXs= -git.akyoto.dev/go/router v0.1.1/go.mod h1:IwwEUJU2ExmozpZKMbDOKdiVT516oAijnxGDg9kvBt4= +git.akyoto.dev/go/router v0.1.2 h1:UZq92uOqQwxH+fS8geEhQ5RLZS7sb/QLvwrRzXjaTcg= +git.akyoto.dev/go/router v0.1.2/go.mod h1:VfSsK/Z6fUhT3pWaAAnuAcj++bWRZD+bzNaqJoTAunU=