Skip to main content

Static router

The ogen tool generates a static blazing fast radix tree-based router.

Router error handling

Not Found

If there is no route for the request, router calls the config.NotFound handler. The default handler is http.NotFound.

You can change the default handler by using WithNotFound option.

h := myHandler{}
srv, err := api.NewServer(h, api.WithNotFound(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound)
_, _ = io.WriteString(w, `{"error": "not found"}`)
}))
if err != nil {
panic(err)
}

Method Not Allowed

If request path matches but the method is not allowed, router calls the config.MethodNotAllowed handler. The default handler returns empty body with 405 Method Not Allowed code and Allow header with comma-separated allowed methods list.

You can change the default handler by using WithMethodNotAllowed option.

caution

The HTTP spec requires that the Allow header is sent with the status code 405 Method Not Allowed.

You should add the Allow header to the response by yourself.

h := myHandler{}
srv, err := api.NewServer(h, api.WithMethodNotAllowed(func(w http.ResponseWriter, r *http.Request, allowed string) {
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Allow", allowed)
w.WriteHeader(http.StatusMethodNotAllowed)
_, _ = fmt.Fprintf(w, "use one of the following methods: %s\n", allowed)
}))
if err != nil {
panic(err)
}

Using net/http middlewares

The Server type implements http.Handler interface. Any net/http-compatible middleware can be used.

Applying middleware to all routes

package main

import (
"net/http"

"github.com/klauspost/compress/gzhttp"

api "<your_api_package>"
)

func main() {
srv, err := api.NewServer(myHandler{})
if err != nil {
panic(err)
}
http.ListenAndServe(":8080", gzhttp.GzipHandler(srv))
}

Using FindRoute method

The Server type defines a FindRoute method that finds and returns the Route for the request, if any.

It's useful for middleware that needs to find the route for the request.

package main

import (
"net/http"

api "<your_api_package>"
)

type myMiddleware struct {
Next *api.Server
}

func (m myMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
route, ok := m.Next.FindRoute(r.Method, r.URL.Path)
if !ok {
// There is no route for the request.
//
// Let server handle 404/405.
m.Next.ServeHTTP(w, r)
return
}
// Match operation by spec operation ID.
// Notice that the operation ID is optional and may be empty.
//
// You can also use `route.Name()` to get the ogen operation name.
// Unlike the operation ID, the name is guaranteed to be unique and non-empty.
switch route.OperationID() {
case "operation1", "operation2":
// Middleware logic:
args := route.Args()
if args[0] == "debug" {
w.Header().Set("X-Debug", "true")
}
}
m.Next.ServeHTTP(w, r)
}

func main() {
s, err := api.NewServer(myHandler{})
if err != nil {
panic(err)
}
http.ListenAndServe(":8080", myMiddleware{Next: s})
}

Adding profiler, metrics or static content route

To serve other routes, you can use any upper-level router.

Example would serve

  • API routes at /api/v1/*
  • pprof handlers on /debug/pprof/*
  • Files from static folder on /static/*
  • Prometheus metrics handler on /metrics
package main

import (
"embed"
"net/http"
"net/http/pprof"

"github.com/prometheus/client_golang/prometheus/promhttp"

api "<your_api_package>"
)

//go:embed static
var static embed.FS

func main() {
srv, err := api.NewServer(myHandler{})
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", srv))
// Register static files.
mux.Handle("/static/", http.FileServer(http.FS(static)))
// Register metrics handler.
mux.Handle("/metrics", promhttp.Handler())
{
// Register pprof handlers.
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
http.ListenAndServe(":8080", mux)
}