net/http 库的客户端实现(上)

前言

Go语言标准库 net/http 是一个非常强大的标准库,使得构建 HTTP 请求和编写 Web 服务器的工作变得非常简单。

我们来看看是他是如何实现客户端服务端的。

net/http 库的客户端实现(上)

net/http 库的客户端实现(下)

net/http 库的服务端实现

使用示例

假设本地有一个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.ListenAndServehttp.HandleFunc,今天我们先看看客户端的代码是怎么封装的。

client 结构体

定义客户端的结构体是net/http.Client{},具体结构如下:

type Client struct {
    Transport RoundTripper
    CheckRedirect func(req *Request, via []*Request) error
    Jar CookieJar
    Timeout time.Duration
}
  • Transport:其类型是RoundTripperRoundTrip代表一个本地事务,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
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值