• 小白学标准库之 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。


    芝兰生于空谷,不以无人而不芳。
  • 相关阅读:
    C++ CheckListBox
    TreeView查获节点并选中节点
    创建文件自动重命名
    bat
    Edit显示行号
    FindStringExact
    Extended ComboBox添加图标
    C++ Combobox输入时自动完成
    C++ ComboBox基础
    C++ Code_combobox
  • 原文地址:https://www.cnblogs.com/xingzheanan/p/15649033.html
Copyright © 2020-2023  润新知