Go Http包 使用簡介

來源:互聯網
上載者:User

標籤:plain   urlencode   bytes   常用   sprintf   length   pen   操作   資訊   

請求的結構

HTTP 的互動以請求和響應的接聽模式。Go 的請求我們早就見過了,handler 函數的第二個參數 http.Requests。其結構為:

type Request struct {    Method string    URL *url.URL    Proto      string // "HTTP/1.0"    ProtoMajor int    // 1    ProtoMinor int    // 0    Header Header    Body io.ReadCloser    ContentLength int64    TransferEncoding []string    Close bool    Host string    Form url.Values    PostForm url.Values    MultipartForm *multipart.Form  ....    ctx context.Context}

從 request 結構可以看到,http 請求的基本資料都囊括了。對於請求而言,主要關注一下請求的 URL,Method,Header,Body 這些結構。

URL

HTTP 的 url 請求格式為 scheme://[[email protected]]host/path[?query][#fragment], Go 的提供了一個 URL 結構,用來映射 HTTP 的請求 URL。

type URL struct {  Scheme   string  Opaque   string  User     *Userinfo  Host     string  Path     string  RawQuery string  Fragment string}

URL 的格式比較明確,其實更好的名詞應該是 URI,統一資源定位。url 中比較重要的是查詢字串 query。通常作為 get 請求的參數。query 是一些使用 & 符號分割的 key1=value1&key2=value2 索引值對,由於 url 編碼是 ASSIC 碼,因此 query 需要進行 urlencode。Go 可以通過 request.URI.RawQuery 讀取 query

func indexHandler(w http.ResponseWriter, r *http.Request) {    info := fmt.Sprintln("URL", r.URL, "HOST", r.Host, "Method", r.Method, "RequestURL", r.RequestURI, "RawQuery", r.URL.RawQuery)    fmt.Fprintln(w, info)}
$  curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d ‘name=vanyar&age=27‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"URL /?lang=zh&version=1.1.0 HOST 127.0.0.1:8000 Method POST RequestURL /?lang=zh&version=1.1.0 RawQuery lang=zh&version=1.1.0
header

header 也是 HTTP 中重要的組成部分。Request 結構中就有 Header 結構,Header 本質上是一個 map(map[string][]string)。將 http 協議的 header的 key-value 進行映射成一個圖:

    Host: example.com    accept-encoding: gzip, deflate    Accept-Language: en-us    fOO: Bar    foo: two    Header = map[string][]string{        "Accept-Encoding": {"gzip, deflate"},        "Accept-Language": {"en-us"},        "Foo": {"Bar", "two"},    }

header 中的欄位包含了很多通訊的設定,很多時候請求都需要指定 Content-Type。

func indexHandler(w http.ResponseWriter, r *http.Request) {    info := fmt.Sprintln(r.Header.Get("Content-Type"))    fmt.Fprintln(w, info)}
$  curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d ‘name=vanyar&age=27‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"application/x-www-form-urlencoded

Golng 提供了不少列印函數,基本上分為三類三種。即 Print Println 和 Printf。
Print 比較簡單,列印輸出到標準輸出資料流,Println 則也一樣不同在於多列印一個分行符號。至於 Printf 則是列印格式化字串,三個方法都返回列印的 bytes 數。Sprint,Sprinln 和 Sprintf 則返回列印的字串,不會輸出到標準流中。Fprint,Fprintf 和 Fprinln 則把輸出的結果列印輸出到 io.Writer 介面中,http 中則是 http.ReponseWriter 這個對象中,返回列印的 bytes 數。

Body

http 中資料通訊,主要通過 body 傳輸。Go 把 body 封裝成 Request 的 Body,它是一個 ReadCloser 介面。介面方法 Reader 也是一個介面,後者有一個Read(p []byte) (n int, err error)方法,因此 body 可以通過讀取 byte 數組擷取請求的資料。

func indexHandler(w http.ResponseWriter, r *http.Request) {    info := fmt.Sprintln(r.Header.Get("Content-Type"))    len := r.ContentLength    body := make([]byte, len)    r.Body.Read(body)    fmt.Fprintln(w, info, string(body))}
$  curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d ‘name=vanyar&age=27‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"application/x-www-form-urlencoded name=vanyar&age=27

可見,當請求的 content-type 為 application/x-www-form-urlencoded, body 也是和 query 一樣的格式,key-value 的索引值對。換成 json 的請求方式則如下:

$  curl -X POST -H "Content-Type: application/json" -d ‘{name: "vanyar", age: 27}‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"application/json {name: "vanyar", age: 27}

multipart/form-data 的格式用來上傳圖片,請求的 body 如下:

#  curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=vanyar" -F "age=27" "http://127.0.0.1:8000?lang=zh&version=1.1.0"multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW; boundary=------------------------d07972c7800e4c23 --------------------------d07972c7800e4c23Content-Disposition: form-data; name="name"vanyar--------------------------d07972c7800e4c23Content-Disposition: form-data; name="age"27--------------------------d07972c7800e4c23--
表單

解析 body 可以讀取用戶端請求的資料。而這個資料是無論是索引值對還是 form-data 資料,都比較原始。直接讀取解析還是挺麻煩的。這些 body 資料通常也是表單提供。因此 Go 提供處理這些表單資料的方法。

Form

Go 提供了 ParseForm 方法用來解析表單提供的資料,即 content-type 為 x-www-form-urlencode 的資料。

func indexHandler(w http.ResponseWriter, r *http.Request) {    contentType := fmt.Sprintln(r.Header.Get("Content-Type"))    r.ParseForm()    fromData := fmt.Sprintf("%#v", r.Form)    fmt.Fprintf(w, contentType, fromData)}
$  curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d ‘name=vanyar&age=27‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"application/x-www-form-urlencoded%!(EXTRA string=url.Values{"name":[]string{"vanyar"}, "age":[]string{"27"}, "lang":[]string{"zh"}, "version":[]string{"1.1.0"}})%

用來讀取資料的結構和方法大致有下面幾個:

    fmt.Println(r.Form["lang"])    fmt.Println(r.PostForm["lang"])    fmt.Println(r.FormValue("lang"))    fmt.Println(r.PostFormValue("lang"))

其中 r.Form 和 r.PostForm 必須在調用 ParseForm 之後,才會有資料,否則則是空數組。而 r.FormValue 和 r.PostFormValue("lang") 無需 ParseForm 的調用就能讀取資料。

此外 r.Form 和 r.PostForm 都是數組結構,對於 body 和 url 都存在的同名參數,r.Form 會有兩個值,即 ["en", "zh"],而帶 POST 首碼的數組和方法,都只能讀取 body 的資料。

$  curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d ‘name=vanyar&age=27&lang=en‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"application/x-www-form-urlencoded%!(EXTRA string=url.Values{"version":[]string{"1.1.0"}, "name":[]string{"vanyar"}, "age":[]string{"27"}, "lang":[]string{"en", "zh"}})%

此時可以看到,lang 參數不僅 url 的 query 提供了,post 的 body 也提供了,Go 預設以 body 的資料優先,兩者的資料都有,並不會覆蓋。

如果不想讀取 url 的參數,調用 PostForm 或 PostFormValue 讀取欄位的值即可。

r.PostForm["lang"][0]r.PostFormValue["lang"]

對於 form-data 的格式的資料,ParseForm 的方法只會解析 url 中的參數,並不會解析 body 中的參數。

$  curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=vanyar" -F "age=27" -F "lang=en" "http://127.0.0.1:8000?lang=zh&version=1.1.0"multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW; boundary=------------------------5f87d5bfa764488d%!(EXTRA string=url.Values{"lang":[]string{"zh"}, "version":[]string{"1.1.0"}})%

因此當請求的 content-type 為 form-data 的時候,ParseFrom 則需要改成 MutilpartFrom,否則 r.From 是讀取不到 body 的內容,只能讀取到 query string 中的內容。

MutilpartFrom

ParseMutilpartFrom 方法需要提供一個讀取資料長度的參數,然後使用同樣的方法讀取表單資料,MutilpartFrom 只會讀取 body 的資料,不會讀取 url 的 query 資料。

func indexHandler(w http.ResponseWriter, r *http.Request) {    r.ParseMultipartForm(1024)    fmt.Println(r.Form["lang"])    fmt.Println(r.PostForm["lang"])    fmt.Println(r.FormValue("lang"))    fmt.Println(r.PostFormValue("lang"))    fmt.Println(r.MultipartForm.Value["lang"])    fmt.Fprintln(w, r.MultipartForm.Value)}

可以看到請求之後返回 map[name:[vanyar] age:[27] lang:[en]]。即 r.MultipartForm.Value 並沒有 url 中的參數。

總結一下,讀取 urlencode 的編碼方式,只需要 ParseForm 即可,讀取 form-data 編碼需要使用 ParseMultipartForm 方法。如果參數中既有 url,又有 body,From 和 FromValue 方法都能讀取。而帶Post 首碼的方法,只能讀取 body 的資料內容。其中 MultipartForm 的資料通過 r.MultipartForm.Value 訪問得到。

檔案上傳

form-data 格式用得最多方式就是在圖片上傳的時候。r.MultipartForm.Value 是 post 的 body 欄位資料,r.MultipartForm.File 則包含了圖片資料:

func indexHandler(w http.ResponseWriter, r *http.Request) {    r.ParseMultipartForm(1024)    fileHeader := r.MultipartForm.File["file"][0]    fmt.Println(fileHeader)    file, err := fileHeader.Open()    if err == nil{        data, err := ioutil.ReadAll(file)        if err == nil{            fmt.Println(len(data))            fmt.Fprintln(w, string(data))        }    }    fmt.Println(err)}

發出請求之後,可以看見返回了圖片。當然,Go 提供了更好的工具函數 r.FormFile,直接讀取上傳檔案資料。而不需要再使用 ParseMultipartForm 方法。

    file, _, err := r.FormFile("file")    if err == nil{        data, err := ioutil.ReadAll(file)        if err == nil{            fmt.Println(len(data))            fmt.Fprintln(w, string(data))        }    }    fmt.Println(err)

這種情況只適用於出了檔案欄位沒有其他欄位的時候,如果仍然需要讀取 lang 參數,還是需要加上 ParseMultipartForm 調用的。讀取到了上傳檔案,接下來就是很普通的寫檔案的 io 操作了。

JSON

現在流行前後端分離,用戶端興起了一些架構,angular,vue,react 等提交的資料,通常習慣為 json 的格式。對於 json 格式,body 就是原生的 json 字串。也就是 Go 解密 json 為 Go 的資料結構。

type Person struct {    Name string    Age int}func indexHandler(w http.ResponseWriter, r *http.Request) {    decode := json.NewDecoder(r.Body)    var p Person    err := decode.Decode(&p)    if err != nil{        log.Fatalln(err)    }    info := fmt.Sprintf("%T\n%#v\n", p, p)    fmt.Fprintln(w, info)}
$  curl -X POST -H "Content-Type: application/json"  -d ‘{"name": "vanyar", "age": 27 }‘ "http://127.0.0.1:8000?lang=zh&version=1.1.0"main.Personmain.Person{Name:"vanyar", Age:27}

更多關於 json 的細節,以後再做討論。訪問官網文檔擷取更多的資訊。

Response

請求和響應是 http 的孿生兄弟,不僅它們的報文格式類似,相關的處理和構造也類似。Go 構造響應的結構是 ResponseWriter 介面。

type ResponseWriter interface {    Header() Header    Write([]byte) (int, error)    WriteHeader(int)}

裡面的方法也很簡單,Header 方法返回一個 header 的 map 結構。WriteHeader 則會返迴響應的狀態代碼。Write 返回給用戶端的資料。

我們已經使用了 fmt.Fprintln 方法,直接向 w 寫入響應的資料。也可以調用 Write 方法返回的字元。

func indexHandler(w http.ResponseWriter, r *http.Request) {    str := `<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>`    w.Write([]byte(str))}                                                                           
$  curl -i http://127.0.0.1:8000/HTTP/1.1 200 OKDate: Wed, 07 Dec 2016 09:13:04 GMTContent-Length: 95Content-Type: text/html; charset=utf-8<html><head><title>Go Web Programming</title></head><body><h1>Hello World</h1></body></html>%   

Go 根據返回的字元,自動修改成了 text/html 的 Content-Type 格式。返回資料自訂通常需要修改 header 相關資訊。

func indexHandler(w http.ResponseWriter, r *http.Request) {    w.WriteHeader(501)    fmt.Fprintln(w, "No such service, try next door")}
$ curl -i http://127.0.0.1:8000/HTTP/1.1 501 Not ImplementedDate: Wed, 07 Dec 2016 09:14:58 GMTContent-Length: 31Content-Type: text/plain; charset=utf-8No such service, try next door
重新導向

重新導向的功能可以更加設定 header 的 location 和 http 狀態代碼實現。

func indexHandler(w http.ResponseWriter, r *http.Request) {    w.Header().Set("Location", "https://google.com")    w.WriteHeader(302)}
$  curl -i http://127.0.0.1:8000/HTTP/1.1 302 FoundLocation: https://google.comDate: Wed, 07 Dec 2016 09:20:19 GMTContent-Length: 31Content-Type: text/plain; charset=utf-8

重新導向是常用的功能,因此 Go 也提供了工具方法,http.Redirect(w, r, "https://google.com", http.StatusFound)。

與請求的 Header 結構一樣,w.Header 也有幾個方法用來設定 headers

func (h Header) Add(key, value string) {    textproto.MIMEHeader(h).Add(key, value)}func (h Header) Set(key, value string) {    textproto.MIMEHeader(h).Set(key, value)}func (h MIMEHeader) Add(key, value string) {    key = CanonicalMIMEHeaderKey(key)    h[key] = append(h[key], value)}func (h MIMEHeader) Set(key, value string) {    h[CanonicalMIMEHeaderKey(key)] = []string{value}}

Set和Add方法都可以設定 headers,對於已經存在的 key,Add 會追加一個值 value 的數組中,,set 則是直接替換 value 的值。即 append 和賦值的差別。

Json

請求發送的資料可以是 JSON,同樣響應的資料也可以是 json。restful 風格的 api 也是返回 json 格式的資料。對於請求是解碼 json 字串,響應則是編碼 json 字串,Go 提供了標準庫 encoding/json

type Post struct {    User string    Threads []string}func indexHandler(w http.ResponseWriter, r *http.Request) {    w.Header().Set("Content-Type", "application/json")    post := &Post{        User: "vanyar",        Threads: []string{"first", "second", "third"},    }    json, _ := json.Marshal(post)    w.Write(json)}
$  curl -i http://127.0.0.1:8000/HTTP/1.1 200 OKContent-Type: application/jsonDate: Thu, 08 Dec 2016 06:45:17 GMTContent-Length: 54{"User":"vanyar","Threads":["first","second","third"]}

當然,更多的 json 處理細節稍後再做介紹。

總結

對於 web 應用程式,處理請求,返迴響應是基本的內容。Golang 很好的封裝了 Request 和 ReponseWriter 給開發人員。無論是請求還是響應,都是針對 url,header 和 body 相關資料的處理。也是 http 協議的基本內容。

除了 body 的資料處理,有時候也需要處理 header 中的資料,一個常見的例子就是處理 cookie。這將會在 cookie 的話題中討論。

Go Http包 使用簡介

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.