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("*/*")