How send request header as response header using go-kit - go

I'm developing a service rest in Go using go-kit. I need send a header response. This header response should have the same value of request header.
This is a part of my transport.go:
func MakeHandler(m **http.ServeMux) http.Handler {
const URL = "..."
var serverOptions []kithttp.ServerOption
logger := log.NewLogfmtLogger(os.Stderr)
var svc repositories.SignDocumentRepository
impl := persistence.NewSignerDocumentRepository()
svc = middleware.LoggingMiddlewareSignDocument{Logger: logger, Next: impl}
registerHandler := kithttp.NewServer(
makeSignDocumentEndpoint(svc),
decodeRequest,
encodeResponse,
serverOptions...,
)
r := mux.NewRouter()
r.Handle(URL, handlers.LoggingHandler(os.Stdout, registerHandler))
(*m).Handle(URL, r)
return nil
}
func decodeRequest(_ context.Context, r *http.Request) (interface{}, error) {
return r, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("headerA", "val1")
w.Header().Set("headerB", "") // This header should be equal that a header request
switch response.(type) {
case model.MsgRsHdr:
w.WriteHeader(http.StatusPartialContent)
default:
w.WriteHeader(http.StatusAccepted)
}
if response != nil {
return json.NewEncoder(w).Encode(response)
}
return nil
}
How do I get a request header in encodeResponse method?

You can use ServerBefore to put *http.Request in the context, and can get it in encodeResponse to read request headers.
type ctxRequestKey struct{}
func putRequestInCtx(ctx context.Context, r *http.Request, _ Request) context.Context {
return context.WithValue(ctx, ctxRequestKey{}, r)
}
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
r := ctx.Value(ctxRequestKey{}).(*http.Request)
// can use r.Header.Get here to read request here.
}
serverOptions := []kithttp.ServerOptions{
kithttp.ServerBefore(putRequestInCtx),
}
registerHandler := kithttp.NewServer(
makeSignDocumentEndpoint(svc),
decodeRequest,
encodeResponse,
serverOptions...,
)

Related

How to log HTTP client's requests with request ID that was created by Gin context

Idea: I want to log incoming and outcoming requests to my Gin server with unique request ID. Also I want to log all HTTP client's requests inside my Gin's routes using the same request ID that route has.
All of that should to work under the hood using middleware.
Logging requests to my server (and responses)
To log each request to my server I wrote this middleware:
import (
"bytes"
"context"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"io/ioutil"
"net/http"
"time"
)
type responseBodyWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (r responseBodyWriter) Write(b []byte) (int, error) {
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
w := &responseBodyWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
c.Writer = w
msg := "Input:"
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
if raw != "" {
path = path + "?" + raw
}
// Read from body and write here again.
var bodyBytes []byte
if c.Request.Body != nil {
bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
inputLogger := log.With().
Str("method", c.Request.Method).
Str("path", path).
Str("requestId", requestid.Get(c)).
Logger()
if len(bodyBytes) > 0 {
inputLogger.Info().RawJSON("body", bodyBytes).Msg(msg)
} else {
inputLogger.Info().Msg(msg)
}
c.Next()
end := time.Now()
latency := end.Sub(start)
msg = "Output:"
outputLogger := log.With().
Str("method", c.Request.Method).
Str("path", path).
Str("requestId", requestid.Get(c)).
RawJSON("body", w.body.Bytes()).
Int("status", c.Writer.Status()).
Dur("latency", latency).
Logger()
switch {
case c.Writer.Status() >= http.StatusBadRequest && c.Writer.Status() < http.StatusInternalServerError:
{
outputLogger.Warn().Msg(msg)
}
case c.Writer.Status() >= http.StatusInternalServerError:
{
outputLogger.Error().Msg(msg)
}
default:
outputLogger.Info().Msg(msg)
}
}
}
Logging requests made inside my servers route
Here is the problem: I don't know how to pass request ID (or Gin's context), created by Gin's middleware to the RoundTrip function:
type Transport struct {
Transport http.RoundTripper
}
var defaultTransport = Transport{
Transport: http.DefaultTransport,
}
func init() {
http.DefaultTransport = &defaultTransport
}
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
ctx := context.WithValue(req.Context(), ContextKeyRequestStart, time.Now())
req = req.WithContext(ctx)
t.logRequest(req)
resp, err := t.transport().RoundTrip(req)
if err != nil {
return resp, err
}
t.logResponse(resp)
return resp, err
}
func (t *Transport) logRequest(req *http.Request) {
log.Info().
Str("method", req.Method).
Str("path", req.URL.String()).
Str("requestId", "how can I get request id here???").
Msg("Api request: ")
}
func (t *Transport) logResponse(resp *http.Response) {
var bodyBytes []byte
if resp.Body != nil {
bodyBytes, _ = ioutil.ReadAll(resp.Body)
}
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
ctx := resp.Request.Context()
log.Info().
Str("method", resp.Request.Method).
Str("path", resp.Request.URL.String()).
Str("requestId", "how can I get request id here???").
RawJSON("body", bodyBytes).
Int("status", resp.StatusCode).
Dur("latency", time.Now().Sub(ctx.Value(ContextKeyRequestStart).(time.Time))).
Msg("API response: ")
}
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
The Transport.RoundTrip function takes a *http.Request parameter, so you should be able to pass the Gin context by just creating a request in your handlers with it:
func MyHandler(c *gin.Context) {
// passing context to the request
req := http.NewRequestWithContext(c, "GET", "http://localhost:8080", nil)
resp, err := http.DefaultClient.Do(req)
}
Note that to be able to make use of the default RoundTripper that you overwrote without additional initialization, you should use the http.DefaultClient.
You can use this:
https://github.com/sumit-tembe/gin-requestid
package main
import (
"net/http"
"github.com/gin-gonic/gin"
requestid "github.com/sumit-tembe/gin-requestid"
)
func main() {
// without any middlewares
router := gin.New()
// Middlewares
{
//recovery middleware
router.Use(gin.Recovery())
//middleware which injects a 'RequestID' into the context and header of each request.
router.Use(requestid.RequestID(nil))
//middleware which enhance Gin request logger to include 'RequestID'
router.Use(gin.LoggerWithConfig(requestid.GetLoggerConfig(nil, nil, nil)))
}
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello world!")
})
router.Run(":8080")
}
Output:
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - "GET /"
[GIN-debug] 2019-12-16T18:50:49+05:30 [bzQg6wTpL4cdZ9bM] - [::1] "GET / HTTP/1.1 200 22.415µs" Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
It also supports custom request id generator which you can design according to need.

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.

How can I unit test http handler embedded in a struct?

I have the following struct
type Server struct {
*http.Server
chain core.Blockchainer
coreServer *network.Server
}
with its corresponding handler
func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) {
.....
}
How can I unit test my handler?
The handler above
func (s *Server) methodHandler(w http.ResponseWriter, req *Request, reqParams Params) {
.....
}
can be tested by following these steps
handler := http.HandlerFunc(s.methodHandler)
req := httptest.NewRequest(...)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
assert.Equal(t, expected, resp)

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.
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/

Golang: Can I remove response headers coming from ReverseProxy?

I'm using httputil.ReverseProxy to proxy Amazon s3 files to my clients. I'd like to hide all headers coming from Amazon - is that possible without having to reimplement Reverse Proxy?
proxy := httputil.ReverseProxy{Director: func(r *http.Request) {
r.Header = http.Header{} // Don't send client's request headers to Amazon.
r.URL = proxyURL
r.Host = proxyURL.Host
}}
proxy.ServeHTTP(w, r) // How do I remove w.Headers ?
You can implement ReverseProxy.Transport
type MyTransport struct{
header http.Header
}
func (t MyTransport) RoundTrip(r *Request) (*Response, error){
resp, err := http.DefaultTransport.RoundTrip(r)
resp.Header = t.header
return resp, err
}
mytransport := MyTransport{
//construct Header
}
proxy := httputil.ReverseProxy{Director: func(r *http.Request) {
r.Header = http.Header{} // Don't send client's request headers to Amazon.
r.URL = proxyURL
r.Host = proxyURL.Host
},
Transport: mytransport,
}
This was my solution to remove/replace all the http.ReverseProxy response headers:
type responseHeadersTransport http.Header
func (t responseHeadersTransport) RoundTrip(r *http.Request) (*http.Response, error) {
resp, err := http.DefaultTransport.RoundTrip(r)
if err != nil {
return nil, err
}
resp.Header = http.Header(t)
return resp, nil
}
func ProxyFile(w http.ResponseWriter, r *http.Request) {
// ...
headers := http.Header{}
headers.Set("Content-Type", file.ContentType)
headers.Set("Content-Length", fmt.Sprintf("%d", file.Filesize))
headers.Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", file.Filename))
proxy := httputil.ReverseProxy{
Director: func(r *http.Request) { // Remove request headers.
r.Header = http.Header{}
r.URL = proxyURL
r.Host = proxyURL.Host
},
Transport: responseHeadersTransport(headers), // Replace response headers.
}
proxy.ServeHTTP(w, r)
}

Resources