don

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Jun 4, 2023 License: MIT Imports: 19 Imported by: 1

README

Don - Go API Framework

GoDoc Codecov Go Report Card

Don is a fast & simple API framework written in Go. It features a super-simple API and thanks to fasthttp and a custom version of httprouter it's blazing fast and has a low memory footprint.

As Don uses Go generics it requires Go 1.18 to work.
Minor version updates should be considered breaking changes.

Contents

Basic Example

package main

import (
  "context"
  "errors"
  "fmt"
  "net/http"

  "github.com/abemedia/go-don"
  _ "github.com/abemedia/go-don/encoding/json" // Enable JSON parsing & rendering.
  _ "github.com/abemedia/go-don/encoding/yaml" // Enable YAML parsing & rendering.
)

type GreetRequest struct {
  Name string `path:"name"`         // Get name from the URL path.
  Age  int    `header:"X-User-Age"` // Get age from HTTP header.
}

type GreetResponse struct {
  // Remember to add all the tags for the renderers you enable.
  Greeting string `json:"data" yaml:"data"`
}

func Greet(ctx context.Context, req GreetRequest) (*GreetResponse, error) {
  if req.Name == "" {
    return nil, don.Error(errors.New("missing name"), http.StatusBadRequest)
  }

  res := &GreetResponse{
    Greeting: fmt.Sprintf("Hello %s, you're %d years old.", req.Name, req.Age),
  }

  return res, nil
}

func Pong(context.Context, any) (string, error) {
  return "pong", nil
}

func main() {
  r := don.New(nil)
  r.Get("/ping", don.H(Pong)) // Handlers are wrapped with `don.H`.
  r.Post("/greet/:name", don.H(Greet))
  r.ListenAndServe(":8080")
}

Configuration

Don is configured by passing in the Config struct to don.New.

r := don.New(&don.Config{
  DefaultEncoding: "application/json",
  DisableNoContent: false,
})
DefaultEncoding

Set this to the format you'd like to use if no Content-Type or Accept headers are in the request.

DisableNoContent

If you return nil from your handler, Don will respond with an empty body and a 204 No Content status code. Set this to true to disable that behaviour.

Support multiple formats

Support multiple request & response formats without writing extra parsing or rendering code. The API uses the Content-Type and Accept headers to determine what input and output encoding to use.

You can mix multiple formats, for example if the Content-Type header is set to application/json, however the Accept header is set to application/x-yaml, then the request will be parsed as JSON, and the response will be YAML encoded.

If no Content-Type or Accept header is passed the default will be used.

Formats need to be explicitly imported e.g.

import _ "github.com/abemedia/go-don/encoding/yaml"
Currently supported formats
JSON

MIME: application/json

Parses JSON requests & encodes responses as JSON. Use the json tag in your request & response structs.

XML

MIME: application/xml, text/xml

Parses XML requests & encodes responses as XML. Use the xml tag in your request & response structs.

YAML

MIME: application/yaml, text/yaml, application/x-yaml, text/x-yaml, text/vnd.yaml

Parses YAML requests & encodes responses as YAML. Use the yaml tag in your request & response structs.

Form (input only)

MIME: application/x-www-form-urlencoded, multipart/form-data

Parses form data requests. Use the form tag in your request struct.

Text

MIME: text/plain

Parses non-struct requests and encodes non-struct responses e.g. string, int, bool etc.

func MyHandler(ctx context.Context, req int64) (string, error) {
  // ...
}

If the request is a struct and the Content-Type header is set to text/plain it returns a 415 Unsupported Media Type error.

MessagePack

MIME: application/msgpack, application/x-msgpack, application/vnd.msgpack

Parses MessagePack requests & encodes responses as MessagePack. Use the msgpack tag in your request & response structs.

TOML

MIME: application/toml

Parses TOML requests & encodes responses as TOML. Use the toml tag in your request & response structs.

Protocol Buffers

MIME: application/protobuf, application/x-protobuf

Parses protobuf requests & encodes responses as protobuf. Use pointer types generated with protoc as your request & response structs.

Adding custom encoding

Adding your own is easy. See encoding/json/json.go.

Request parsing

Automatically unmarshals values from headers, URL query, URL path & request body into your request struct.

type MyRequest struct {
  // Get from the URL path.
  ID int64 `path:"id"`

  // Get from the URL query.
  Filter string `query:"filter"`

  // Get from the JSON, YAML, XML or form body.
  Content float64 `form:"bar" json:"bar" yaml:"bar" xml:"bar"`

  // Get from the HTTP header.
  Lang string `header:"Accept-Language"`
}

Please note that using a pointer as the request type negatively affects performance.

Headers & response codes

Implement the StatusCoder and Headerer interfaces to customise headers and response codes.

type MyResponse struct {
  Foo  string `json:"foo"`
}

// Set a custom HTTP response code.
func (nr *MyResponse) StatusCode() int {
  return 201
}

// Add custom headers to the response.
func (nr *MyResponse) Header() http.Header {
  header := http.Header{}
  header.Set("foo", "bar")
  return header
}

Sub-routers

You can create sub-routers using the Group function:

r := don.New(nil)
sub := r.Group("/api")
sub.Get("/hello", don.H(Hello))

Middleware

Don uses the standard fasthttp middleware format of func(fasthttp.RequestHandler) fasthttp.RequestHandler.

For example:

func loggingMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
  return func(ctx *fasthttp.RequestCtx) {
    log.Println(string(ctx.RequestURI()))
    next(ctx)
  }
}

It is registered on a router using Use e.g.

r := don.New(nil)
r.Post("/", don.H(handler))
r.Use(loggingMiddleware)

Middleware registered on a group only applies to routes in that group and child groups.

r := don.New(nil)
r.Get("/login", don.H(loginHandler))
r.Use(loggingMiddleware) // applied to all routes

api := r.Group("/api")
api.Get("/hello", don.H(helloHandler))
api.Use(authMiddleware) // applied to routes `/api/hello` and `/api/v2/bye`


v2 := api.Group("/v2")
v2.Get("/bye", don.H(byeHandler))
v2.Use(corsMiddleware) // only applied to `/api/v2/bye`

To pass values from the middleware to the handler extend the context e.g.

func myMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
  return func(ctx *fasthttp.RequestCtx) {
    ctx.SetUserValue(ContextUserKey, "my_user")
    next(ctx)
  }
}

This can now be accessed in the handler:

user := ctx.Value(ContextUserKey).(string)

Benchmarks

To give you a rough idea of Don's performance, here is a comparison with Gin.

Request Parsing

Don has extremely fast & efficient binding of request data.

Benchmark name (1) (2) (3) (4)
BenchmarkDon_BindRequest 2947474 390.3 ns/op 72 B/op 2 allocs/op
BenchmarkGin_BindRequest 265609 4377 ns/op 1193 B/op 21 allocs/op

Source: benchmarks/binding_test.go

Serving HTTP Requests

Keep in mind that the majority of time here is actually the HTTP roundtrip.

Benchmark name (1) (2) (3) (4)
BenchmarkDon_HTTP 45500 25384 ns/op 32 B/op 3 allocs/op
BenchmarkGin_HTTP 22995 49865 ns/op 2313 B/op 21 allocs/op

Source: benchmarks/http_test.go

Documentation

Index

Constants

Variables

View Source
var DefaultEncoding = "text/plain"

DefaultEncoding contains the media type of the default encoding to fall back on if no `Accept` or `Content-Type` header was provided.

Functions

func E

func H

func H[T, O any](handle Handle[T, O]) httprouter.Handle

H wraps your handler function with the Go generics magic.

Types

type API

type API struct {
	NotFound         fasthttp.RequestHandler
	MethodNotAllowed fasthttp.RequestHandler
	PanicHandler     func(*fasthttp.RequestCtx, any)
	// contains filtered or unexported fields
}

func New

func New(c *Config) *API

New creates a new API instance.

func (*API) Delete

func (r *API) Delete(path string, handle httprouter.Handle)

Delete is a shortcut for router.Handle(http.MethodDelete, path, handle).

func (*API) Get

func (r *API) Get(path string, handle httprouter.Handle)

Get is a shortcut for router.Handle(http.MethodGet, path, handle).

func (*API) Group

func (r *API) Group(path string) Router

Group creates a new sub-router with a common prefix.

func (*API) Handle

func (r *API) Handle(method, path string, handle httprouter.Handle)

Handle registers a new request handle with the given path and method.

func (*API) HandleFunc

func (r *API) HandleFunc(method, path string, handle http.HandlerFunc)

HandlerFunc is an adapter which allows the usage of an http.HandlerFunc as a request handle.

func (*API) Handler

func (r *API) Handler(method, path string, handle http.Handler)

Handler is an adapter which allows the usage of an http.Handler as a request handle.

func (*API) ListenAndServe

func (r *API) ListenAndServe(addr string) error

ListenAndServe serves HTTP requests from the given TCP4 addr.

func (*API) Patch

func (r *API) Patch(path string, handle httprouter.Handle)

Patch is a shortcut for router.Handle(http.MethodPatch, path, handle).

func (*API) Post

func (r *API) Post(path string, handle httprouter.Handle)

Post is a shortcut for router.Handle(http.MethodPost, path, handle).

func (*API) Put

func (r *API) Put(path string, handle httprouter.Handle)

Put is a shortcut for router.Handle(http.MethodPut, path, handle).

func (*API) RequestHandler

func (r *API) RequestHandler() fasthttp.RequestHandler

RequestHandler creates a fasthttp.RequestHandler for the API.

func (*API) Serve added in v0.1.3

func (r *API) Serve(ln net.Listener) error

Serve serves incoming connections from the given listener.

func (*API) Use

func (r *API) Use(mw ...Middleware)

Use registers a middleware.

type Config

type Config struct {
	// DefaultEncoding contains the mime type of the default encoding to fall
	// back on if no `Accept` or `Content-Type` header was provided.
	DefaultEncoding string

	// DisableNoContent controls whether a nil or zero value response should
	// automatically return 204 No Content with an empty body.
	DisableNoContent bool
}

type HTTPError

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

func Error

func Error(err error, code int) *HTTPError

func (*HTTPError) Error

func (e *HTTPError) Error() string

func (*HTTPError) Is added in v0.1.3

func (e *HTTPError) Is(err error) bool

func (*HTTPError) MarshalJSON

func (e *HTTPError) MarshalJSON() ([]byte, error)

func (*HTTPError) MarshalText

func (e *HTTPError) MarshalText() ([]byte, error)

func (*HTTPError) MarshalXML

func (e *HTTPError) MarshalXML(enc *xml.Encoder, start xml.StartElement) error

func (*HTTPError) MarshalYAML

func (e *HTTPError) MarshalYAML() (any, error)

func (*HTTPError) StatusCode

func (e *HTTPError) StatusCode() int

func (*HTTPError) Unwrap added in v0.1.3

func (e *HTTPError) Unwrap() error

type Handle

type Handle[T, O any] func(ctx context.Context, request T) (O, error)

Handle is the type for your handlers.

type Headerer

type Headerer interface {
	Header() http.Header
}

Headerer allows you to customise the HTTP headers.

type Router

type Router interface {
	Get(path string, handle httprouter.Handle)
	Post(path string, handle httprouter.Handle)
	Put(path string, handle httprouter.Handle)
	Patch(path string, handle httprouter.Handle)
	Delete(path string, handle httprouter.Handle)
	Handle(method, path string, handle httprouter.Handle)
	Handler(method, path string, handle http.Handler)
	HandleFunc(method, path string, handle http.HandlerFunc)
	Group(path string) Router
	Use(mw ...Middleware)
}

type StatusCoder

type StatusCoder interface {
	StatusCode() int
}

StatusCoder allows you to customise the HTTP response code.

type StatusError

type StatusError int

StatusError creates an error from an HTTP status code.

func (StatusError) Error

func (e StatusError) Error() string

func (StatusError) StatusCode

func (e StatusError) StatusCode() int

Directories

Path Synopsis
xml
examples
internal
pkg

Jump to

Keyboard shortcuts

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