README

gentleman Build Status GitHub release GoDoc Coverage Status Go Report Card Go Version

Full-featured, plugin-driven, middleware-oriented toolkit to easily create rich, versatile and composable HTTP clients in Go.

gentleman embraces extensibility and composition principles in order to provide a flexible way to easily create featured HTTP client layers based on built-in or third-party plugins that you can register and reuse across HTTP clients.

As an example, you can easily provide retry policy capabilities or dynamic server discovery in your HTTP clients simply attaching the retry or consul plugins.

Take a look to the examples, list of supported plugins, HTTP entities or middleware layer to get started.

For testing purposes, see baloo, an utility library for expressive end-to-end HTTP API testing, built on top of gentleman toolkit. For HTTP mocking, see gentleman-mock, which uses gock under the hood for easy and expressive HTTP client request mocking.

Versions

  • v2 - Latest version. Stable. Recommended.
  • v1 - First version. Stable. Actively maintained.

Features

  • Plugin driven architecture.
  • Simple, expressive, fluent API.
  • Idiomatic built on top of net/http package.
  • Context-aware hierarchical middleware layer supporting all the HTTP life cycle.
  • Built-in multiplexer for easy composition capabilities.
  • Easy to extend via plugins/middleware.
  • Ability to easily intercept and modify HTTP traffic on-the-fly.
  • Convenient helpers and abstractions over Go's HTTP primitives.
  • URL template path params.
  • Built-in JSON, XML and multipart bodies serialization and parsing.
  • Easy to test via HTTP mocking (e.g: gentleman-mock).
  • Supports data passing across plugins/middleware via its built-in context.
  • Fits good while building domain-specific HTTP API clients.
  • Easy to hack.
  • Dependency free.

Installation

go get -u gopkg.in/h2non/gentleman.v2

Requirements

  • Go 1.9+

Plugins

Name Docs Status Description
url Easily declare URL, base URL and path values in HTTP requests
auth Declare authorization headers in your requests
body Easily define bodies based on JSON, XML, strings, buffers or streams
bodytype Define body MIME type by alias
cookies Declare and store HTTP cookies easily
compression Helpers to define enable/disable HTTP compression
headers Manage HTTP headers easily
multipart Create multipart forms easily. Supports files and text fields
proxy Configure HTTP proxy servers
query Easily manage query params
redirect Easily configure a custom redirect policy
timeout Easily configure the HTTP timeouts (request, dial, TLS...)
transport Define a custom HTTP transport easily
tls Configure the TLS options used by the HTTP transport
retry Provide retry policy capabilities to your HTTP clients
mock Easy HTTP mocking using gock
consul Consul based server discovery with configurable retry/backoff policy
Community plugins
Name Docs Status Description
logger Easily log requests and responses

Send a PR to add your plugin to the list.

Creating plugins

You can create your own plugins for a wide variety of purposes, such as server discovery, custom HTTP tranport, modify any request/response param, intercept traffic, authentication and so on.

Plugins are essentially a set of middleware function handlers for one or multiple HTTP life cycle phases exposing a concrete interface consumed by gentleman middleware layer.

For more details about plugins see the plugin package and examples.

Also you can take a look to a plugin implementation example.

HTTP entities

gentleman provides two HTTP high level entities: Client and Request.

Each of these entities provides a common API and are both middleware capable, giving you the ability to plug in custom components with own logic into any of them.

gentleman was designed to provide strong reusability capabilities. This is mostly achieved via its built-in hierarchical, inheritance-based middleware layer.

The following list describes how inheritance hierarchy works and is used across gentleman's entities.

  • Client entity can inherit from other Client entity.
  • Request entity can inherit from a Client entity.
  • Client entity is mostly designed for reusability.
  • Client entity can create multiple Request entities who implicitly inherits from Client entity itself.
  • Request entity is designed to have specific HTTP request logic that is not typically reused.
  • Both Client and Request entities are full middleware capable interfaces.
  • Both Client and Request entities can be cloned in order to produce a copy but side-effects free new entity.

You can see an inheritance usage example here.

Middleware

gentleman is completely based on a hierarchical middleware layer based on plugins that executes one or multiple function handlers (aka plugin interface) providing a simple way to plug in intermediate custom logic in your HTTP client.

It supports multiple phases which represents the full HTTP request/response life cycle, giving you the ability to perform actions before and after an HTTP transaction happen, even intercepting and stopping it.

The middleware stack chain is executed in FIFO order designed for single thread model. Plugins can support goroutines, but plugins implementors should prevent data race issues due to concurrency in multithreading programming.

For more implementation details about the middleware layer, see the middleware package and examples.

Middleware phases

Supported middleware phases triggered by gentleman HTTP dispatcher:

  • request - Executed before a request is sent over the network.
  • response - Executed when the client receives the response, even if it failed.
  • error - Executed in case that an error ocurrs, support both injected or native error.
  • stop - Executed in case that the request has been manually stopped via middleware (e.g: after interception).
  • intercept - Executed in case that the request has been intercepted before network dialing.
  • before dial - Executed before a request is sent over the network.
  • after dial - Executed after the request dialing was done and the response has been received.

Note that the middleware layer has been designed for easy extensibility, therefore new phases may be added in the future and/or the developer could be able to trigger custom middleware phases if needed.

Feel free to fill an issue to discuss this capabilities in detail.

API

See godoc reference for detailed API documentation.

Subpackages
  • plugin - godoc - Plugin layer for gentleman.
  • mux - godoc - HTTP client multiplexer with built-in matchers.
  • middleware - godoc - Middleware layer used by gentleman.
  • context - godoc - HTTP context implementation for gentleman's middleware.
  • utils - godoc - HTTP utilities internally used.

Examples

See examples directory for featured examples.

Simple request
package main

import (
  "fmt"

  "gopkg.in/h2non/gentleman.v2"
)

func main() {
  // Create a new client
  cli := gentleman.New()

  // Define base URL
  cli.URL("http://httpbin.org")

  // Create a new request based on the current client
  req := cli.Request()

  // Define the URL path at request level
  req.Path("/headers")

  // Set a new header field
  req.SetHeader("Client", "gentleman")

  // Perform the request
  res, err := req.Send()
  if err != nil {
    fmt.Printf("Request error: %s\n", err)
    return
  }
  if !res.Ok {
    fmt.Printf("Invalid server response: %d\n", res.StatusCode)
    return
  }

  // Reads the whole body and returns it as string
  fmt.Printf("Body: %s", res.String())
}
Send JSON body
package main

import (
  "fmt"

  "gopkg.in/h2non/gentleman.v2"
  "gopkg.in/h2non/gentleman.v2/plugins/body"
)

func main() {
  // Create a new client
  cli := gentleman.New()

  // Define the Base URL
  cli.URL("http://httpbin.org/post")

  // Create a new request based on the current client
  req := cli.Request()

  // Method to be used
  req.Method("POST")

  // Define the JSON payload via body plugin
  data := map[string]string{"foo": "bar"}
  req.Use(body.JSON(data))

  // Perform the request
  res, err := req.Send()
  if err != nil {
    fmt.Printf("Request error: %s\n", err)
    return
  }
  if !res.Ok {
    fmt.Printf("Invalid server response: %d\n", res.StatusCode)
    return
  }

  fmt.Printf("Status: %d\n", res.StatusCode)
  fmt.Printf("Body: %s", res.String())
}
Composition via multiplexer
package main

import (
  "fmt"

  "gopkg.in/h2non/gentleman.v2"
  "gopkg.in/h2non/gentleman.v2/mux"
  "gopkg.in/h2non/gentleman.v2/plugins/url"
)

func main() {
  // Create a new client
  cli := gentleman.New()

  // Define the server url (must be first)
  cli.Use(url.URL("http://httpbin.org"))

  // Create a new multiplexer based on multiple matchers
  mx := mux.If(mux.Method("GET"), mux.Host("httpbin.org"))

  // Attach a custom plugin on the multiplexer that will be executed if the matchers passes
  mx.Use(url.Path("/headers"))

  // Attach the multiplexer on the main client
  cli.Use(mx)

  // Perform the request
  res, err := cli.Request().Send()
  if err != nil {
    fmt.Printf("Request error: %s\n", err)
    return
  }
  if !res.Ok {
    fmt.Printf("Invalid server response: %d\n", res.StatusCode)
    return
  }

  fmt.Printf("Status: %d\n", res.StatusCode)
  fmt.Printf("Body: %s", res.String())
}

License

MIT - Tomas Aparicio

Expand ▾ Collapse ▴

Documentation

Index

Constants

View Source
const (
	// UserAgent represents the static user agent name and version.
	UserAgent = "gentleman/" + Version
)
View Source
const Version = "2.0.4"

Version defines the package semantic version

Variables

View Source
var (
	// DialTimeout represents the maximum amount of time the network dialer can take.
	DialTimeout = 30 * time.Second

	// DialKeepAlive represents the maximum amount of time too keep alive the socket.
	DialKeepAlive = 30 * time.Second

	// TLSHandshakeTimeout represents the maximum amount of time that
	// TLS handshake can take defined in the default http.Transport.
	TLSHandshakeTimeout = 10 * time.Second

	// RequestTimeout represents the maximum about of time that
	// a request can take, including dial / request / redirect processes.
	RequestTimeout = 60 * time.Second

	// DefaultDialer defines the default network dialer.
	DefaultDialer = &net.Dialer{
		Timeout:   DialTimeout,
		KeepAlive: DialKeepAlive,
	}

	// DefaultTransport stores the default HTTP transport to be used.
	DefaultTransport = NewDefaultTransport(DefaultDialer)
)
View Source
var NewContext = context.New

NewContext is a convenient alias to context.New factory.

View Source
var NewHandler = context.NewHandler

NewHandler is a convenient alias to context.NewHandler factory.

View Source
var NewMiddleware = middleware.New

NewMiddleware is a convenient alias to middleware.New factory.

Functions

func NewDefaultTransport

func NewDefaultTransport(dialer *net.Dialer) *http.Transport

NewDefaultTransport returns a new http.Transport with default values based on the given net.Dialer.

Types

type Client

type Client struct {
	// Client entity can inherit behavior from a parent Client.
	Parent *Client

	// Each Client entity has it's own Context that will be inherited by requests or child clients.
	Context *context.Context

	// Client entity has its own Middleware layer to compose and inherit behavior.
	Middleware middleware.Middleware
}

Client represents a high-level HTTP client entity capable with a built-in middleware and context.

func New

func New() *Client

New creates a new high level client entity able to perform HTTP requests.

func (*Client) AddCookie

func (c *Client) AddCookie(cookie *http.Cookie) *Client

AddCookie sets a new cookie field based on the given http.Cookie struct without overwriting any existent cookie.

func (*Client) AddCookies

func (c *Client) AddCookies(data []*http.Cookie) *Client

AddCookies sets a new cookie field based on a list of http.Cookie without overwriting any existent cookie.

func (*Client) AddHeader

func (c *Client) AddHeader(name, value string) *Client

AddHeader adds a new header field by name and value without overwriting any existent header.

func (*Client) AddPath

func (c *Client) AddPath(path string) *Client

AddPath concatenates a path slice to the existent path in at client level.

func (*Client) BaseURL

func (c *Client) BaseURL(uri string) *Client

BaseURL defines the URL schema and host for client requests. Useful to define at client level the base URL used by client child requests.

func (*Client) CookieJar

func (c *Client) CookieJar() *Client

CookieJar creates a cookie jar to store HTTP cookies when they are sent down.

func (*Client) Delete

func (c *Client) Delete() *Request

Delete creates a new DELETE request.

func (*Client) Get

func (c *Client) Get() *Request

Get creates a new GET request.

func (*Client) Head

func (c *Client) Head() *Request

Head creates a new HEAD request.

func (*Client) Method

func (c *Client) Method(name string) *Client

Method defines a the default HTTP method used by outgoing client requests.

func (*Client) Options

func (c *Client) Options() *Request

Options creates a new OPTIONS request.

func (*Client) Param

func (c *Client) Param(name, value string) *Client

Param replaces a path param based on the given param name and value.

func (*Client) Params

func (c *Client) Params(params map[string]string) *Client

Params replaces path params based on the given params key-value map.

func (*Client) Patch

func (c *Client) Patch() *Request

Patch creates a new PATCH request.

func (*Client) Path

func (c *Client) Path(path string) *Client

Path defines the URL base path for client requests.

func (*Client) Post

func (c *Client) Post() *Request

Post creates a new POST request.

func (*Client) Put

func (c *Client) Put() *Request

Put creates a new PUT request.

func (*Client) Request

func (c *Client) Request() *Request

Request creates a new Request based on the current Client

func (*Client) SetHeader

func (c *Client) SetHeader(key, value string) *Client

SetHeader sets a new header field by name and value. If another header exists with the same key, it will be overwritten.

func (*Client) SetHeaders

func (c *Client) SetHeaders(fields map[string]string) *Client

SetHeaders adds new header fields based on the given map.

func (*Client) URL

func (c *Client) URL(uri string) *Client

URL defines the URL for client requests. Useful to define at client level the base URL and base path used by child requests.

func (*Client) Use

func (c *Client) Use(p plugin.Plugin) *Client

Use uses a new plugin to the middleware stack.

func (*Client) UseError

func (c *Client) UseError(fn context.HandlerFunc) *Client

UseError uses a new middleware function for error phase.

func (*Client) UseHandler

func (c *Client) UseHandler(phase string, fn context.HandlerFunc) *Client

UseHandler uses a new middleware function for the given phase.

func (*Client) UseParent

func (c *Client) UseParent(parent *Client) *Client

UseParent uses another Client as parent inheriting its middleware stack and configuration.

func (*Client) UseRequest

func (c *Client) UseRequest(fn context.HandlerFunc) *Client

UseRequest uses a new middleware function for request phase.

func (*Client) UseResponse

func (c *Client) UseResponse(fn context.HandlerFunc) *Client

UseResponse uses a new middleware function for response phase.

type Dispatcher

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

Dispatcher dispatches a given request triggering the middleware layer per specific phase and handling the request/response/error states accondingly.

func NewDispatcher

func NewDispatcher(req *Request) *Dispatcher

NewDispatcher creates a new Dispatcher based on the given Context.

func (*Dispatcher) Dispatch

func (d *Dispatcher) Dispatch() *c.Context

Dispatch triggers the middleware chains and performs the HTTP request.

type Request

type Request struct {

	// Optional reference to the gentleman.Client instance
	Client *Client

	// Request scope Context instance
	Context *context.Context

	// Request scope Middleware instance
	Middleware middleware.Middleware
	// contains filtered or unexported fields
}

Request HTTP entity for gentleman. Provides middleware capabilities, built-in context and convenient methods to easily setup request params.

func NewRequest

func NewRequest() *Request

NewRequest creates a new Request entity.

func (*Request) AddCookie

func (r *Request) AddCookie(cookie *http.Cookie) *Request

AddCookie sets a new cookie field bsaed on the given http.Cookie struct without overwriting any existent cookie.

func (*Request) AddCookies

func (r *Request) AddCookies(data []*http.Cookie) *Request

AddCookies sets a new cookie field based on a list of http.Cookie without overwriting any existent cookie.

func (*Request) AddHeader

func (r *Request) AddHeader(name, value string) *Request

AddHeader adds a new header field by name and value without overwriting any existent header.

func (*Request) AddPath

func (r *Request) AddPath(path string) *Request

AddPath concatenates a path slice to the existent path in at request level.

func (*Request) AddQuery

func (r *Request) AddQuery(name, value string) *Request

AddQuery adds a new URL query param field without overwriting any existent query field.

func (*Request) BaseURL

func (r *Request) BaseURL(uri string) *Request

BaseURL parses the given URL and uses the URL schema and host in the outgoing request.

func (*Request) Body

func (r *Request) Body(reader io.Reader) *Request

Body defines the request body based on a io.Reader stream.

func (*Request) BodyString

func (r *Request) BodyString(data string) *Request

BodyString defines the request body based on the given string. If using this method, you should define the proper Content-Type header representing the real content MIME type.

func (*Request) Clone

func (r *Request) Clone() *Request

Clone creates a new side-effects free Request based on the current one.

func (*Request) CookieJar

func (r *Request) CookieJar() *Request

CookieJar creates a cookie jar to store HTTP cookies when they are sent down.

func (*Request) DelHeader

func (r *Request) DelHeader(name string) *Request

DelHeader deletes a header field by its name

func (*Request) Do

func (r *Request) Do() (*Response, error)

Do performs the HTTP request and returns the HTTP response.

func (*Request) File

func (r *Request) File(name string, reader io.Reader) *Request

File serializes and defines the request body as multipart/form-data containing one file field.

func (*Request) Files

func (r *Request) Files(files []multipart.FormFile) *Request

Files serializes and defines the request body as multipart/form-data containing the given file fields.

func (*Request) Form

func (r *Request) Form(data multipart.FormData) *Request

Form serializes and defines the request body as multipart/form-data based on the given form data.

func (*Request) JSON

func (r *Request) JSON(data interface{}) *Request

JSON serializes and defines as request body based on the given input. The proper Content-Type header will be transparently added for you.

func (*Request) Method

func (r *Request) Method(method string) *Request

Method defines the HTTP verb to be used.

func (*Request) Mux

func (r *Request) Mux() *mux.Mux

Mux is a middleware multiplexer for easy plugin composition.

func (*Request) Param

func (r *Request) Param(name, value string) *Request

Param replaces a path param based on the given param name and value.

func (*Request) Params

func (r *Request) Params(params map[string]string) *Request

Params replaces path params based on the given params key-value map.

func (*Request) Path

func (r *Request) Path(path string) *Request

Path defines the request URL path to be used in the outgoing request.

func (*Request) Send

func (r *Request) Send() (*Response, error)

Send is an alias to Do(), which executes the current request and returns the response.

func (*Request) SetClient

func (r *Request) SetClient(cli *Client) *Request

SetClient Attach a client to the current Request This is mostly done internally.

func (*Request) SetHeader

func (r *Request) SetHeader(name, value string) *Request

SetHeader sets a new header field by name and value. If another header exists with the same key, it will be overwritten.

func (*Request) SetHeaders

func (r *Request) SetHeaders(fields map[string]string) *Request

SetHeaders adds new header fields based on the given map.

func (*Request) SetQuery

func (r *Request) SetQuery(name, value string) *Request

SetQuery sets a new URL query param field. If another query param exists with the same key, it will be overwritten.

func (*Request) SetQueryParams

func (r *Request) SetQueryParams(params map[string]string) *Request

SetQueryParams sets URL query params based on the given map.

func (*Request) Type

func (r *Request) Type(name string) *Request

Type defines the Content-Type header field based on the given type name alias or value. You can use the following content type aliases: json, xml, form, html, text and urlencoded.

func (*Request) URL

func (r *Request) URL(uri string) *Request

URL parses and defines the URL to be used in the outgoing request.

func (*Request) Use

func (r *Request) Use(p plugin.Plugin) *Request

Use uses a new plugin in the middleware stack.

func (*Request) UseError

func (r *Request) UseError(fn context.HandlerFunc) *Request

UseError uses an error middleware handler.

func (*Request) UseHandler

func (r *Request) UseHandler(phase string, fn context.HandlerFunc) *Request

UseHandler uses an new middleware handler for the given phase.

func (*Request) UseRequest

func (r *Request) UseRequest(fn context.HandlerFunc) *Request

UseRequest uses a request middleware handler.

func (*Request) UseResponse

func (r *Request) UseResponse(fn context.HandlerFunc) *Request

UseResponse uses a response middleware handler.

func (*Request) XML

func (r *Request) XML(data interface{}) *Request

XML serializes and defines the request body based on the given input. The proper Content-Type header will be transparently added for you.

type Response

type Response struct {
	// Ok is a boolean flag that validates that the server returned a 2xx code.
	Ok bool

	// This is the Go error flag – if something went wrong within the request, this flag will be set.
	Error error

	// Sugar to check if the response status code is a client error (4xx).
	ClientError bool

	// Sugar to check if the response status code is a server error (5xx).
	ServerError bool

	// StatusCode is the HTTP Status Code returned by the HTTP Response. Taken from resp.StatusCode.
	StatusCode int

	// Header stores the response headers as http.Header interface.
	Header http.Header

	// Cookies stores the parsed response cookies.
	Cookies []*http.Cookie

	// Expose the native Go http.Response object for convenience.
	RawResponse *http.Response

	// Expose the native Go http.Request object for convenience.
	RawRequest *http.Request

	// Expose original request Context for convenience.
	Context *context.Context
	// contains filtered or unexported fields
}

Response provides a more convenient and higher level Response struct. Implements an io.ReadCloser interface.

func (*Response) Bytes

func (r *Response) Bytes() []byte

Bytes returns the response as a byte array.

func (*Response) ClearInternalBuffer

func (r *Response) ClearInternalBuffer()

ClearInternalBuffer is a function that will clear the internal buffer that we use to hold the .String() and .Bytes() data. Once you have used these functions you may want to free up the memory.

func (*Response) Close

func (r *Response) Close() error

Close is part of our ability to support io.ReadCloser if someone wants to make use of the raw body.

func (*Response) JSON

func (r *Response) JSON(userStruct interface{}) error

JSON is a method that will populate a struct that is provided `userStruct` with the JSON returned within the response body.

func (*Response) Read

func (r *Response) Read(p []byte) (n int, err error)

Read is part of our ability to support io.ReadCloser if someone wants to make use of the raw body.

func (*Response) SaveToFile

func (r *Response) SaveToFile(fileName string) error

SaveToFile allows you to download the contents of the response to a file.

func (*Response) String

func (r *Response) String() string

String returns the response as a string.

func (*Response) XML

func (r *Response) XML(userStruct interface{}, charsetReader utils.XMLCharDecoder) error

XML is a method that will populate a struct that is provided `userStruct` with the XML returned within the response body.

Directories

Path Synopsis
context Package context implements a request-aware HTTP context used by plugins and exposed by the middleware layer, designed to share polymorfic data types across plugins in the middleware call chain.
middleware Package middleware implements an HTTP client domain-specific phase-oriented middleware layer used internally by gentleman packages.
mux Package mux implements an HTTP domain-specific traffic multiplexer with built-in matchers and features for easy plugin composition and activable logic.
plugin Package plugin implements a plugin layer for gentleman components.
plugins/auth
plugins/body
plugins/bodytype
plugins/compression
plugins/cookies
plugins/headers
plugins/multipart
plugins/proxy
plugins/query
plugins/redirect
plugins/timeout
plugins/tls
plugins/transport
plugins/url
utils Package utils provides a set of reusable HTTP client utilities used internally in gentleman for required functionality and testing.