Golang HTTP编程及源码解析-请求/响应处理

1. HTTP协议

HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,基于TCP/IP通信协议来传递数据(HTML 文件、图片文件、查询结果等)。

  • HTTP 是无连接的:无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。
  • HTTP是独立于媒体的:只要客户端和服务器都知道如何处理数据内容,任何类型的数据都可以通过HTTP发送。客户端和服务器都需要使用适当的 MIME 类型 指定内容类型。
  • HTTP是无状态的:HTTP 协议是无状态协议,无状态是指协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大,另一方面,在服务器不需要先前信息时它的应答就较快。

 

2. HTTP请求报文格式

HTTP请求报文包含以下三个部分。

  • 请求行
  • 请求头
  • 请求体

基本结构如下图所示

 

3. HTTP响应报文格式

与请求报文结构类型,HTTP响应报文包含以下三个部分

  • 状态行
  • 响应头
  • 响应体

基本结构如下图所示。

下面通过代码结合实例来看Golang的HTTP的请求处理和响应处理。

 

4. Golang HTTP请求处理

4.1 请求行

func requestLineHandler(w http.ResponseWriter, r *http.Request) {
    // 请求行
    fmt.Fprintf(w, "\nresp method:%v url:%v Proto:%v\n", r.Method, r.URL, r.Proto)
    // URL参数获取
    fmt.Fprintf(w, "resp URL查询参数:%v\n", r.URL.Query())
    w.WriteHeader(http.StatusOK)
}

func main() {
    // 1. 新建路由解码器
    h := http.NewServeMux()
    // 2. 路由注册
    h.HandleFunc("/reqline", requestLineHandler)
    // 3. 服务启动 阻塞监听
    http.ListenAndServe(":8000", h)
}

通过curl发起POST请求,通过-v打印请求报文和响应报文。

$ curl -v -X POST  http://localhost:8000/reqline?name=jack
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /reqline?name=jack HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.86.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sat, 04 Mar 2023 12:13:25 GMT
< Content-Length: 94
< Content-Type: text/plain; charset=utf-8
<

resp method:POST url:/reqline?name=jack Proto:HTTP/1.1
resp URL查询参数:map[name:[jack]]

 

4.2 请求头

func requestHeaderHandler(w http.ResponseWriter, r *http.Request) {
    // 头部
    fmt.Fprintf(w, "header:\n")
    for key, val := range r.Header {
        fmt.Fprintf(w, "%v:%v\n", key, val)
    }
    fmt.Fprintf(w, "Get(\"Content-Type\"):%v\n", r.Header.Get("Content-Type"))
}
func main() {
    // 1. 新建路由解码器
    h := http.NewServeMux()
    // 2. 路由注册
    h.HandleFunc("/reqheader", requestHeaderHandler)
    // 3. 服务启动 阻塞监听
    http.ListenAndServe(":8000", h)
}

通过curl发起POST请求

可以看出curl的请求报文和HTTP服务端获取到的请求Header都包含version:1.1.1

$ curl -v -d "age=18" -H "Accept-Language:en-US" -H "version:1.1.1" http://localhost:8000/reqheader
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /reqheader HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.86.0
> Accept: */*
> Accept-Language:en-US
> version:1.1.1
> Content-Length: 6
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sat, 04 Mar 2023 12:21:13 GMT
< Content-Length: 208
< Content-Type: text/plain; charset=utf-8
<
header:
Version:[1.1.1]
Content-Length:[6]
Content-Type:[application/x-www-form-urlencoded]
User-Agent:[curl/7.86.0]
Accept:[*/*]
Accept-Language:[en-US]
Get("Content-Type"):application/x-www-form-urlencoded

 

4.3 请求体

以下代码大致说明了Content-Typeapplication/x-www-form-urlencodedapplication/json的处理方法

func requestBodyHandler(w http.ResponseWriter, r *http.Request) {
    contentType := r.Header.Get("Content-Type")
    fmt.Fprintf(w, "Content-Type:%v\n", contentType)
    // URL参数获取
    fmt.Fprintf(w, "URL查询参数:%v\n", r.URL.Query())
    // 解析 Content-Type为multipart/form-data的请求体
    switch contentType {
    case "application/x-www-form-urlencoded": // 表单默认的提交数据的格式
        // 解析 URL查询参数 和 POST、PUT、PATCH的请求体参数
        // 并将结果放入r.Form, 且POST、PUT、PATCH的请求体参数的优先级比URL查询参数高
        // r.PostForm只存放POST、PUT、PATCH的请求体参数
        r.ParseForm()
        fmt.Fprintf(w, "PostForm:%v\n", r.PostForm)
        fmt.Fprintf(w, "Form:%v\n", r.Form)
    case "application/json": // json数据格式
        json, _ := ioutil.ReadAll(r.Body)
        fmt.Fprintf(w, "Json:%v", string(json))
    default:
        fmt.Fprintf(w, "unkown Content-Type")
    }
}

通过curl发起Content-Typeapplication/x-www-form-urlencodedPOST请求,

URL查询参数和请求体都包含name=,从Form:map[age:[18] name:[juli jack]]可以看出POST的请求体参数优先级更高。

$ curl -v http://localhost:8000/reqbody?name=jack -d "age=18" -d "name=juli"
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /reqbody?name=jack HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.86.0
> Accept: */*
> Content-Length: 16
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sat, 04 Mar 2023 12:35:15 GMT
< Content-Length: 151
< Content-Type: text/plain; charset=utf-8
<
Content-Type:application/x-www-form-urlencoded
URL查询参数:map[name:[jack]]
PostForm:map[age:[18] name:[juli]]
Form:map[age:[18] name:[juli jack]]

通过curl发起Content-Typeapplication/json的请求。

$ curl -v http://localhost:8000/reqbody -H "Content-Type: application/json" -d "{"name":"jack","age":"18"}"
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> POST /reqbody HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.86.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 18
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sat, 04 Mar 2023 12:38:34 GMT
< Content-Length: 75
< Content-Type: text/plain; charset=utf-8
<
Content-Type:application/json
URL查询参数:map[]
Json:{name:jack,age:18}

 

5. Golang HTTP响应处理

func respHandler(w http.ResponseWriter, r *http.Request) {
	// 设置响应头
	w.Header().Set("name", "jack")
	// 设置cookie
	ck := &http.Cookie{
		Name:     "testCookie",
		Value:    "cookieval",
		Path:     "/",
		HttpOnly: true,
	}
	http.SetCookie(w, ck)
	// 设置响应体
	w.Write([]byte("hello i'm jack"))
	// 设置响应状态码
	w.WriteHeader(http.StatusOK)
}

func main() {
	// 1. 新建路由解码器
	h := http.NewServeMux()
	// 2. 路由注册
	h.HandleFunc("/resp", respHandler)
	// 3. 服务启动 阻塞监听
	http.ListenAndServe(":8000", h)
}

值得注意的是,设置Header、设置Cookie必须先于设置响应体,否则不会生效

$ curl -v http://localhost:8000/resp
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /resp HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.86.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Name: jack
< Set-Cookie: testCookie=cookieval; Path=/; HttpOnly
< Date: Sat, 04 Mar 2023 13:15:15 GMT
< Content-Length: 14
< Content-Type: text/plain; charset=utf-8
<
hello i'm jack

 

posted @ 2023-03-04 22:13  bulldozer  阅读(984)  评论(0编辑  收藏  举报