前言
Go语言标准库 net/http
是一个非常强大的标准库,使得构建 HTTP
请求和编写 Web
服务器的工作变得非常简单。
我们来看看是他是如何实现客户端
和服务端
的。
使用示例
假设本地有一个GET
方法的HTTP
接口,响应 Hello World!
使用 net/http
库构建HTTP
客户端请求这个接口。
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.DefaultClient.Get("http://127.0.0.1:8080/hello")
if err != nil {
fmt.Printf("get failed, err:%v\n", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("read from resp.body failed, err:%v\n", err)
return
}
fmt.Println(string(body))
}
可以获得响应内容 Hello World!
通过这样一个简单的例子,可以看到客户端主要使用http.Client{}
,服务端主要使用http.ListenAndServe
和http.HandleFunc
,今天我们先看看客户端的代码是怎么封装的。
client 结构体
定义客户端的结构体是net/http.Client{}
,具体结构如下:
type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration
}
Transport
:其类型是RoundTripper
,RoundTrip
代表一个本地事务,RoundTripper
接口的实现主要有三个,主要目的是支持更好的扩展性。Transport
http2Transport
fileTransport
;
CheckRedirect
:用来做重定向Jar
:其类型是CookieJar
,用来做cookie
管理,CookieJar
接口的实现Jar
结构体在源码包net/http/cookiejar/jar.go
;Timeout
超时时间
我们可以直接通过net/http.DefaultClient
发起HTTP请求,也可以自己构建新的net/http.Client
实现自定义的HTTP事务。
client 基本结构
Request
Request
结构体,其中包含了 HTTP
请求的方法、URL
、协议版本、协议头以及请求体等字段,
还包括了指向响应的引用:Response
;
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
ContentLength int64
removed as necessary when sending and
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
Response *Response
ctx context.Context
}
提供了 NewRequest()
、NewRequestWithContext()
两个方法用来构建请求,这个方法可以校验HTTP
请求的字段并根据输入的参数拼装成新的请求结构体。
NewRequest()
方法内部也是调用的NewRequestWithContext
。
区别就是是否使用 context
来做goroutine
上下文传递;
NewRequestWithContext()
创建 request
请求结构体
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
// 默认使用 GET 方法
if method == "" {
method = "GET"
}
// 校验请求方法是否有效,常用 GET、POST、PUT,DELETE 等
//OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT
if !validMethod(method) {
return nil, fmt.Errorf("net/http: invalid method %q", method)
}
// ctx 必传,NewRequest() 方法调用时会传递 context.Background()
if ctx == nil {
return nil, errors.New("net/http: nil Context")
}
// 解析URL,解析Scheme、Host、Path等信息
u, err := urlpkg.Parse(url)
if err != nil {
return nil, err
}
// body 在下面会根据其类型包装成 io.ReadCloser 类型
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = io.NopCloser(body)
}
// The host's colon:port should be normalized. See Issue 14836.
u.Host = removeEmptyPort(u.Host)
req := &Request{
ctx: ctx,
Method: method,
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(Header),
Body: rc,
Host: u.Host,
}
if body != nil {
switch v := body.(type) {
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
buf := v.Bytes()
req.GetBody = func() (io.ReadCloser, error) {
r := bytes.NewReader(buf)
return io.NopCloser(r), nil
}
case *bytes.Reader:
req.ContentLength = int64(v.Len())
snapshot := *v
req.GetBody = func() (io.ReadCloser, error) {
r := snapshot
return io.NopCloser(&r), nil
}
case *strings.Reader:
req.ContentLength = int64(v.Len())
snapshot := *v
req.GetBody = func() (io.ReadCloser, error) {
r := snapshot
return io.NopCloser(&r), nil
}
default:
if req.GetBody != nil && req.ContentLength == 0 {
req.Body = NoBody
req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
}
}
return req, nil
}
}