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-Type
为application/x-www-form-urlencoded
和application/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-Type
为application/x-www-form-urlencoded
的POST
请求,
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-Type
为application/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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南