lit

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 27, 2024 License: MIT Imports: 12 Imported by: 0

README

Go Reference Go Report Card codecov

Lit 🔥

Lit is an expressive and fast HTTP framework for Golang. It aims to enhance development by providing great simplicity, extensibility and maintainability.

Documentation

Check Lit documentation.

Getting started

Create a new Go project and import Lit with the command:

go get github.com/jvcoutinho/lit

Write this to your main.go file:

package main

import (
  "log"
  "net/http"

  "github.com/jvcoutinho/lit"
  "github.com/jvcoutinho/lit/render"
)

func main() {
  r := lit.NewRouter()
  r.Use(lit.Log)
  r.Use(lit.Recover)

  r.GET("/", HelloWorld)

  server := http.Server{Addr: ":8080", Handler: r}
  log.Fatalln(server.ListenAndServe())
}

func HelloWorld(r *lit.Request) lit.Response {
  return render.OK("Hello, World!")
}

Then you can start adding new routes and middlewares.

Features

  • Speed and efficiency: It uses httprouter, a very fast zero-allocation router. See benchmarks.
  • Expressiveness: Its constructs make room for declarative programming, creating code that is more readable, maintainable, extensible and testable.
  • Flexibility: It allows one to easily extend its constructs. Creating new validations, middlewares and responses, for example, is very simple and intuitive.
  • Idiomatic: It uses latest Go features, such as generics, in order to build a framework that is elegant to code.

Check an example of lit.Handler vs http.Handler.

Benchmarks

The instances below were tested with the specifications:

goos: windows
goarch: amd64
pkg: github.com/julienschmidt/go-http-routing-benchmark
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz

You can check the methodology or try yourself with go-http-routing-benchmark.

GitHub API
Router Memory for handler registration Number repetitions Latency per repetition Heap memory per repetition Allocations per repetition
Chi 94888 B 10000 105680 ns/op 61713 B/op 406 allocs/op
Gin 94888 B 84152 14056 ns/op 0 B/op 0 allocs/op
Lit 🔥 42088 B 65989 17919 ns/op 0 B/op 0 allocs/op
Gorilla 1319632 B 612 1886488 ns/op 199684 B/op 1588 allocs/op
HttpRouter 37136 B 114933 10511 ns/op 0 B/op 0 allocs/op
Martini 485032 B 596 2070638 ns/op 231420 B/op 2731 allocs/op
Parse API
Router Memory for handler registration Number repetitions Latency per repetition Heap memory per repetition Allocations per repetition
Chi 9656 B 315666 11549 ns/op 7904 B/op 52 allocs/op
Gin 7864 B 3118021 1144 ns/op 0 B/op 0 allocs/op
Lit 🔥 5776 B 2670331 1342 ns/op 0 B/op 0 allocs/op
Gorilla 105448 B 75290 47507 ns/op 23632 B/op 198 allocs/op
HttpRouter 5072 B 4421617 816 ns/op 0 B/op 0 allocs/op
Martini 45808 B 45362 79979 ns/op 25696 B/op 305 allocs/op
Fake API (only static routes)
Router Memory for handler registration Number repetitions Latency per repetition Heap memory per repetition Allocations per repetition
Chi 83160 B 51685 69608 ns/op 47728 B/op 314 allocs/op
Gin 34344 B 385328 9302 ns/op 0 B/op 0 allocs/op
Lit 🔥 25560 B 450072 7944 ns/op 0 B/op 0 allocs/op
Gorilla 582536 B 7695 500949 ns/op 113042 B/op 1099 allocs/op
HttpRouter 21712 B 638422 5716 ns/op 0 B/op 0 allocs/op
Martini 309880 B 3915 916608 ns/op 129211 B/op 2031 allocs/op

As seen, Lit consumes less memory to register a batch of routes and has performance comparable to the top performers, such as Gin Gonic and HttpRouter.

Contributing

Feel free to open issues or pull requests!


Copyright (c) 2023-2024

João Victor de Sá Ferraz Coutinho joao.coutinho9@gmail.com

Documentation

Overview

Package lit is a fast and expressive HTTP framework.

The basics

In Lit, an HTTP handler is a function that receives a *Request and returns a Response. Register new handlers using the *Router.Handle method.

For instance, the Divide function below is a handler that returns the division of two integers coming from query parameters:

type Request struct {
	A int `query:"a"`
	B int `query:"b"`
}

func (r *Request) Validate() []validate.Field {
	return []validate.Field{
		validate.NotEqual(&r.B, 0),
	}
}

func Divide(r *lit.Request) lit.Response {
	req, err := bind.Query[Request](r)
	if err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A / req.B)
}

In order to extend a handler's functionality and to be able to reuse the logic in several handlers, such as logging or authorization logic, one can use middlewares. In Lit, a middleware is a function that receives a Handler and returns a Handler. Register new middlewares using the *Router.Use method.

For instance, the AppendRequestID function below is a middleware that assigns an ID to the request and appends it to the context:

type ContextKeyType string

var RequestIDKey ContextKeyType = "request-id"

func AppendRequestID(h lit.Handler) lit.Handler {
	return func(r *lit.Request) lit.Response {
		var (
			requestID = uuid.New()
			ctx       = context.WithValue(r.Context(), RequestIDKey, requestID)
		)

		r.WithContext(ctx)

		return h(r)
	}
}

It is recommended to use the Log and Recover middlewares.

Check the package-level examples for more use cases.

Model binding and receiving files

Lit can parse and validate data coming from a request's URI parameters, header, body or query parameters to Go structs, including files from multipart form requests.

Check github.com/jvcoutinho/lit/bind package.

Validation

Lit can validate Go structs with generics and compile-time assertions.

Check github.com/jvcoutinho/lit/validate package.

Responding requests, redirecting, serving files and streams

Lit responds requests with implementations of the Response interface. Current provided implementations include JSON responses, redirections, no content responses, files and streams.

Check github.com/jvcoutinho/lit/render package.

Testing handlers

Handlers can be unit tested in several ways. The simplest and idiomatic form is calling the handler with a crafted request and asserting the response:

type Request struct {
	A int `query:"a"`
	B int `query:"b"`
}

func (r *Request) Validate() []validate.Field {
	return []validate.Field{
		validate.NotEqual(&r.B, 0),
	}
}

func Divide(r *lit.Request) lit.Response {
	req, err := bind.Query[Request](r)
	if err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A / req.B)
}

func TestDivide(t *testing.T) {
	t.Parallel()

	tests := []struct {
		description string
		a           int
		b           int
		want        lit.Response
	}{
		{
			description: "BEquals0",
			a:           3,
			b:           0,
			want:        render.BadRequest("b should not be equal to 0"),
		},
		{
			description: "Division",
			a:           6,
			b:           3,
			want:        render.OK(2),
		},
	}

	for _, test := range tests {
		test := test
		t.Run(test.description, func(t *testing.T) {
			t.Parallel()

			var (
				path    = fmt.Sprintf("/?a=%d&b=%d", test.a, test.b)
				request = lit.NewRequest(
					httptest.NewRequest(http.MethodGet, path, nil),
				)
				got  = Divide(request)
				want = test.want
			)

			if !reflect.DeepEqual(got, want) {
				t.Fatalf("got: %v; want: %v", got, want)
			}
		})
	}
}

Testing middlewares

Middlewares can be tested in the same way as handlers (crafting a request and asserting the response of the handler after the transformation):

func ValidateXAPIKeyHeader(h lit.Handler) lit.Handler {
	return func(r *lit.Request) lit.Response {
		apiKeyHeader, err := bind.HeaderField[string](r, "X-API-KEY")
		if err != nil {
			return render.BadRequest(err)
		}

		if apiKeyHeader == "" {
			return render.Unauthorized("API Key must be provided")
		}

		return h(r)
	}
}

func TestValidateXAPIKeyHeader(t *testing.T) {
	t.Parallel()

	testHandler := func(r *lit.Request) lit.Response {
		return render.NoContent()
	}

	tests := []struct {
		description  string
		apiKeyHeader string
		want         lit.Response
	}{
		{
			description:  "EmptyHeader",
			apiKeyHeader: "",
			want:         render.Unauthorized("API Key must be provided"),
		},
		{
			description:  "ValidAPIKey",
			apiKeyHeader: "api-key-1",
			want:         render.NoContent(),
		},
	}

	for _, test := range tests {
		test := test
		t.Run(test.description, func(t *testing.T) {
			t.Parallel()

			r := httptest.NewRequest(http.MethodGet, "/", nil)
			r.Header.Add("X-API-KEY", test.apiKeyHeader)

			var (
				request = lit.NewRequest(r)
				got     = ValidateXAPIKeyHeader(testHandler)(request)
			)

			if !reflect.DeepEqual(got, test.want) {
				t.Fatalf("got: %v; want: %v", got, test.want)
			}
		})
	}
}
Example (AuthorizationMiddlewares)
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/bind"
	"github.com/jvcoutinho/lit/render"
	"github.com/jvcoutinho/lit/validate"
)

// ValidateXAPIKeyHeader validates if the X-API-Key header is not empty.
func ValidateXAPIKeyHeader(h lit.Handler) lit.Handler {
	return func(r *lit.Request) lit.Response {
		apiKeyHeader, err := bind.HeaderField[string](r, "X-API-KEY")
		if err != nil {
			return render.BadRequest(err)
		}

		if apiKeyHeader == "" {
			return render.Unauthorized("X-API-Key must be provided")
		}

		fmt.Printf("Authorized request for %s\n", apiKeyHeader)

		return h(r)
	}
}

type GetUserNameRequest struct {
	UserID string `uri:"user_id"`
}

func (r *GetUserNameRequest) Validate() []validate.Field {
	return []validate.Field{
		validate.UUID(&r.UserID),
	}
}

type GetUserNameResponse struct {
	Name string `json:"name"`
}

// GetUserName gets the name of an identified user.
func GetUserName(r *lit.Request) lit.Response {
	_, err := bind.URIParameters[GetUserNameRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	// getting user name...
	res := GetUserNameResponse{"John"}

	return render.OK(res)
}

type PatchUserNameRequest struct {
	UserID string `uri:"user_id"`
	Name   string `json:"name"`
}

func (r *PatchUserNameRequest) Validate() []validate.Field {
	return []validate.Field{
		validate.UUID(&r.UserID),
		validate.MinLength(&r.Name, 3),
	}
}

// PatchUserName patches the name of an identified user.
func PatchUserName(r *lit.Request) lit.Response {
	_, err := bind.Request[PatchUserNameRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	// patching user name...

	return render.NoContent()
}

func main() {
	r := lit.NewRouter()

	r.GET("/users/:user_id/name", GetUserName)
	r.PATCH("/users/:user_id/name", PatchUserName, ValidateXAPIKeyHeader)

	requestAPIKey(r, http.MethodGet, "/users/19fb2f66-f335-47ef-a1ca-1d02d1a117c8/name", "", nil)
	requestAPIKey(r, http.MethodPatch, "/users/19fb2f66-f335-47ef-a1ca-1d02d1a117c8/name",
		"", strings.NewReader(`{"name":"John"}`))
	requestAPIKey(r, http.MethodPatch, "/users/19fb2f66-f335-47ef-a1ca-1d02d1a117c8/name",
		"api-key-1", strings.NewReader(`{"name":"John"}`))

}

func requestAPIKey(r *lit.Router, method, path, header string, body io.Reader) {
	res := httptest.NewRecorder()
	req := httptest.NewRequest(method, path, body)
	req.Header.Add("X-API-KEY", header)

	r.ServeHTTP(res, req)

	fmt.Println(res.Body, res.Code)
}
Output:

{"name":"John"} 200
{"message":"X-API-Key must be provided"} 401
Authorized request for api-key-1
 204
Example (CalculatorAPI)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/bind"
	"github.com/jvcoutinho/lit/render"
	"github.com/jvcoutinho/lit/validate"
)

type BinaryOperationRequest struct {
	A int `query:"a"`
	B int `query:"b"`
}

// Add returns the sum of two integers.
func Add(r *lit.Request) lit.Response {
	req, err := bind.Query[BinaryOperationRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A + req.B)
}

// Subtract returns the subtraction of two integers.
func Subtract(r *lit.Request) lit.Response {
	req, err := bind.Query[BinaryOperationRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A - req.B)
}

// Multiply returns the multiplication of two integers.
func Multiply(r *lit.Request) lit.Response {
	req, err := bind.Query[BinaryOperationRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A * req.B)
}

// Divide returns the division of two integers, granted the divisor is different from zero.
func Divide(r *lit.Request) lit.Response {
	req, err := bind.Query[BinaryOperationRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	if err := validate.Fields(&req,
		validate.NotEqual(&req.B, 0),
	); err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A / req.B)
}

func main() {
	r := lit.NewRouter()
	r.GET("/add", Add)
	r.GET("/sub", Subtract)
	r.GET("/mul", Multiply)
	r.GET("/div", Divide)

	request(r, "/add?a=2&b=3")
	request(r, "/sub?a=3&b=3")
	request(r, "/mul?a=3&b=9")
	request(r, "/div?a=6&b=2")

	request(r, "/div?a=6&b=0")
	request(r, "/add?a=2a&b=3")

}

func request(r *lit.Router, path string) {
	res := httptest.NewRecorder()

	r.ServeHTTP(
		res,
		httptest.NewRequest(http.MethodGet, path, nil),
	)

	fmt.Println(res.Body)
}
Output:

5
0
27
3
{"message":"b should not be equal to 0"}
{"message":"a: 2a is not a valid int: invalid syntax"}
Example (CorsHandler)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/render"
)

// EnableCORS handles a preflight CORS OPTIONS request.
func EnableCORS(_ *lit.Request) lit.Response {
	res := render.NoContent()

	return lit.ResponseFunc(func(w http.ResponseWriter) {
		res.Write(w)

		header := w.Header()
		header.Set("Access-Control-Allow-Origin", "*")
		header.Set("Access-Control-Allow-Credentials", "false")
		header.Set("Access-Control-Allow-Methods", header.Get("Allow"))
	})
}

func HelloWorld(_ *lit.Request) lit.Response {
	return render.OK("Hello, World!")
}

func main() {
	r := lit.NewRouter()
	r.HandleOPTIONS(EnableCORS)

	r.GET("/", HelloWorld)

	req := httptest.NewRequest(http.MethodOptions, "/", nil)
	res := httptest.NewRecorder()

	r.ServeHTTP(res, req)

	fmt.Println(res.Header(), res.Code)

}
Output:

map[Access-Control-Allow-Credentials:[false] Access-Control-Allow-Methods:[GET, OPTIONS] Access-Control-Allow-Origin:[*] Allow:[GET, OPTIONS]] 204
Example (CustomMethodNotAllowedHandler)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/render"
)

func MethodNotAllowedHandler(_ *lit.Request) lit.Response {
	return render.JSON(http.StatusMethodNotAllowed, "Unsupported method")
}

func main() {
	r := lit.NewRouter()
	r.HandleMethodNotAllowed(MethodNotAllowedHandler)

	r.GET("/", HelloWorld)

	req := httptest.NewRequest(http.MethodPost, "/", nil)
	res := httptest.NewRecorder()

	r.ServeHTTP(res, req)

	fmt.Println(res.Body, res.Code)

}
Output:

{"message":"Unsupported method"} 405
Example (CustomNotFoundHandler)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/render"
)

func NotFoundHandler(_ *lit.Request) lit.Response {
	return render.NotFound("Not found. Try again later.")
}

func main() {
	r := lit.NewRouter()
	r.HandleNotFound(NotFoundHandler)

	req := httptest.NewRequest(http.MethodGet, "/", nil)
	res := httptest.NewRecorder()

	r.ServeHTTP(res, req)

	fmt.Println(res.Body, res.Code)

}
Output:

{"message":"Not found. Try again later."} 404

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler func(r *Request) Response

Handler handles requests.

func Log

func Log(h Handler) Handler

Log is a simple middleware that logs information data about the request:

  • The method and path of the request;
  • The status code of the response;
  • The time of the request;
  • The client's remote address;
  • The duration of the request;
  • The content length of the response body.

func Recover

func Recover(h Handler) Handler

Recover is a simple middleware that recovers if h panics, responding a 500 Internal Server Error with the panic value as the body and logging the stack trace in os.Stderr.

func (Handler) Base

func (h Handler) Base() http.HandlerFunc

Base returns the equivalent http.HandlerFunc of this handler.

type Middleware

type Middleware func(h Handler) Handler

Middleware transforms a Handler, extending its functionality.

type Recorder

type Recorder struct {

	// StatusCode of this response.
	StatusCode int

	// Size of this response.
	ContentLength int

	http.ResponseWriter
	http.Hijacker
	http.Flusher
	// contains filtered or unexported fields
}

Recorder is a http.ResponseWriter that keeps track of the response' status code and content length.

func NewRecorder

func NewRecorder(w http.ResponseWriter) *Recorder

NewRecorder creates a new *Recorder instance from a http.ResponseWriter.

func (*Recorder) Write

func (r *Recorder) Write(b []byte) (int, error)

func (*Recorder) WriteHeader

func (r *Recorder) WriteHeader(statusCode int)

type Request

type Request struct {
	// contains filtered or unexported fields
}

Request is the input of a Handler.

func NewEmptyRequest added in v0.1.1

func NewEmptyRequest() *Request

NewEmptyRequest creates a new Request instance.

func NewRequest

func NewRequest(request *http.Request) *Request

NewRequest creates a new Request instance from a *http.Request.

If request is nil, NewRequest panics.

func (*Request) Base

func (r *Request) Base() *http.Request

Base returns the equivalent *http.Request of this request.

func (*Request) Body

func (r *Request) Body() io.ReadCloser

Body of this request.

func (*Request) Context

func (r *Request) Context() context.Context

Context of this request.

func (*Request) Header

func (r *Request) Header() http.Header

Header fields of this request.

func (*Request) Method

func (r *Request) Method() string

Method of this request.

func (*Request) URIParameters

func (r *Request) URIParameters() map[string]string

URIParameters returns this request's URL path parameters and their values. It can be nil, meaning the handler expects no parameters.

Use [bind.URIParameters] for standard model binding and validation features.

The keys from this map don't start with the ":" prefix.

func (*Request) URL

func (r *Request) URL() *url.URL

URL of this request.

func (*Request) WithContext

func (r *Request) WithContext(ctx context.Context) *Request

WithContext sets the context of this request.

If ctx is nil, WithContext panics.

func (*Request) WithRequest added in v0.1.1

func (r *Request) WithRequest(req *http.Request) *Request

WithRequest sets the base request of this request.

If req is nil, WithRequest panics.

func (*Request) WithURIParameters

func (r *Request) WithURIParameters(parameters map[string]string) *Request

WithURIParameters sets the URI parameters (associated with their values) of this request.

type Response

type Response interface {
	// Write responds the request.
	Write(w http.ResponseWriter)
}

Response is the output of a Handler.

type ResponseFunc

type ResponseFunc func(w http.ResponseWriter)

ResponseFunc writes response data into http.ResponseWriter.

func (ResponseFunc) Write

func (r ResponseFunc) Write(w http.ResponseWriter)

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router manages, listens and serves HTTP requests.

func NewRouter

func NewRouter() *Router

NewRouter creates a new Router instance.

func (*Router) DELETE

func (r *Router) DELETE(path string, handler Handler, middlewares ...Middleware)

DELETE registers handler for path and DELETE method and optional local middlewares.

It's equivalent to:

Handle(path, "DELETE", handler, middlewares)

func (*Router) GET

func (r *Router) GET(path string, handler Handler, middlewares ...Middleware)

GET registers handler for path and GET method and optional local middlewares.

It's equivalent to:

Handle(path, "GET", handler, middlewares)

func (*Router) HEAD

func (r *Router) HEAD(path string, handler Handler, middlewares ...Middleware)

HEAD registers handler for path and HEAD method and optional local middlewares.

It's equivalent to:

Handle(path, "HEAD", handler, middlewares)

func (*Router) Handle

func (r *Router) Handle(path string, method string, handler Handler, middlewares ...Middleware)

Handle registers handler for path and method and optional local middlewares.

Middlewares transform handler. They are applied in reverse order, and local middlewares are always applied first. For example, suppose there have been defined global middlewares G1 and G2 in this order and local middlewares L1 and L2 in this order. The response for the request r is

(G1(G2(L1(L2(handler)))))(r)

If path does not contain a leading slash, method is empty, handler is nil or a middleware is nil, Handle panics.

func (*Router) HandleMethodNotAllowed

func (r *Router) HandleMethodNotAllowed(handler Handler)

HandleMethodNotAllowed registers handler to be called when there is a match to a route, but not with that method. By default, Lit uses a wrapped http.Error with status code 405 Method Not Allowed.

If handler is nil, HandleMethodNotAllowed clears the current set handler. In this case, the behaviour is to call the Not Found handler (either the one defined in HandleNotFound or the default one).

func (*Router) HandleNotFound

func (r *Router) HandleNotFound(handler Handler)

HandleNotFound registers handler to be called when no matching route is found. By default, Lit uses a wrapped http.NotFound.

If handler is nil, HandleNotFound panics.

func (*Router) HandleOPTIONS added in v0.1.4

func (r *Router) HandleOPTIONS(handler Handler)

HandleOPTIONS registers handler to be called when the request method is OPTIONS. By default, Lit sets the Allow header with supported methods.

Useful to support preflight CORS requests, for instance.

If handler is nil, HandleOPTIONS clears the current set handler. In this case, the behaviour is to call the registered handler normally, if there is one.

func (*Router) OPTIONS

func (r *Router) OPTIONS(path string, handler Handler, middlewares ...Middleware)

OPTIONS registers handler for path and OPTIONS method and optional local middlewares.

It's equivalent to:

Handle(path, "OPTIONS", handler, middlewares)

func (*Router) PATCH

func (r *Router) PATCH(path string, handler Handler, middlewares ...Middleware)

PATCH registers handler for path and PATCH method and optional local middlewares.

It's equivalent to:

Handle(path, "PATCH", handler, middlewares)

func (*Router) POST

func (r *Router) POST(path string, handler Handler, middlewares ...Middleware)

POST registers handler for path and POST method and optional local middlewares.

It's equivalent to:

Handle(path, "POST", handler, middlewares)

func (*Router) PUT

func (r *Router) PUT(path string, handler Handler, middlewares ...Middleware)

PUT registers handler for path and PUT method and optional local middlewares.

It's equivalent to:

Handle(path, "PUT", handler, middlewares)

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(writer http.ResponseWriter, request *http.Request)

ServeHTTP dispatches the request to the handler whose pattern most closely matches the request URL and whose method is the same as the request method.

func (*Router) Use

func (r *Router) Use(m Middleware)

Use registers m as a global middleware. They run in every request.

Middlewares transform the request handler. They are applied in reverse order, and local middlewares are always applied first. For example, suppose there have been defined global middlewares G1 and G2 in this order and local middlewares L1 and L2 in this order. The response for the request r handled by h is

(G1(G2(L1(L2(h)))))(r)

Global middlewares should be set before any handler is registered.

If m is nil, Use panics.

Directories

Path Synopsis
Package bind contains model binding features to be used along [*lit.Request].
Package bind contains model binding features to be used along [*lit.Request].
Package render contains implementations of [lit.Response], suitable for responding requests.
Package render contains implementations of [lit.Response], suitable for responding requests.
Package validate contains field validations for Go structs, appropriated to use with [*lit.Request].
Package validate contains field validations for Go structs, appropriated to use with [*lit.Request].

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL