1. http.Transport简介
Go语言中的http包提供了访问HTTP资源的基本功能。在使用http.Client发送请求时,需要通过http.Transport来控制请求的行为。http.Transport提供了对请求的一些许多个性化的配置选项,优化了对并发请求的支持。在实际使用场景中,效率和性能表现十分优秀。
http.Transport类型的对象被多个goroutine共享,因此线程安全是一个关键问题。同时,它使用了连接池技术,避免了因创建和关闭TCP连接而导致的性能问题。接下来,我们将通过一个例子来了解http.Transport的使用。
2. 实例演示
2.1 请求管道化的技巧
在进行HTTP请求时,系统常常会需要一些链接,而且这些链接的数量是动态的,可能无法预测。这可能会导致在并发处理请求时遇到一些瓶颈。一种方法是使用常规的并发机制,例如Go语言的goroutine。但是,这种方法可能无法处理大量的链接。
在Go语言中,有一个名为管道的数据结构,它可以帮助我们有效地处理这些请求。简单来说,它是同时可以读取和写入数据的一系列协程通信机制。
使用管道来处理HTTP请求的方法如下:
创建一个管道,它的数据类型为http.Request和http.Response。
创建指定数量的goroutine,每个goroutine从管道中读取http.Request。
对于每个从管道中读取的http.Request,处理它,然后将结果写入Response通道。
从Response通道中读取结果。
这种方法可以处理大量的HTTP请求,而不需要创建很多goroutine。
接下来,我们编写一个简短的代码片段来说明使用HTTP请求管道的过程。
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func handleReq(req *http.Request, respChan chan <- *http.Response) {
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
respChan <- resp
}
func main() {
req, err := http.NewRequest(http.MethodGet, "http://www.google.com", nil)
if err != nil {
fmt.Println(err)
return
}
respChan := make(chan *http.Response)
for i := 0; i < 10; i++ {
go handleReq(req, respChan)
}
for i := 0; i < 10; i++ {
resp := <-respChan
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(body))
resp.Body.Close()
}
}
在上面的代码中,我们从Google.com获取了十次HTTP请求。运行程序后,可以看到结果,并且每个HTTP响应体都被正确地读取。
2.2 http.Transport的应用举例
接下来,我们将通过一个更具体的例子来了解如何使用http.Transport。
假设我们需要量化我们应用程序中的HTTP请求。为了实现此功能,我们可以创建一个HTTP拦截器,在每一次HTTP请求被发送的时候,在请求体中添加一段JavaScript代码,来测量请求的时间。在HTTP响应体返回前,通过JavaScript代码停止计时器,从而计算请求时间。
http.Transport类型对象通过RoundTripper接口进行和http.Client的交互。因此,我们可以创建一个http.Transport对象,为http.Client提供自定义RoundTripper。我们可以在RoundTripper接口的RoundTrip方法中添加一些行为。
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
type RoundTripMetrics struct {
roundTripper http.RoundTripper
}
func NewRoundTripMetrics(roundTripper http.RoundTripper) *RoundTripMetrics {
return &RoundTripMetrics{
roundTripper: roundTripper,
}
}
func (rtm *RoundTripMetrics) RoundTrip(req *http.Request) (*http.Response, error) {
records, err := metricRequest(req)
if err != nil {
return nil, err
}
resp, err := rtm.roundTripper.RoundTrip(req)
if err != nil {
return resp, err
}
metricResponse(resp, records)
return resp, err
}
func metricRequest(req *http.Request) (map[string]interface{}, error) {
start := time.Now()
req.Header.Set("X-HTTP-Start", fmt.Sprintf("%v", start.Unix()))
js := `
var start = new Date().getTime();
function calcEndTime() {
var end = new Date().getTime();
var time = end - start;
console.log("Request time: " + time + "ms");
}
setTimeout(calcEndTime, 0);
`
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("%s
", js))
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
req.Body.Close()
newBody := strings.Replace(string(body), "", buffer.String(), 1)
req.Body = ioutil.NopCloser(bytes.NewBufferString(newBody))
req.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBody)))
record := make(map[string]interface{})
record["start"] = start
record["url"] = req.URL.String()
return record, nil
}
func metricResponse(resp *http.Response, records map[string]interface{}) {
if resp == nil {
return
}
end := time.Now()
startUnix, ok := records["start"].(time.Time)
if !ok {
return
}
respHeader := `
var start = new Date().getTime();
var end = new Date().getTime();
console.log("Request time: " + (end - start) + "ms");
`
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
defer resp.Body.Close()
newBody := strings.Replace(string(body), "", respHeader, 1)
resp.Body = ioutil.NopCloser(bytes.NewBufferString(newBody))
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(newBody)))
resp.Header.Set("X-HTTP-Time", fmt.Sprintf("%v", end.Unix()-startUnix.Unix()))
resp.Header.Set("X-HTTP-Req", records["url"].(string))
}
func main() {
proxyUri := "https://:@:"
proxyURL, err := url.Parse(proxyUri)
if err != nil {
panic(err)
}
c := &http.Client{
Transport: &RoundTripMetrics{
http.DefaultTransport.(*http.Transport),
},
}
req, err := http.NewRequest(http.MethodGet, "https://www.google.com/search?q=golang",
nil)
if err != nil {
panic(err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
req.Header.Set("Accept-Language", "en-us")
req.Header.Set("Content-Type", "text/html")
resp, err := c.Do(req)
if err != nil {
panic(err)
}
resBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println(string(resBody))
}
在上面的代码中,我们重载了http.RoundTripper接口的RoundTrip方法。我们在该方法中测量HTTP请求的时间。对于每个HTTP请求和响应,我们添加了JavaScript代码,并在响应体中计算了请求时间。
总结
在本文中,我们通过两个示例了解了http.Transport类型对象的实际应用。同时,我们了解了如何使用管道机制来高效处理http请求。在我们的第二个示例中,我们展示了如何调整Go HTTP客户端的行为,并添加自定义JavaScript脚本来测量每个HTTP请求和响应。