1. 程式人生 > 其它 >小白學標準庫之 http

小白學標準庫之 http


1. 前言

標準庫是工具,是手段,是拿來用的。一味的學標準庫就忽視了語言的核心,關鍵。語言層面的特性,記憶體管理,垃圾回收。資料結構,設計模式。這些是程式的核心,要熟練,乃至精通它們,而不是精通標準庫。

標準庫是需要掌握的,瞭解的。可以通過標準庫深挖語言的特性,但不能只學標準庫,學所謂的表面的東西。

基於這個目的,這裡不會深入介紹 http 標準庫,因為它內容太廣,想深亦難。當然不是說不要,是要的,部分內容留作後續研究。

2. net/http 介紹

http 是超文字傳輸協議,是基於 TCP/IP 協議之上的應用層協議。HTTP 協議入門 清晰的介紹了 HTTP 協議。

Go 中實現 http 協議的包是 net/http。實現 http 協議需要 HTTP request 請求和 HTTP response 響應,請求和響應分別對應 Request 和 Response 結構體,如下:

type Request struct {
	Method string

	URL *url.URL

	Proto      string // "HTTP/1.0"
	ProtoMajor int    // 1
	ProtoMinor int    // 0

	Header Header
    ...
}

type Response struct {
	Status     string // e.g. "200 OK"
	StatusCode int    // e.g. 200
	Proto      string // e.g. "HTTP/1.0"
	ProtoMajor int    // e.g. 1
	ProtoMinor int    // e.g. 0

	Header Header
	Body io.ReadCloser
    ...
}

抓住了結構體就抓住了例項對應的屬性和方法。

這裡構造 server 端實現 http response 響應:

package main

import (
    "io"
    "log"
    "net/http"
)

func main() {
    helloHandler := func(w http.ResponseWriter, req *http.Request) {
        sr := "hello, world with request " + req.Method
        io.WriteString(w, sr)
    }

    http.HandleFunc("/hello", helloHandler)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

其中:

  • ListenAndServe 呼叫 net 包的 Listen 方法實現 tcp 地址 (ip + port) 的偵聽,Go 標準庫 net 介紹了 net 包相關內容。

  • http.ResponseWriter 是介面,它實現了 Header,Write,WriteHeader 方法向響應新增 header 和 body 內容。如定義當呼叫 /hello api 時返回 404 狀態碼,可呼叫 WriterHeader 方法如下:

    w.WriteHeader(404)
    sr := "hello, world with request " + req.Method
    io.WriteString(w, sr)
    

    注意狀態碼不能重複寫,如將 WriteHeader(404) 置於 WriteString 後會報錯 http: superfluous response.WriteHeader call from

  • http.Request 是客戶端發來的請求,在 Handler 中可使用該請求組合生成響應資訊。這裡將返回字元和請求方法結合作為響應發給客戶端。

繼續構造客戶端實現 HTTP request 請求:

func main() {
    response, err := http.Get("http://127.0.0.1:8082/hello")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    defer response.Body.Close()
    body, _ := ioutil.ReadAll(response.Body)
    fmt.Println(string(body))
    fmt.Println(response)
    fmt.Println(*response.Request)
}

執行 server 和 client:

// run server
[chunqiu@test http]$ go run server/server.go

// run client
[chunqiu@test http]$ go run main.go
hello, world with request GET

&{404 Not Found 404 HTTP/1.1 1 1 map[Content-Length:[29] Content-Type:[text/plain; charset=utf-8] Date:[Mon, 06 Dec 2021 02:18:11 GMT]] 0xc00009c040 29 [] false false map[] 0xc000140000 <nil>}

{GET http://127.0.0.1:8082/hello HTTP/1.1 1 1 map[] <nil> <nil> 0 [] false 127.0.0.1:8082 map[] map[] <nil> map[]   <nil> <nil> <nil> 0xc0000160a0}

從列印返回值可以看到:

  • server response 為前面寫入的狀態碼 404;server 和 client 通訊使用的 HTTP 協議為 HTTP/1.1;response 的 Header 頭資訊包括 Content-Length,Content-Type 和 Date 資訊,其中 Content-Length 表示文字,或其它型別的“長度”,如對於 zip 型別,返回的 Content-Length 是 zip 的大小:Length: 3116622545 (2.9G) [application/zip]
  • server 的 response 也包括了 request 的資訊,request 是 response 的屬性,可通過 response.Request 呼叫 Request 資訊。

不僅是返回值頭資訊,在 Request 也可以定義頭資訊,如 Content-Type 定義接收型別,Accept 定義接收資料格式等。

3. 程式示例

看一段程式碼:

req, err := http.NewRequest(method, url, data)
if err != nil {
    return nil, false, err
}

req.Header.Add("Content-Type", "application/json; charset=utf-8")
resp, err := client.Do(req)
if err != nil {
    return nil, false, err
}
defer resp.Body.Close()
out, err := ioutil.ReadAll(resp.Body)
if err != nil {
    return nil, false, err
}

這裡使用了 NewRequest 函式建立 req 例項,通過 client 呼叫 req 的 url 和相應的方法,並且在 req 的頭資訊新增 Content-Type 宣告請求的 body 資訊。有一點需要注意的是 ioutil 包的 ReadAll 方法,它的函式原型為:

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
//
// As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error)

相關的描述資訊見原始碼註釋。其中 ReadAll 函式引數為 io.Reader,它是一個實現了 Read 方法的介面。而 resp.Body 是 io.ReadCloser 介面的例項,io.ReadCloser 實現了 Reader 和 Closer 方法。看到了嗎,這裡發生了介面的賦值,關於介面設計與實現及介面賦值部分留作後續研究。

還有一部分內容有待後續研究的是:http 是基於 TCP/IP 之上的應用層協議,它的實現不需要關心底層 TCP/IP 的實現,這是好處又是不好的地方,底層做了什麼, TCP/IP 怎麼處理 http 包,從 client 到 server 經過了什麼,具體流程是什麼樣的。這部分是不明確的,如果不掌握這部分內容 http 傳輸出現問題很難 debug。


芝蘭生於空谷,不以無人而不芳。