Added router benchmarks

This commit is contained in:
Eduard Urbach 2024-03-12 20:35:19 +01:00
parent 070b374c6e
commit cb6106a26d
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
10 changed files with 129 additions and 11 deletions

38
Benchmarks_test.go Normal file
View File

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

4
Handler.go Normal file
View File

@ -0,0 +1,4 @@
package server
// Handler is a function that deals with the given request/response context.
type Handler func(Context) error

11
NullResponse_test.go Normal file
View File

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

View File

@ -47,7 +47,8 @@ coverage: 100.0% of statements
## Benchmarks ## 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 ## License

46
Route_test.go Normal file
View File

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

View File

@ -8,9 +8,6 @@ import (
"git.akyoto.dev/go/router" "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. // Server represents a single web service.
type Server struct { type Server struct {
router *router.Router[Handler] router *router.Router[Handler]
@ -43,15 +40,21 @@ func (server *Server) Put(path string, handler Handler) {
server.router.Add(http.MethodPut, path, 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. // ServeHTTP responds to the given request.
func (server *Server) ServeHTTP(response http.ResponseWriter, request *http.Request) { func (server *Server) ServeHTTP(response http.ResponseWriter, request *http.Request) {
ctx := newContext(request, response) ctx := newContext(request, response)
defer contextPool.Put(ctx)
handler := server.router.LookupNoAlloc(request.Method, request.URL.Path, ctx.addParameter) handler := server.router.LookupNoAlloc(request.Method, request.URL.Path, ctx.addParameter)
if handler == nil { if handler == nil {
response.WriteHeader(http.StatusNotFound) response.WriteHeader(http.StatusNotFound)
response.(io.StringWriter).WriteString(http.StatusText(http.StatusNotFound)) response.(io.StringWriter).WriteString(http.StatusText(http.StatusNotFound))
contextPool.Put(ctx)
return return
} }
@ -61,6 +64,4 @@ func (server *Server) ServeHTTP(response http.ResponseWriter, request *http.Requ
response.(io.StringWriter).WriteString(err.Error()) response.(io.StringWriter).WriteString(err.Error())
log.Println(request.URL, err) log.Println(request.URL, err)
} }
contextPool.Put(ctx)
} }

View File

@ -121,7 +121,7 @@ func TestRouter(t *testing.T) {
func TestPanic(t *testing.T) { func TestPanic(t *testing.T) {
s := server.New() 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") panic("Something unbelievable happened")
}) })

17
examples/hello/main.go Normal file
View File

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

2
go.mod
View File

@ -4,5 +4,5 @@ go 1.22
require ( require (
git.akyoto.dev/go/assert v0.1.3 git.akyoto.dev/go/assert v0.1.3
git.akyoto.dev/go/router v0.1.1 git.akyoto.dev/go/router v0.1.2
) )

4
go.sum
View File

@ -1,4 +1,4 @@
git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8= 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/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.2 h1:UZq92uOqQwxH+fS8geEhQ5RLZS7sb/QLvwrRzXjaTcg=
git.akyoto.dev/go/router v0.1.1/go.mod h1:IwwEUJU2ExmozpZKMbDOKdiVT516oAijnxGDg9kvBt4= git.akyoto.dev/go/router v0.1.2/go.mod h1:VfSsK/Z6fUhT3pWaAAnuAcj++bWRZD+bzNaqJoTAunU=