go標準庫的學習-net/http

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

概念解釋:html

  • request:用戶請求的信息,用來解析用戶的請求信息,包括post、get、cookie、url等信息
  • response:服務器返回給客戶端的信息
  • conn:用戶的每次請求連接
  • handler:處理請求和生成返回信息的處理邏輯

該圖來自https://www.sohu.com/a/208720509_99960938golang

 

下面的內容來自http://www.runoob.com/http/http-messages.html

HTTP使用統一資源標識符(Uniform Resource Identifiers, URI)來傳輸數據和創建鏈接。web

一旦創建鏈接後,數據消息就經過相似Internet郵件所使用的格式[RFC5322]和多用途Internet郵件擴展(MIME)[RFC2045]來傳送。算法

 

客戶端請求消息

客戶端發送一個HTTP請求到服務器的請求消息包括如下格式:請求行(request line)、請求頭部(header)、空行和請求數據四個部分組成,下圖給出了請求報文的通常格式。json

舉例:

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 指定的資源的不一樣操做方式,具體介紹以下:
ruby

  •  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("\n<?xml!")) //text/xml; charset=utf-8 cont5 := http.DetectContentType([]byte(`GIF87a`)) //image/gif cont6 := http.DetectContentType([]byte("MThd\x00\x00\x00\x06\x00\x01")) //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:\n got err: %v", i, err) } continue //有錯的進入這後繼續下一個循環,不往下執行  } if test.err { //test.err爲true,因此該例子中這裏不會進入 fmt.Errorf("#%d:\n should err", i) continue } if !expect.Equal(d) { //說明後三個例子的結果和expect是相同的,因此沒有報錯 fmt.Errorf("#%d:\n got: %v\nwant: %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\r\nContent-Type: text/html; charset=UTF-8\r\n",
    },
    {
        http.Header{
            "Expires":          {"-1"},
            "Content-Length":   {"0"},
            "Content-Encoding": {"gzip"},
        },
        map[string]bool{"Content-Length": true}, //"Content-Length"字段將不會寫入io.Writer
        "Content-Encoding: gzip\r\nExpires: -1\r\n",
    },
}

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:\n got: %q\nwant: %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: "\t"},
        ``,
    },
}


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:\n%s\nGot:\n%s\n", 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\n", 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\n", err)
    } else if req.Method != "GET" { //當請求方法爲空時,會默認使用的是"GET方法"
        fmt.Printf("NewRequest(empty method) has method %q; want GET\n", 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:\nwant: %s\n got: %s\n", 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\r\nheader:foo\r\n\r\n", "", http.Header{"Header": {"foo"}}},
    1: {"GET / HTTP/1.1\r\nheader:foo\r\n", io.ErrUnexpectedEOF.Error(), nil},
    2: {"", io.EOF.Error(), nil},
    3: {
        in:  "HEAD / HTTP/1.1\r\nContent-Length:4\r\n\r\n",
        err: "http: method cannot contain a Content-Length",
    },
    4: {
        in:     "HEAD / HTTP/1.1\r\n\r\n",
        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\r\nContent-Length: 10\r\nContent-Length: 0\r\n\r\nGopher hey\r\n",
        err: "cannot contain multiple Content-Length headers",
    },
    6: {
        in:  "POST / HTTP/1.1\r\nContent-Length: 10\r\nContent-Length: 6\r\n\r\nGopher\r\n",
        err: "cannot contain multiple Content-Length headers",
    },
    7: {
        in:     "PUT / HTTP/1.1\r\nContent-Length: 6 \r\nContent-Length: 6\r\nContent-Length:6\r\n\r\nGopher\r\n",
        err:    "",
        header: http.Header{"Content-Length": {"6"}},
    },
    8: {
        in:  "PUT / HTTP/1.1\r\nContent-Length: 1\r\nContent-Length: 6 \r\n\r\n",
        err: "cannot contain multiple Content-Length headers",
    },
    9: {
        in:  "POST / HTTP/1.1\r\nContent-Length:\r\nContent-Length: 3\r\n\r\n",
        err: "cannot contain multiple Content-Length headers",
    },
    10: {
        in:     "HEAD / HTTP/1.1\r\nContent-Length:0\r\nContent-Length: 0\r\n\r\n",
        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\n", i, tt.err)
            }

            if !reflect.DeepEqual(tt.header, req.Header) {//若是發現二者不一樣
                fmt.Printf("#%d: gotHeader: %q wantHeader: %q\n", 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\n", 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
    //注意value一、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\n", 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\n", 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\n", 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\n", 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
    //注意value一、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\n", 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\n", 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\n", 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指明寫入的文件
    //注意value一、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\n", 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\r\n" +
            "Connection: close\r\n" +
            "\r\n" +
            "Body here\n",

        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\n",
    },

    // Unchunked HTTP/1.1 response without Content-Length or
    // Connection headers.
    {
        "HTTP/1.1 200 OK\r\n" +
            "\r\n" +
            "Body here\n",

        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\n",
    },

    // Unchunked HTTP/1.1 204 response without Content-Length.
    {
        "HTTP/1.1 204 No Content\r\n" +
            "\r\n" +
            "Body should not be read!\n",

        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\r\n" +
            "Content-Length: 10\r\n" +
            "Connection: close\r\n" +
            "\r\n" +
            "Body here\n",

        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\n",
    },

    // Chunked response without Content-Length.
    {
        "HTTP/1.1 200 OK\r\n" +
            "Transfer-Encoding: chunked\r\n" +
            "\r\n" +
            "0a\r\n" +
            "Body here\n\r\n" +
            "09\r\n" +
            "continued\r\n" +
            "0\r\n" +
            "\r\n",

        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\ncontinued",
    },

    // Chunked response with Content-Length.
    {
        "HTTP/1.1 200 OK\r\n" +
            "Transfer-Encoding: chunked\r\n" +
            "Content-Length: 10\r\n" +
            "\r\n" +
            "0a\r\n" +
            "Body here\n\r\n" +
            "0\r\n" +
            "\r\n",

        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\n",
    },

    // Chunked response in response to a HEAD request
    {
        "HTTP/1.1 200 OK\r\n" +
            "Transfer-Encoding: chunked\r\n" +
            "\r\n",

        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\r\n" +
            "Content-Length: 256\r\n" +
            "\r\n",

        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\r\n" +
            "Content-Length: 256\r\n" +
            "\r\n",

        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\r\n" +
            "\r\n",

        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\r\n" +
            "Content-Length: 0\r\n" +
            "\r\n",

        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 \r\n\r\n",
        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\r\n\r\n",
        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\r\n" +
            "Connection: close\r\n" +
            "\r\n" +
            "Body here\n",

        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\n",
    },

    // 206 Partial Content. golang.org/issue/8923
    {
        "HTTP/1.1 206 Partial Content\r\n" +
            "Content-Type: text/plain; charset=utf-8\r\n" +
            "Accept-Ranges: bytes\r\n" +
            "Content-Range: bytes 0-5/1862\r\n" +
            "Content-Length: 6\r\n\r\n" +
            "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\r\n" +
            "Content-Length: 256\r\n" +
            "Connection: keep-alive, close\r\n" +
            "\r\n",

        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\r\n" +
            "Content-Length: 256\r\n" +
            "Connection: keep-alive\r\n" +
            "Connection: close\r\n" +
            "\r\n",

        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\r\n" +
            "Transfer-Encoding: bogus\r\n" +
            "\r\n" +
            "Body here\n",

        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\n",
    },

    // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
    // With a Content-Length.
    {
        "HTTP/1.0 200 OK\r\n" +
            "Transfer-Encoding: bogus\r\n" +
            "Content-Length: 10\r\n" +
            "\r\n" +
            "Body here\n",

        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\n",
    },

    {
        "HTTP/1.1 200 OK\r\n" +
            "Content-Encoding: gzip\r\n" +
            "Content-Length: 23\r\n" +
            "Connection: keep-alive\r\n" +
            "Keep-Alive: timeout=7200\r\n\r\n" +
            "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
        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,
        },
        "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
    },

    // Issue 19989: two spaces between HTTP version and status.
    {
        "HTTP/1.0  401 Unauthorized\r\n" +
            "Content-type: text/html\r\n" +
            "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
            "Your Authentication failed.\r\n",
        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.\r\n",
    },
}



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{ //首先定義一個結構體,而後再其上調用接口
        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,
}DialerDialContext

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\n", 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
相關文章
相關標籤/搜索