Documentation
¶
Overview ¶
Example ¶
package main import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "log" "net/http" "net/http/httptest" "github.com/Prastiwar/Go-flow/httpf" ) // runServer runs new server and returns its address and close function. func runServer(router httpf.Router) (string, func()) { server := httptest.NewServer(router) return server.URL, server.Close } func main() { mux := httpf.NewServeMuxBuilder() mux.WithErrorHandler(httpf.ErrorHandlerFunc(func(w http.ResponseWriter, r *http.Request, err error) { // define standard structure for error response type httpError struct { Error string `json:"error"` Code int `json:"code"` } // map infrastructure errors to http error response var resultError httpError if errors.Is(err, context.DeadlineExceeded) { resultError = httpError{ Error: http.StatusText(http.StatusRequestTimeout), Code: http.StatusRequestTimeout, } } else { resultError = httpError{ Error: http.StatusText(http.StatusInternalServerError), Code: http.StatusInternalServerError, } } // marshal error and write to Response result, err := json.Marshal(resultError) if err != nil { log.Fatal(err) } _, err = w.Write(result) if err != nil { log.Fatal(err) } })) mux.Post("/api/test/", httpf.HandlerFunc(func(w httpf.ResponseWriter, r *http.Request) error { result := struct { Id string `json:"id"` }{ Id: "1234", } return w.Response(http.StatusCreated, result) })) serverAddress, cleanup := runServer(mux.Build()) defer cleanup() resp, err := http.Post(serverAddress+"/api/test/", httpf.ApplicationJsonType, bytes.NewBufferString("{}")) if err != nil { fmt.Println(err) return } defer resp.Body.Close() fmt.Println(resp.StatusCode) body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
Output: 201 {"id":"1234"}
Index ¶
- Constants
- Variables
- func HasParam(r *http.Request, key string) bool
- func IsErrorStatus(code int) bool
- func Json(w http.ResponseWriter, status int, data interface{}) error
- func NewClient(opts ...ClientOption) *client
- func NewServeMuxBuilder() *serveMuxBuilder
- func NewServer(addr string, router Router) *http.Server
- func Param(r *http.Request, key string) string
- func Params(r *http.Request) map[string]string
- func WithParams(r *http.Request, params map[string]string) *http.Request
- type BodyUnmarshaler
- type Client
- type ClientOption
- type ClientOptions
- type ErrorHandler
- type ErrorHandlerFunc
- type Handler
- type HandlerFunc
- type ParamsParser
- type ParamsParserFunc
- type RateHttpKeyFactory
- type ResponseWriter
- type RouteBuilder
- type Router
- type Server
Examples ¶
Constants ¶
const ( AcceptHeader = "Accept" AcceptCharsetHeader = "Accept-Charset" AcceptEncodingHeader = "Accept-Encoding" AcceptLanguageHeader = "Accept-Language" AcceptRangesHeader = "Accept-Ranges" CacheControlHeader = "Cache-Control" CcHeader = "Cc" ConnectionHeader = "Connection" ContentIdHeader = "Content-Id" ContentLanguageHeader = "Content-Language" ContentLengthHeader = "Content-Length" ContentTransferEncodingHeader = "Content-Transfer-Encoding" ContentTypeHeader = "Content-Type" CookieHeader = "Cookie" DateHeader = "Date" ExpiresHeader = "Expires" FromHeader = "From" HostHeader = "Host" LocationHeader = "Location" ServerHeader = "Server" SetCookieHeader = "Set-Cookie" UserAgentHeader = "User-Agent" XForwardedForHeader = "X-Forwarded-For" ApplicationJsonType = "application/json" ApplicationFormEncodedType = "application/x-www-form-urlencoded" )
const ( RateLimitLimitHeader = "X-Rate-Limit-Limit" RateLimitRemainingHeader = "X-Rate-Limit-Remaining" RateLimitResetHeader = "X-Rate-Limit-Reset" RetryAfterHeader = "Retry-After" )
Variables ¶
var ( ErrMissingRateStore = errors.New("nil LimiterStore passed as parameter") ErrMissingRateKeyFactory = errors.New("nil RateHttpKeyFactory passed as parameter") )
Functions ¶
func IsErrorStatus ¶ added in v0.12.0
IsErrorStatus returns true if status code is greater or equal than 400 and less than 600.
func Json ¶
func Json(w http.ResponseWriter, status int, data interface{}) error
Json marshals the data and writes it to http.ResponseWriter with given status code. "Content-Type" header is set to "application/json".
func NewClient ¶
func NewClient(opts ...ClientOption) *client
NewClient returns a new instace of Client which is adapter for http.Client. Provided options can be set optionally to pass the values in http.Client construction.
func NewServeMuxBuilder ¶
func NewServeMuxBuilder() *serveMuxBuilder
NewServeMuxBuilder returns RouterBuilder which build results in adapting http.ServeMux implementation to handle errors, decorate http.ResponseWriter or use ParamsParser. Note http.ServeMux does not support defining parameters in pattern. For default behaviour of corresponding With.. option can be found in option func comment.
func Param ¶
Params returns raw value for path param by key. If no key was set it returns "". To distinguish between empty value and value was not set use HasParam or Params directly.
Types ¶
type BodyUnmarshaler ¶ added in v0.12.0
BodyUnmarshaler is an interface that defines a method to unmarshal the body of an HTTP response into a value of any type. The implementation of this interface should handle the cases where the response status code indicates an error and return an appropriate error value.
func NewBodyUnmarshaler ¶ added in v0.12.0
func NewBodyUnmarshaler(u datas.ReaderUnmarshaler, errorHandler func(r *http.Response, u datas.ReaderUnmarshaler) error) BodyUnmarshaler
NewBodyUnmarshaler returns an implementation for BodyUnmarshaler which unmarshals response body and supports error handling. Passed unmarshaler will be used to unmarshal both, actual body value or error value from body response. If IsErrorStatus will return true during Unmarshal, it will call the errorHandler to transform error or handle it.
Example ¶
package main import ( "fmt" "net/http" "strconv" "github.com/Prastiwar/Go-flow/datas" "github.com/Prastiwar/Go-flow/httpf" ) type DummyJsonProducts struct { Products []DummyJsonProduct `json:"products"` } type DummyJsonProduct struct { ID int `json:"id"` Title string `json:"title"` Price int `json:"price"` } type HttpErr struct { Message string `json:"message"` } func (err *HttpErr) Error() string { return err.Message } type DummyJsonClient struct { unmarshaler httpf.BodyUnmarshaler } func NewDummyJsonClient() *DummyJsonClient { return &DummyJsonClient{ unmarshaler: httpf.NewBodyUnmarshalerWithError(datas.Json(), &HttpErr{}), } } func (c *DummyJsonClient) GetProducts() (*DummyJsonProducts, error) { resp, err := http.Get("https://dummyjson.com/products") if err != nil { return nil, err } var data DummyJsonProducts if err := c.unmarshaler.Unmarshal(resp, &data); err != nil { if _, ok := err.(*HttpErr); ok { return nil, err } return nil, err } return &data, nil } func (c *DummyJsonClient) GetProduct(id int) (*DummyJsonProduct, error) { resp, err := http.Get("https://dummyjson.com/products/" + strconv.Itoa(id)) if err != nil { return nil, err } var data DummyJsonProduct if err := c.unmarshaler.Unmarshal(resp, &data); err != nil { if _, ok := err.(*HttpErr); ok { return nil, err } return nil, err } return &data, nil } func main() { client := NewDummyJsonClient() data, err := client.GetProducts() if err != nil { panic(err) } product, err := client.GetProduct(data.Products[0].ID) if err != nil { panic(err) } fmt.Println(data.Products[0] == *product) }
Output: true
func NewBodyUnmarshalerWithError ¶ added in v0.12.0
func NewBodyUnmarshalerWithError(u datas.ReaderUnmarshaler, errorStruct error) BodyUnmarshaler
NewBodyUnmarshalerWithError returns an implementation for BodyUnmarshaler which unmarshals response body and supports http error unmarshaling. Passed unmarshaler will be used to unmarshal both, actual body value or error value from body response. errorStruct must be a pointer to error struct which indicates structure of the error returned from API in body response. If IsErrorStatus will return true during Unmarshal, it will copy errorStruct and unmarshal error response into copied value and return it. If custom error handling e.g for 404 codes is needed, you should use NewBodyUnmarshaler.
type Client ¶
type Client interface { Send(ctx context.Context, req *http.Request) (*http.Response, error) Get(ctx context.Context, url string) (*http.Response, error) Post(ctx context.Context, url string, body io.Reader) (*http.Response, error) PostForm(ctx context.Context, url string, form url.Values) (*http.Response, error) Put(ctx context.Context, url string, body io.Reader) (*http.Response, error) Delete(ctx context.Context, url string) (*http.Response, error) Close() }
A Client is an HTTP client containing convenient API to send request with common HTTP methods. Send function is the fundamental implementation for the Client which provides a way to send request over HTTP and receive response.
type ClientOption ¶
type ClientOption func(*ClientOptions)
ClientOption defines single function to mutate options.
func WithCookies ¶
func WithCookies(cookieJar http.CookieJar) ClientOption
WithCookies sets option which specifies cookie jar used to insert relevant cookies into every outbound Request and is updated with the cookie values of every inbound Response.
func WithRedirectHandler ¶
WithRedirectHandler sets option which specifies the policy for handling redirects.
func WithTimeout ¶
func WithTimeout(timeout time.Duration) ClientOption
WithTimeout sets option which specifies a time limit for requests made by Client.
func WithTransport ¶
func WithTransport(transport http.RoundTripper) ClientOption
WithTransport sets option which specifies the mechanism by which individual HTTP requests are made.
type ClientOptions ¶
type ClientOptions struct { Transport http.RoundTripper CheckRedirect func(req *http.Request, via []*http.Request) error Jar http.CookieJar Timeout time.Duration }
ClientOptions defines http.Client constructor parameters which can be set on NewClient.
func NewClientOptions ¶
func NewClientOptions(opts ...ClientOption) ClientOptions
NewClientOptions returns a new instance of ClientOptions with is result of merged ClientOption slice.
type ErrorHandler ¶
type ErrorHandler interface {
Handle(w http.ResponseWriter, r *http.Request, err error)
}
A ErrorHandler handles error returned from Handler
Handle should write response to the ResponseWriter in common used format with proper mapped error.
type ErrorHandlerFunc ¶
type ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
The ErrorHandlerFunc type is an adapter to allow the use of ordinary functions as Error handlers. If h is a function with the appropriate signature, ErrorHandlerFunc(h) is a ErrorHandler that calls h.
func (ErrorHandlerFunc) Handle ¶
func (h ErrorHandlerFunc) Handle(w http.ResponseWriter, r *http.Request, err error)
Handle calls h(w, r, err)
type Handler ¶
type Handler interface {
ServeHTTP(w ResponseWriter, r *http.Request) error
}
A Handler responds to an HTTP request
ServeHTTP should write reply headers and data to the ResponseWriter and then return any occurring error. The error should be handler by Router which should finish request process.
func RateLimitMiddleware ¶ added in v0.7.0
func RateLimitMiddleware(h Handler, store rate.LimiterStore, keyFactory RateHttpKeyFactory) Handler
RateLimitMiddleware returns httpf.Handler which uses rate-limiting feature to decide if h Handler can be requested. If rate limit exceeds maximum value, the error is returned and should be handled by ErrorHandler to actually return 429 status code with appropriate body. This middleware writes all of "X-Rate-Limit-Limit", "X-Rate-Limit-Remaining" and "X-Rate-Limit-Reset" headers with correct values.
Example ¶
package main import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "time" "github.com/Prastiwar/Go-flow/httpf" "github.com/Prastiwar/Go-flow/rate" "github.com/Prastiwar/Go-flow/tests/mocks" ) // runServer runs new server and returns its address and close function. func runServer(router httpf.Router) (string, func()) { server := httptest.NewServer(router) return server.URL, server.Close } func main() { mux := httpf.NewServeMuxBuilder() mux.WithErrorHandler(httpf.ErrorHandlerFunc(func(w http.ResponseWriter, r *http.Request, err error) { if errors.Is(err, rate.ErrRateLimitExceeded) { w.WriteHeader(http.StatusTooManyRequests) return } panic(err) })) var ( resetPeriod time.Duration = time.Second maxTokens uint64 = 2 tokens uint64 = maxTokens ) storeLimiter := mocks.LimiterStoreMock{ OnLimit: func(ctx context.Context, key string) (rate.Limiter, error) { return mocks.LimiterMock{ OnLimit: func() uint64 { return maxTokens }, OnTokens: func(ctx context.Context) (uint64, error) { return tokens, nil }, OnTake: func(ctx context.Context) (rate.Token, error) { return mocks.TokenMock{ OnUse: func() error { if tokens <= 0 { return rate.ErrRateLimitExceeded } tokens-- go func() { time.Sleep(resetPeriod) tokens++ }() return nil }, OnResetsAt: func() time.Time { return time.Now().Add(resetPeriod) }, }, nil }, }, nil }, } mux.Get("/api/test/", httpf.RateLimitMiddleware(httpf.HandlerFunc(func(w httpf.ResponseWriter, r *http.Request) error { return w.Response(http.StatusOK, nil) }), storeLimiter, httpf.PathRateKey())) serverAddress, cleanup := runServer(mux.Build()) defer cleanup() callGet := func() { resp, err := http.Get(serverAddress + "/api/test/") if err != nil { panic(err) } defer resp.Body.Close() fmt.Println(resp.StatusCode) } for i := uint64(0); i <= maxTokens; i++ { callGet() } time.Sleep(resetPeriod) callGet() }
Output: 200 200 429 200
func RecoverMiddleware ¶ added in v0.14.0
RecoverMiddleware returns httpf.Handler which uses recover feature to return an error in case of panic.
type HandlerFunc ¶
type HandlerFunc func(w ResponseWriter, r *http.Request) error
The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If h is a function with the appropriate signature, HandlerFunc(h) is a Handler that calls h.
func (HandlerFunc) ServeHTTP ¶
func (h HandlerFunc) ServeHTTP(w ResponseWriter, r *http.Request) error
ServeHTTP calls h(w, r).
type ParamsParser ¶
ParamsParser is parser for request to retrieve path parameters. Implementation should decide how to retrieve params based on values in specified http request.
type ParamsParserFunc ¶
The ParamsParserFunc type is an adapter to allow the use of ordinary functions as ParamsParser. If p is a function with the appropriate signature, ParamsParserFunc(p) is a ParamsParser that will return p.
func (ParamsParserFunc) ParseParams ¶
func (p ParamsParserFunc) ParseParams(r *http.Request) map[string]string
ParseParams returns p(r)
type RateHttpKeyFactory ¶ added in v0.7.0
RateHttpKeyFactory is factory func to create a string key based on http.Request.
func ComposeRateKeyFactories ¶ added in v0.7.0
func ComposeRateKeyFactories(factories ...RateHttpKeyFactory) RateHttpKeyFactory
ComposeRateKeyFactories aggregates many RateHttpKeyFactory into single which will invoke all factories and merge the keys into single string key using whitespace as separator.
func IPRateKey ¶ added in v0.7.0
func IPRateKey(headerNames ...string) RateHttpKeyFactory
IPRateKey returns RateHttpKeyFactory that extracts value from http.Request that's either remote IP or header value sent with request and are specified in 'headerNames'. If more than one header would match, only the first value will be returned.
func PathRateKey ¶ added in v0.7.0
func PathRateKey() RateHttpKeyFactory
PathRateKey returns RateKeyFactory that extracts url path from http.Request. Note this extracts whole URL path without query and not the registered route pattern therefore should not be used together with endpoints which use path parameters. To distinguish between same pattern using different methods it appends http.Request Method as prefix with ':' separator.
type ResponseWriter ¶
type ResponseWriter interface { http.ResponseWriter Response(code int, data interface{}) error }
A ResponseWriter interface is used by an HTTP handler to construct an HTTP response. It extends http.ResponseWriter with Response function which should be used to share common response format.
type RouteBuilder ¶
type RouteBuilder interface { Get(pattern string, handler Handler) RouteBuilder Post(pattern string, handler Handler) RouteBuilder Put(pattern string, handler Handler) RouteBuilder Delete(pattern string, handler Handler) RouteBuilder Patch(pattern string, handler Handler) RouteBuilder Options(pattern string, handler Handler) RouteBuilder WithErrorHandler(handler ErrorHandler) RouteBuilder WithWriterDecorator(decorator func(http.ResponseWriter) ResponseWriter) RouteBuilder WithParamsParser(parser ParamsParser) RouteBuilder Build() Router }
A RouteBuilder is convenient builder for routing registration. It defines function for each HTTP Method. Pattern should be able to be registered with any method. It's also responsible to use ErrorHandler and WriterDecorator in mapping from Handler to http.Handler so errors can be handled gracefully and http.ResponseWriter would be decorated with Response function.
type Router ¶
A Router is an HTTP request multiplexer. It should match the URL of each incoming request against a list of registered patterns and call the handler for the pattern that most closely matches the URL. Router also should take care of sanitizing the URL request path and the Host header, stripping the port number and redirecting any request containing . or .. elements or repeated slashes to an equivalent, cleaner URL.
type Server ¶
type Server interface { Close() error Shutdown(ctx context.Context) error RegisterOnShutdown(f func()) ListenAndServe() error Serve(l net.Listener) error ListenAndServeTLS(certFile, keyFile string) error ServeTLS(l net.Listener, certFile, keyFile string) error }
A Server defines functionality for running an HTTP server.