zoukankan      html  css  js  c++  java
  • go标准库的学习-net/http

    参考:https://studygolang.com/pkgdoc

    概念解释:

    • request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息
    • response:服务器返回给客户端的信息
    • conn:用户的每次请求链接
    • handler:处理请求和生成返回信息的处理逻辑

    该图来自https://www.sohu.com/a/208720509_99960938

    下面的内容来自http://www.runoob.com/http/http-messages.html

    HTTP使用统一资源标识符(Uniform Resource Identifiers, URI)来传输数据和建立连接。

    一旦建立连接后,数据消息就通过类似Internet邮件所使用的格式[RFC5322]和多用途Internet邮件扩展(MIME)[RFC2045]来传送。

    客户端请求消息

    客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

    举例:

    GET /hello.txt HTTP/1.1
    User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
    Host: www.example.com
    Accept-Language: en, mi

    HTTP 协议的 8 种请求方法介绍:

    HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法

    HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

    HTTP 协议中共定义了八种请求方法或者叫“动作”来表明对 Request-URI 指定的资源的不同操作方式,具体介绍如下:

    •  OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。 
    •  HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。 
    •  GET:向特定的资源发出请求。 
    •  POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。 
    •  PUT:向指定资源位置上传其最新内容。 
    •  DELETE:请求服务器删除 Request-URI 所标识的资源。 
    •  TRACE:回显服务器收到的请求,主要用于测试或诊断。 
    •  CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 

    虽然 HTTP 的请求方式有 8 种,但是我们在实际应用中常用的也就是 get 和 post,其他请求方式也都可以通过这两种方式间接的来实现。

    服务器响应消息

    HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

    举例

    服务端响应:

    HTTP/1.1 200 OK
    Date: Mon, 27 Jul 2009 12:28:53 GMT
    Server: Apache
    Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
    ETag: "34aa387-d-1568eb00"
    Accept-Ranges: bytes
    Content-Length: 51
    Vary: Accept-Encoding
    Content-Type: text/plain

    输出结果:

    Hello World! My payload includes a trailing CRLF.

    1>常量

    const (
        StatusContinue           = 100
        StatusSwitchingProtocols = 101
        StatusOK                   = 200
        StatusCreated              = 201
        StatusAccepted             = 202
        StatusNonAuthoritativeInfo = 203
        StatusNoContent            = 204
        StatusResetContent         = 205
        StatusPartialContent       = 206
        StatusMultipleChoices   = 300
        StatusMovedPermanently  = 301
        StatusFound             = 302
        StatusSeeOther          = 303
        StatusNotModified       = 304
        StatusUseProxy          = 305
        StatusTemporaryRedirect = 307
        StatusBadRequest                   = 400
        StatusUnauthorized                 = 401
        StatusPaymentRequired              = 402
        StatusForbidden                    = 403
        StatusNotFound                     = 404
        StatusMethodNotAllowed             = 405
        StatusNotAcceptable                = 406
        StatusProxyAuthRequired            = 407
        StatusRequestTimeout               = 408
        StatusConflict                     = 409
        StatusGone                         = 410
        StatusLengthRequired               = 411
        StatusPreconditionFailed           = 412
        StatusRequestEntityTooLarge        = 413
        StatusRequestURITooLong            = 414
        StatusUnsupportedMediaType         = 415
        StatusRequestedRangeNotSatisfiable = 416
        StatusExpectationFailed            = 417
        StatusTeapot                       = 418
        StatusInternalServerError     = 500
        StatusNotImplemented          = 501
        StatusBadGateway              = 502
        StatusServiceUnavailable      = 503
        StatusGatewayTimeout          = 504
        StatusHTTPVersionNotSupported = 505
    )

    HTTP状态码

    当你得到一个值的时候,如果你想要知道这个值代表的是什么状态,可以使用:

    func StatusText

    func StatusText(code int) string

    StatusText返回HTTP状态码code对应的文本,如220对应"OK"。如果code是未知的状态码,会返回""。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    func main() {
        sta1 := http.StatusText(307)
        sta2 := http.StatusText(200)
        fmt.Println(sta1) //Temporary Redirect
        fmt.Println(sta2) //OK
    }

    2>变量

    var DefaultClient = &Client{}

    DefaultClient是用于包函数Get、Head和Post的默认Client。

    var DefaultServeMux = NewServeMux()

    DefaultServeMux是用于Serve的默认ServeMux。

    3>

    func CanonicalHeaderKey

    func CanonicalHeaderKey(s string) string

    CanonicalHeaderKey函数返回头域(表示为Header类型)的键s的规范化格式。规范化过程中让单词首字母和'-'后的第一个字母大写,其余字母小写。例如,"accept-encoding"规范化为"Accept-Encoding"。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    func main() {
        hea1 := http.CanonicalHeaderKey("uid-test")
        hea2 := http.CanonicalHeaderKey("accept-encoding")
        fmt.Println(hea1) //Uid-Test
        fmt.Println(hea2) //Accept-Encoding
    }

    func DetectContentType

    func DetectContentType(data []byte) string

    DetectContentType函数实现了http://mimesniff.spec.whatwg.org/描述的算法,用于确定数据的Content-Type。函数总是返回一个合法的MIME类型;如果它不能确定数据的类型,将返回"application/octet-stream"。它最多检查数据的前512字节。

    出处:https://www.cnblogs.com/52fhy/p/5436673.html

    Http Header里的Content-Type一般有这三种:

    • application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。
    • multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。
    • text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。

    网页中form的enctype属性为编码方式,常用有两种:

    • application/x-www-form-urlencoded,默认编码方式
    • multipart/form-data

    1)当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),然后把这个字串追加到url后面,用?分割,加载这个新的url。

    2)当action为post时候,浏览器把form数据封装到http body中,然后发送到server。 如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。

    3)当action为post且Content-Type类型是multipart/form-data,浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。

    4)当然还有很多其他的类型,可以查看http://www.runoob.com/http/http-content-type.html

    因此可以使用DetectContentType来检测传入的[]byte类型的数据是哪种Content-Type,举例说明:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    func main() {
        cont1 := http.DetectContentType([]byte{}) //text/plain; charset=utf-8
        cont2 := http.DetectContentType([]byte{1, 2, 3}) //application/octet-stream
        cont3 := http.DetectContentType([]byte(`<HtMl><bOdY>blah blah blah</body></html>`)) //text/html; charset=utf-8
        cont4 := http.DetectContentType([]byte("
    <?xml!")) //text/xml; charset=utf-8
        cont5 := http.DetectContentType([]byte(`GIF87a`)) //image/gif
        cont6 := http.DetectContentType([]byte("MThdx00x00x00x06x00x01")) //audio/midi
        fmt.Println(cont1)
        fmt.Println(cont2)
        fmt.Println(cont3)
        fmt.Println(cont4)
        fmt.Println(cont5)
        fmt.Println(cont6)
    }
    const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"

    TimeFormat是当解析或生产HTTP头域中的时间时,用与time.Parse或time.Format函数的时间格式。这种格式类似time.RFC1123但强制采用GMT时区。

    func ParseTime

    func ParseTime(text string) (t time.Time, err error)

    ParseTime用3种格式TimeFormat, time.RFC850和time.ANSIC尝试解析一个时间头的值(如Date: header)。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "time"
    )
    
    
    var parseTimeTests = []struct {
        h   http.Header
        err bool
    }{
        {http.Header{"Date": {""}}, true},
        {http.Header{"Date": {"invalid"}}, true},
        {http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true},
        {http.Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false},
        {http.Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false},
        {http.Header{"Date": {"Sun Nov  6 08:49:37 1994"}}, false},
    }
    
    func main() {
        expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC)
        fmt.Println(expect) //1994-11-06 08:49:37 +0000 UTC
        for i, test := range parseTimeTests {
            d, err := http.ParseTime(test.h.Get("Date"))
            fmt.Println(d)
            if err != nil {
                fmt.Println(i, err)
                if !test.err { //test.err为false才进这里
                    fmt.Errorf("#%d:
     got err: %v", i, err)
                }
                continue //有错的进入这后继续下一个循环,不往下执行
            }
            if test.err { //test.err为true,所以该例子中这里不会进入
                fmt.Errorf("#%d:
      should err", i)
                continue
            }
            if !expect.Equal(d) { //说明后三个例子的结果和expect是相同的,所以没有报错
                fmt.Errorf("#%d:
     got: %v
    want: %v", i, d, expect)
            }
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    1994-11-06 08:49:37 +0000 UTC
    0001-01-01 00:00:00 +0000 UTC //默认返回的空值
    0 parsing time "" as "Mon Jan _2 15:04:05 2006": cannot parse "" as "Mon"
    0001-01-01 00:00:00 +0000 UTC
    1 parsing time "invalid" as "Mon Jan _2 15:04:05 2006": cannot parse "invalid" as "Mon"
    0001-01-01 00:00:00 +0000 UTC
    2 parsing time "1994-11-06T08:49:37Z00:00" as "Mon Jan _2 15:04:05 2006": cannot parse "1994-11-06T08:49:37Z00:00" as "Mon"
    1994-11-06 08:49:37 +0000 UTC
    1994-11-06 08:49:37 +0000 GMT
    1994-11-06 08:49:37 +0000 UTC

    额外补充,time.Date():

    func Date

    func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time

    详情看go标准库的学习-time

     

    func ParseHTTPVersion

    func ParseHTTPVersion(vers string) (major, minor int, ok bool)

    ParseHTTPVersion解析HTTP版本字符串。如"HTTP/1.0"返回(1, 0, true)。

    举例:
    package main 
    import(
        "fmt"
        "net/http"
    )
    
    func main() {
        m, n, ok := http.ParseHTTPVersion("HTTP/1.0")
        fmt.Println(m, n, ok) //1 0 true

    4>

    1)header-服务端和客户端的数据都有头部

    type Header map[string][]string

    Header代表HTTP头域的键值对。

    你可以自定义自己的Header,下面的Header中只有Date字段,你还可以加入其他字段:

    http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}

    然后就能够调用下面的几种方法来对Header进行修改:

    func (Header) Get

    func (h Header) Get(key string) string

    Get返回键对应的第一个值,如果键不存在会返回""。如要获取该键对应的值切片,请直接用规范格式的键访问map。

    func (Header) Set

    func (h Header) Set(key, value string)

    Set添加键值对到h,如键已存在则会用只有新值一个元素的切片取代旧值切片。

    func (Header) Add

    func (h Header) Add(key, value string)

    Add添加键值对到h,如键已存在则会将新的值附加到旧值切片后面。

    func (Header) Del

    func (h Header) Del(key string)

    Del删除键值对。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    func main() {
        header := http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}
        fmt.Println(header.Get("Date")) //1994-11-06T08:49:37Z00:00
        fmt.Println(header.Get("Content-Type")) //因为没有该字段,返回为空
    
        header.Set("Content-Type", "text/plain; charset=UTF-8") //设置"Content-Type"字段
        fmt.Println(header.Get("Content-Type")) //返回text/plain; charset=UTF-8
        header.Set("Content-Type", "application/x-www-form-urlencoded;") //覆盖原先的值,返回application/x-www-form-urlencoded;
        fmt.Println(header.Get("Content-Type"))
    
        header.Add("Content-Type", "charset=UTF-8") //在"Content-Type"字段中追加值
        fmt.Println(header) //map[Date:[1994-11-06T08:49:37Z00:00] Content-Type:[application/x-www-form-urlencoded; charset=UTF-8]],可见添加进去
        fmt.Println(header.Get("Content-Type")) //但是这样获取是返回值仍是application/x-www-form-urlencoded;
    
        header.Del("Content-Type") //删除该字段
        fmt.Println(header.Get("Content-Type")) //然后返回又为空
    }

    func (Header) Write

    func (h Header) Write(w io.Writer) error

    Write以有线格式将头域写入w。

    func (Header) WriteSubset

    func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error

    WriteSubset以有线格式将头域写入w。当exclude不为nil时,如果h的键值对的键在exclude中存在且其对应值为真,该键值对就不会被写入w。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "bytes"
        "os"
    )
    
    var headerWriteTests = []struct {
        h        http.Header
        exclude  map[string]bool
        expected string
    }{
        {http.Header{}, nil, ""},
        {
            http.Header{
                "Content-Type":   {"text/html; charset=UTF-8"},
                "Content-Length": {"0"},
            },
            nil,
            "Content-Length: 0
    Content-Type: text/html; charset=UTF-8
    ",
        },
        {
            http.Header{
                "Expires":          {"-1"},
                "Content-Length":   {"0"},
                "Content-Encoding": {"gzip"},
            },
            map[string]bool{"Content-Length": true}, //"Content-Length"字段将不会写入io.Writer
            "Content-Encoding: gzip
    Expires: -1
    ",
        },
    }
    
    func main() {
        var buf bytes.Buffer //得到io.Writer
        for i, test := range headerWriteTests {
            test.h.WriteSubset(&buf, test.exclude)
            fmt.Println(i)
            buf.WriteTo(os.Stdout)
            fmt.Println()
            if buf.String() != test.expected {
                fmt.Errorf("#%d:
     got: %q
    want: %q", i, buf.String(), test.expected)
            }
            buf.Reset()
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    0
    
    1
    Content-Length: 0
    Content-Type: text/html; charset=UTF-8
    
    2
    Content-Encoding: gzip
    Expires: -1

    2)Cookie

    ⚠️session和cookie的区别:

    session是存储在服务器的文件,cookie内容保存在客户端,存在被客户篡改的情况,session保存在服务端端防止被用户篡改的情况。

    1》

    type Cookie struct {
        Name       string
        Value      string
        Path       string
        Domain     string
        Expires    time.Time
        RawExpires string
        // MaxAge=0表示未设置Max-Age属性
        // MaxAge<0表示立刻删除该cookie,等价于"Max-Age: 0"
        // MaxAge>0表示存在Max-Age属性,单位是秒
        MaxAge   int
        Secure   bool
        HttpOnly bool
        Raw      string
        Unparsed []string // 未解析的“属性-值”对的原始文本
    }

    Cookie代表一个出现在HTTP回复的头域中Set-Cookie头的值里或者HTTP请求的头域中Cookie头的值里的HTTP cookie。

    func (*Cookie) String

    func (c *Cookie) String() string

    String返回该cookie的序列化结果。如果只设置了Name和Value字段,序列化结果可用于HTTP请求的Cookie头或者HTTP回复的Set-Cookie头;如果设置了其他字段,序列化结果只能用于HTTP回复的Set-Cookie头。

    1)举例:

    package main 
    import(
        "fmt"
        "net/http"
        "bytes"
        "os"
        "log"
        "time"
    )
    
    var writeSetCookiesTests = []struct {
        Cookie *http.Cookie
        Raw    string
    }{
        {
            &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
            "cookie-2=two; Max-Age=3600",
        },
        {
            &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
            "cookie-3=three; Domain=example.com",
        },
        {
            &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
            "cookie-4=four; Path=/restricted/",
        },
        {
            &http.Cookie{Name: "cookie-9", Value: "expiring", Expires: time.Unix(1257894000, 0)},
            "cookie-9=expiring; Expires=Tue, 10 Nov 2009 23:00:00 GMT",
        },
        // According to IETF 6265 Section 5.1.1.5, the year cannot be less than 1601
        {//故意将这里的cookie-10写成cookie-101,然后下面就会报错
            &http.Cookie{Name: "cookie-10", Value: "expiring-1601", Expires: time.Date(1601, 1, 1, 1, 1, 1, 1, time.UTC)},
            "cookie-101=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT",
        },
        { //因此其返回值中没有Expires
            &http.Cookie{Name: "cookie-11", Value: "invalid-expiry", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)},
            "cookie-11=invalid-expiry",
        },
        // The "special" cookies have values containing commas or spaces which
        // are disallowed by RFC 6265 but are common in the wild.
        {
            &http.Cookie{Name: "special-1", Value: "a z"},
            `special-1="a z"`,
        },
        {
            &http.Cookie{Name: "empty-value", Value: ""},
            `empty-value=`,
        },
        {
            nil,
            ``,
        },
        {
            &http.Cookie{Name: ""},
            ``,
        },
        {
            &http.Cookie{Name: "	"},
            ``,
        },
    }
    
    
    func main() {
        defer log.SetOutput(os.Stderr)
        var logbuf bytes.Buffer
        log.SetOutput(&logbuf)
    
        for i, tt := range writeSetCookiesTests {//没有报错则说明得到的Cookie的值与Raw字符串相等
            if g, e := tt.Cookie.String(), tt.Raw; g != e {
                fmt.Printf("Test %d, expecting:
    %s
    Got:
    %s
    ", i, e, g)
                continue
            }
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    Test 4, expecting:
    cookie-101=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT
    Got:
    cookie-10=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT

    2)

    func SetCookie

    func SetCookie(w ResponseWriter, cookie *Cookie)

    SetCookie在w的头域中添加Set-Cookie头,该HTTP头的值为cookie。

    常用SetCookie来给http的request请求或者http的response响应设置cookie。

    然后使用request的Cookies()、Cookie(name string)函数和response的Cookies()函数来获取设置的cookie信息

    type ResponseWriter

    type ResponseWriter interface {
        // Header返回一个Header类型值,该值会被WriteHeader方法发送。
        // 在调用WriteHeader或Write方法后再改变该对象是没有意义的。
        Header() Header
        // WriteHeader该方法发送HTTP回复的头域和状态码。
        // 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)
        // WriterHeader的显式调用主要用于发送错误码。
        WriteHeader(int)
        // Write向连接中写入作为HTTP的一部分回复的数据。
        // 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)
        // 如果Header中没有"Content-Type"键,
        // 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。
        Write([]byte) (int, error)
    }

    ResponseWriter接口被HTTP处理器用于构造HTTP回复。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    type headerOnlyResponseWriter http.Header
    //下面定义这些函数是为了使headerOnlyResponseWriter实现ResponseWriter接口,然后可以作为SetCookie的参数传入
    func (ho headerOnlyResponseWriter) Header() http.Header {
        return http.Header(ho)
    }
    
    func (ho headerOnlyResponseWriter) Write([]byte) (int, error) {
        panic("NOIMPL")
    }
    
    func (ho headerOnlyResponseWriter) WriteHeader(int) {
        panic("NOIMPL")
    }
    
    
    func main() {
        m := make(http.Header) //创建一个map[string][]string类型的映射m,headerOnlyResponseWriter(m)即将Header类型的m转成自定义headerOnlyResponseWriter类型
        fmt.Println(m) //运行SetCookie()之前为 map[]
        fmt.Println(headerOnlyResponseWriter(m)) //运行SetCookie()之前为 map[]
        
        //SetCookie在w的头域中添加Set-Cookie头,该HTTP头的值为cookie
        http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
        http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
        fmt.Println(m) //返回:map[Set-Cookie:[cookie-1=one; Path=/restricted/ cookie-2=two; Max-Age=3600]]
    
        //下面的内容都没有报错,说明得到的值和给出的字符串是相同的
        if l := len(m["Set-Cookie"]); l != 2 {
            fmt.Printf("expected %d cookies, got %d", 2, l)
        }
        if g, e := m["Set-Cookie"][0], "cookie-1=one; Path=/restricted/"; g != e {
            fmt.Printf("cookie #1: want %q, got %q", e, g)
        }
        if g, e := m["Set-Cookie"][1], "cookie-2=two; Max-Age=3600"; g != e {
            fmt.Printf("cookie #2: want %q, got %q", e, g)
        }
    }

    2》

    type CookieJar

    type CookieJar interface {
        // SetCookies管理从u的回复中收到的cookie
        // 根据其策略和实现,它可以选择是否存储cookie
        SetCookies(u *url.URL, cookies []*Cookie)
        // Cookies返回发送请求到u时应使用的cookie
        // 本方法有责任遵守RFC 6265规定的标准cookie限制
        Cookies(u *url.URL) []*Cookie
    }

    CookieJar管理cookie的存储和在HTTP请求中的使用。CookieJar的实现必须能安全的被多个go程同时使用。

    net/http/cookiejar包提供了一个CookieJar的实现。

     
    3》

    type Request

    type Request struct {
        // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
        Method string
        // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
        //
        // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
        // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
        // (参见RFC 2616, Section 5.1.2)
        //
        // 在客户端,URL的Host字段指定了要连接的服务器,
        // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
        URL *url.URL
        // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
        Proto      string // "HTTP/1.0"
        ProtoMajor int    // 1
        ProtoMinor int    // 0
        // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
        //    accept-encoding: gzip, deflate
        //    Accept-Language: en-us
        //    Connection: keep-alive
        // 则:
        //    Header = map[string][]string{
        //        "Accept-Encoding": {"gzip, deflate"},
        //        "Accept-Language": {"en-us"},
        //        "Connection": {"keep-alive"},
        //    }
        // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
        // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
        Header Header
        // Body是请求的主体。
        //
        // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
        // Client的Transport字段会负责调用Body的Close方法。
        //
        // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
        // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
        Body io.ReadCloser
        // ContentLength记录相关内容的长度。
        // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
        // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
        ContentLength int64
        // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
        // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
        TransferEncoding []string
        // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
        Close bool
        // 在服务端,Host指定URL会在其上寻找资源的主机。
        // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
        // Host的格式可以是"host:port"。
        //
        // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
        // 如过该字段为"",Request.Write方法会使用URL字段的Host。
        Host string
        // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
        // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
        Form url.Values
        // PostForm是解析好的POST或PUT的表单数据。
        // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
        PostForm url.Values
        // MultipartForm是解析好的多部件表单,包括上传的文件。
        // 本字段只有在调用ParseMultipartForm后才有效。
        // 在客户端,会忽略请求中的本字段而使用Body替代。
        MultipartForm *multipart.Form
        // Trailer指定了会在请求主体之后发送的额外的头域。
        //
        // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
        // (客户端会声明哪些trailer会发送)
        // 在处理器从Body读取时,不能使用本字段。
        // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
        // (如果客户端发送了这些键值对),此时才可以访问本字段。
        //
        // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
        // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
        // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
        // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
        //
        // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
        Trailer Header
        // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
        // 本字段不是ReadRequest函数填写的,也没有定义格式。
        // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
        // 客户端会忽略请求中的RemoteAddr字段。
        RemoteAddr string
        // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
        // (参见RFC 2616, Section 5.1)
        // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
        RequestURI string
        // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
        // 本字段不是ReadRequest函数填写的。
        // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
        // 客户端会忽略请求中的TLS字段。
        TLS *tls.ConnectionState
    }

    Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求

    Request各字段的意义和用途在服务端和客户端是不同的。除了字段本身上方文档,还可参见Request.Write方法和RoundTripper接口的文档。

    func NewRequest

    func NewRequest(method, urlStr string, body io.Reader) (*Request, error)

    NewRequest使用指定的方法、网址和可选的主题创建并返回一个新的*Request。

    如果body参数实现了io.Closer接口,Request返回值的Body 字段会被设置为body,并会被Client类型的Do、Post和PostFOrm方法以及Transport.RoundTrip方法关闭。

    对于该NewRequest方法的三个参数的不同输入对返回request中相应数据的影响:

    1)NewRequest中urlStr参数对req.Host值的影响,举例说明:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    var newRequestHostTests = []struct {
        in, out string
    }{
        {"http://www.example.com/", "www.example.com"},
        {"http://www.example.com:8080/", "www.example.com:8080"},
    
        {"http://192.168.0.1/", "192.168.0.1"},
        {"http://192.168.0.1:8080/", "192.168.0.1:8080"},
        {"http://192.168.0.1:/", "192.168.0.1"},
    
        {"http://[fe80::1]/", "[fe80::1]"},
        {"http://[fe80::1]:8080/", "[fe80::1]:8080"},
        {"http://[fe80::1%25en0]/", "[fe80::1%en0]"},
        {"http://[fe80::1%25en0]:8080/", "[fe80::1%en0]:8080"},
        {"http://[fe80::1%25en0]:/", "[fe80::1%en0]"},
    }
    
    
    func main() {
        for i, tt := range newRequestHostTests {
            req, err := http.NewRequest("GET", tt.in, nil)
            if err != nil {
                fmt.Printf("#%v: %v", i, err)
                continue
            }
            if req.Host != tt.out { //返回结果中没有报错,则说明req.Host == tt.out
                fmt.Printf("got %q; want %q", req.Host, tt.out)
            }
        }
    }

    2)NewRequest中method参数对req.Method值的影响,举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
    )
    
    func main() {
        _, err := http.NewRequest("bad method", "http://foo.com/", nil)
        if err == nil { //返回没有输出则说明"bad method"是错误的请求方法,err != nil
            fmt.Println("expected error from NewRequest with invalid method")
        }
        fmt.Println(err) //net/http: invalid method "bad method"
    
        req, err := http.NewRequest("GET", "http://foo.example/", nil)
        if err != nil { //当你使用的是正确的请求方法时,就不会出现错误
            fmt.Println(err)
        }
        req.Method = "bad method" //将请求方法改成错误的"bad method"
    
        _, err = http.DefaultClient.Do(req) //然后发送该请求,然后会返回HTTP response和error
        if err == nil || !strings.Contains(err.Error(), "invalid method") { //没有返回,则说明返回的err !=  nil或err中包含字符串"invalid method"
            fmt.Printf("Transport error = %v; want invalid method
    ", err)
        }
        fmt.Println(err) //bad method http://foo.example/: net/http: invalid method "bad method"
    
        req, err = http.NewRequest("", "http://foo.com/", nil)
        fmt.Println(req) //&{GET http://foo.com/ HTTP/1.1 1 1 map[] <nil> <nil> 0 [] false foo.com map[] map[] <nil> map[]   <nil> <nil> <nil> <nil>}
        if err != nil {//没返回说明err == nil,说明请求方法可以为空
            fmt.Printf("NewRequest(empty method) = %v; want nil
    ", err)
        } else if req.Method != "GET" { //当请求方法为空时,会默认使用的是"GET方法"
            fmt.Printf("NewRequest(empty method) has method %q; want GET
    ", req.Method)
        }
    }

    3)NewRequest中body参数对req.Body、req.ContentLength值的影响:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "bytes"
        "io"
    )
    
    func main() {
        readByte := func(r io.Reader) io.Reader {
            var b [1]byte
            r.Read(b[:])
            return r
        }
        tests := []struct {
            r    io.Reader
            want int64
        }{
            {bytes.NewReader([]byte("123")), 3},
            {bytes.NewBuffer([]byte("1234")), 4},
            {strings.NewReader("12345"), 5},
            {strings.NewReader(""), 0},
    
            // Not detected. During Go 1.8 we tried to make these set to -1, but
            // due to Issue 18117, we keep these returning 0, even though they're
            // unknown.
            {struct{ io.Reader }{strings.NewReader("xyz")}, 0},
            {io.NewSectionReader(strings.NewReader("x"), 0, 6), 0},
            {readByte(io.NewSectionReader(strings.NewReader("xy"), 0, 6)), 0},
        }
        for i, tt := range tests {
            req, err := http.NewRequest("POST", "http://localhost/", tt.r)
            fmt.Println(req.Body)
            if err != nil {
                fmt.Println(err)
            }
            if req.ContentLength != tt.want {//没有返回,说明req.ContentLength == tt.want
                fmt.Printf("test[%d]: ContentLength(%T) = %d; want %d", i, tt.r, req.ContentLength, tt.want)
            }
        }
    }

    req.Body返回:

    userdeMBP:go-learning user$ go run test.go
    {0xc000094cc0}
    {1234}
    {0xc0000ac360}
    {}
    {{0xc0000ac3a0}}
    {0xc000094cf0}
    {0xc000094d20}

    request请求中与Cookie相关的方法:

    func (*Request) AddCookie

    func (r *Request) AddCookie(c *Cookie)

    AddCookie向请求中添加一个cookie。按照RFC 6265 section 5.4的跪地,AddCookie不会添加超过一个Cookie头字段。这表示所有的cookie都写在同一行,用分号分隔(cookie内部用逗号分隔属性)。

    func (*Request) Cookies

    func (r *Request) Cookies() []*Cookie

    Cookies解析并返回该请求的Cookie头设置的cookie。

    func (*Request) Cookie

    func (r *Request) Cookie(name string) (*Cookie, error)

    Cookie返回请求中名为name的cookie,如果未找到该cookie会返回nil, ErrNoCookie。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    var addCookieTests = []struct {
        Cookies []*http.Cookie
        Raw     string
    }{
        {
            []*http.Cookie{},
            "",
        },
        {
            []*http.Cookie{{Name: "cookie-1", Value: "v$11"}},
            "cookie-1=v$11",
        },
        {
            []*http.Cookie{
                {Name: "cookie-1", Value: "v$21"},
                {Name: "cookie-2", Value: "v$2"},
                {Name: "cookie-3", Value: "v$3"},
            },
            "cookie-1=v$21; cookie-2=v$2; cookie-3=v$3",
        },
    }
    
    
    func main() {
        for i, tt := range addCookieTests {
            req, _ := http.NewRequest("GET", "http://example.com/", nil)
            for _, c := range tt.Cookies {
                req.AddCookie(c)
            }
            //没有报错,则说明添加进的Cookie的值与给的Raw的字符串的值相同
         //得到Cookie的值可以使用req.Header.Get("Cookie"),也可以使用下面的req.Cookies() if g := req.Header.Get("Cookie"); g != tt.Raw { fmt.Printf("Test %d: want: %s got: %s ", i, tt.Raw, g) continue } fmt.Println(req.Cookies()) value, _ := req.Cookie("cookie-1") fmt.Println(value) } }

    返回:

    userdeMBP:go-learning user$ go run test.go
    []
    
    [cookie-1=v$11]
    cookie-1=v$11
    [cookie-1=v$21 cookie-2=v$2 cookie-3=v$3]
    cookie-1=v$21

    其他方法:

    func ReadRequest

    func ReadRequest(b *bufio.Reader) (req *Request, err error)

    ReadRequest从b读取并解析出一个HTTP请求。(本函数主要用在服务端从下层获取请求)

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "io"
        "reflect"
        "bufio"
    )
    
    var readRequestErrorTests = []struct {
        in  string
        err string
    
        header http.Header
    }{
        0: {"GET / HTTP/1.1
    header:foo
    
    ", "", http.Header{"Header": {"foo"}}},
        1: {"GET / HTTP/1.1
    header:foo
    ", io.ErrUnexpectedEOF.Error(), nil},
        2: {"", io.EOF.Error(), nil},
        3: {
            in:  "HEAD / HTTP/1.1
    Content-Length:4
    
    ",
            err: "http: method cannot contain a Content-Length",
        },
        4: {
            in:     "HEAD / HTTP/1.1
    
    ",
            header: http.Header{},
        },
    
        // Multiple Content-Length values should either be
        // deduplicated if same or reject otherwise
        // See Issue 16490.
        5: {
            in:  "POST / HTTP/1.1
    Content-Length: 10
    Content-Length: 0
    
    Gopher hey
    ",
            err: "cannot contain multiple Content-Length headers",
        },
        6: {
            in:  "POST / HTTP/1.1
    Content-Length: 10
    Content-Length: 6
    
    Gopher
    ",
            err: "cannot contain multiple Content-Length headers",
        },
        7: {
            in:     "PUT / HTTP/1.1
    Content-Length: 6 
    Content-Length: 6
    Content-Length:6
    
    Gopher
    ",
            err:    "",
            header: http.Header{"Content-Length": {"6"}},
        },
        8: {
            in:  "PUT / HTTP/1.1
    Content-Length: 1
    Content-Length: 6 
    
    ",
            err: "cannot contain multiple Content-Length headers",
        },
        9: {
            in:  "POST / HTTP/1.1
    Content-Length:
    Content-Length: 3
    
    ",
            err: "cannot contain multiple Content-Length headers",
        },
        10: {
            in:     "HEAD / HTTP/1.1
    Content-Length:0
    Content-Length: 0
    
    ",
            header: http.Header{"Content-Length": {"0"}},
        },
    }
    
    
    func main() {
        for i, tt := range readRequestErrorTests {
            req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(tt.in)))
            if err == nil { //从返回可以看出,只有0,4,7,10返回的err是nil,即能够成功解析出一个HTTP请求
                fmt.Println(i, " : ", req)
                if tt.err != "" {
                    fmt.Printf("#%d: got nil err; want %q
    ", i, tt.err)
                }
    
                if !reflect.DeepEqual(tt.header, req.Header) {//如果发现两者不同
                    fmt.Printf("#%d: gotHeader: %q wantHeader: %q
    ", i, req.Header, tt.header)
                }
                continue
            }
    
            if tt.err == "" || !strings.Contains(err.Error(), tt.err) { //如果tt.err != "" 或者 返回的err中包含tt.err的内容,则不会输出下面的字符串
                fmt.Printf("%d: got error = %v; want %v
    ", i, err, tt.err)
            }
            fmt.Println(i, "when err is not nil : ", err)
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    0  :  &{GET / HTTP/1.1 1 1 map[Header:[foo]] {} <nil> 0 [] false  map[] map[] <nil> map[]  / <nil> <nil> <nil> <nil>}
    1 when err is not nil :  unexpected EOF
    2 when err is not nil :  EOF
    3 when err is not nil :  http: method cannot contain a Content-Length; got ["4"]
    4  :  &{HEAD / HTTP/1.1 1 1 map[] {} <nil> 0 [] false  map[] map[] <nil> map[]  / <nil> <nil> <nil> <nil>}
    5 when err is not nil :  http: message cannot contain multiple Content-Length headers; got ["10" "0"]
    6 when err is not nil :  http: message cannot contain multiple Content-Length headers; got ["10" "6"]
    7  :  &{PUT / HTTP/1.1 1 1 map[Content-Length:[6]] 0xc000096200 <nil> 6 [] false  map[] map[] <nil> map[]  / <nil> <nil> <nil> <nil>}
    8 when err is not nil :  http: message cannot contain multiple Content-Length headers; got ["1" "6"]
    9 when err is not nil :  http: message cannot contain multiple Content-Length headers; got ["" "3"]
    10  :  &{HEAD / HTTP/1.1 1 1 map[Content-Length:[0]] {} <nil> 0 [] false  map[] map[] <nil> map[]  / <nil> <nil> <nil> <nil>}

    func (*Request) ProtoAtLeast

    func (r *Request) ProtoAtLeast(major, minor int) bool

    ProtoAtLeast报告该请求使用的HTTP协议版本至少是major.minor。

    func (*Request) UserAgent

    func (r *Request) UserAgent() string

    UserAgent返回请求中的客户端用户代理信息(请求的User-Agent头)。

    func (*Request) Referer

    func (r *Request) Referer() string

    Referer返回请求中的访问来路信息。(请求的Referer头)即得到访问的信息的来源,比如某个链接的来源地址

    Referer在请求中就是拼错了的,这是HTTP早期就有的错误。该值也可以从用Header["Referer"]获取; 让获取Referer字段变成方法的好处是,编译器可以诊断使用正确单词拼法的req.Referrer()的程序,但却不能诊断使用Header["Referrer"]的程序。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    func main() {
        req, _ := http.NewRequest("GET", "http://www.baidu.com/", nil)
        req.Header.Set("User-Agent", "Mozilla/5.0")
    
        //没有解析前req.Form和req.PostForm中的值为空
        fmt.Println(req.ProtoAtLeast(1,0)) //true
        fmt.Println(req.ProtoAtLeast(1,1)) //true
        fmt.Println(req.UserAgent()) //Mozilla/5.0
        fmt.Println(req.Referer())//因为没有来源,为空
    }

    func (*Request) SetBasicAuth

    func (r *Request) SetBasicAuth(username, password string)

    SetBasicAuth使用提供的用户名和密码,采用HTTP基本认证,设置请求的Authorization头。HTTP基本认证会明码传送用户名和密码,即用户名和密码是不加密的

    func (r *Request) BasicAuth

    func (r *Request) BasicAuth() (username, password string, ok bool)

    如果请求使用http基本认证,返回request header中的用户名和密码。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    type getBasicAuthTest struct {
        username, password string
        ok                 bool
    }
    
    type basicAuthCredentialsTest struct {
        username, password string
    }
    
    var getBasicAuthTests = []struct {
        username, password string
        ok                 bool
    }{
        {"Aladdin", "open sesame", true},
        {"Aladdin", "open:sesame", true},
        {"", "", true},
    }
    
    
    func main() {
        for _, tt := range getBasicAuthTests {
            r, _ := http.NewRequest("GET", "http://example.com/", nil)
            r.SetBasicAuth(tt.username, tt.password)
            fmt.Println(r.Header.Get("Authorization"))//在Header中授权信息是加密过的,返回:
            // Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
            // Basic QWxhZGRpbjpvcGVuOnNlc2FtZQ==
            // Basic Og==
    
            username, password, ok := r.BasicAuth()
            if ok != tt.ok || username != tt.username || password != tt.password { //满足其中的任意一种情况都说明有错
                fmt.Printf("BasicAuth() = %#v, want %#v", getBasicAuthTest{username, password, ok},
                    getBasicAuthTest{tt.username, tt.password, tt.ok})
            }
        }
    
        //没有授权的request
        r, _ := http.NewRequest("GET", "http://example.com/", nil)
        username, password, ok := r.BasicAuth()
        fmt.Println(username, password, ok) //因为没有授权,返回 "" "" false
        if ok {
            fmt.Printf("expected false from BasicAuth when the request is unauthenticated")
        }
        want := basicAuthCredentialsTest{"", ""} //没有授权返回的username和password都应该为""
        if username != want.username || password != want.password {
            fmt.Printf("expected credentials: %#v when the request is unauthenticated, got %#v",
                want, basicAuthCredentialsTest{username, password})
        }
    }

    对授权信息进行手动加密后再添加到Header中:

    package main 
    import(
        "fmt"
        "net/http"
        "encoding/base64"
    )
    
    
    type getBasicAuthTest struct {
        username, password string
        ok                 bool
    }
    
    var parseBasicAuthTests = []struct {
        header, username, password string
        ok                         bool
    }{
        {"Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true},
    
        // 大小写不影响
        {"BASIC " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true},
        {"basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "Aladdin", "open sesame", true},
    
        {"Basic " + base64.StdEncoding.EncodeToString([]byte("Aladdin:open:sesame")), "Aladdin", "open:sesame", true},
        {"Basic " + base64.StdEncoding.EncodeToString([]byte(":")), "", "", true},
        {"Basic" + base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "", "", false},
        {base64.StdEncoding.EncodeToString([]byte("Aladdin:open sesame")), "", "", false},
        {"Basic ", "", "", false},
        {"Basic Aladdin:open sesame", "", "", false},
        {`Digest username="Aladdin"`, "", "", false},
    }
    
    
    func main() {
        for _, tt := range parseBasicAuthTests {
            r, _ := http.NewRequest("GET", "http://example.com/", nil)
            r.Header.Set("Authorization", tt.header)
            fmt.Println(r.Header.Get("Authorization")) //得到的是加密后的结果
            
            username, password, ok := r.BasicAuth()
            if ok != tt.ok || username != tt.username || password != tt.password {
                fmt.Printf("BasicAuth() = %#v, want %#v", getBasicAuthTest{username, password, ok},
                    getBasicAuthTest{tt.username, tt.password, tt.ok})
            }
        }
    }

     

    func (*Request) Write

    func (r *Request) Write(w io.Writer) error

    Write方法以有线格式将HTTP/1.1请求写入w(用于将请求写入下层TCPConn等)。本方法会考虑请求的如下字段:

    Host
    URL
    Method (defaults to "GET")
    Header
    ContentLength
    TransferEncoding
    Body

    如果存在Body,ContentLength字段<= 0且TransferEncoding字段未显式设置为["identity"],Write方法会显式添加"Transfer-Encoding: chunked"到请求的头域。Body字段会在发送完请求后关闭。

    func (*Request) WriteProxy

    func (r *Request) WriteProxy(w io.Writer) error

    WriteProxy类似Write但会将请求以HTTP代理期望的格式发送。

    尤其是,按照RFC 2616 Section 5.1.2,WriteProxy会使用绝对URI(包括协议和主机名)来初始化请求的第1行(Request-URI行)。无论何种情况,WriteProxy都会使用r.Host或r.URL.Host设置Host头。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
    )
    
    type logWrites struct {
        dst *[]string
    }
    
    //实现Write函数说明logWrites实现了io.Writer接口
    func (l logWrites) Write(p []byte) (n int, err error) {
        *l.dst = append(*l.dst, string(p))
        return len(p), nil
    }
    
    
    func main() {
        got1 := []string{}
        got2 := []string{}
        req, _ := http.NewRequest("GET", "http://foo.com/", nil)
        fmt.Println(req)
        req.Write(logWrites{&got1}) //logWrites{&got}得到的是一个io.Writer对象作为req.Write的参数,这样就会自动调用func (l logWrites) Write(p []byte),将req写入got中
        req.WriteProxy(logWrites{&got2}) //logWrites{&got}得到的是一个io.Writer对象作为req.Write的参数,这样就会自动调用func (l logWrites) Write(p []byte),将req写入got中
        fmt.Println(got1)
        fmt.Println(got2)
    }

    func (*Request) ParseForm

    func (r *Request) ParseForm() error

    ParseForm解析URL中的查询字符串,并将解析结果更新到r.Form字段。

    对于POST或PUT请求,ParseForm还会将body当作表单解析,并将结果既更新到r.PostForm也更新到r.Form。解析结果中,POST或PUT请求主体要优先于URL查询字符串(同名变量,主体的值在查询字符串的值前面)。

    如果请求的主体的大小没有被MaxBytesReader函数设定限制,其大小默认限制为开头10MB。

    ParseMultipartForm会自动调用ParseForm。重复调用本方法是无意义的。

    ⚠️ParseForm方法用来解析表单提供的数据,即content-type 为 x-www-form-urlencode的数据。

    对于form-data的格式的数据,ParseForm的方法只会解析url中的参数,并不会解析body中的参数。因此当请求的content-type为form-data的时候,ParseFrom则需要改成 MutilpartFrom,否则r.From是读取不到body的内容,只能读取到query string中的内容。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
    )
    
    func main() {
        req, _ := http.NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not",
            strings.NewReader("z=post&both=y&prio=2&=nokey&orphan;empty=&"))
        req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
        //没有解析前req.Form和req.PostForm中的值为空
        fmt.Println(req)
        fmt.Println(req.Form)
        fmt.Println(req.PostForm)
        fmt.Println()
        
        //解析后对应的值才写入req.Form和req.PostForm
        req.ParseForm()
        fmt.Println(req)
        fmt.Println(req.Form)
        fmt.Println(req.PostForm)
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    &{POST http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not HTTP/1.1 1 1 map[Content-Type:[application/x-www-form-urlencoded; param=value]] {0xc00000c360} 0x11e9430 42 [] false www.google.com map[] map[] <nil> map[]   <nil> <nil> <nil> <nil>}
    map[]
    map[]
    
    &{POST http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not HTTP/1.1 1 1 map[Content-Type:[application/x-www-form-urlencoded; param=value]] {0xc00000c360} 0x11e9430 42 [] false www.google.com map[prio:[2 1] :[nokey] q:[foo bar] orphan:[ nope] empty:[ not] z:[post] both:[y x]] map[z:[post] both:[y] prio:[2] :[nokey] orphan:[] empty:[]] <nil> map[]   <nil> <nil> <nil> <nil>}
    map[orphan:[ nope] empty:[ not] z:[post] both:[y x] prio:[2 1] :[nokey] q:[foo bar]]
    map[orphan:[] empty:[] z:[post] both:[y] prio:[2] :[nokey]]

    func (*Request) ParseMultipartForm

    func (r *Request) ParseMultipartForm(maxMemory int64) error

    ParseMultipartForm将请求的主体作为multipart/form-data解析。请求的整个主体都会被解析,得到的文件记录最多maxMemery字节保存在内存,其余部分保存在硬盘的temp文件里。如果必要,ParseMultipartForm会自行调用ParseForm。重复调用本方法是无意义的。

    form-data格式用得最多方式就是在图片上传的时候。MultipartForm.Value是post的body字段数据,MultipartForm.File则包含了图片数据

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "io/ioutil"
        "net/url"
        "reflect"
    )
    
    func main() {
        //说明写入PostForm中的字段name-value,field2有两个value,一个作为Form,一个作为PostForm
        //注意value1、value2和binary data上面的空行不能够删除,否则会报错:malformed MIME header initial line
        postData :=
            `--xxx
    Content-Disposition: form-data; name="field1" 
    
    value1
    --xxx
    Content-Disposition: form-data; name="field2"
    
    value2
    --xxx
    Content-Disposition: form-data; name="file"; filename="file"
    Content-Type: application/octet-stream
    Content-Transfer-Encoding: binary
    
    binary data
    --xxx--
    `
        req := &http.Request{
            Method: "POST",
            Header: http.Header{"Content-Type": {`multipart/form-data; boundary=xxx`}},
            Body:   ioutil.NopCloser(strings.NewReader(postData)), //NopCloser用一个无操作的Close方法包装输入参数然后返回一个ReadCloser接口
        }
        // req.ParseForm()//在POST表单中,这种解析是没有用的,要使用下面的,当然,这个只是为了查看目前的表单值,其实不应该在这里解析
        err := req.ParseMultipartForm(10000)
        if err != nil {
            fmt.Printf("unexpected multipart error %v
    ", err)
        }
        fmt.Println(req)
        fmt.Println(req.Body)
        fmt.Println(req.Form) //map[field1:[value1] field2:[value2]]
        fmt.Println(req.PostForm) //map[field1:[value1] field2:[value2]],现在两者值是相同的,但是下面req.Form.Add后就变了
        fmt.Println()
    
        initialFormItems := map[string]string{
            "language": "Go",
            "name":     "gopher",
            "skill":    "go-ing",
            "field2":   "initial-value2",
        }
    
        req.Form = make(url.Values) //url.Values即map[string][]string
        for k, v := range initialFormItems {
            req.Form.Add(k, v)
        }
    
        //应该解析的位置是这里,否则会导致最终的结果与构想的结果不同
        // err := req.ParseMultipartForm(10000)
        // if err != nil {
        //     fmt.Printf("unexpected multipart error %v
    ", err)
        // }
        fmt.Println(req)
        fmt.Println(req.Body)
        fmt.Println(req.Form) //map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2]]
        fmt.Println(req.PostForm) //map[field1:[value1] field2:[value2]]
    
        wantForm := url.Values{
            "language": []string{"Go"},
            "name":     []string{"gopher"},
            "skill":    []string{"go-ing"},
            "field1":   []string{"value1"},
            "field2":   []string{"initial-value2", "value2"},
        }
        if !reflect.DeepEqual(req.Form, wantForm) { //这里会报出不相等的结果
            fmt.Printf("req.Form = %v, want %v
    ", req.Form, wantForm)
        }
    
        wantPostForm := url.Values{
            "field1": []string{"value1"},
            "field2": []string{"value2"},
        }
        if !reflect.DeepEqual(req.PostForm, wantPostForm) {
            fmt.Printf("req.PostForm = %v, want %v
    ", req.PostForm, wantPostForm)
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    &{POST <nil>  0 0 map[Content-Type:[multipart/form-data; boundary=xxx]] {0xc00000c3a0} <nil> 0 [] false  map[field2:[value2] field1:[value1]] map[field1:[value1] field2:[value2]] 0xc000010ce0 map[]   <nil> <nil> <nil> <nil>}
    {0xc00000c3a0}
    map[field1:[value1] field2:[value2]]
    map[field1:[value1] field2:[value2]]
    
    &{POST <nil>  0 0 map[Content-Type:[multipart/form-data; boundary=xxx]] {0xc00000c3a0} <nil> 0 [] false  map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2]] map[field1:[value1] field2:[value2]] 0xc000010ce0 map[]   <nil> <nil> <nil> <nil>}
    {0xc00000c3a0}
    map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2]]
    map[field1:[value1] field2:[value2]]
    req.Form = map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2]], want map[language:[Go] name:[gopher] skill:[go-ing] field1:[value1] field2:[initial-value2 value2]]

    正确为:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "io/ioutil"
        "net/url"
        "reflect"
    )
    
    func main() {
        //说明写入PostForm中的字段name-value,field2有两个value,一个作为Form,一个作为PostForm
        //注意value1、value2和binary data上面的空行不能够删除,否则会报错:malformed MIME header initial line
        postData :=
            `--xxx
    Content-Disposition: form-data; name="field1" 
    
    value1
    --xxx
    Content-Disposition: form-data; name="field2"
    
    value2
    --xxx
    Content-Disposition: form-data; name="file"; filename="file"
    Content-Type: application/octet-stream
    Content-Transfer-Encoding: binary
    
    binary data
    --xxx--
    `
        req := &http.Request{
            Method: "POST",
            Header: http.Header{"Content-Type": {`multipart/form-data; boundary=xxx`}},
            Body:   ioutil.NopCloser(strings.NewReader(postData)), //NopCloser用一个无操作的Close方法包装输入参数然后返回一个ReadCloser接口
        }
    
        initialFormItems := map[string]string{
            "language": "Go",
            "name":     "gopher",
            "skill":    "go-ing",
            "field2":   "initial-value2",
        }
    
        req.Form = make(url.Values) //url.Values即map[string][]string
        for k, v := range initialFormItems {
            req.Form.Add(k, v)
        }
    
        //应该解析的位置是这里,否则会导致最终的结果与构想的结果不同
        err := req.ParseMultipartForm(10000)
        if err != nil {
            fmt.Printf("unexpected multipart error %v
    ", err)
        }
        fmt.Println(req)
        fmt.Println(req.Body)
        fmt.Println(req.Form) //map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2 value2] field1:[value1]]
        fmt.Println(req.PostForm) //map[field1:[value1] field2:[value2]]
    
        wantForm := url.Values{
            "language": []string{"Go"},
            "name":     []string{"gopher"},
            "skill":    []string{"go-ing"},
            "field1":   []string{"value1"},
            "field2":   []string{"initial-value2", "value2"},
        }
        if !reflect.DeepEqual(req.Form, wantForm) {
            fmt.Printf("req.Form = %v, want %v
    ", req.Form, wantForm)
        }
    
        wantPostForm := url.Values{
            "field1": []string{"value1"},
            "field2": []string{"value2"},
        }
        if !reflect.DeepEqual(req.PostForm, wantPostForm) {
            fmt.Printf("req.PostForm = %v, want %v
    ", req.PostForm, wantPostForm)
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    &{POST <nil>  0 0 map[Content-Type:[multipart/form-data; boundary=xxx]] {0xc0000ac340} <nil> 0 [] false  map[name:[gopher] skill:[go-ing] field2:[initial-value2 value2] field1:[value1] language:[Go]] map[field1:[value1] field2:[value2]] 0xc000090d00 map[]   <nil> <nil> <nil> <nil>}
    {0xc0000ac340}
    map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2 value2] field1:[value1]]
    map[field1:[value1] field2:[value2]]

    func (*Request) FormValue

    func (r *Request) FormValue(key string) string

    FormValue返回key为键查询r.Form字段得到结果[]string切片的第一个值。POST和PUT主体中的同名参数优先于URL查询字符串。如果必要,本函数会隐式调用ParseMultipartForm和ParseForm

    func (*Request) PostFormValue

    func (r *Request) PostFormValue(key string) string

    PostFormValue返回key为键查询r.PostForm字段得到结果[]string切片的第一个值。如果必要,本函数会隐式调用ParseMultipartForm和ParseForm

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "reflect"
    )
    
    func main() {
        req, _ := http.NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not",
            strings.NewReader("z=post&both=y&prio=2&=nokey&orphan;empty=&"))
        req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value")
    
        if q := req.FormValue("q"); q != "foo" {
            fmt.Printf(`req.FormValue("q") = %q, want "foo"`, q)
        }
       //因为上面的req.FormValue方法会隐式解析,所以下面能够得到值 fmt.Println(req) fmt.Println(req.Form) fmt.Println(req.PostForm)
    if z := req.FormValue("z"); z != "post" { fmt.Printf(`req.FormValue("z") = %q, want "post"`, z) } if bq, found := req.PostForm["q"]; found {//PostForm 中没有"q" fmt.Printf(`req.PostForm["q"] = %q, want no entry in map`, bq) } if bz := req.PostFormValue("z"); bz != "post" { fmt.Printf(`req.PostFormValue("z") = %q, want "post"`, bz) } if qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) { fmt.Printf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs) } if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) { fmt.Printf(`req.Form["both"] = %q, want ["y", "x"]`, both) } if prio := req.FormValue("prio"); prio != "2" { fmt.Printf(`req.FormValue("prio") = %q, want "2" (from body)`, prio) } if orphan := req.Form["orphan"]; !reflect.DeepEqual(orphan, []string{"", "nope"}) { fmt.Printf(`req.FormValue("orphan") = %q, want "" (from body)`, orphan) } if empty := req.Form["empty"]; !reflect.DeepEqual(empty, []string{"", "not"}) { fmt.Printf(`req.FormValue("empty") = %q, want "" (from body)`, empty) } if nokey := req.Form[""]; !reflect.DeepEqual(nokey, []string{"nokey"}) { fmt.Printf(`req.FormValue("nokey") = %q, want "nokey" (from body)`, nokey) } }

    返回:

    userdeMBP:go-learning user$ go run test.go
    &{POST http://www.google.com/search?q=foo&q=bar&both=x&prio=1&orphan=nope&empty=not HTTP/1.1 1 1 map[Content-Type:[application/x-www-form-urlencoded; param=value]] {0xc00000c380} 0x11ef3e0 42 [] false www.google.com map[:[nokey] orphan:[ nope] empty:[ not] z:[post] q:[foo bar] both:[y x] prio:[2 1]] map[empty:[] z:[post] both:[y] prio:[2] :[nokey] orphan:[]] <nil> map[]   <nil> <nil> <nil> <nil>}
    map[z:[post] q:[foo bar] both:[y x] prio:[2 1] :[nokey] orphan:[ nope] empty:[ not]]
    map[z:[post] both:[y] prio:[2] :[nokey] orphan:[] empty:[]]

     

    func (*Request) FormFile

    func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)

    FormFile返回以key为键查询r.MultipartForm字段得到结果中的第一个文件和它的信息。如果必要,本函数会隐式调用ParseMultipartForm和ParseForm。查询失败会返回ErrMissingFile错误。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "io/ioutil"
        "net/url"
        "log"
    )
    
    func main() {
        //说明写入PostForm中的字段name-value,field2有两个value,一个作为Form,一个作为PostForm
        //filename指明写入的文件
        //注意value1、value2和binary data上面的空行不能够删除,否则会报错:malformed MIME header initial line
        postData :=
            `--xxx
    Content-Disposition: form-data; name="field1" 
    
    value1
    --xxx
    Content-Disposition: form-data; name="field2"
    
    value2
    --xxx
    Content-Disposition: form-data; name="file"; filename="file"
    Content-Type: application/octet-stream
    Content-Transfer-Encoding: binary
    
    binary data
    --xxx--
    `
        req := &http.Request{
            Method: "POST",
            Header: http.Header{"Content-Type": {`multipart/form-data; boundary=xxx`}},
            Body:   ioutil.NopCloser(strings.NewReader(postData)), //NopCloser用一个无操作的Close方法包装输入参数然后返回一个ReadCloser接口
        }
    
        initialFormItems := map[string]string{
            "language": "Go",
            "name":     "gopher",
            "skill":    "go-ing",
            "field2":   "initial-value2",
        }
    
        req.Form = make(url.Values) //url.Values即map[string][]string
        for k, v := range initialFormItems {
            req.Form.Add(k, v)
        }
    
        //应该解析的位置是这里,否则会导致最终的结果与构想的结果不同
        err := req.ParseMultipartForm(10000)
        if err != nil {
            fmt.Printf("unexpected multipart error %v
    ", err)
        }
    
        fmt.Println(req.Form) //map[language:[Go] name:[gopher] skill:[go-ing] field2:[initial-value2 value2] field1:[value1]]
        fmt.Println(req.PostForm) //map[field1:[value1] field2:[value2]]
        //本字段只有在调用ParseMultipartForm后才有效
        fmt.Println(req.MultipartForm) //&{map[field1:[value1] field2:[value2]] map[file:[0xc00009e140]]}
    
        // file, fileHeader, err := req.FormFile("field1") // 出错,返回:
        // 2019/02/13 18:40:02 http: no such file
        // exit status 1
        file, fileHeader, err := req.FormFile("file") // 成功
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(file) //{0xc0000a3080}
        fmt.Println(fileHeader) //&{file map[Content-Transfer-Encoding:[binary] Content-Disposition:[form-data; name="file"; filename="file"] Content-Type:[application/octet-stream]] 11 [98 105 110 97 114 121 32 100 97 116 97] }
    }

    func (*Request) MultipartReader

    func (r *Request) MultipartReader() (*multipart.Reader, error)

    如果请求是multipart/form-data POST请求,MultipartReader返回一个multipart.Reader接口,否则返回nil和一个错误。使用本函数代替ParseMultipartForm,可以将r.Body作为流处理

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "io/ioutil"
        "bytes"
    )
    
    func main() {
        req := &http.Request{
            Method: "POST",
            Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
            Body:   ioutil.NopCloser(new(bytes.Buffer)),
        }
        multipart, err := req.MultipartReader()//r.Body将作为流处理
        if multipart == nil {
            fmt.Printf("expected multipart; error: %v", err)
        }
        fmt.Println(multipart)//&{0xc00007e240 <nil> 0 [13 10] [13 10 45 45 102 111 111 49 50 51] [45 45 102 111 111 49 50 51 45 45] [45 45 102 111 111 49 50 51]}
    
        req = &http.Request{
            Method: "POST",
            Header: http.Header{"Content-Type": {`multipart/mixed; boundary="foo123"`}},
            Body:   ioutil.NopCloser(new(bytes.Buffer)),
        }
        multipart, err = req.MultipartReader()
        if multipart == nil {
            fmt.Printf("expected multipart; error: %v", err)
        }
        fmt.Println(multipart)//&{0xc00007e2a0 <nil> 0 [13 10] [13 10 45 45 102 111 111 49 50 51] [45 45 102 111 111 49 50 51 45 45] [45 45 102 111 111 49 50 51]}
    
        req.Header = http.Header{"Content-Type": {"text/plain"}}
        multipart, err = req.MultipartReader()
        if multipart != nil {
            fmt.Printf("unexpected multipart for text/plain")
        }
        fmt.Println(multipart)//<nil>
    }

     总结:

    • FormValue和Form可以读取到body和url的数据
    • PostFormValue和PostForm只读取body的数据
    • MultipartForm只会读取body的数据,不会读取url的query数据

    5>客户端

     1>Response

    type Response

    type Response struct {
        Status     string // 例如"200 OK"
        StatusCode int    // 例如200
        Proto      string // 例如"HTTP/1.0"
        ProtoMajor int    // 例如1
        ProtoMinor int    // 例如0
        // Header保管头域的键值对。
        // 如果回复中有多个头的键相同,Header中保存为该键对应用逗号分隔串联起来的这些头的值
        // (参见RFC 2616 Section 4.2)
        // 被本结构体中的其他字段复制保管的头(如ContentLength)会从Header中删掉。
        //
        // Header中的键都是规范化的,参见CanonicalHeaderKey函数
        Header Header
        // Body代表回复的主体。
        // Client类型和Transport类型会保证Body字段总是非nil的,即使回复没有主体或主体长度为0。
        // 关闭主体是调用者的责任。
        // 如果服务端采用"chunked"传输编码发送的回复,Body字段会自动进行解码。
        Body io.ReadCloser
        // ContentLength记录相关内容的长度。
        // 其值为-1表示长度未知(采用chunked传输编码)
        // 除非对应的Request.Method是"HEAD",其值>=0表示可以从Body读取的字节数
        ContentLength int64
        // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
        TransferEncoding []string
        // Close记录头域是否指定应在读取完主体后关闭连接。(即Connection头)
        // 该值是给客户端的建议,Response.Write方法的ReadResponse函数都不会关闭连接。
        Close bool
        // Trailer字段保存和头域相同格式的trailer键值对,和Header字段相同类型
        Trailer Header
        // Request是用来获取此回复的请求
        // Request的Body字段是nil(因为已经被用掉了)
        // 这个字段是被Client类型发出请求并获得回复后填充的
        Request *Request
        // TLS包含接收到该回复的TLS连接的信息。 对未加密的回复,本字段为nil。
        // 返回的指针是被(同一TLS连接接收到的)回复共享的,不应被修改。
        TLS *tls.ConnectionState
    }

    Response代表一个HTTP请求的回复。

    func ReadResponse

    func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)

    ReadResponse从r读取并返回一个HTTP 回复。req参数是可选的,指定该回复对应的请求(即是对该请求的回复)。如果是nil,将假设请求是GET请求。客户端必须在结束resp.Body的读取后关闭它。读取完毕并关闭后,客户端可以检查resp.Trailer字段获取回复的trailer的键值对。(本函数主要用在客户端从下层获取回复)

    func (*Response) ProtoAtLeast

    func (r *Response) ProtoAtLeast(major, minor int) bool

    ProtoAtLeast报告该回复使用的HTTP协议版本至少是major.minor。

    func (*Response) Write

    func (r *Response) Write(w io.Writer) error

    Write以有线格式将回复写入w(用于将回复写入下层TCPConn等)。本方法会考虑如下字段:

    StatusCode
    ProtoMajor
    ProtoMinor
    Request.Method
    TransferEncoding
    Trailer
    Body
    ContentLength
    Header(不规范的键名和它对应的值会导致不可预知的行为)

    Body字段在发送完回复后会被关闭。

     举例1:

    package main 
    import(
        "fmt"
        "net/http"
        "bufio"
        "strings"
        "os"
    
    )
    
    type respTest struct {
        Raw  string
        Resp http.Response
        Body string
    }
    
    func dummyReq(method string) *http.Request {
        return &http.Request{Method: method}
    }
    
    var respTests = []respTest{
        // Unchunked response without Content-Length.
        {
            "HTTP/1.0 200 OK
    " +
                "Connection: close
    " +
                "
    " +
                "Body here
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.0",
                ProtoMajor: 1,
                ProtoMinor: 0,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Connection": {"close"}, // TODO(rsc): Delete?
                },
                Close:         true,
                ContentLength: -1,
            },
    
            "Body here
    ",
        },
    
        // Unchunked HTTP/1.1 response without Content-Length or
        // Connection headers.
        {
            "HTTP/1.1 200 OK
    " +
                "
    " +
                "Body here
    ",
    
            http.Response{
                Status:        "200 OK",
                StatusCode:    200,
                Proto:         "HTTP/1.1",
                ProtoMajor:    1,
                ProtoMinor:    1,
                Header:        http.Header{},
                Request:       dummyReq("GET"),
                Close:         true,
                ContentLength: -1,
            },
    
            "Body here
    ",
        },
    
        // Unchunked HTTP/1.1 204 response without Content-Length.
        {
            "HTTP/1.1 204 No Content
    " +
                "
    " +
                "Body should not be read!
    ",
    
            http.Response{
                Status:        "204 No Content",
                StatusCode:    204,
                Proto:         "HTTP/1.1",
                ProtoMajor:    1,
                ProtoMinor:    1,
                Header:        http.Header{},
                Request:       dummyReq("GET"),
                Close:         false,
                ContentLength: 0,
            },
    
            "",
        },
    
        // Unchunked response with Content-Length.
        {
            "HTTP/1.0 200 OK
    " +
                "Content-Length: 10
    " +
                "Connection: close
    " +
                "
    " +
                "Body here
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.0",
                ProtoMajor: 1,
                ProtoMinor: 0,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Connection":     {"close"},
                    "Content-Length": {"10"},
                },
                Close:         true,
                ContentLength: 10,
            },
    
            "Body here
    ",
        },
    
        // Chunked response without Content-Length.
        {
            "HTTP/1.1 200 OK
    " +
                "Transfer-Encoding: chunked
    " +
                "
    " +
                "0a
    " +
                "Body here
    
    " +
                "09
    " +
                "continued
    " +
                "0
    " +
                "
    ",
    
            http.Response{
                Status:           "200 OK",
                StatusCode:       200,
                Proto:            "HTTP/1.1",
                ProtoMajor:       1,
                ProtoMinor:       1,
                Request:          dummyReq("GET"),
                Header:           http.Header{},
                Close:            false,
                ContentLength:    -1,
                TransferEncoding: []string{"chunked"},
            },
    
            "Body here
    continued",
        },
    
        // Chunked response with Content-Length.
        {
            "HTTP/1.1 200 OK
    " +
                "Transfer-Encoding: chunked
    " +
                "Content-Length: 10
    " +
                "
    " +
                "0a
    " +
                "Body here
    
    " +
                "0
    " +
                "
    ",
    
            http.Response{
                Status:           "200 OK",
                StatusCode:       200,
                Proto:            "HTTP/1.1",
                ProtoMajor:       1,
                ProtoMinor:       1,
                Request:          dummyReq("GET"),
                Header:           http.Header{},
                Close:            false,
                ContentLength:    -1,
                TransferEncoding: []string{"chunked"},
            },
    
            "Body here
    ",
        },
    
        // Chunked response in response to a HEAD request
        {
            "HTTP/1.1 200 OK
    " +
                "Transfer-Encoding: chunked
    " +
                "
    ",
    
            http.Response{
                Status:           "200 OK",
                StatusCode:       200,
                Proto:            "HTTP/1.1",
                ProtoMajor:       1,
                ProtoMinor:       1,
                Request:          dummyReq("HEAD"),
                Header:           http.Header{},
                TransferEncoding: []string{"chunked"},
                Close:            false,
                ContentLength:    -1,
            },
    
            "",
        },
    
        // Content-Length in response to a HEAD request
        {
            "HTTP/1.0 200 OK
    " +
                "Content-Length: 256
    " +
                "
    ",
    
            http.Response{
                Status:           "200 OK",
                StatusCode:       200,
                Proto:            "HTTP/1.0",
                ProtoMajor:       1,
                ProtoMinor:       0,
                Request:          dummyReq("HEAD"),
                Header:           http.Header{"Content-Length": {"256"}},
                TransferEncoding: nil,
                Close:            true,
                ContentLength:    256,
            },
    
            "",
        },
    
        // Content-Length in response to a HEAD request with HTTP/1.1
        {
            "HTTP/1.1 200 OK
    " +
                "Content-Length: 256
    " +
                "
    ",
    
            http.Response{
                Status:           "200 OK",
                StatusCode:       200,
                Proto:            "HTTP/1.1",
                ProtoMajor:       1,
                ProtoMinor:       1,
                Request:          dummyReq("HEAD"),
                Header:           http.Header{"Content-Length": {"256"}},
                TransferEncoding: nil,
                Close:            false,
                ContentLength:    256,
            },
    
            "",
        },
    
        // No Content-Length or Chunked in response to a HEAD request
        {
            "HTTP/1.0 200 OK
    " +
                "
    ",
    
            http.Response{
                Status:           "200 OK",
                StatusCode:       200,
                Proto:            "HTTP/1.0",
                ProtoMajor:       1,
                ProtoMinor:       0,
                Request:          dummyReq("HEAD"),
                Header:           http.Header{},
                TransferEncoding: nil,
                Close:            true,
                ContentLength:    -1,
            },
    
            "",
        },
    
        // explicit Content-Length of 0.
        {
            "HTTP/1.1 200 OK
    " +
                "Content-Length: 0
    " +
                "
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.1",
                ProtoMajor: 1,
                ProtoMinor: 1,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Content-Length": {"0"},
                },
                Close:         false,
                ContentLength: 0,
            },
    
            "",
        },
    
        // Status line without a Reason-Phrase, but trailing space.
        // (permitted by RFC 7230, section 3.1.2)
        {
            "HTTP/1.0 303 
    
    ",
            http.Response{
                Status:        "303 ",
                StatusCode:    303,
                Proto:         "HTTP/1.0",
                ProtoMajor:    1,
                ProtoMinor:    0,
                Request:       dummyReq("GET"),
                Header:        http.Header{},
                Close:         true,
                ContentLength: -1,
            },
    
            "",
        },
    
        // Status line without a Reason-Phrase, and no trailing space.
        // (not permitted by RFC 7230, but we'll accept it anyway)
        {
            "HTTP/1.0 303
    
    ",
            http.Response{
                Status:        "303",
                StatusCode:    303,
                Proto:         "HTTP/1.0",
                ProtoMajor:    1,
                ProtoMinor:    0,
                Request:       dummyReq("GET"),
                Header:        http.Header{},
                Close:         true,
                ContentLength: -1,
            },
    
            "",
        },
    
        // golang.org/issue/4767: don't special-case multipart/byteranges responses
        {
            `HTTP/1.1 206 Partial Content
    Connection: close
    Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
    
    some body`,
            http.Response{
                Status:     "206 Partial Content",
                StatusCode: 206,
                Proto:      "HTTP/1.1",
                ProtoMajor: 1,
                ProtoMinor: 1,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
                },
                Close:         true,
                ContentLength: -1,
            },
    
            "some body",
        },
    
        // Unchunked response without Content-Length, Request is nil
        {
            "HTTP/1.0 200 OK
    " +
                "Connection: close
    " +
                "
    " +
                "Body here
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.0",
                ProtoMajor: 1,
                ProtoMinor: 0,
                Header: http.Header{
                    "Connection": {"close"}, // TODO(rsc): Delete?
                },
                Close:         true,
                ContentLength: -1,
            },
    
            "Body here
    ",
        },
    
        // 206 Partial Content. golang.org/issue/8923
        {
            "HTTP/1.1 206 Partial Content
    " +
                "Content-Type: text/plain; charset=utf-8
    " +
                "Accept-Ranges: bytes
    " +
                "Content-Range: bytes 0-5/1862
    " +
                "Content-Length: 6
    
    " +
                "foobar",
    
            http.Response{
                Status:     "206 Partial Content",
                StatusCode: 206,
                Proto:      "HTTP/1.1",
                ProtoMajor: 1,
                ProtoMinor: 1,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Accept-Ranges":  []string{"bytes"},
                    "Content-Length": []string{"6"},
                    "Content-Type":   []string{"text/plain; charset=utf-8"},
                    "Content-Range":  []string{"bytes 0-5/1862"},
                },
                ContentLength: 6,
            },
    
            "foobar",
        },
    
        // Both keep-alive and close, on the same Connection line. (Issue 8840)
        {
            "HTTP/1.1 200 OK
    " +
                "Content-Length: 256
    " +
                "Connection: keep-alive, close
    " +
                "
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.1",
                ProtoMajor: 1,
                ProtoMinor: 1,
                Request:    dummyReq("HEAD"),
                Header: http.Header{
                    "Content-Length": {"256"},
                },
                TransferEncoding: nil,
                Close:            true,
                ContentLength:    256,
            },
    
            "",
        },
    
        // Both keep-alive and close, on different Connection lines. (Issue 8840)
        {
            "HTTP/1.1 200 OK
    " +
                "Content-Length: 256
    " +
                "Connection: keep-alive
    " +
                "Connection: close
    " +
                "
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.1",
                ProtoMajor: 1,
                ProtoMinor: 1,
                Request:    dummyReq("HEAD"),
                Header: http.Header{
                    "Content-Length": {"256"},
                },
                TransferEncoding: nil,
                Close:            true,
                ContentLength:    256,
            },
    
            "",
        },
    
        // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
        // Without a Content-Length.
        {
            "HTTP/1.0 200 OK
    " +
                "Transfer-Encoding: bogus
    " +
                "
    " +
                "Body here
    ",
    
            http.Response{
                Status:        "200 OK",
                StatusCode:    200,
                Proto:         "HTTP/1.0",
                ProtoMajor:    1,
                ProtoMinor:    0,
                Request:       dummyReq("GET"),
                Header:        http.Header{},
                Close:         true,
                ContentLength: -1,
            },
    
            "Body here
    ",
        },
    
        // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
        // With a Content-Length.
        {
            "HTTP/1.0 200 OK
    " +
                "Transfer-Encoding: bogus
    " +
                "Content-Length: 10
    " +
                "
    " +
                "Body here
    ",
    
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.0",
                ProtoMajor: 1,
                ProtoMinor: 0,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Content-Length": {"10"},
                },
                Close:         true,
                ContentLength: 10,
            },
    
            "Body here
    ",
        },
    
        {
            "HTTP/1.1 200 OK
    " +
                "Content-Encoding: gzip
    " +
                "Content-Length: 23
    " +
                "Connection: keep-alive
    " +
                "Keep-Alive: timeout=7200
    
    " +
                "x1fx8bx00x00x00x00x00x00x00sxf3xf7ax00xab'xd4x1ax03x00x00x00",
            http.Response{
                Status:     "200 OK",
                StatusCode: 200,
                Proto:      "HTTP/1.1",
                ProtoMajor: 1,
                ProtoMinor: 1,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Content-Length":   {"23"},
                    "Content-Encoding": {"gzip"},
                    "Connection":       {"keep-alive"},
                    "Keep-Alive":       {"timeout=7200"},
                },
                Close:         false,
                ContentLength: 23,
            },
            "x1fx8bx00x00x00x00x00x00x00sxf3xf7ax00xab'xd4x1ax03x00x00x00",
        },
    
        // Issue 19989: two spaces between HTTP version and status.
        {
            "HTTP/1.0  401 Unauthorized
    " +
                "Content-type: text/html
    " +
                "WWW-Authenticate: Basic realm=""
    
    " +
                "Your Authentication failed.
    ",
            http.Response{
                Status:     "401 Unauthorized",
                StatusCode: 401,
                Proto:      "HTTP/1.0",
                ProtoMajor: 1,
                ProtoMinor: 0,
                Request:    dummyReq("GET"),
                Header: http.Header{
                    "Content-Type":     {"text/html"},
                    "Www-Authenticate": {`Basic realm=""`},
                },
                Close:         true,
                ContentLength: -1,
            },
            "Your Authentication failed.
    ",
        },
    }
    
    
    
    func main() {
        for i, tt := range respTests {
            resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
            if err != nil {
                fmt.Printf("#%d: %v", i, err)
                continue
            }
    
            fmt.Println(i, resp) //返回得到的response
            fmt.Println("ProtoAtLeast 1.0 : ", resp.ProtoAtLeast(1,0))
            fmt.Println()
    
            fmt.Println("write : ")
            err = resp.Write(os.Stdout) //将得到的response写到终端上
            fmt.Println()
            if err != nil {
                fmt.Printf("#%d: %v", i, err)
                continue
            }
        }
    }
    View Code

    返回:

    wanghuideMBP:go-learning wanghui$ go run test.go
    0 &{200 OK 200 HTTP/1.0 1 0 map[Connection:[close]] 0xc00001e200 -1 [] true false map[] 0xc0000f2000 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Connection: close
    
    Body here
    
    1 &{200 OK 200 HTTP/1.1 1 1 map[] 0xc00001e240 -1 [] true false map[] 0xc0000f2100 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Connection: close
    
    Body here
    
    2 &{204 No Content 204 HTTP/1.1 1 1 map[] {} 0 [] false false map[] 0xc0000f2200 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 204 No Content
    
    
    3 &{200 OK 200 HTTP/1.0 1 0 map[Content-Length:[10] Connection:[close]] 0xc00001e280 10 [] true false map[] 0xc0000f2300 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Content-Length: 10
    Connection: close
    
    Body here
    
    4 &{200 OK 200 HTTP/1.1 1 1 map[] 0xc00001e2c0 -1 [chunked] false false map[] 0xc0000f2400 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Transfer-Encoding: chunked
    
    13
    Body here
    continued
    0
    
    
    5 &{200 OK 200 HTTP/1.1 1 1 map[] 0xc00001e300 -1 [chunked] false false map[] 0xc0000f2500 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Transfer-Encoding: chunked
    
    a
    Body here
    
    0
    
    
    6 &{200 OK 200 HTTP/1.1 1 1 map[] {} -1 [chunked] false false map[] 0xc0000f2600 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Transfer-Encoding: chunked
    
    
    
    7 &{200 OK 200 HTTP/1.0 1 0 map[Content-Length:[256]] {} 256 [] true false map[] 0xc0000f2700 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Connection: close
    Content-Length: 256
    
    
    8 &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[256]] {} 256 [] false false map[] 0xc0000f2800 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Content-Length: 256
    
    
    9 &{200 OK 200 HTTP/1.0 1 0 map[] {} -1 [] true false map[] 0xc0000f2900 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Connection: close
    
    
    10 &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[0]] {} 0 [] false false map[] 0xc0000f2a00 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Content-Length: 0
    
    
    11 &{303  303 HTTP/1.0 1 0 map[] 0xc00001e340 -1 [] true false map[] 0xc0000f2b00 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 303 
    Connection: close
    
    
    12 &{303 303 HTTP/1.0 1 0 map[] 0xc00001e380 -1 [] true false map[] 0xc0000f2c00 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 303 303
    Connection: close
    
    
    13 &{206 Partial Content 206 HTTP/1.1 1 1 map[Content-Type:[multipart/byteranges; boundary=18a75608c8f47cef]] 0xc00001e3c0 -1 [] true false map[] 0xc0000f2d00 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 206 Partial Content
    Connection: close
    Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
    
    some body
    14 &{200 OK 200 HTTP/1.0 1 0 map[Connection:[close]] 0xc00001e400 -1 [] true false map[] <nil> <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Connection: close
    
    Body here
    
    15 &{206 Partial Content 206 HTTP/1.1 1 1 map[Content-Type:[text/plain; charset=utf-8] Accept-Ranges:[bytes] Content-Range:[bytes 0-5/1862] Content-Length:[6]] 0xc00001e480 6 [] false false map[] 0xc0000f2e00 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 206 Partial Content
    Content-Length: 6
    Accept-Ranges: bytes
    Content-Range: bytes 0-5/1862
    Content-Type: text/plain; charset=utf-8
    
    foobar
    16 &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[256]] {} 256 [] true false map[] 0xc0000f2f00 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Connection: close
    Content-Length: 256
    
    
    17 &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[256]] {} 256 [] true false map[] 0xc0000f3000 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Connection: close
    Content-Length: 256
    
    
    18 &{200 OK 200 HTTP/1.0 1 0 map[] 0xc00001e4c0 -1 [] true false map[] 0xc0000f3100 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Connection: close
    
    Body here
    
    19 &{200 OK 200 HTTP/1.0 1 0 map[Content-Length:[10]] 0xc00001e500 10 [] true false map[] 0xc0000f3200 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 200 OK
    Connection: close
    Content-Length: 10
    
    Body here
    
    20 &{200 OK 200 HTTP/1.1 1 1 map[Content-Encoding:[gzip] Content-Length:[23] Connection:[keep-alive] Keep-Alive:[timeout=7200]] 0xc00001e580 23 [] false false map[] 0xc0000f3300 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.1 200 OK
    Content-Length: 23
    Connection: keep-alive
    Content-Encoding: gzip
    Keep-Alive: timeout=7200
    
    s???'?
    21 &{401 Unauthorized 401 HTTP/1.0 1 0 map[Content-Type:[text/html] Www-Authenticate:[Basic realm=""]] 0xc00001e5c0 -1 [] true false map[] 0xc0000f3400 <nil>}
    ProtoAtLeast 1.0 :  true
    
    write : 
    HTTP/1.0 401 Unauthorized
    Connection: close
    Content-Type: text/html
    Www-Authenticate: Basic realm=""
    
    Your Authentication failed.
    
    wanghuideMBP:go-learning wanghui$ 
    View Code

    举例2:

    package main 
    import(
        "fmt"
        "net/http"
        "bytes"
        "strings"
    )
    
    func main() {
        r := &http.Response{
            Status:     "123 some status",
            StatusCode: 123,
            ProtoMajor: 1,
            ProtoMinor: 3,
        }
        var buf bytes.Buffer
        r.Write(&buf)
        fmt.Println(buf.String())
        if strings.Contains(buf.String(), "123 123") {
            fmt.Printf("stutter in status: %s", buf.String())
        }
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    HTTP/1.3 123 some status
    
    
    userdeMBP:go-learning user$ 

    其他方法:

    func (*Response) Cookies

    func (r *Response) Cookies() []*Cookie

    Cookies解析并返回该回复中的Set-Cookie头设置的cookie。

    func (*Response) Location

    func (r *Response) Location() (*url.URL, error)

    Location返回该回复的Location头设置的URL。相对地址的重定向会相对于该回复对应的请求request.url来确定绝对地址。如果回复中没有Location头,会返回nil, ErrNoLocation。

    举例:
    package main 
    import(
        "fmt"
        "net/http"
        "net/url"
    )
    
    type responseLocationTest struct {
        location string // Response's Location header or ""
        requrl   string // Response.Request.URL or ""
        want     string
        wantErr  error
    }
    
    var responseLocationTests = []responseLocationTest{
        {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
        {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
        {"", "http://bar.com/baz", "", http.ErrNoLocation},
        {"/bar", "", "/bar", nil},
    }
    
    func main() {
        for i, tt := range responseLocationTests {
            res := new(http.Response)
            res.Header = make(http.Header)
            res.Header.Set("Location", tt.location)
            if tt.requrl != "" {
                res.Request = &http.Request{}
                var err error
                res.Request.URL, err = url.Parse(tt.requrl)
                if err != nil {
                    fmt.Printf("bad test URL %q: %v", tt.requrl, err)
                }else{
                    fmt.Println(i, "URL : ", res.Request.URL)
                }
                
            }
    
            got, err := res.Location()
            if tt.wantErr != nil {
                if err == nil {
                    fmt.Printf("%d. err=nil; want %q", i, tt.wantErr)
                    continue
                }
                if g, e := err.Error(), tt.wantErr.Error(); g != e {
                    fmt.Printf("%d. err=%q; want %q", i, g, e)
                    continue
                }else{
                    fmt.Println(i, "err : ", err.Error())
                }
                continue
            }
            if err != nil {
                fmt.Printf("%d. err=%q", i, err)
                continue
            }
            if g, e := got.String(), tt.want; g != e {
                fmt.Printf("%d. Location=%q; want %q", i, g, e)
            }else{
                fmt.Println(i, "got : ", got.String())
            }
        }
    
    }

    返回:

    userdeMBP:go-learning user$ go run test.go
    0 URL :  http://bar.com/baz
    0 got :  http://bar.com/foo
    1 URL :  http://bar.com/baz
    1 got :  http://foo.com/
    2 URL :  http://bar.com/baz
    2 err :  http: no Location header in response
    3 got :  /bar
    2>Client
    1)RoundTripperTransport
    要管理代理、TLS配置(证书)、keep-alive、压缩和其他设置,创建一个Transport
    Client和Transport类型都可以安全的被多个go程同时使用。出于效率考虑,应该一次建立、尽量重用。
    简单使用:
    tr := &http.Transport{
        TLSClientConfig:    &tls.Config{RootCAs: pool},
        DisableCompression: true,
    }
    client := &http.Client{Transport: tr}
    resp, err := client.Get("https://example.com")

    type RoundTripper

    type RoundTripper interface {
        // RoundTrip执行单次HTTP事务,接收并发挥请求req的回复。
        // RoundTrip不应试图解析/修改得到的回复。
        // 尤其要注意,只要RoundTrip获得了一个回复,不管该回复的HTTP状态码如何,
        // 它必须将返回值err设置为nil。
        // 非nil的返回值err应该留给获取回复失败的情况。
        // 类似的,RoundTrip不能试图管理高层次的细节,如重定向、认证、cookie。
        //
        // 除了从请求的主体读取并关闭主体之外,RoundTrip不应修改请求,包括(请求的)错误。
        // RoundTrip函数接收的请求的URL和Header字段可以保证是(被)初始化了的。
        RoundTrip(*Request) (*Response, error)
    }

    RoundTripper接口是具有执行单次HTTP事务的能力(接收指定请求的回复)的接口。

    RoundTripper接口的类型必须可以安全的被多线程同时使用。

    type Transport

    type Transport struct {
        // Proxy指定一个对给定请求返回代理的函数。
        // 如果该函数返回了非nil的错误值,请求的执行就会中断并返回该错误。
        // 如果Proxy为nil或返回nil的*URL置,将不使用代理。
        Proxy func(*Request) (*url.URL, error)
      //DialContext指定创建未加密的TCP连接的拨号函数,如果值为nil,则传输使用net包拨号。此方法返回一个Conn接口
      DialContext func(ctx context.Context, network, addr string) (net.Conn, error)
    // Dial指定创建未加密的TCP连接的拨号函数。如果Dial为nil,会使用net.Dial。该函数已经被DialContext函数取代
      //DialContext可以在不需要的时候取消拨号
      //如果这两个字段都设置了,那么DialContext的优先级更高
    Dial func(network, addr string) (net.Conn, error) // TLSClientConfig指定用于tls.Client的TLS配置信息。 // 如果该字段为nil,会使用默认的配置信息。 TLSClientConfig *tls.Config // TLSHandshakeTimeout指定等待TLS握手完成的最长时间。零值表示不设置超时。 TLSHandshakeTimeout time.Duration // 如果DisableKeepAlives为真,会禁止不同HTTP请求之间TCP连接的重用。 DisableKeepAlives bool // 如果DisableCompression为真,会禁止Transport在请求中没有Accept-Encoding头时, // 主动添加"Accept-Encoding: gzip"头,以获取压缩数据。 // 如果Transport自己请求gzip并得到了压缩后的回复,它会主动解压缩回复的主体。 // 但如果用户显式的请求gzip压缩数据,Transport是不会主动解压缩的。 DisableCompression bool // 如果MaxIdleConnsPerHost!=0,会控制每个主机下的最大闲置连接。 // 如果MaxIdleConnsPerHost==0,会使用DefaultMaxIdleConnsPerHost。 MaxIdleConnsPerHost int // ResponseHeaderTimeout指定在发送完请求(包括其可能的主体)之后, // 等待接收服务端的回复的头域的最大时间。零值表示不设置超时。 // 该时间不包括获取回复主体的时间。 ResponseHeaderTimeout time.Duration // 内含隐藏或非导出字段 }

    Transport类型实现了RoundTripper接口,支持http、https和http/https代理。Transport类型可以缓存连接以在未来重用。

    var DefaultTransport RoundTripper = &Transport{
        Proxy: ProxyFromEnvironment,
        DialContext: (&net.Dialer{ //首先定义一个Dialer结构体,然后再其上调用DialContext接口
            Timeout:   30 * time.Second,
            KeepAlive: 30 * time.Second,
            DualStack: true,
        }).DialContext,
        MaxIdleConns:          100,
        IdleConnTimeout:       90 * time.Second,
        TLSHandshakeTimeout:   10 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
    }

    DefaultTransport是被包变量DefaultClient使用的默认RoundTripper接口。它会根据需要创建网络连接,并缓存以便在之后的请求中重用这些连接。它使用环境变量$HTTP_PROXY和$NO_PROXY(或$http_proxy和$no_proxy)指定的HTTP代理。

    上面的定义可见设置的大多数是超时的时间

    func (*Transport) RoundTrip

    func (t *Transport) RoundTrip(req *Request) (resp *Response, err error)

    RoundTrip方法实现了RoundTripper接口。

    高层次的HTTP客户端支持(如管理cookie和重定向)请参见Get、Post等函数和Client类型。

     

    2)type Client

    type Client struct {
        // Transport指定执行独立、单次HTTP请求的机制。
        // 如果Transport为nil,则使用DefaultTransport。
        Transport RoundTripper
        // CheckRedirect指定处理重定向的策略。
        // 如果CheckRedirect不为nil,客户端会在执行重定向之前调用本函数字段。
        // 参数req和via是将要执行的请求和已经执行的请求(切片,越新的请求越靠后)。
        // 如果CheckRedirect返回一个错误,本类型的Get方法不会发送请求req,
        // 而是返回之前得到的最后一个回复和该错误。(包装进url.Error类型里)
        //
        // 如果CheckRedirect为nil,会采用默认策略:连续10此请求后停止。
        CheckRedirect func(req *Request, via []*Request) error
        // Jar指定cookie管理器。
        // 如果Jar为nil,请求中不会发送cookie,回复中的cookie会被忽略。
        Jar CookieJar
        // Timeout指定本类型的值执行请求的时间限制。
        // 该超时限制包括连接时间、重定向和读取回复主体的时间。
        // 计时器会在Head、Get、Post或Do方法返回后继续运作并在超时后中断回复主体的读取。
        //
        // Timeout为零值表示不设置超时。
        //
        // Client实例的Transport字段必须支持CancelRequest方法,
        // 否则Client会在试图用Head、Get、Post或Do方法执行请求时返回错误。
        // 本类型的Transport字段默认值(DefaultTransport)支持CancelRequest方法。
        Timeout time.Duration
    }

    Client类型代表HTTP客户端。它的零值(DefaultClient)是一个可用的使用DefaultTransport的客户端。

    Client的Transport字段一般会含有内部状态(缓存TCP连接),因此Client类型值应尽量被重用而不是每次需要都创建新的。Client类型值可以安全的被多个go程同时使用。

    Client类型的层次比RoundTripper接口(如Transport)高,还会管理HTTP的cookie和重定向等细节。

    func (*Client) Do

    func (c *Client) Do(req *Request) (resp *Response, err error)

    Do方法发送请求,返回HTTP回复。它会遵守客户端c设置的策略(如重定向、cookie、认证)。

    如果客户端的策略(如重定向)返回错误或存在HTTP协议错误时,本方法将返回该错误;如果回应的状态码不是2xx,本方法并不会返回错误。

    如果返回值err为nil,resp.Body总是非nil的,调用者应该在读取完resp.Body后关闭它。如果返回值resp的主体未关闭,c下层的RoundTripper接口(一般为Transport类型)可能无法重用resp主体下层保持的TCP连接去执行之后的请求。

    请求的主体,如果非nil,会在执行后被c.Transport关闭,即使出现错误。

    该方法一般和http.NewRequest()方法结合使用,一般应使用Get、Post或PostForm方法代替Do方法。

    func (*Client) Head

    func (c *Client) Head(url string) (resp *Response, err error)

    Head向指定的URL发出一个HEAD请求,如果回应的状态码如下,Head会在调用c.CheckRedirect后执行重定向:

    301 (Moved Permanently)
    302 (Found)
    303 (See Other)
    307 (Temporary Redirect)

    func (*Client) Get

    func (c *Client) Get(url string) (resp *Response, err error)

    Get向指定的URL发出一个GET请求,如果回应的状态码如下,Get会在调用c.CheckRedirect后执行重定向:

    301 (Moved Permanently)
    302 (Found)
    303 (See Other)
    307 (Temporary Redirect)

    如果c.CheckRedirect执行失败或存在HTTP协议错误时,本方法将返回该错误;如果回应的状态码不是2xx,本方法并不会返回错误。如果返回值err为nil,resp.Body总是非nil的,调用者应该在读取完resp.Body后关闭它。

    func (*Client) Post

    func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error)

    Post向指定的URL发出一个POST请求。bodyType为POST数据的类型, body为POST数据,作为请求的主体。如果参数body实现了io.Closer接口,它会在发送请求后被关闭。调用者有责任在读取完返回值resp的主体后关闭它。

    func (*Client) PostForm

    func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)

    PostForm向指定的URL发出一个POST请求,url.Values类型的data会被编码为请求的主体。POST数据的类型一般会设为"application/x-www-form-urlencoded"。如果返回值err为nil,resp.Body总是非nil的,调用者应该在读取完resp.Body后关闭它。

    下面的方法和上面的差别就在于下面调用的是DefaultClient

    var DefaultClient = &Client{}

    DefaultClient是用于包函数Get、Head和Post的默认Client。

    func Head(url string) (resp *Response, err error)

    Head向指定的URL发出一个HEAD请求,如果回应的状态码如下,Head会在调用c.CheckRedirect后执行重定向:

    301 (Moved Permanently)
    302 (Found)
    303 (See Other)
    307 (Temporary Redirect)

    Head是对包变量DefaultClient的Head方法的包装。

    func Get

    func Get(url string) (resp *Response, err error)

    Get向指定的URL发出一个GET请求,如果回应的状态码如下,Get会在调用c.CheckRedirect后执行重定向:

    301 (Moved Permanently)
    302 (Found)
    303 (See Other)
    307 (Temporary Redirect)

    如果c.CheckRedirect执行失败或存在HTTP协议错误时,本方法将返回该错误;如果回应的状态码不是2xx,本方法并不会返回错误。如果返回值err为nil,resp.Body总是非nil的,调用者应该在读取完resp.Body后关闭它。

    Get是对包变量DefaultClient的Get方法的包装。

    举例:
    服务端test.go:
    package main 
    import(
        "fmt"
        "net/http"
        "time"
    )
    
    func sayhelloName(w http.ResponseWriter, req *http.Request){
        fmt.Fprintf(w, "hello web server") //将字符串写入到w,即在客户端输出
    }
    
    func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", sayhelloName) //设置访问的路由
    
        server := &http.Server{
            Addr: ":8000",
            ReadTimeout: 60 * time.Second,
            WriteTimeout: 60 * time.Second,
            Handler: mux,
        }
        server.ListenAndServe()
    }

    客户端test1.go:

    package main
    
    import(
        "fmt"
        "net/http"
        "io/ioutil"
        "log"
    )
    
    func main() {
        res, err := http.Get("http://localhost:8000")
        if err != nil {
            log.Fatal(err)
        }
        robots, err := ioutil.ReadAll(res.Body)
        res.Body.Close()
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s
    ", robots) 
    }

    返回:

    userdeMacBook-Pro:go-learning user$ go run test1.go 
    hello web server

    func Post

    func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)

    Post向指定的URL发出一个POST请求。bodyType为POST数据的类型, body为POST数据,作为请求的主体。如果参数body实现了io.Closer接口,它会在发送请求后被关闭。调用者有责任在读取完返回值resp的主体后关闭它。

    Post是对包变量DefaultClient的Post方法的包装。

    func PostForm

    func PostForm(url string, data url.Values) (resp *Response, err error)

    PostForm向指定的URL发出一个POST请求,url.Values类型的data会被编码为请求的主体。如果返回值err为nil,resp.Body总是非nil的,调用者应该在读取完resp.Body后关闭它。

    PostForm是对包变量DefaultClient的PostForm方法的包装。

     
     
    ⚠️客户端和服务器之间交互的过程即:
    • client客户端发送创建好的request请求到server服务端
    • server服务端接收到请求后就会使用serveMux路由解析收到的request得到请求的path,寻找处理器函数handler中pattern与之相符的处理器
    • handler处理器就会调用对应的handler函数来处理该request请求
    • 最后server端就会返回response到client客户端
     

     6>服务器

    1)ResponseWriter

    type ResponseWriter

    type ResponseWriter interface {
        // Header返回一个Header类型值,该值会被WriteHeader方法发送。
        // 在调用WriteHeader或Write方法后再改变该对象是没有意义的。
        Header() Header
        // WriteHeader该方法发送HTTP回复的头域和状态码。
        // 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)
        // WriterHeader的显式调用主要用于发送错误码。
        WriteHeader(int)
        // Write向连接中写入作为HTTP的一部分回复的数据。
        // 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)
        // 如果Header中没有"Content-Type"键,
        // 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。
        Write([]byte) (int, error)
    }

    ResponseWriter接口被HTTP处理器用于构造HTTP回复。

    使用其要注意的点:

    • WriteHeader只能调用一次,否则会出错:http: multiple response.WriteHeader calls

    • WriteHeader必须在Write()之前调用, 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)

    •  使用Set/WriteHeader/Write这三个方法时的顺序必须是Set->WriteHeader->Write,否则会出现意想不到的结果,如:

    http.ResponseWriter.Header().Set("Content-type", "application/text")
    http.ResponseWriter.Write([]byte(resp)) //会默认调用WriteHeader(http.StatusOK)
    http.ResponseWriter.WriteHeader(403)    //所以这里的设置就没有用了,返回一直是200,即http.StatusOK

    2)type Handler

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

    实现了Handler接口的对象可以注册到HTTP服务端,为特定的路径及其子树提供服务。

    ServeHTTP应该将回复的头域和数据写入ResponseWriter接口然后返回。返回标志着该请求已经结束,HTTP服务端可以转移向该连接上的下一个请求。

    ⚠️该接口用于开发者能够实现自己的Handler,只要实现ServeHTTP(ResponseWriter, *Request)方法即可

    实现了ServeHTTP方法的结构都能够称之为handler对象,ServeMux会使用handler(如它的函数func (*ServeMux) Handle)并调用其ServeHTTP方法处理请求并返回响应

     

    3)type HandlerFunc

    type HandlerFunc func(ResponseWriter, *Request)

    HandlerFunc type是一个适配器,通过类型转换让我们可以将普通的函数作为HTTP处理器使用。如果f是一个具有适当签名的函数,HandlerFunc(f)通过调用f实现了Handler接口(因为HandlerFunc实现了ServeHTTP函数),其实就是将函数f显示转换成HandlerFunc类型

    func (HandlerFunc) ServeHTTP

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

    ServeHTTP方法会调用f(w, r)

    ⚠️来自https://www.jianshu.com/p/16210100d43d

    所谓中间件,就是连接上下级不同功能的函数或者软件,通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。前文的HandleFunc就能把签名为 func(w http.ResponseWriter, r *http.Reqeust)的函数包裹成handler。这个函数也算是中间件。
    func middlewareHandler(next http.Handler) http.Handler{
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
            // 执行handler之前的逻辑
            next.ServeHTTP(w, r)
            // 执行完毕handler后的逻辑
        })
    }

    进行类似上面的处理我们就能够得到一个http.Handler对象,然后直接传入http.Handle或ServeMux的Handle方法中

    4)type ServeMux —— 作用就是当访问某个URL网址时指明要做出的操作

    type ServeMux struct {
        mu    sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
        m     map[string]muxEntry //路由规则,一个string对应一个mux实体,这里的string就是注册的路由
        hosts bool // whether any patterns contain hostnames
    }

    路由规则:

    type muxEntry struct{
        explicit bool //是否精确匹配
        h          Handler //这个路由表达式对应哪个handler
    }

    ⚠️ServeMux也实现了ServeHTTP接口,也算是一个handler,不过ServeMux的ServeHTTP方法不是用来处理request和respone,而是用来找到路由注册的handler

    func (*ServeMux) ServeHTTP

    func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

    ServeHTTP将请求派遣到与请求的URL最匹配的模式对应的处理器。

    ServeMux类型是HTTP请求的多路转接器。它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。

    模式是固定的、由根开始的路径,如"/favicon.ico",或由根开始的子树,如"/images/"(注意结尾的斜杠)。较长的模式优先于较短的模式,因此如果模式"/images/"和"/images/thumbnails/"都注册了处理器,后一个处理器会用于路径以"/images/thumbnails/"开始的请求,前一个处理器会接收到其余的路径在"/images/"子树下的请求。

    注意,因为以斜杠结尾的模式代表一个由根开始的子树,模式"/"会匹配所有的未被其他注册的模式匹配的路径,而不仅仅是路径"/"。

    模式也能(可选地)以主机名开始,表示只匹配该主机上的路径。指定主机的模式优先于一般的模式,因此一个注册了两个模式"/codesearch"和"codesearch.google.com/"的处理器不会接管目标为"http://www.google.com/"的请求。

    ServeMux还会注意到请求的URL路径的无害化,将任何路径中包含"."或".."元素的请求重定向到等价的没有这两种元素的URL。(参见path.Clean函数)

    func NewServeMux

    func NewServeMux() *ServeMux

    NewServeMux创建并返回一个新的*ServeMux

    var DefaultServeMux = NewServeMux()

    DefaultServeMux是用于Serve的默认ServeMux。

    func (*ServeMux) Handle

    func (mux *ServeMux) Handle(pattern string, handler Handler)

    Handle注册HTTP处理器handler和对应的模式pattern。如果该模式已经注册有一个处理器,Handle会panic。

    ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应

    func (*ServeMux) HandleFunc

    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

    HandleFunc注册一个处理器函数handler和对应的模式pattern。

    ⚠️从下面的源码可以看出HandleFunc其实是调用了Handle的:

    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        if handler == nil {
            panic("http: nil handler")
        }
        mux.Handle(pattern, HandlerFunc(handler))
    }

    因此当ServeMux使用handler时,调用ServeHTTP方法其实就是调用handler(w, r)

    所以一般我们使用的时候使用的其实都是HandleFunc函数,如下面的例子:

    当然,如果想要使用Handle方法,就看上面HandleFunc处说的中间件概念

        http.HandleFunc("/", sayhelloName) //设置访问的路由
        err := http.ListenAndServe(":9090", nil) //设置监听的端口

    其过程其实就是你先创建一个名为sayhelloName的handler(ResponseWriter, *Request)函数,类型为func(ResponseWriter, *Request),然后调用HandleFunc()函数,在该函数中调用HandlerFunc(sayhelloName)来将handler函数转成handler处理器函数,使其具有ServeHTTP方法。在这里调用handler处理器ServeHTTP方法等价于调用handler函数sayhelloName(ResponseWriter, *Request)

    这两个函数和下面的两个函数是不同的:

    上面的是将处理器handler和对应的模式pattern注册到指定的ServeMux,下面的则是注册到DefaultServeMux

    func Handle

    func Handle(pattern string, handler Handler)

    Handle注册HTTP处理器handler和对应的模式pattern(注册到DefaultServeMux)。如果该模式已经注册有一个处理器,Handle会panic。ServeMux的文档解释了模式的匹配机制。

    源码:

    func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

    func HandleFunc

    func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

    HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。

    从他的源代码可见是注册到了DefaultServeMux上的:

    func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        DefaultServeMux.HandleFunc(pattern, handler)
    }

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "strings"
        "log"
    )
    
    func sayhelloName(w http.ResponseWriter, req *http.Request){
        req.ParseForm() //解析参数,默认是不会解析的
        fmt.Println(req.Form) //这些信息是输出到服务端的打印信息
        fmt.Println("path", req.URL.Path)
        fmt.Println("scheme", req.URL.Scheme)
        fmt.Println(req.Form["url_long"])
        for k, v := range req.Form {
            fmt.Println("key : ", k)
            fmt.Println("value : ", strings.Join(v, ""))
        }
        fmt.Fprintf(w, "hello web server") //将字符串写入到w,即在客户端输出
    }
    
    func main() {
        http.HandleFunc("/", sayhelloName) //设置访问的路由
        err := http.ListenAndServe(":9090", nil) //设置监听的端口
        if err != nil {
            log.Fatal("ListenAndServe : ", err)
        }
    }

    返回:

    userdeMacBook-Pro:go-learning user$ go run test.go
    map[]
    path /
    scheme 
    []
    map[]
    path /favicon.ico
    scheme 
    []

    在浏览器中访问:

    如果访问的是http://localhost:9090/%EF%BC%9Furl_long=111&url_long=222,就返回:

    map[]
    path /?url_long=111&url_long=222
    scheme 
    []
    map[]
    path /favicon.ico
    scheme 
    []

    func ListenAndServe

    func ListenAndServe(addr string, handler Handler) error

    ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。

    ListenAndServe使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采用包变量DefaultServeMux作为处理器。

    其底层其实就是初始化了一个server对象,然后调用了net.Listen("tcp", addr),也就是底层用TCP协议搭建了一个服务器来监控我们设置的端口

    http的Handle和HandleFunc函数可以向DefaultServeMux添加处理器,看上面。所以一般两者的结合都写成类似下面的形式:

        http.HandleFunc("/hello", HelloServer)
        err := http.ListenAndServe(":12345", nil)
        if err != nil {
            log.Fatal("ListenAndServe: ", err)
        }

    该代码的执行过程是:

    1.首先调用http.HandleFunc时:

    • 调用了DefaultServeMux的HandleFunc
    • 然后调用了DefaultServeMux的Handle
    • 接着就是往DefaultServeMux的map[string]muxEntry中添加请求URL对应的路由规则,并且路由规则中存储对应的handler处理器

    2.其次就是调用http.ListenAndServe(":12345", nil):

    • 首先实例化Server
    • 然后调用Server.ListenAndServe()
    • 再调用net.Listen("tcp", addr)监听端口启动一个for循环,在循环体中Accept请求
    • 然后对每个请求都实例化一个Conn,即srv.newConn(rw);并开启一个goroutine为这个请求进行服务go c.serve()
    • 读取每个请求的内容 w, err := c.readRequest()
    • 然后判断handler是否为空,如果没有设置handler(即第二个参数为nil时),handler就设置为DefaultServeMux
    • 然后要选择DefaultServeMux合适的handler,即要判断是否有路由能够满足这个request(循环遍历ServerMux的muxEntry)。如果有路由满足,则调用该handler的ServeHTTP;如果没有路由满足,则调用NotFoundHandler的ServeHTTP

    当然你也可以使用自己设置的mux作为ListenAndServe函数中第二个参数

     一个简单的例子是:

    package main 
    import(
        "fmt"
        "net/http"
    )
    type MyMux struct{}
    func (mux *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request){
        if r.URL.Path == "/"{
            sayhello(w, r)
            return
        }
        http.NotFound(w, r)
        return
    }
    
    func sayhello(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, "hello world!!")
    }
    
    func main() {
        mux := &MyMux{}
        http.ListenAndServe(":9090", mux)
    }

    运行后访问浏览器得:

    举一个使用handle的例子,该例子来自https://www.jianshu.com/p/16210100d43d:
    package main 
    import(
        "fmt"
        "net/http"
    )
    
    type textHandler struct {
        responseText string
    }
    
    func (th *textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, th.responseText)
    }
    
    type indexHandler struct {}
    
    func (ih *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "text/html")
    
        html := `<doctype html>
            <html>
            <head>
              <title>Hello World</title>
            </head>
            <body>
            <p>
              <a href="/welcome">Welcome</a> |  <a href="/message">Message</a>
            </p>
            </body>
    </html>`
        fmt.Fprintln(w, html)
    }
    
    func main() {
        mux := http.NewServeMux()
    
        mux.Handle("/", &indexHandler{})
    
        thWelcome := &textHandler{"TextHandler !"}
        mux.Handle("/text",thWelcome)
    
        http.ListenAndServe(":8000", mux)
    }
    运行访问http://localhost:8000,得到:

    访问http://localhost:8000/text,得到:

     

    func (*ServeMux) Handler

    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)

    Handler根据r.Method、r.Host和r.URL.Path等数据,返回将用于处理该请求的HTTP处理器。它总是返回一个非nil的处理器。如果路径不是它的规范格式,将返回内建的用于重定向到等价的规范路径的处理器。

    Handler也会返回匹配该请求的的已注册模式;在内建重定向处理器的情况下,pattern会在重定向后进行匹配。如果没有已注册模式可以应用于该请求,本方法将返回一个内建的"404 page not found"处理器和一个空字符串模式。

     其源代码为:

    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    
        // CONNECT requests are not canonicalized.
        if r.Method == "CONNECT" {
            // If r.URL.Path is /tree and its handler is not registered,
            // the /tree -> /tree/ redirect applies to CONNECT requests
            // but the path canonicalization does not.
            if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
                return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
            }
    
            return mux.handler(r.Host, r.URL.Path)
        }
    
        // All other requests have any port stripped and path cleaned
        // before passing to mux.handler.
        host := stripHostPort(r.Host)
        path := cleanPath(r.URL.Path)
    
        // If the given path is /tree and its handler is not registered,
        // redirect for /tree/.
        if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }
    
        if path != r.URL.Path {
            _, pattern = mux.handler(host, path)
            url := *r.URL
            url.Path = path
            return RedirectHandler(url.String(), StatusMovedPermanently), pattern
        }
    
        return mux.handler(host, r.URL.Path)
    }
    
    // handler is the main implementation of Handler.
    // The path is known to be in canonical form, except for CONNECT methods.
    func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
        mux.mu.RLock()
        defer mux.mu.RUnlock()
    
        // Host-specific pattern takes precedence over generic ones
        if mux.hosts {
            h, pattern = mux.match(host + path)
        }
        if h == nil {
            h, pattern = mux.match(path)
        }
        if h == nil {
            h, pattern = NotFoundHandler(), ""
        }
        return
    }
    
    func (mux *ServeMux) match(path string) (h Handler, pattern string) {
        // Check for exact match first.
        v, ok := mux.m[path] //匹配路由规则
        if ok {
            return v.h, v.pattern
        }
    
        // Check for longest valid match.  mux.es contains all patterns
        // that end in / sorted from longest to shortest.
        for _, e := range mux.es {
            if strings.HasPrefix(path, e.pattern) {
                return e.h, e.pattern
            }
        }
        return nil, ""
    }

    那么当路由器ServeMux里面存储好了相应的路由规则m后,具体的请求又是怎么分发的呢:

    • 当路由器ServeMux接收到请求request后就会调用
    handler, _ : = mux.Handler(request)
    handler.ServeHTTP(w, request)
    • 由上面源码中可见调用mux.Handler(request)的代码中又调用了mux.handler(host, r.URL.Path),他就是使用用户请求的URL和路由器中的路由规则map相匹配,匹配成功后返回map中的handler值
    • 然后再调用该handler的ServeHTTP即可


    5)Server

    type Server

    type Server struct {
        Addr           string        // 监听的TCP地址,如果为空字符串会使用":http"
        Handler        Handler       // 调用的处理器,如为nil会调用http.DefaultServeMux
        ReadTimeout    time.Duration // 请求的读取操作在超时前的最大持续时间
        WriteTimeout   time.Duration // 回复的写入操作在超时前的最大持续时间
        MaxHeaderBytes int           // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
        TLSConfig      *tls.Config   // 可选的TLS配置,用于ListenAndServeTLS方法
        // TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
        // 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
        // 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
        // 连接在函数返回时会自动关闭。
        TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
        // ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
        // 参见ConnState类型和相关常数获取细节。
        ConnState func(net.Conn, ConnState)
        // ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
        // 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
        ErrorLog *log.Logger
        // 内含隐藏或非导出字段
    }

    Server类型定义了运行HTTP服务端的参数。Server的零值是合法的配置。

    func (*Server) Serve

    func (srv *Server) Serve(l net.Listener) error

    Serve会接手监听器l收到的每一个连接,并为每一个连接创建一个新的服务go程。该go程会读取请求,然后调用srv.Handler回复请求。

    func (*Server) ListenAndServe

    func (srv *Server) ListenAndServe() error

    ListenAndServe监听srv.Addr指定的TCP地址,并且会调用Serve方法接收到的连接。如果srv.Addr为空字符串,会使用":http"。

    func (*Server) ListenAndServeTLS

    func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error

    ListenAndServeTLS监听srv.Addr确定的TCP地址,并且会调用Serve方法处理接收到的连接。必须提供证书文件和对应的私钥文件。如果证书是由权威机构签发的,certFile参数必须是顺序串联的服务端证书和CA证书。如果srv.Addr为空字符串,会使用":https"。

    举例:

    package main 
    import(
        "fmt"
        "net/http"
        "time"
    )
    
    func sayhelloName(w http.ResponseWriter, req *http.Request){
        fmt.Fprintf(w, "hello web server") //将字符串写入到w,即在客户端输出
    }
    
    func main() {
        mux := http.NewServeMux()
        mux.HandleFunc("/", sayhelloName) //设置访问的路由
        // err := http.ListenAndServe(":9090", nil) //设置监听的端口
        // if err != nil {
        //     log.Fatal("ListenAndServe : ", err)
        // }
        //如果使用的是Server,则等价于:
        server := &http.Server{
            Addr: ":8000",
            ReadTimeout: 60 * time.Second,
            WriteTimeout: 60 * time.Second,
            Handler: mux,
        }
        server.ListenAndServe()
    }

    返回:

    func (*Server) SetKeepAlivesEnabled

    func (s *Server) SetKeepAlivesEnabled(v bool)

    SetKeepAlivesEnabled控制是否允许HTTP闲置连接重用(keep-alive)功能。默认该功能总是被启用的。只有资源非常紧张的环境或者服务端在关闭进程中时,才应该关闭该功能。

    7>Conn

    http中的两核心功能是Conn连接 和ServeMux路由器,客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,保证了每个请求的独立性

    type ConnState

    type ConnState int

    ConnState代表一个客户端到服务端的连接的状态。本类型用于可选的Server.ConnState回调函数。

    const (
        // StateNew代表一个新的连接,将要立刻发送请求。
        // 连接从这个状态开始,然后转变为StateAlive或StateClosed。
        StateNew ConnState = iota
        // StateActive代表一个已经读取了请求数据1到多个字节的连接。
        // 用于StateAlive的Server.ConnState回调函数在将连接交付给处理器之前被触发,
        // 等到请求被处理完后,Server.ConnState回调函数再次被触发。
        // 在请求被处理后,连接状态改变为StateClosed、StateHijacked或StateIdle。
        StateActive
        // StateIdle代表一个已经处理完了请求、处在闲置状态、等待新请求的连接。
        // 连接状态可以从StateIdle改变为StateActive或StateClosed。
        StateIdle
        // 代表一个被劫持的连接。这是一个终止状态,不会转变为StateClosed。
        StateHijacked
        // StateClosed代表一个关闭的连接。
        // 这是一个终止状态。被劫持的连接不会转变为StateClosed。
        StateClosed
    )

    func (ConnState) String

    func (c ConnState) String() string
  • 相关阅读:
    vs2010 + .net3.5 MSCharts使用介绍与例子
    TFS服务连接TF31002 出错
    SharePoint CAML 通过时间查询
    SharePoint2010项目总结汇总
    jquery 获取和设置 select下拉框的值
    How to Create Multilingual Webpart in SharePoint 2010 (C# 方式)
    sharepoint母版页固定宽度与纵向滚动条靠右边(修改版)
    JavaScript进行GET和POST请求
    端口简介大全
    程序员学习能力提升三要素
  • 原文地址:https://www.cnblogs.com/wanghui-garcia/p/10354854.html
Copyright © 2011-2022 走看看