httpio

package module
v0.0.0-...-b9f693f Latest Latest
Warning

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

Go to latest
Published: Nov 28, 2017 License: MIT Imports: 13 Imported by: 0

README

go-httpio

Go is a fantastic language for developing web services and it is said that the standard library is powerfull enough (with one of the many routers) to achieve this. Other claim that this is not good enough when you want to hit the ground running and setup a new project up quickly as you'll be writing a lot of boilerplate code. The latter has let to the development of more holistic web frameworks that take care of this but these come with a lot more then you might need and can be very opinionated.

This project aims to find a middleground by only focussing on removing the boilerplate code that is required for parsing HTTP requests and writing back a (error) response. As such it sits between your router and
and the business logic you're trying to focus development effort on.

Features

  • Build fully on the standard library without any mandatory external dependencies
  • Bring your own router, any multiplexer in the ecosystem that can route to an http.HandlerFunc is usable
  • Takes care of decoding request bodies and encoding to responses using a flexible stack of encoders and the Content-Type header
  • Uses these encoding stacks to do content negotiation based on the Accept header
  • Provide a central error handling mechanism for logging or providing specific user feedback
  • Comes with a http client that be used to write easily write client side code
  • Optionally allows form value decoding and encoding using third-party libraries
  • Optionally allows parsed request bodies to be validated using third-party libraries
  • Optionally allows full rendering customization, for example to support template rendering.

An Example

Consider the development of a web service that manages accounts. You would to like to focus on the logic of creating accounts not on the work that is required to turn http.Requests into its input or turning create account output into responses. Ideally you would like to isolate this like so:

type MyAccountService struct{}

type CreateAccountInput struct{
  Name string
}

type CreateAccountOutput struct{
  ID string
  Name string
}

func (ctrl *MyAccountCtrl) CreateAccount(ctx context.Context, input *CreateAccountInput) (*CreateAccountOutput, error) {
  //you would like to focus on what happens here
}

Instead of having to write your own decoding and encoding logic for each input or output struct this library simply allows you do the following:

r := mux.NewRouter() //bring your own router, for example github.com/gorilla/mux
svc := &MyAccountService{} //this holds your account creation implementation

ctrl := httpio.NewCtrl(
  &encoding.JSON{}, //enables JSON decoding for inputs and JSON encoding for outputs
)

ctrl.SetValidator(/* choose an validator from the eco system */)
ctrl.SetErrorHandler(/* allow error handling to be customized */)

//and voila, your request handlers can now look like this. Notice you don't have to write any
//logic for decoding the CreateAccountInput or encoding its output.
r.HandleFunc("/accounts/create", func(w http.ResponseWriter, r *http.Request) {
  input := &CreateAccountInput{}
  if render, ok := ctrl.Handle(w, r, input); ok {
    render(svc.CreateAccount(r.Context(), input))
  }
})

//now serve the router and your good to go
log.Fatal(http.ListenAndServe(":8080", r))

Recipes

Although the library designed to be flexible and serve different needs for different web applications. Much of these are still need to written but you can take a look at the examples/sink code for most of it.

  • Using *template.Templates to render Outputs: WIP
  • Using the github.com/go-playground/validator validator: WIP
  • Allow inputs to be decoded from from submissions and query parameters: WIP
  • Handle certain (user) errors differently: WIP
  • Customize response status code: WIP
  • Disable the 'X-Has-Handling-Error' header: WIP
  • Using the client with application specific errors: WIP

Future Ideas

Adding ctx values to implementation inputs

Another part that can be cumbersome in setting up for your business logic is fetching values from the request context that are set by certain middleware, e.g: session, request ids etc. It would be cool if the struct tags of input could indicate that their value should be fetched from the request context:

type MyInput {
  Session *Session `json:"-" form:"-" ctx:"MySession"`
}

httpio then would take care of injecting these into the input struct before handing it off to the business logic. Main problem is that it is recommended to use package specific type values for context keys, so a annotation as the one above would not be possible. If you have any ideas for this let me know.

Adding header values to the implementation input

Similarly some implementation functions might want to retrieve a specific header. Middleware would allow this for a set of endpoints but sometimes the client might send over a specific header.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	//MediaTypeForm identifies form content
	MediaTypeForm = "application/x-www-form-urlencoded"
)
View Source
var (
	//MediaTypeJSON identifies JSON content
	MediaTypeJSON = "application/json"
)
View Source
var (
	//MediaTypeXML identifies XML content
	MediaTypeXML = "application/xml"
)

Functions

func IsDecodeErr

func IsDecodeErr(err error) bool

IsDecodeErr can be used to

func StatusValue

func StatusValue(ctx context.Context) (code int)

StatusValue returns a specific status stored in the (request) context, returns 0 if its not specified

func WithStatus

func WithStatus(ctx context.Context, code int) context.Context

WithStatus will write a status to the (request)Context for transware down the chain

Types

type Client

type Client struct {
	ErrReceiver ErrReceiver
	// contains filtered or unexported fields
}

Client is a client that uses encoding stacks to facilitate communication

func NewClient

func NewClient(hclient *http.Client, base string, def EncoderFactory, defd DecoderFactory, others ...DecoderFactory) (c *Client, err error)

NewClient will setup a client that encodes and decodes using the encoding stack

func (*Client) Request

func (c *Client) Request(ctx context.Context, m, p string, hdr http.Header, in, out interface{}) (err error)

Request output 'out' using method 'm' on path 'p' using headers 'hdr' and input 'in' encoded as the default encodinbg scheme from the stack. The "Content-Type" header will be set regardless of what is provided as an argument

type Decoder

type Decoder interface {
	Decode(v interface{}) error
}

Decoder allows for values to be encoded

type DecoderFactory

type DecoderFactory interface {
	MimeType() string
	Decoder(r io.Reader) Decoder
}

DecoderFactory creates encoders for a writer

func NewFormDecoding

func NewFormDecoding(p FormDecodeProvider) DecoderFactory

NewFormDecoding creates the factory using a provider, often third party library

type DecoderList

type DecoderList []DecoderFactory

DecoderList offers encoder factories

func (DecoderList) Default

func (s DecoderList) Default() DecoderFactory

Default returns the first avaible encoding set

func (DecoderList) Find

func (s DecoderList) Find(mime string) DecoderFactory

Find an encoding mechanism by its mime type: O(N). Returns nil if none is found

func (DecoderList) Supported

func (s DecoderList) Supported() (supported []string)

Supported lists all media types by the encoding stack in order of preference

type Egress

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

Egress takes care of encoding outgoing responses

func NewEgress

func NewEgress(def EncoderFactory, others ...EncoderFactory) *Egress

NewEgress uses the provided encoder factories to setup encoding

func (*Egress) MustRender

func (e *Egress) MustRender(out interface{}, w http.ResponseWriter, r *http.Request)

MustRender will render 'out' onto 'w' if this fails it will attemp to render the error. If this fails, it panics.

func (*Egress) Render

func (e *Egress) Render(out interface{}, w http.ResponseWriter, r *http.Request) (err error)

Render will take value 'v' and encode it onto response 'w' in context of request 'r'

func (*Egress) Use

func (e *Egress) Use(wares ...Transware)

Use will append the transware(s) to the egress render chain

type Encoder

type Encoder interface {
	Encode(v interface{}) error
}

Encoder allows for values to be encoded

type EncoderFactory

type EncoderFactory interface {
	MimeType() string
	Encoder(w io.Writer) Encoder
}

EncoderFactory creates encoders for a writer

func NewFormEncoding

func NewFormEncoding(p FormEncodeProvider) EncoderFactory

NewFormEncoding creates the factory using a provider, often third party library

type EncoderList

type EncoderList []EncoderFactory

EncoderList offers encoder factories

func (EncoderList) Default

func (s EncoderList) Default() EncoderFactory

Default returns the first avaible encoding set

func (EncoderList) Find

func (s EncoderList) Find(mime string) EncoderFactory

Find an encoding mechanism by its mime type: O(N). Returns nil if none is found

func (EncoderList) Supported

func (s EncoderList) Supported() (supported []string)

Supported lists all media types by the encoding stack in order of preference

type ErrReceiver

type ErrReceiver func(ctx context.Context, resp *http.Response) error

ErrReceiver is used by the client to determine if the response holds an error value and specify the struct to decode it into. If the reponse holds an error this function should return a non-nil value

type FormDecodeProvider

type FormDecodeProvider interface {
	Decode(dst interface{}, src map[string][]string) error
}

FormDecodeProvider can be implemented to provide decoding of form maps into structs Incidentally, this interface is implemented immediately by `github.com/gorilla/schema`

type FormDecoder

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

FormDecoder uses the form encoding provider to implement the Encoding interface

func (*FormDecoder) Decode

func (e *FormDecoder) Decode(v interface{}) error

Decode into v from the reader

type FormEncodeProvider

type FormEncodeProvider interface {
	Encode(src interface{}, dst map[string][]string) error
}

FormEncodeProvider can be implemented to provide encoding of form maps from structs Incidentally, this interface is implemented immediately by `github.com/gorilla/schema`

type FormEncoder

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

FormEncoder uses the form encoding provider to implement the Encoding interface

func (*FormEncoder) Encode

func (e *FormEncoder) Encode(v interface{}) error

Encode the value v into the encoder writer

type Ingress

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

Ingress stack takes care of decoding incoming requests

func NewIngress

func NewIngress(e *Egress, def DecoderFactory, others ...DecoderFactory) *Ingress

NewIngress will setup the ingress stack, errors during parsing will be returned to using the egress stack.

func (*Ingress) Handle

func (i *Ingress) Handle(w http.ResponseWriter, r *http.Request, in interface{}) (fn RenderFunc, ok bool)

Handle will parse request 'r' and decode it into 'in', it returns a renderfunction that is bound to response 'w'

func (*Ingress) Parse

func (i *Ingress) Parse(r *http.Request, in interface{}) error

Parse 'r' into 'in'

func (*Ingress) Use

func (i *Ingress) Use(wares ...Transware)

Use will append the transware(s) to the egress render chain

type JSON

type JSON struct{}

JSON allows encode and decode into JSOn

func (*JSON) Decoder

func (e *JSON) Decoder(r io.Reader) Decoder

Decoder will create decoders

func (*JSON) Encoder

func (e *JSON) Encoder(w io.Writer) Encoder

Encoder will create encoders

func (*JSON) MimeType

func (e *JSON) MimeType() string

MimeType will report the EncodingMimeType

type RenderFunc

type RenderFunc func(interface{}, error)

RenderFunc is bound to an request but renders once called

type TransFunc

type TransFunc func(a interface{}, r *http.Request, w http.ResponseWriter) error

TransFunc implements Transformer when casted to

func (TransFunc) Transform

func (f TransFunc) Transform(a interface{}, r *http.Request, w http.ResponseWriter) error

Transform allows a transfunc to be used as a Transformer

type Transformer

type Transformer interface {
	Transform(a interface{}, r *http.Request, w http.ResponseWriter) error
}

Transformer is used to transform value a in the context of responding with 'w' to request 'r'

func Chain

func Chain(base Transformer, others ...Transware) Transformer

Chain builds a recursing transformer with 'base' at the end and 'others' in front. If any transformer returns an error the recursion is unwound and the error is returned.

type Transware

type Transware func(next Transformer) Transformer

Transware is used to implement a chain of transformers, works like router middlewares

type XML

type XML struct{}

XML allows encode and decode into JSOn

func (*XML) Decoder

func (e *XML) Decoder(r io.Reader) Decoder

Decoder will create decoders

func (*XML) Encoder

func (e *XML) Encoder(w io.Writer) Encoder

Encoder will create encoders

func (*XML) MimeType

func (e *XML) MimeType() string

MimeType will report the EncodingMimeType

Directories

Path Synopsis
Package header provides functions for parsing HTTP headers.
Package header provides functions for parsing HTTP headers.

Jump to

Keyboard shortcuts

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