概念解釋:html
該圖來自https://www.sohu.com/a/208720509_99960938golang
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
雖然 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(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(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(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通常有這三種:
網頁中form的enctype屬性爲編碼方式,經常使用有兩種:
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(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(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
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 (h Header) Get(key string) string
Get返回鍵對應的第一個值,若是鍵不存在會返回""。如要獲取該鍵對應的值切片,請直接用規範格式的鍵訪問map。
func (h Header) Set(key, value string)
Set添加鍵值對到h,如鍵已存在則會用只有新值一個元素的切片取代舊值切片。
func (h Header) Add(key, value string)
Add添加鍵值對到h,如鍵已存在則會將新的值附加到舊值切片後面。
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 (h Header) Write(w io.Writer) error
Write以有線格式將頭域寫入w。
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 (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
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 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 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的實現。
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(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 (r *Request) AddCookie(c *Cookie)
AddCookie向請求中添加一個cookie。按照RFC 6265 section 5.4的跪地,AddCookie不會添加超過一個Cookie頭字段。這表示全部的cookie都寫在同一行,用分號分隔(cookie內部用逗號分隔屬性)。
func (r *Request) Cookies() []*Cookie
Cookies解析並返回該請求的Cookie頭設置的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(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 (r *Request) ProtoAtLeast(major, minor int) bool
ProtoAtLeast報告該請求使用的HTTP協議版本至少是major.minor。
func (r *Request) UserAgent() string
UserAgent返回請求中的客戶端用戶代理信息(請求的User-Agent頭)。
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 (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 (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 (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 (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 (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 (r *Request) FormValue(key string) string
FormValue返回key爲鍵查詢r.Form字段獲得結果[]string切片的第一個值。POST和PUT主體中的同名參數優先於URL查詢字符串。若是必要,本函數會隱式調用ParseMultipartForm和ParseForm。
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 (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 (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> }
總結:
5>客戶端
1>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(r *bufio.Reader, req *Request) (*Response, error)
ReadResponse從r讀取並返回一個HTTP 回覆。req參數是可選的,指定該回復對應的請求(便是對該請求的回覆)。若是是nil,將假設請求是GET請求。客戶端必須在結束resp.Body的讀取後關閉它。讀取完畢並關閉後,客戶端能夠檢查resp.Trailer字段獲取回覆的trailer的鍵值對。(本函數主要用在客戶端從下層獲取回覆)
func (r *Response) ProtoAtLeast(major, minor int) bool
ProtoAtLeast報告該回復使用的HTTP協議版本至少是major.minor。
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 } } }
返回:
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$
舉例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 (r *Response) Cookies() []*Cookie
Cookies解析並返回該回復中的Set-Cookie頭設置的cookie。
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
tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, DisableCompression: true, } client := &http.Client{Transport: tr} resp, err := client.Get("https://example.com")
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 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 (t *Transport) RoundTrip(req *Request) (resp *Response, err error)
RoundTrip方法實現了RoundTripper接口。
高層次的HTTP客戶端支持(如管理cookie和重定向)請參見Get、Post等函數和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 (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 (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 (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 (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 (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(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方法的包裝。
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(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(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方法的包裝。
6>服務器
1)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
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
實現了Handler接口的對象能夠註冊到HTTP服務端,爲特定的路徑及其子樹提供服務。
ServeHTTP應該將回復的頭域和數據寫入ResponseWriter接口而後返回。返回標誌着該請求已經結束,HTTP服務端能夠轉移向該鏈接上的下一個請求。
⚠️該接口用於開發者可以實現本身的Handler,只要實現ServeHTTP(ResponseWriter, *Request)方法便可
實現了ServeHTTP方法的結構都可以稱之爲handler對象,ServeMux會使用handler(如它的函數func (*ServeMux) Handle)並調用其ServeHTTP方法處理請求並返回響應
type HandlerFunc func(ResponseWriter, *Request)
HandlerFunc type是一個適配器,經過類型轉換讓咱們能夠將普通的函數做爲HTTP處理器使用。若是f是一個具備適當簽名的函數,HandlerFunc(f)經過調用f實現了Handler接口(由於HandlerFunc實現了ServeHTTP函數),其實就是將函數f顯示轉換成HandlerFunc類型
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
ServeHTTP方法會調用f(w, r)
⚠️來自https://www.jianshu.com/p/16210100d43d
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
方法中
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 }
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() *ServeMux
NewServeMux建立並返回一個新的*ServeMux
var DefaultServeMux = NewServeMux()
DefaultServeMux是用於Serve的默認ServeMux。
func (mux *ServeMux) Handle(pattern string, handler Handler)
Handle註冊HTTP處理器handler和對應的模式pattern。若是該模式已經註冊有一個處理器,Handle會panic。
ServeMux會使用handler並調用其ServeHTTP方法處理請求並返回響應
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(pattern string, handler Handler)
Handle註冊HTTP處理器handler和對應的模式pattern(註冊到DefaultServeMux)。若是該模式已經註冊有一個處理器,Handle會panic。ServeMux的文檔解釋了模式的匹配機制。
源碼:
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
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(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時:
2.其次就是調用http.ListenAndServe(":12345", nil):
一個簡單的例子是:
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) }
運行後訪問瀏覽器得:
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/text,獲得:
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後,具體的請求又是怎麼分發的呢:
handler, _ : = mux.Handler(request)
handler.ServeHTTP(w, request)
5)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 (srv *Server) Serve(l net.Listener) error
Serve會接手監聽器l收到的每個鏈接,併爲每個鏈接建立一個新的服務go程。該go程會讀取請求,而後調用srv.Handler回覆請求。
func (srv *Server) ListenAndServe() error
ListenAndServe監聽srv.Addr指定的TCP地址,而且會調用Serve方法接收到的鏈接。若是srv.Addr爲空字符串,會使用":http"。
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 (s *Server) SetKeepAlivesEnabled(v bool)
SetKeepAlivesEnabled控制是否容許HTTP閒置鏈接重用(keep-alive)功能。默認該功能老是被啓用的。只有資源很是緊張的環境或者服務端在關閉進程中時,才應該關閉該功能。
7>Conn
http中的兩核心功能是Conn鏈接 和ServeMux路由器,客戶端的每次請求都會建立一個Conn,這個Conn裏面保存了該次請求的信息,而後再傳遞到對應的handler,該handler中即可以讀取到相應的header信息,保證了每一個請求的獨立性
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 (c ConnState) String() string