quangpn88
quangpn88

Reputation: 615

Is there 'middleware' for Go http client?

I would like to ask if we can create 'middleware' functions for Go http client? Example I want to add a log function, so every sent request will be logged, or add setAuthToken so the token will be added to each request's header.

Upvotes: 22

Views: 15755

Answers (5)

Japheth Obala
Japheth Obala

Reputation: 81

I worked on a project that had similar requirement so I built a middleware pipeline library that allows setting multiple middleware to the http client. You can check it out here.

Using the library, you would solve this in the following way

type LoggingMiddleware struct{}

func (s LoggingMiddleware) Intercept(pipeline pipeline.Pipeline, req *http.Request) (*http.Response, error) {

    body, _ := httputil.DumpRequest(req, true)
    log.Println(fmt.Sprintf("%s", string(body)))

    /*
    If you want to perform an action based on the response, do the following
    
    resp, err = pipeline.Next
    // perform some action

    return resp, err
    */
    return pipeline.Next(req)
}

transport := pipeline.NewCustomTransport(&LoggingMiddleware{})
client := &http.Client{Transport: transport}
resp, err := client.Get("https://example.com")

if err != nil {
   // handle err
}
fmt.Println(resp.Status)

Upvotes: 1

Jean Hominal
Jean Hominal

Reputation: 16796

You can use the Transport parameter in HTTP client to that effect, with a composition pattern, using the fact that:

  • http.Client.Transport defines the function that will handle all HTTP requests;
  • http.Client.Transport has interface type http.RoundTripper, and can thus be replaced with your own implementation;

For example:

package main

import (
    "fmt"
    "net/http"
)

// This type implements the http.RoundTripper interface
type LoggingRoundTripper struct {
    Proxied http.RoundTripper
}

func (lrt LoggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
    // Do "before sending requests" actions here.
    fmt.Printf("Sending request to %v\n", req.URL)

    // Send the request, get the response (or the error)
    res, e = lrt.Proxied.RoundTrip(req)

    // Handle the result.
    if (e != nil) {
        fmt.Printf("Error: %v", e)
    } else {
        fmt.Printf("Received %v response\n", res.Status)
    }

    return
}

func main() {
    httpClient := &http.Client{
        Transport: LoggingRoundTripper{http.DefaultTransport},
    }
    httpClient.Get("https://example.com/")
}

Feel free to alter names as you wish, I did not think on them for very long.

Upvotes: 59

dyy.alex
dyy.alex

Reputation: 534

Good idea! Here is a simple implementation of HTTP service middleware in Go. Usually a simple http service framework is to register a bunch of routes, and then call different logics to process them according to the routes.

But in fact, there may be some unified processing involving almost all routes, such as logs, permissions, and so on.

So it is a good idea to engage in intermediate preprocessing at this time.

  1. Define a middleware unit:
package main

import (
    "net/http"
)

// AdaptorHandle middleware func type
type AdaptorHandle func(w http.ResponseWriter, r *http.Request) (next bool, err error)

// MiddleWareAdaptor router middlewares mapped by url
type MiddleWareAdaptor struct {
    URLs map[string][]AdaptorHandle
}

// MakeMiddleWareAdaptor make a middleware adaptor
func MakeMiddleWareAdaptor() *MiddleWareAdaptor {
    mwa := &MiddleWareAdaptor{
        URLs: make(map[string][]AdaptorHandle),
    }

    return mwa
}

// Regist regist a adaptor
func (mw *MiddleWareAdaptor) Regist(url string, Adaptor ...AdaptorHandle) {
    for _, adp := range Adaptor {
        mw.URLs[url] = append(mw.URLs[url], adp)
        // mw.URLs[url] = adp
    }
}

// Exec exec middleware adaptor funcs...
func (mw *MiddleWareAdaptor) Exec(url string, w http.ResponseWriter, r *http.Request) (bool, error) {
    if adps, ok := mw.URLs[url]; ok {
        for _, adp := range adps {
            if next, err := adp(w, r); !next || (err != nil) {
                return next, err
            }
        }
    }
    return true, nil
}
  1. Then wrap the route processing function with a middleware entry:
func middlewareHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // before call handler
        start := time.Now()
        do, _ := mwa.Exec(r.URL.Path, w, r) // exec middleware
        // call next handler
        if do {
            log.Println("middleware done. next...")
            next.ServeHTTP(w, r)
        } else {
            log.Println("middleware done.break...")
        }
        // after call handle
        log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
    })
}

mux.Handle("/", middlewareHandler(&uPlusRouterHandler{}))

type uPlusRouterHandler struct {
}

func (rh *uPlusRouterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
}
  1. Finally, register the middleware you need:
mwa = MakeMiddleWareAdaptor() // init middleware
mwa.Regist("/", testMWAfunc, testMWAfunc2) // regist middleware
...
func testMWAfunc(w http.ResponseWriter, r *http.Request) (bool, error) {
    log.Println("I am Alice Middleware...")
    log.Printf("Started %s %s", r.Method, r.URL.Path)
    return true, nil
}

func testMWAfunc2(w http.ResponseWriter, r *http.Request) (bool, error) {
    log.Println("I am Ben Middleware...")
    return false, nil // return false,break follow-up actions.
}

Upvotes: -1

Tal G.
Tal G.

Reputation: 493

I wrote a small tutorial/library to do just that https://github.com/HereMobilityDevelopers/mediary

Here is some basic usage example:

client := mediary.Init().AddInterceptors(dumpInterceptor).Build()
client.Get("https://golang.org")

func dumpInterceptor(req *http.Request, handler mediary.Handler) (*http.Response, error) {
    if bytes, err := httputil.DumpRequestOut(req, true); err == nil {
        fmt.Printf("%s", bytes)

        //GET / HTTP/1.1
        //Host: golang.org
        //User-Agent: Go-http-client/1.1
        //Accept-Encoding: gzip
    }
    return handler(req)
}

There is also an explanation here https://github.com/HereMobilityDevelopers/mediary/wiki/Reasoning

Upvotes: -1

Bugless
Bugless

Reputation: 69

This can be achieved using closure functions. It's probably more clear with an example:

package main

import (  
  "fmt"
  "net/http"
)

func main() {  
  http.HandleFunc("/hello", logged(hello))
  http.ListenAndServe(":3000", nil)
}

func logged(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {  
  return func(w http.ResponseWriter, r *http.Request) {
    fmt.Println("logging something")
    f(w, r)
    fmt.Println("finished handling request")
  }
}

func hello(w http.ResponseWriter, r *http.Request) {  
  fmt.Fprintln(w, "<h1>Hello!</h1>")
}

credit goes to: http://www.calhoun.io/5-useful-ways-to-use-closures-in-go/

Upvotes: -3

Related Questions