go 自定义http.Client - 动态修改请求Body
前言
在对接Alexa Smart Home时,有的请求Payload中需要传入Access Token,但是这个Token是由OAuth2 Client管理的,封装Payload时并不知道Access Token。
所以使用自定义RoundTripper,在请求前取出Header里的token,修改body,实现动态修改payload。
原理
go中可以使用http.DefaultClient进行http请求,也可以自己创建http.Client,传入自定义Transport可以实现对request的处理。
http.Client
// A Client is an HTTP client. Its zero value (DefaultClient) is a
// usable client that uses DefaultTransport.
//
// The Client's Transport typically has internal state (cached TCP
// connections), so Clients should be reused instead of created as
// needed. Clients are safe for concurrent use by multiple goroutines.
//
// A Client is higher-level than a RoundTripper (such as Transport)
// and additionally handles HTTP details such as cookies and
// redirects.
//
// When following redirects, the Client will forward all headers set on the
// initial Request except:
//
// • when forwarding sensitive headers like "Authorization",
// "WWW-Authenticate", and "Cookie" to untrusted targets.
// These headers will be ignored when following a redirect to a domain
// that is not a subdomain match or exact match of the initial domain.
// For example, a redirect from "foo.com" to either "foo.com" or "sub.foo.com"
// will forward the sensitive headers, but a redirect to "bar.com" will not.
//
// • when forwarding the "Cookie" header with a non-nil cookie Jar.
// Since each redirect may mutate the state of the cookie jar,
// a redirect may possibly alter a cookie set in the initial request.
// When forwarding the "Cookie" header, any mutated cookies will be omitted,
// with the expectation that the Jar will insert those mutated cookies
// with the updated values (assuming the origin matches).
// If Jar is nil, the initial cookies are forwarded without change.
//
type Client struct {
// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper // CheckRedirect specifies the policy for handling redirects.
// If CheckRedirect is not nil, the client calls it before
// following an HTTP redirect. The arguments req and via are
// the upcoming request and the requests made already, oldest
// first. If CheckRedirect returns an error, the Client's Get
// method returns both the previous Response (with its Body
// closed) and CheckRedirect's error (wrapped in a url.Error)
// instead of issuing the Request req.
// As a special case, if CheckRedirect returns ErrUseLastResponse,
// then the most recent response is returned with its body
// unclosed, along with a nil error.
//
// If CheckRedirect is nil, the Client uses its default policy,
// which is to stop after 10 consecutive requests.
CheckRedirect func(req *Request, via []*Request) error // Jar specifies the cookie jar.
//
// The Jar is used to insert relevant cookies into every
// outbound Request and is updated with the cookie values
// of every inbound Response. The Jar is consulted for every
// redirect that the Client follows.
//
// If Jar is nil, cookies are only sent if they are explicitly
// set on the Request.
Jar CookieJar // Timeout specifies a time limit for requests made by this
// Client. The timeout includes connection time, any
// redirects, and reading the response body. The timer remains
// running after Get, Head, Post, or Do return and will
// interrupt reading of the Response.Body.
//
// A Timeout of zero means no timeout.
//
// The Client cancels requests to the underlying Transport
// as if the Request's Context ended.
//
// For compatibility, the Client will also use the deprecated
// CancelRequest method on Transport if found. New
// RoundTripper implementations should use the Request's Context
// for cancellation instead of implementing CancelRequest.
Timeout time.Duration
}
http.RoundTripper
// RoundTripper is an interface representing the ability to execute a
// single HTTP transaction, obtaining the Response for a given Request.
//
// A RoundTripper must be safe for concurrent use by multiple
// goroutines.
type RoundTripper interface {
// RoundTrip executes a single HTTP transaction, returning
// a Response for the provided Request.
//
// RoundTrip should not attempt to interpret the response. In
// particular, RoundTrip must return err == nil if it obtained
// a response, regardless of the response's HTTP status code.
// A non-nil err should be reserved for failure to obtain a
// response. Similarly, RoundTrip should not attempt to
// handle higher-level protocol details such as redirects,
// authentication, or cookies.
//
// RoundTrip should not modify the request, except for
// consuming and closing the Request's Body. RoundTrip may
// read fields of the request in a separate goroutine. Callers
// should not mutate or reuse the request until the Response's
// Body has been closed.
//
// RoundTrip must always close the body, including on errors,
// but depending on the implementation may do so in a separate
// goroutine even after RoundTrip returns. This means that
// callers wanting to reuse the body for subsequent requests
// must arrange to wait for the Close call before doing so.
//
// The Request's URL and Header fields must be initialized.
RoundTrip(*Request) (*Response, error)
}
实现
我们先写一个server,打印出访问的payload信息。
package main import (
"fmt"
"io/ioutil"
"net/http"
) func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
req, err := ioutil.ReadAll(r.Body)
if err != nil {
rw.WriteHeader(500)
rw.Write([]byte(err.Error()))
return
}
fmt.Println(string(req))
})
if err := http.ListenAndServe(":8000", mux); err != nil {
panic(err)
}
}
如果使用默认的DefaultClient,只会打印出我们传入的payload。
package main import (
"fmt"
"io/ioutil"
"net/http"
"strings" "github.com/google/uuid"
) func main() {
id := uuid.NewString()
req, _ := http.NewRequest("GET", "http://localhost:8000", strings.NewReader(fmt.Sprintf(`{"id":"%s"}`, id)))
req.Header.Add("Authorization", fmt.Sprintf("Bearer token%s", id))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
fmt.Println(resp)
}()
结果:
{"id":"912733ce-4e17-4209-ad9e-71159fd37845"}
&{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[0] Date:[Sun, 28 Nov 2021 06:48:50 GMT]] {} 0 [] false false map[] 0xc000194000 <nil>}
使用自定义Transport
package main import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
) type customTransport struct {
} func (t *customTransport) RoundTrip(req *http.Request) (*http.Response, error) {
token := req.Header.Get("Authorization")
if len(token) != 0 && strings.HasPrefix(token, "Bearer ") {
token = token[7:]
var bodyBytes []byte
if req.Body != nil {
bodyBytes, _ = ioutil.ReadAll(req.Body)
}
var payload map[string]interface{}
if err := json.Unmarshal(bodyBytes, &payload); err != nil {
return nil, err
} else {
payload["token"] = token
if bodyBytes, err := json.Marshal(payload); err != nil {
return nil, err
} else {
req.ContentLength = int64(len(bodyBytes))
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
}
}
}
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return nil, err
}
return resp, nil
}
使用自定义Client
package main import (
"fmt"
"io/ioutil"
"net/http"
"strings" "github.com/google/uuid"
) func main() {
id := uuid.NewString()
req, _ := http.NewRequest("GET", "http://localhost:8000", strings.NewReader(fmt.Sprintf(`{"id":"%s"}`, id)))
req.Header.Add("Authorization", fmt.Sprintf("Bearer token%s", id))
client := &http.Client{
Transport: &customTransport{},
}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
fmt.Println(resp)
}()
最终结果:
{"id":"ebcceb4b-1979-457b-bf49-9255ceb77322","token":"tokenebcceb4b-1979-457b-bf49-9255ceb77322"}
&{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[0] Date:[Sun, 28 Nov 2021 06:49:25 GMT]] {} 0 [] false false map[] 0xc000140000 <nil>}
总结
我们可以使用http.DefaultClient完成大部分http请求,但是如果我们需要实现一些自定义逻辑时,可以传入http.Client中对应自定义的部分,实现自定义逻辑。
本文中通过修改Transport,读取请求Header,并修改请求Body,动态修改请求Payload。
go 自定义http.Client - 动态修改请求Body的更多相关文章
- Spring Cloud Gateway 动态修改请求参数解决 # URL 编码错误传参问题
Spring Cloud Gateway 动态修改请求参数解决 # URL 编码错误传参问题 继实现动态修改请求 Body 以及重试带 Body 的请求之后,我们又遇到了一个小问题.最近很多接口,收到 ...
- vue打包之后动态修改请求接口方法
1.可以根据自身情况封装获取配置文件接口信息 1.1我在static中新建一个config.json配置文件 { "DEV_URL":"/apis",//开发模 ...
- springboot+zuul(一)------实现自定义过滤器、动态路由、动态负载。
参考:https://blog.csdn.net/u014091123/article/details/75433656 https://blog.csdn.net/u013815546/articl ...
- 动态修改 NodeJS 程序中的变量值
如果一个 NodeJS 进程正在运行,有办法修改程序中的变量值么?答案是:通过 V8 的 Debugger 接口可以!本文将详细介绍实现步骤. 启动一个 HTTP Server 用简单的 Hello ...
- Logback中使用TurboFilter实现日志级别等内容的动态修改
可能看到这个标题,读者会问:要修改日志的级别,不是直接修改log.xxx就好了吗?为何要搞那么复杂呢?所以,先说一下场景,为什么要通过TurboFilter去动态的修改日志级别.我们在使用Java开发 ...
- vue-cli3抽离配置文件,动态修改打包后配置
由于项目有外部部署需求,对不同的环境前端调用后台的地址不一样,且不能提前预知必须到部署现场后才能确定后端地址,故需要将调用后端相关的配置抽离到文件中,打包后部署人员在方便现场修改. 思路如下: 1.由 ...
- thinkphp 3.2.3 动态修改conf配置文件
thinkphp 3.2.3 的C()方法能修改配置文件,但是是动态修改的,没有真正的更改文件. 我查了网上网友分享的方法,都不怎么合适,我就自己摸索写了一个,配置写到text.php中,我的目录如下 ...
- 动态修改 C 语言函数的实现
Objective-C 作为基于 Runtime 的语言,它有非常强大的动态特性,可以在运行期间自省.进行方法调剂.为类增加属性.修改消息转发链路,在代码运行期间通过 Runtime 几乎可以修改 O ...
- 基于Vue的SPA动态修改页面title的方法
最近基于VUE做个SPA手机端web发现动态修改页面标题通过document.title=xxxx 来修改着实蛋疼,而且在IOS的微信端据说没效果.百度发现要针对IOS的微信做点额外的操作,即:创建一 ...
随机推荐
- 2020.10.23-vj个人赛补题
B - B Polycarp loves lowercase letters and dislikes uppercase ones. Once he got a string s consistin ...
- Docker 常见命令
Docker 运行流程 辅助命令 # 1.安装完成辅助命令 docker version -------------------------- 查看docker的信息 docker info ---- ...
- 图解Redis6中的9种数据结构,墙裂建议准备去面试的人先看(干货,建议收藏)
如图所示,Redis中提供了9种不同的数据操作类型,他们分别代表了不同的数据存储结构. 图2-17 数据类型 String类型 String类型是Redis用的较多的一个基本类型,也是最简单的一种类型 ...
- 【UE4 C++】 射线检测 LineTrace 及 BoxTrace、SphereTrace、CapsuleTrace API
World.h 库里的 Trace API Trace模式 TraceSingle 单个结果 TraceMulti 多个结果 Trace 的检测依据 ByChanne ByObjectType ByP ...
- Coursera Deep Learning笔记 改善深层神经网络:超参数调试 Batch归一化 Softmax
摘抄:https://xienaoban.github.io/posts/2106.html 1. 调试(Tuning) 超参数 取值 #学习速率:\(\alpha\) Momentum:\(\bet ...
- 【二食堂】Alpha - Scrum Meeting 4
Scrum Meeting 4 例会时间:4.14 12:30 - 12:50 进度情况 组员 昨日进度 今日任务 李健 1. 主页面的搭建工作issue 1. 完成主页搭建**issue2. 与后端 ...
- 2021.10.27考试总结[冲刺NOIP模拟17]
T1 宝藏 发现每个数成为中位数的长度是关于权值单调的.线段树二分判断是否合法,单调指针扫即可. 考场上写了二分,平添\(\log\). \(code:\) T1 #include<bits/s ...
- WPF PropertyChanged实现子属性通知
今天用WPF的View绑定了ViewModel的一个属性类,结果在属性类的子属性修改时,没有通知到UI. 如有要显示一个学生信息,采用WPF MVVM的模式,则前端代码 <StackPanel& ...
- jquery正则表达式验证【是否带有小数、是否中文名称组成、是否全由8位数字组成、电话码格式、邮件地址】
1 <form name="myform" action="" onsubmit="return fun1()"> 2 < ...
- 求1+2+3...+n 牛客网 剑指Offer
求1+2+3...+n 牛客网 剑指Offer 题目描述 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). ...