net/http

辅助方法

CanonicalHeaderKey

textproto.CanonicalMIMEHeaderKey效果相同。

Handler 和 HandlerFunc

Handler是一个interface

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

HandlerFunc是一类函数,它的参数列表与HandlerServeHTTP方法的参数列表相同:

type HandlerFunc func(ResponseWriter, *Request)

并且它实现了ServeHTTP方法:

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

因此,HandlerFunc的主要作用就是将一个普通函数包装成一个Handler

var f = func(w http.ResponseWriter, r *http.Request) {}
var g interface{} = http.HandlerFunc(f)
_, ok := g.(http.Handler)
fmt.Println(ok) // true
// This implementation is done according to RFC 6265:
//
//    http://tools.ietf.org/html/rfc6265

// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
type Cookie struct {
    Name       string
    Value      string
    Path       string
    Domain     string
    Expires    time.Time
    RawExpires string

    // MaxAge=0 means no 'Max-Age' attribute specified.
    // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
    // MaxAge>0 means Max-Age attribute present and given in seconds
    MaxAge   int
    Secure   bool
    HttpOnly bool
    Raw      string
    Unparsed []string // Raw text of unparsed attribute-value pairs
}

例如:

c := http.Cookie{
    Name:   "token",
    Value:  "abcd1234",
    Path:   "/",
    MaxAge: 3600,
}
fmt.Println(c.String()) // "token=abcd1234; Path=/; Max-Age=3600"

CookieJar是一个接口:

// A CookieJar manages storage and use of cookies in HTTP requests.
//
// Implementations of CookieJar must be safe for concurrent use by multiple
// goroutines.
//
// The net/http/cookiejar package provides a CookieJar implementation.
type CookieJar interface {
    // SetCookies handles the receipt of the cookies in a reply for the
    // given URL.  It may or may not choose to save the cookies, depending
    // on the jar's policy and implementation.
    SetCookies(u *url.URL, cookies []*Cookie)

    // Cookies returns the cookies to send in a request for the given URL.
    // It is up to the implementation to honor the standard cookie use
    // restrictions such as in RFC 6265.
    Cookies(u *url.URL) []*Cookie
}

net/http/cookiejar提供了一个具体的实现。

textproto.MIMEHeader类似,本质上也是一个map[string][]string。同样拥有AddSetGetDel方法,此外还有WriteWriteSubset方法。

h := http.Header{}
h.Set("Content-Type", "application/json")
h.Add("Accept-Encoding", "gzip")
h.Add("Accept-Encoding", "deflate")
h.Set("Cache-Control", "no-cache")

h.Write(os.Stdout)
// Accept-Encoding: gzip
// Accept-Encoding: deflate
// Cache-Control: no-cache
// Content-Type: application/json

h.WriteSubset(os.Stdout, map[string]bool{
    "Accept-Encoding": true,
})
// Cache-Control: no-cache
// Content-Type: application/json

Request

Request是对HTTP的封装,包括作为服务器接收到的请求以及作为客户端发出的请求。相关的字段可以参看http.Request

作为客户端发出的请求时,有以下方法:

  • AddCookie(c *Cookie)
  • SetBasicAuth(username, password string)

作为服务器接收的请求时,有以下方法:

  • BasicAuth() (username, password string, ok bool)
  • Cookie(name string) (*Cookie, error)
  • Cookies() []*Cookie
  • FormFile(key string) (multipart.File, *multipart.FileHeader, error)
  • FormValue(key string) string
  • PostFormValue(key string) string
  • Referer() string
  • UserAgent() string

为了获取表单的参数值,需要先调用ParseForm或者ParseMultipartForm,例如:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()

    fmt.Println("UserAgent:", r.UserAgent())
    fmt.Println(r.Form)
    fmt.Println(r.PostForm)

    w.Write([]byte("hello world"))
})
http.ListenAndServe(":8080", nil)

执行:

curl -X POST -d "hello=world" "localhost:8080/?a=b&c=d"

输出为:

UserAgent: curl/7.51.0
map[hello:[world] a:[b] c:[d]]
map[hello:[world]]

Response

当作为客户端发请求时,服务器的返回就是一个Response。具体字段可以参看http.Response

以下几个HTTP方法都可以得到一个Response

  • Get(url string) (resp *Response, err error)
  • Head(url string) (resp *Response, err error)
  • Post(url string, contentType string, body io.Reader) (resp *Response, err error)
  • PostForm(url string, data url.Values) (resp *Response, err error)

例如:

resp, err := http.Get("https://httpbin.org/user-agent")
if err != nil {
    fmt.Println(err)
    return
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
// { "user-agent": "Go-http-client/1.1" }

Client

Client作为客户端发请求。有如下方法:

  • func (c *Client) Do(req *Request) (*Response, error)
  • func (c *Client) Get(url string) (resp *Response, err error)
  • func (c *Client) Head(url string) (resp *Response, err error)
  • func (c *Client) Post(url string, contentType string, body io.Reader) (resp *Response, err error)
  • func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)

事实上,GetHeadPostPostForm都是构建好Request对象后,调用Do方法。

有一个全局的DefaultClient对象,全局的GetHeadPostPostForm其实是调用了DefaultClient的相应方法。

如果只是发送简单的请求,直接用http.Get等这些方法就可以了,如果需要定制header等行为,则需要显式使用client.Do(req),例如:

client := &http.Client{
    CheckRedirect: redirectPolicyFunc,
}

resp, err := client.Get("http://example.com")
// ...

req, err := http.NewRequest("GET", "http://example.com", nil)
// ...
req.Header.Add("If-None-Match", `W/"wyzzy"`)
resp, err := client.Do(req)
// ...

Transport

Transport是比Client更底层的东西,如果需要控制proxy等更具体的行为,需要使用Transport,例如:

tr := &http.Transport{
    MaxIdleConns:       10,
    IdleConnTimeout:    30 * time.Second,
    DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")

ResponseWriter

当作为服务器时,接收到请求后会进行处理,然后发送回复。ResponseWriter是一个接口,就是对服务器回复的抽象。

type ResponseWriter interface {
    Header() Header

    Write([]byte) (int, error)

    WriteHeader(int)
}

ServeMux

可以认为ServeMux是一个简单的路由器,当接收到请求时,它会将请求分发给相应的Handler来处理。

通过NewServeMux()可以创建一个新的ServeMux。有一个全局的默认ServeMuxDefaultServeMux

有如下方法:

  • Handle(pattern string, handler Handler):注册Handler
  • HandleFunc(pattern string, handler func(ResponseWriter, *Request)):注册处理函数
  • Handler(r *Request) (h Handler, pattern string):返回响应request对象应该使用的Handler
  • ServeHTTP(w ResponseWriter, r *Request):实现了该方法,因此ServeMux本身也是Handler,该方法先调用Handler(r *Request)得到应当使用的Handler,然后调用该Handler的ServeHTTP方法

全局的HandleHandleFunc方法分别调用了DefaultServeMux的相应方法。