Skip to content

tomas-fp/gentleman

 
 

Repository files navigation

gentleman Build Status GitHub release GoDoc Coverage Status Go Report Card

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

gentleman embraces extensibility and composition principles to provide a powerful way to create simple and featured HTTP client layers based on built-in or third-party plugins. For instance, you can provide retry policy capabilities or dynamic server discovery to your HTTP clients simply attaching the retry or consul plugins respectively.

Take a look to the examples or list of supported plugins to get started.

Features

  • Plugin driven architecture.
  • Simple, expressive, fluent API.
  • Idiomatic built on top of net/http package.
  • Context-aware middleware layer supporting all the HTTP life cycle.
  • Multiplexer for easy composition capabilities.
  • Strong extensibility via plugins.
  • Easy to configure and use.
  • Ability to easily intercept and modify HTTP traffic.
  • 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).
  • Data passing across plugins/middleware via context.
  • Dependency free.

Installation

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

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

Send a PR to add your plugin to the list.

Creating plugins

You can create your own plugins for a 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 middleware capable, giving the ability to plug in logic in any of them.

gentleman was designed to provide strong reusability capabilities, achieved via simple middleware layer inheritance. The following describes how inheritance affects to gentleman's entities.

  • Client can inherit from other Client.
  • Request can inherit from Client.
  • Client is mostly designed for reusability.
  • Client can create multiple Request entities who implicitly inherits from the current Client.
  • Both Client and Request are full middleware capable interfaces.
  • Both Client and Request can be cloned to have a new side-effects free entity.

You can see an inheritance example here.

Middleware

gentleman is completely based on a hierarchical middleware layer based on plugin that executes one or multiple function handlers, providing a simple way to plug in intermediate logic.

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.v1"
)

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.v1"
  "gopkg.in/h2non/gentleman.v1/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.v1"
  "gopkg.in/h2non/gentleman.v1/mux"
  "gopkg.in/h2non/gentleman.v1/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

About

Full-featured, plugin-oriented, composable HTTP client toolkit for Go (golang) (͡° ͜ʖ ͡°)

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 100.0%