前言
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
	}
}


















