Is there 'middleware' for Go http client? - go

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.

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.

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)

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

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.
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
}
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) {
...
}
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.
}

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/

Related

How to simulate multiple different HTTP responses using Go's httptest?

I have created some Go functions that make HTTP GET calls to services that are out there on the internet and parse the results.
I am now working on writing test-cases for these functions.
In my test cases, I'm using the go package httptest to simulate calls to these external services. Below is my code. Error checking is purposefully removed for brevity. Here is the go-playground.
package main
import (
"fmt"
"io"
"context"
"net/http"
"net/http/httptest"
)
func handlerResponse() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"A":"B"}`))
})
}
func buildMyRequest(ctx context.Context, url string) *http.Request {
request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
return request
}
func myPrint(response *http.Response) {
b := make([]byte, 60000)
for {
_, err := response.Body.Read(b)
if err == io.EOF {
break
}
}
fmt.Println(string(b))
}
func main() {
srv := httptest.NewServer(handlerResponse())
client := http.Client{}
myResponse1, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
fmt.Println("myResponse1:")
myPrint(myResponse1)
myResponse2, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
fmt.Println("myResponse2:")
myPrint(myResponse2)
}
This is the output it produces:
myResponse1:
{"A":"B"}
myResponse2:
{"A":"B"}
As you can see, I have created some dummy HTTP response data {"A":"B"} and when you send an HTTP request to srv.URL, it actually hits an ephemeral HTTP server which responds with the dummy data. Cool!
When you send the second HTTP request to srv.URL, it again responds with the same dummy data. But this is where my problem arises. I want the ephemeral HTTP server to return some different data the second time {"C":"D"} and third time {"E":"F"} it receives a request.
How can I change the first line of the main() function so that the server responds with my desired data on subsequent HTTP calls?
you could use a hack like follows ( playground : here)
package main
import (
"fmt"
"io"
"context"
"net/http"
"net/http/httptest"
"sync"
)
type responseWriter struct{
resp map[int]string
count int
lock *sync.Mutex
}
func NewResponseWriter()*responseWriter{
r := new(responseWriter)
r.lock = new(sync.Mutex)
r.resp = map[int]string{
0: `{"E":"F"}`,
1: `{"A":"B"}`,
2: `{"C":"D"}`,
}
r.count = 0
return r
}
func (r *responseWriter)GetResp()string{
r.lock.Lock()
defer r.lock.Unlock()
r.count ++
return r.resp[r.count%3]
}
func handlerResponse(rr *responseWriter) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(rr.GetResp()))
})
}
func buildMyRequest(ctx context.Context, url string) *http.Request {
request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
return request
}
func myPrint(response *http.Response) {
b := make([]byte, 60000)
for {
_, err := response.Body.Read(b)
if err == io.EOF {
break
}
}
fmt.Println(string(b))
}
func main() {
rr := NewResponseWriter()
srv := httptest.NewServer(handlerResponse(rr))
client := http.Client{}
myResponse1, err := client.Do(buildMyRequest(context.Background(), srv.URL))
if err != nil{
fmt.Println(err)
return
}
defer myResponse1.Body.Close()
fmt.Println("myResponse1:")
myPrint(myResponse1)
myResponse2, err := client.Do(buildMyRequest(context.Background(), srv.URL))
if err != nil{
fmt.Println(err)
return
}
defer myResponse2.Body.Close()
fmt.Println("myResponse2:")
myPrint(myResponse2)
}

Lookup a string with a function

I'm designing a router API and I'd like to be able to lookup a path by its function. Something like:
createUser := func(w http.ResponseWriter, r *http.Request) {
// create a user
}
createPost := func(w http.ResponseWriter, r *http.Request) {
// create a post
}
router.Post("/users", createUser)
router.Post("/posts", createPost)
fmt.Println(router.Lookup(createPost))
Here's a playground link: https://play.golang.org/p/ec6U0jJUbfx
This is surprisingly hard to do because you can't test for equality on a function or stick it as a key in a map. Is this even possible?
Are there any other workarounds I'm not thinking of? A reflect solution would be just fine.
You can create a server struct with a ServerHTTP method that handles all the request. When there is a request you can look up for an specific method by the path and function
Here is an example:
type Server struct {
routes []route
}
func (s *Server) handlerServer(db mydb.IDB, ctx context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := ServerContext{w, r, db, ctx}
for i := 0; i < len(s.routes); i++ {
currentRoute := s.routes[i]
if isValidMethod(currentRoute, r) {
err := currentRoute.h(&ctx)
if err != nil {
log.Fatal(err)
}
}
}
}
}
I hope this is helpful

How to access handler value globally

I have this simple http server. How can i access the request data to a global variable and use it in any part of the application.
package main
import (
"io"
"net/http"
)
var data string // Get URL data globally and use it in other part of the application
func hello(w http.ResponseWriter, r *http.Request) {
data := r.URL.Query().Get("somestring")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", hello)
http.ListenAndServe(":8000", mux)
}
You could use net/context with http.Handler. for example you have "X-Request-ID" in header, you could define middlware like this:
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := newContextWithRequestID(req.Context(), req)
next.ServeHTTP(rw, req.WithContext(ctx))
})
}
type key int
const requestIDKey key = 0
func newContextWithRequestID(ctx context.Context, req *http.Request) context.Context {
reqID := req.Header.Get("X-Request-ID")
if reqID == "" {
reqID = generateRandomID()
}
return context.WithValue(ctx, requestIDKey, reqID)
}
func requestIDFromContext(ctx context.Context) string {
return ctx.Value(requestIDKey).(string)
}
you could get requestIDKey in any handler with Context object.
func handler(rw http.ResponseWriter, req *http.Request) {
reqID := requestIDFromContext(req.Context())
fmt.Fprintf(rw, "Hello request ID %v\n", reqID)
}
this is just an example. insted of requestIDKey you could have any data which you should put in Context and read from it with a key.
for more detail information visit this blog.

Gorilla/mux middleware not being hit

I am on go version go1.10.4 linux/amd64
I am registering middleware, but they don't seem to be hit.
package main
import (
"encoding/json"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/context"
"github.com/mitchellh/mapstructure"
"huru/migrations"
"huru/models"
"huru/models/person"
"huru/routes"
"net/http"
"os"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
log "github.com/sirupsen/logrus"
)
func loggingMiddleware(next http.Handler) http.Handler {
log.Println("logging middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println("Here is the request URI:",r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
type Exception struct {
Message string `json:"message"`
}
func authMiddleware(next http.Handler) http.Handler {
log.Println("auth middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
fmt.Println("the params are:", params);
token, _ := jwt.Parse(params["token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("there was an error")
}
return []byte("secret"), nil
})
claims, ok := token.Claims.(jwt.MapClaims)
if ! (ok && token.Valid) {
json.NewEncoder(w).Encode(Exception{Message: "Invalid authorization token"})
return;
}
var user person.Model
mapstructure.Decode(claims, &user)
context.Set(r, "logged_in_user", user)
next.ServeHTTP(w, r)
})
}
func errorMiddleware(next http.Handler) http.Handler {
log.Println("error handling middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Error("Caught error in defer/recover middleware: ", err)
originalError := err.(struct{ OriginalError error }).OriginalError
if originalError != nil {
log.Error("Original error in defer/recover middleware: ", originalError)
}
statusCode := err.(struct{ StatusCode int }).StatusCode
if statusCode != 0 {
w.WriteHeader(statusCode)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
message := err.(struct{ Message string }).Message
if message == "" {
message = "Unknown error message."
}
json.NewEncoder(w).Encode(struct {
ID string
}{
message,
})
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
routerParent := mux.NewRouter()
routerParent.Use(loggingMiddleware)
routerParent.Use(errorMiddleware)
routerParent.Use(authMiddleware)
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
// register and login
{
handler := routes.LoginHandler{}
subRouter := router.PathPrefix("/").Subrouter()
handler.Mount(subRouter, struct{}{});
}
{
handler := routes.RegisterHandler{}
subRouter := router.PathPrefix("/").Subrouter()
handler.Mount(subRouter, struct{}{})
}
{
// people
handler := routes.PersonHandler{}
subRouter := router.PathPrefix("/").Subrouter()
subRouter.Use(authMiddleware)
handler.Mount(subRouter, routes.PersonInjection{People: models.PersonInit()})
}
// ...
}
none of these get logged:
log.Println("error handling middleware registered");
log.Println("auth middleware registered");
log.Println("logging middleware registered");
and at runtime none of middleware routes seem to get hit, nothing is logged there. Anyone know why that may be?
Note that I don't expect to need all these:
routerParent := mux.NewRouter()
routerParent.Use(loggingMiddleware)
routerParent.Use(errorMiddleware)
routerParent.Use(authMiddleware)
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
in reality I probably just want:
routerParent := mux.NewRouter()
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
but it's just there to prove that something is off. At the end of the main func, I have this to start the server:
host := os.Getenv("huru_api_host")
port := os.Getenv("huru_api_port")
if host == "" {
host = "localhost"
}
if port == "" {
port = "80"
}
log.Info(fmt.Sprintf("Huru API server listening on port %s", port))
path := fmt.Sprintf("%s:%s", host, port)
log.Fatal(http.ListenAndServe(path, routerParent))

Middleware using Alice and HttpRouter

I can't seem to work out how to use middleware and Http Router properly together.
My code is:
type appContext struct {
db *mgo.Database
}
func main(){
c := appContext{session.DB("db-name")}
commonHandlers := alice.New(context.ClearHandler, basicAuthHandler)
router := NewRouter()
router.Post("/", commonHandlers.ThenFunc(c.final))
http.ListenAndServe(":5000", router)
}
The final middleware is:
func (c *appContext) final(w http.ResponseWriter, r *http.Request) {
log.Println("Executing finalHandler")
w.Write([]byte("TESTING"))
}
but I want my basicAuthHandler to be part of the commonHandlers. It also needs the context so that I can query the db.
I have tried this:
func (c *appContext) basicAuthHandler(w http.ResponseWriter, r *http.Request) {
var app App
err := c.db.C("apps").Find(bson.M{"id":"abcde"}).One(&app)
if err != nil {
panic(err)
}
//do something with the app
}
but I get the error undefined: basicAuthHandler. I understand why I'm getting the error but I don't know how to avoid it. How can I provide the context to the basicAuthHandler and still use it in the commonHandlers list for Alice?
Your middleware needs to have the signature
func(http.Handler) http.Handler
This way your middleware is wrapping handlers, not just providing a final handler. You need to accept an http.Handler, do whatever processing needs to be done, and call ServeHTTP on the next handler in the chain. Your basicAuthHandler example could look like this:
func (c *appContext) basicAuthHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var app App
err := c.db.C("apps").Find(bson.M{"id": "abcde"}).One(&app)
if err != nil {
panic(err)
}
h.ServeHTTP(w, r)
})
}
(though you don't want to panic in your app, and should provide a better error response)

Resources