github.com/abemedia/go-don@v0.2.2-0.20240329015135-be88e32bb73b/api.go (about)

     1  // Package don provides a fast and efficient API framework.
     2  package don
     3  
     4  import (
     5  	"bytes"
     6  	"net"
     7  	"net/http"
     8  
     9  	"github.com/abemedia/httprouter"
    10  	"github.com/valyala/fasthttp"
    11  )
    12  
    13  // DefaultEncoding contains the media type of the default encoding to fall back
    14  // on if no `Accept` or `Content-Type` header was provided.
    15  var DefaultEncoding = "text/plain"
    16  
    17  type Middleware func(fasthttp.RequestHandler) fasthttp.RequestHandler
    18  
    19  type Router interface {
    20  	Get(path string, handle httprouter.Handle)
    21  	Post(path string, handle httprouter.Handle)
    22  	Put(path string, handle httprouter.Handle)
    23  	Patch(path string, handle httprouter.Handle)
    24  	Delete(path string, handle httprouter.Handle)
    25  	Handle(method, path string, handle httprouter.Handle)
    26  	Handler(method, path string, handle http.Handler)
    27  	HandleFunc(method, path string, handle http.HandlerFunc)
    28  	Group(path string) Router
    29  	Use(mw ...Middleware)
    30  }
    31  
    32  type API struct {
    33  	NotFound         fasthttp.RequestHandler
    34  	MethodNotAllowed fasthttp.RequestHandler
    35  	PanicHandler     func(*fasthttp.RequestCtx, any)
    36  
    37  	router *httprouter.Router
    38  	config *Config
    39  	mw     []Middleware
    40  }
    41  
    42  type Config struct {
    43  	// DefaultEncoding contains the mime type of the default encoding to fall
    44  	// back on if no `Accept` or `Content-Type` header was provided.
    45  	DefaultEncoding string
    46  
    47  	// DisableNoContent controls whether a nil or zero value response should
    48  	// automatically return 204 No Content with an empty body.
    49  	DisableNoContent bool
    50  }
    51  
    52  // New creates a new API instance.
    53  func New(c *Config) *API {
    54  	if c == nil {
    55  		c = &Config{}
    56  	}
    57  
    58  	if c.DefaultEncoding == "" {
    59  		c.DefaultEncoding = DefaultEncoding
    60  	}
    61  
    62  	return &API{
    63  		router:           httprouter.New(),
    64  		config:           c,
    65  		NotFound:         E(ErrNotFound),
    66  		MethodNotAllowed: E(ErrMethodNotAllowed),
    67  	}
    68  }
    69  
    70  // Get is a shortcut for router.Handle(http.MethodGet, path, handle).
    71  func (r *API) Get(path string, handle httprouter.Handle) {
    72  	r.Handle(http.MethodGet, path, handle)
    73  }
    74  
    75  // Post is a shortcut for router.Handle(http.MethodPost, path, handle).
    76  func (r *API) Post(path string, handle httprouter.Handle) {
    77  	r.Handle(http.MethodPost, path, handle)
    78  }
    79  
    80  // Put is a shortcut for router.Handle(http.MethodPut, path, handle).
    81  func (r *API) Put(path string, handle httprouter.Handle) {
    82  	r.Handle(http.MethodPut, path, handle)
    83  }
    84  
    85  // Patch is a shortcut for router.Handle(http.MethodPatch, path, handle).
    86  func (r *API) Patch(path string, handle httprouter.Handle) {
    87  	r.Handle(http.MethodPatch, path, handle)
    88  }
    89  
    90  // Delete is a shortcut for router.Handle(http.MethodDelete, path, handle).
    91  func (r *API) Delete(path string, handle httprouter.Handle) {
    92  	r.Handle(http.MethodDelete, path, handle)
    93  }
    94  
    95  // Handle registers a new request handle with the given path and method.
    96  func (r *API) Handle(method, path string, handle httprouter.Handle) {
    97  	r.router.Handle(method, path, handle)
    98  }
    99  
   100  // Handler is an adapter which allows the usage of an http.Handler as a request handle.
   101  func (r *API) Handler(method, path string, handle http.Handler) {
   102  	r.router.Handler(method, path, handle)
   103  }
   104  
   105  // HandleFunc is an adapter which allows the usage of an http.HandlerFunc as a request handle.
   106  func (r *API) HandleFunc(method, path string, handle http.HandlerFunc) {
   107  	r.router.HandlerFunc(method, path, handle)
   108  }
   109  
   110  // Group creates a new sub-router with a common prefix.
   111  func (r *API) Group(path string) Router {
   112  	return &group{prefix: path, r: r}
   113  }
   114  
   115  // Use registers a middleware.
   116  func (r *API) Use(mw ...Middleware) {
   117  	r.mw = append(r.mw, mw...)
   118  }
   119  
   120  // RequestHandler creates a fasthttp.RequestHandler for the API.
   121  func (r *API) RequestHandler() fasthttp.RequestHandler {
   122  	r.router.NotFound = r.NotFound
   123  	r.router.MethodNotAllowed = r.MethodNotAllowed
   124  	r.router.PanicHandler = r.PanicHandler
   125  
   126  	h := r.router.HandleFastHTTP
   127  	for _, mw := range r.mw {
   128  		h = mw(h)
   129  	}
   130  
   131  	return func(ctx *fasthttp.RequestCtx) {
   132  		ct := ctx.Request.Header.ContentType()
   133  		if len(ct) == 0 || bytes.HasPrefix(ct, anyEncoding) {
   134  			ctx.Request.Header.SetContentType(r.config.DefaultEncoding)
   135  		}
   136  
   137  		ac := ctx.Request.Header.Peek(fasthttp.HeaderAccept)
   138  		if len(ac) == 0 || bytes.HasPrefix(ac, anyEncoding) {
   139  			ctx.Request.Header.Set(fasthttp.HeaderAccept, r.config.DefaultEncoding)
   140  		}
   141  
   142  		h(ctx)
   143  
   144  		// Content-Length of -3 means handler returned nil.
   145  		if ctx.Response.Header.ContentLength() == -3 {
   146  			ctx.Response.Header.Del(fasthttp.HeaderTransferEncoding)
   147  
   148  			if !r.config.DisableNoContent {
   149  				ctx.Response.SetBody(nil)
   150  
   151  				if ctx.Response.StatusCode() == fasthttp.StatusOK {
   152  					ctx.Response.SetStatusCode(fasthttp.StatusNoContent)
   153  				}
   154  			}
   155  		}
   156  	}
   157  }
   158  
   159  // ListenAndServe serves HTTP requests from the given TCP4 addr.
   160  func (r *API) ListenAndServe(addr string) error {
   161  	return newServer(r).ListenAndServe(addr)
   162  }
   163  
   164  // Serve serves incoming connections from the given listener.
   165  func (r *API) Serve(ln net.Listener) error {
   166  	return newServer(r).Serve(ln)
   167  }
   168  
   169  func newServer(r *API) *fasthttp.Server {
   170  	return &fasthttp.Server{
   171  		Handler:               r.RequestHandler(),
   172  		StreamRequestBody:     true,
   173  		NoDefaultContentType:  true,
   174  		NoDefaultServerHeader: true,
   175  	}
   176  }
   177  
   178  var anyEncoding = []byte("*/*")