Golang supports developers to create a great application with its solid and handful of built-in packages. One of them is HTTP Client. HTTP Client, just like its name, helps developers to create an HTTP Client that can make HTTP requests to other services. Golang even provides developers with its default client so, you don’t need to create one. But sometimes, you need to create one that fits your usage.
For Example, you have a Golang application that needs to make requests to one service. That service has a defined standard of the HTTP request body. Let’s say like this:
1
2
3
4
5
{
"aStandardWrapperRequired": {
// your real request here
}
}
Let’s say you need to wrap all your hundreds of request bodies to fits the requirement. Usually, you may make a higher-order function that adjusts your body request to that requirement. But in this article, you will learn another way to handle that using an HTTP Interceptor.
Now let’s simulate and create the server and client. All the codes below will require you to use at least Golang version 1.16.
Initiate the Project
First thing first, let’s create a simple project called interceptor.
Please be aware that inside this project, all errors are ignored to simplify the code. You may not want to copy and paste all of this code into a production code. Please take it with a grain of salt.
Both the server and the client will reflect this into the terminal:
1
2
3
{
"data": "json"}
Now let’s create the custom HTTP Client that will intercept our request to the server.
Intercept the Client Request
Golang has this one interface called RoundTripper that is implemented by Golang as a DefaultTransport, which is called every time you make an HTTP Request using the DefaultClient. I advise you to really read the docs before implementing this RoundTripper.
...
func (Interceptor) modifyResponse(r *http.Response) *http.Response {
respBody := json.MustHumanize(r.Body)
modRespBody := []byte(fmt.Sprintf(`{"resp": %s}`, respBody))
ModRespBodyLen := len(modRespBody)
r.Body = io.NopCloser(bytes.NewReader(modRespBody))
r.ContentLength = int64(ModRespBodyLen)
r.Header.Set("Content-Length", fmt.Sprintf("%d", ModRespBodyLen))
return r
}
func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) {
deferfunc() {
_ = r.Body.Close()
}()
// modify before the request is sent
newReq := i.modifyRequest(r)
// send the request using the DefaultTransport
resp, _ := i.core.RoundTrip(newReq)
deferfunc() {
_ = resp.Body.Close()
}()
// modify after the response is received
newResp := i.modifyResponse(resp)
return newResp, nil}
Now, if you re-run the client, the output of the server should be the same as before:
1
2
3
4
5
{
"req": {
"data": "json" }
}
But the client output has been altered to this:
1
2
3
4
5
6
7
{
"resp": {
"req": {
"data": "json" }
}
}
Conclusion
You may find a better solution for the case above. All those experiments are only for learning purposes, that you may find them interesting. Once again, I’m not recommending you to copy-paste the codes above unless you know what you’re doing. Working with the RoundTripper is not that hard, but it is quite tricky since you may coincidentally violate and create bugs inside the interceptor.
Thank you for reading!
···
Love This Content?
Any kind of supports is greatly appreciated! Kindly support me via Bitcoin, Ko-fi, Trakteer, or just continue to read another content. You can write a response via Webmention and let me know the URL via Telegraph.