httpf

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2023 License: MIT Imports: 15 Imported by: 0

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

Examples

Constants

View Source
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"
)
View Source
const (
	RateLimitLimitHeader     = "X-Rate-Limit-Limit"
	RateLimitRemainingHeader = "X-Rate-Limit-Remaining"
	RateLimitResetHeader     = "X-Rate-Limit-Reset"
	RetryAfterHeader         = "Retry-After"
)

Variables

View Source
var (
	ErrMissingRateStore      = errors.New("nil LimiterStore passed as parameter")
	ErrMissingRateKeyFactory = errors.New("nil RateHttpKeyFactory passed as parameter")
)

Functions

func HasParam

func HasParam(r *http.Request, key string) bool

HasParam returns true if key exists in path params map.

func IsErrorStatus added in v0.12.0

func IsErrorStatus(code int) bool

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 NewServer

func NewServer(addr string, router Router) *http.Server

NewServer returns a new instance of Server.

func Param

func Param(r *http.Request, key string) string

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.

func Params

func Params(r *http.Request) map[string]string

Params retrieves path param values from request context or empty map if it was not set. Router is responsible to decorate http request with WithParams using ParamsParser.

func WithParams

func WithParams(r *http.Request, params map[string]string) *http.Request

WithParams returns a shallow copy of r with its context changed to contain params as context value.

Types

type BodyUnmarshaler added in v0.12.0

type BodyUnmarshaler interface {
	Unmarshal(r *http.Response, v any) error
}

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

func WithRedirectHandler(handler func(req *http.Request, via []*http.Request) error) ClientOption

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

func RecoverMiddleware(h Handler) Handler

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

type ParamsParser interface {
	ParseParams(r *http.Request) map[string]string
}

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

type ParamsParserFunc func(r *http.Request) map[string]string

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

type RateHttpKeyFactory func(r *http.Request) string

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

type Router interface {
	http.Handler

	Handle(pattern string, handler http.Handler)
}

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.

Jump to

Keyboard shortcuts

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