Go Web编程--深入学习解析HTTP请求
之前这个系列的文章一直在讲用 Go
语言怎么编写HTTP服务器来提供服务,如何给服务器配置路由来匹配请求到对应的处理程序,如何添加中间件把一些通用的处理任务从具体的Handler中解耦出来,以及如何更规范地在项目中应用数据库。不过一直漏掉了一个环节是服务器接收到请求后如何解析请求拿到想要的数据, Go
语言使用 net/http
包中的 Request
结构体对象来表示 HTTP
请求,通过 Request
结构对象上定义的方法和数据字段,应用程序能够便捷地访问和设置 HTTP
请求中的数据。
一般服务端解析请求的需求有如下几种
HTTP请求头中的字段值
URL 查询字符串中的字段值
请求体中的
Form
表单数据请求体中的
JSON
格式数据读取客户端的上传的文件
今天这篇文章我们就按照这几种常见的服务端对 HTTP
请求的操作来说一下服务器应用程序如何通过 Request
对象解析请求头和请求体。
Request 结构定义
在说具体操作的使用方法之前我们先来看看 net/http
包中 Request
结构体的定义,了解一下 Request
拥有什么样的数据结构。 Request
结构在源码中的定义如下。
type Request struct {Method stringURL *url.URLProto string // "HTTP/1.0"ProtoMajor int // 1ProtoMinor int // 0Header HeaderBody io.ReadCloserGetBody func() (io.ReadCloser, error)ContentLength int64TransferEncoding []stringClose boolHost stringForm url.ValuesPostForm url.ValuesMultipartForm *multipart.FormTrailer HeaderRemoteAddr stringRequestURI stringTLS *tls.ConnectionStateCancel <-chan struct{}Response *Responsectx context.Context
}
我们快速地了解一下每个字段大致的含义,了解了每个字段的含义在不同的应用场景下需要读取访问 HTTP
请求的不同部分时就能够有的放矢了。
Method
指定HTTP方法(GET,POST,PUT等)。
URL
URL指定要请求的URI(对于服务器请求)或要访问的URL(用于客户请求)。它是一个表示 URL
的类型 url.URL
的指针, url.URL
的结构定义如下:
type URL struct {Scheme stringOpaque stringUser *UseriHost stringPath stringRawPath stringForceQuery bool RawQuery stringFragment string
}
Proto
Proto
, ProtoMajor
, ProtoMinor
三个字段表示传入服务器请求的协议版本。对于客户请求,这些字段将被忽略。 HTTP
客户端代码始终使用 HTTP/1.1
或 HTTP/2
。
Header
Header
包含服务端收到或者由客户端发送的 HTTP
请求头,该字段是一个 http.Header
类型的指针, http.Header
类型的声明如下:
type Header map[string][]string
是 map[string][]string
类型的别名, http.Header
类型实现了 GET
, SET
, Add
等方法用于存取请求头。如果服务端收到带有如下请求头的请求:
Host: example.com
accept-encoding: gzip, deflate
Accept-Language: en-us
fOO: Bar
foo: two
那么 Header
的值为:
Header = map[string][]string{"Accept-Encoding": {"gzip, deflate"},"Accept-Language": {"en-us"},"Foo": {"Bar", "two"},
}
对于传入的请求, Host
标头被提升为 Request.Host
字段,并将其从 Header
对象中删除。 HTTP
定义头部的名称是不区分大小写的。 Go
使用 CanonicalHeaderKey
实现的请求解析器使得请求头名称第一个字母以及跟随在短横线后的第一个字母大写其他都为小写形式,比如: Content-Length
。对于客户端请求,某些标头,例如 Content-Length
和 Connection
会在需要时自动写入,并且标头中的值可能会被忽略。
Body
这个字段的类型是 io.ReadCloser
, Body
是请求的主体。对于客户端发出的请求, nil
主体表示该请求没有 Body
,例如 GET
请求。 HTTP
客户端的传输会负责调用 Close
方法。对于服务器接收的请求,请求主体始终为非 nil
,但如果请求没有主体,则将立即返回 EOF
。服务器将自动关闭请求主体。服务器端的处理程序不需要关心此操作。
GetBody
客户端使用的方法的类型,其声明为:
GetBody func() (io.ReadCloser, error)
ContentLength
ContentLength
记录请求关联内容的长度。值-1表示长度未知。值>=0表示从 Body
中读取到的字节数。对于客户请求,值为0且非 nil
的 Body
也会被视为长度未知。
TransferEncoding
TransferEncoding
为字符串切片,其中会列出从最外层到最内层的传输编码, TransferEncoding
通常可以忽略;在发送和接收请求时,分块编码会在需要时自动被添加或者删除。
Close
Close
表示在服务端回复请求或者客户端读取到响应后是否要关闭连接。对于服务器请求,HTTP服务器会自动处理 并且处理程序不需要此字段。对于客户请求,设置此字段为 true
可防止重复使用到相同主机的请求之间的TCP连接,就像已设置 Transport.DisableKeepAlives
一样。
Host
对于服务器请求, Host
指定URL所在的主机,为防止DNS重新绑定攻击,服务器处理程序应验证 Host
标头具有的值。 http
库中的 ServeMux
(复用器)支持注册到特定 Host
的模式,从而保护其注册的处理程序。对于客户端请求, Host
可以用来选择性地覆盖请求头中的 Host
,如果不设置, Request.Write
使用 URL.Host
来设置请求头中的 Host
。
Form
Form
包含已解析的表单数据,包括 URL
字段的查询参数以及 PATCH
, POST
或 PUT
表单数据。此字段仅在调用 Request.ParseForm
之后可用。 HTTP
客户端会忽略 Form
并改用 Body
。 Form
字段的类型是 url.Values
类型的指针。 url.Values
类型的声明如下:
type Values map[string][]string
也是 map[string][]string
类型的别名。 url.Values
类型实现了 GET
, SET
, Add
, Del
等方法用于存取表单数据。
PostForm
PostForm
类型与 Form
字段一样,包含来自 PATCH
, POST
的已解析表单数据或PUT主体参数。此字段仅在调用 ParseForm
之后可用。 HTTP
客户端会忽略 PostForm
并改用 Body
。
MultipartForm
MultipartForm
是已解析的多部分表单数据,包括文件上传。仅在调用 Request.ParseMultipartForm
之后,此字段才可用。 HTTP
客户端会忽略 MultipartForm
并改用 Body
。该字段的类型是 *multipart.Form
。
RemoteAddr
RemoteAddr
允许 HTTP
服务器和其他软件记录发送请求的网络地址,通常用于记录。 net/http
包中的HTTP服务器在调用处理程序之前将 RemoteAddr
设置为“ IP:端口”, HTTP客户端会忽略此字段。
RequestURI
RequestURI
是未修改的 request-target
客户端发送的请求行(RFC 7230,第3.1.1节)。在服务器端,通常应改用URL字段。在HTTP客户端请求中设置此字段是错误的。
Response
Response
字段类型为 *Response
,它指定了导致此请求被创建的重定向响应,此字段仅在客户端发生重定向时被填充。
ctx
ctx
是客户端上下文或服务器上下文。它应该只通过使用 WithContext
复制整个 Request
进行修改。这个字段未导出以防止人们错误使用 Context
并更改同一请求的调用方所拥有的上下文。
读取请求头
上面分析了 Go
将 HTTP
请求头存储在 Request
结构体对象的 Header
字段里, Header
字段实质上是一个 Map
,请求头的名称为Map key
, MapValue
的类型为字符串切片,有的请求头像 Accept
会有多个值,在切片中就对应多个元素。
Header
类型的 Get
方法可以获取请求头的第一个值,
func exampleHandler(w http.ResponseWriter, r *http.Request) {ua := r.Header.Get("User-Agent")...
}
或者是获取值时直接通过 key
获取对应的切片值就好,比如将上面的改为:
ua := r.Header["User-Agent"]
下面我们写个遍历请求头信息的示例程序,同时也会通上面介绍的 Request
结构中定义的 Method
, URL
, Host
, RemoteAddr
等字段把请求的通用信息打印出来。在我们一直使用的 http_demo
项目中增加一个 DisplayHeadersHandler
,其源码如下:
package handler
import ("fmt""net/http"
)
func DisplayHeadersHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Method: %s URL: %s Protocol: %s \n", r.Method, r.URL, r.Proto)// 遍历所有请求头for k, v := range r.Header {fmt.Fprintf(w, "Header field %q, Value %q\n", k, v)}fmt.Fprintf(w, "Host = %q\n", r.Host)fmt.Fprintf(w, "RemoteAddr= %q\n", r.RemoteAddr)// 通过 Key 获取指定请求头的值fmt.Fprintf(w, "\n\nFinding value of \"Accept\" %q", r.Header["Accept"])
}
将其处理程序绑定到 /index/display_headers
路由上:
indexRouter.HandleFunc("/display_headers", handler.DisplayHeadersHandler)
然后启动项目,打开浏览器访问:
http://localhost:8000/index/display_headers
可以看到如下输出:
http_demo
项目中已经添加了本文中所有示例的源码,公众号内回复gohttp06
可以获取源码的下载链接。
获取URL参数值
GET
请求中的 URL
查询字符串中的参数可以通过 url.Query()
,我们来看一下啊 url.Query()
函数的源码:
func (u *URL) Query() Values {v, _ := ParseQuery(u.RawQuery)return v
}
它通过 ParseQuery
函数解析 URL
参数然后返回一个 url.Values
类型的值。 url.Values
类型上面我们已经介绍过了是 map[string][]string
类型的别名,实现了 GET
, SET
, Add
, Del
等方法用于存取数据。
所以我们可以使用 r.URL.Query().Get("ParamName")
获取参数值,也可以使用 r.URL.Query()["ParamName"]
。两者的区别是 Get
只返回切片中的第一个值,如果参数对应多个值时(比如复选框表单那种请求就是一个 name
对应多个值),记住要使用第二种方式。
我们通过运行一个示例程序 display_url_params.go
来看一下两种获取 URL
参数的区别
package handler
import (
"fmt"
"net/http"
)
func DisplayUrlParamsHandler(w http.ResponseWriter, r *http.Request) {for k, v := range r.URL.Query() {fmt.Fprintf(w, "ParamName %q, Value %q\n", k, v)fmt.Fprintf(w, "ParamName %q, Get Value %q\n", k, r.URL.Query().Get(k))}
}
将其处理程序绑定到 /index/display_url_params
路由上:
indexRouter.HandleFunc("/display_url_params", handler.DisplayUrlParamsHandler)
打开浏览器访问
http://localhost:8000/index/display_url_params?a=b&c=d&a=c
浏览器会输出:
ParamName "a", Value ["b" "c"]
ParamName "a", Get Value "b"
ParamName "c", Value ["d"]
ParamName "c", Get Value "d"
我们为参数 a
传递了两个参数值,可以看到通过 url.Query.Get()
只能读取到第一个参数值。
获取表单中的参数值
Request
结构的 Form
字段包含已解析的表单数据,包括 URL
字段的查询参数以及 PATCH
, POST
或 PUT
表单数据。此字段仅在调用 Request.ParseForm
之后可用。不过 Request
对象提供一个 FormValue
方法来获取指定名称的表单数据, FormValue
方法会根据 Form
字段是否有设置来自动执行 ParseForm
方法。
func (r *Request) FormValue(key string) string {if r.Form == nil {r.ParseMultipartForm(defaultMaxMemory)}if vs := r.Form[key]; len(vs) > 0 {return vs[0]}return ""
}
可以看到 FormValue
方法也是只返回切片中的第一个值。如果需要获取字段对应的所有值,那么需要通过字段名访问 Form
字段。如下:
获取表单字段的单个值
r.FormValue(key)
获取表单字段的多个值
r.ParseForm()
r.Form["key"]
下面是我们的示例程序,以及对应的路由:
//handler/display_form_data.go
package handler
import ("fmt""net/http"
)
func DisplayFormDataHandler(w http.ResponseWriter, r *http.Request) {if err := r.ParseForm(); err != nil {panic(err)}for key, values := range r.Form {fmt.Fprintf(w, "Form field %q, Values %q\n", key, values)fmt.Fprintf(w, "Form field %q, Value %q\n", key, r.FormValue(key))}
}
//router.go
indexRouter.HandleFunc("/display_form_data", handler.DisplayFormDataHandler)
我们在命令行中使用 cURL
命令发送表单数据到处理程序,看看效果。
curl -X POST -d 'username=James&password=123' \http://localhost:8000/index/display_form_data
返回的响应如下:
Form field "username", Values ["James"]
Form field "username", Value "James"
Form field "password", Values ["123"]
Form field "password", Value "123"
获取Cookie
Request
对象专门提供了一个 Cookie
方法用来访问请求中携带的 Cookie
数据,方法会返回一个 *Cookie
类型的值以及 error
。 Cookie
类型的定义如下:
type Cookie struct {Name stringValue stringPath string // optionalDomain string // optionalExpires time.Time // optionalRawExpires string // for reading cookies onlyMaxAge intSecure boolHttpOnly boolSameSite SameSiteRaw stringUnparsed []string
}
所以要读取请求中指定名称的 Cookie
值,只需要
cookie, err := r.Cookie(name)
// 错误检查
...
value := cookie.Value
Request.Cookies()
方法会返回 []*Cookie
切片,其中会包含请求中所有的 Cookie
下面的示例程序,会打印请求中所有的 Cookie
// handler/read_cookie.go
package handler
import ("fmt""net/http"
)
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {for _, cookie := range r.Cookies() {fmt.Fprintf(w, "Cookie field %q, Value %q\n", cookie.Name, cookie.Value)}
}
//router/router.go
indexRouter.HandleFunc("/read_cookie", handler.ReadCookieHandler)
我们通过 cURL
在命令行请求 http://localhost:8000/index/read_cookie
curl --cookie "USER_TOKEN=Yes" http://localhost:8000/index/read_cookie
执行命令后会返回:
Cookie field "USER_TOKEN", Value "Yes"
解析请求体中的JSON数据
现在前端都倾向于把请求数据以 JSON
格式放到请求主体中传给服务器,针对这个使用场景,我们需要把请求体作为 json.NewDecoder()
的输入流,然后将请求体中携带的 JSON
格式的数据解析到声明的结构体变量中
//handler/parse_json_request
package handler
import ("encoding/json""fmt""net/http"
)
type Person struct {Name stringAge int
}
func DisplayPersonHandler(w http.ResponseWriter, r *http.Request) {var p Person// 将请求体中的 JSON 数据解析到结构体中// 发生错误,返回400 错误码err := json.NewDecoder(r.Body).Decode(&p)if err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}fmt.Fprintf(w, "Person: %+v", p)
}
// router/router.go
indexRouter.HandleFunc("/parse_json_request", handler.ParseJsonRequestHandler)
在命令行里用 cURL
命令测试我们的程序:
curl -X POST -d '{"name": "James", "age": 18}' \-H "Content-Type: application/json" \http://localhost:8000/index/parse_json_request
返回响应如下:
Person: {Name:James Age:18}%
读取上传文件
服务器接收客户端上传的文件,使用 Request
定义的 FormFile()
方法。该方法会自动调用 r.ParseMultipartForm(32<<20)
方法解析请求多部表单中的上传文件,并把文件可读入内存的大小设置为 32M
(32向左位移20位),如果内存大小需要单独设置,就要在程序里单独调用 ParseMultipartForm()
方法才行。
func ReceiveFile(w http.ResponseWriter, r *http.Request) {r.ParseMultipartForm(32 << 20) var buf bytes.Bufferfile, header, err := r.FormFile("file")if err != nil {panic(err)}defer file.Close()name := strings.Split(header.Filename, ".")fmt.Printf("File name %s\n", name[0])io.Copy(&buf, file)contents := buf.String()fmt.Println(contents)buf.Reset()return
}
Go语言解析 HTTP
请求比较常用的方法我们都介绍的差不多了。因为想总结全一点,篇幅还是有点长,不过整体不难懂,而且也可以下载程序中的源码自己运行调试,动手实践一下更有助于理解吸收。 HTTP
客户端发送请求要设置的内容也只今天讲的 Request
结构体的字段, Request
对象也提供了一些设置相关的方法供开发人员使用,今天就先说这么多了。
公众号内回复 gohttp06
可以下载文章中项目的源码,赶快下载下来自己试一试吧。
前文回顾
深入学习用Go编写HTTP服务器
Web服务器路由
十分钟学会用Go编写Web中间件
Go Web编程--应用ORM
Go Web编程--深入学习解析HTTP请求相关推荐
- easyui datagrid url不请求请求_Go Web编程--深入学习解析HTTP请求
之前这个系列的文章一直在讲用Go语言怎么编写HTTP服务器来提供服务,如何给服务器配置路由来匹配请求到对应的处理程序,如何添加中间件把一些通用的处理任务从具体的Handler中解耦出来,以及如何更规范 ...
- 【web编程技术学习笔记】因特网与万维网简介
目录 Client客户端 Server服务器端 TCP/IP五层协议 IP 查看IP地址的的两种方法 TCP URL 样式一 样式二 DNS URL&DNS HTTP 与因特网有关的组织 IE ...
- 《ASP.NET MVC4 WEB编程》学习笔记------Web API 续
目录 ASP.NET WEB API的出现缘由 ASP.NET WEB API的强大功能 ASP.NET WEB API的出现缘由 随着UI AJAX 请求适量的增加,ASP.NET MVC基于Jso ...
- 全栈公开课(深入浅出现代Web编程)学习——Part0-Web 应用的基础设施
0. 介绍 课程简介: 这个课程是由芬兰的赫尔辛基大学计算机科学系推出的, 主要是介绍如何使用JavaScript开发现代Web应用程序. 课程的重点是使用ReactJS构建单页面应用程序(SPA), ...
- bootstrap网页模板源码_Go Web 编程--超详细的模板库应用指南
如果你有过Web编程的经验,那么或多或少都听说过或者使用过模板.简而言之,模板是可用于创建动态内容的文本文件.例如,你有一个网站导航栏的模板,其中动态内容的一部分可能是根据当前用户是否登录显示登录还是 ...
- Go Web 编程--如何确保Cookie数据的安全传输
什么是Cookie Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上.通常, ...
- Go Web编程--使用Go语言创建静态文件服务器
上篇关于Go模板库应用实践的文章最后我们留下一个问题,页面模板是通过 CDN引用的 BootStrap的 css, js文件.到目前位置我们的服务器还无法伺服客户端的静态文件请求把服务器磁盘上的文件响 ...
- Go Web 编程--超详细的模板库应用指南
如果你有过Web编程的经验,那么或多或少都听说过或者使用过模板.简而言之,模板是可用于创建动态内容的文本文件.例如,你有一个网站导航栏的模板,其中动态内容的一部分可能是根据当前用户是否登录显示登录还是 ...
- Go Web编程--解析JSON请求和生成JSON响应
现在无论是网站.App.小程序还是移动端H5页面应用,都是采用前端与后端单独部署,相互之间以API接口交互的形式构建而成的.因为在结合可读性.编码数据大小和开发者使用难度上都JSON格式是一个比较好的 ...
最新文章
- python下载安装教程3.8.0-Python3.8下载
- C++ Primer 5th笔记(chap 16 模板和泛型编程)函数模板显式实参
- kernel pca与传统pca的区别
- mysql使用过程中的几个细节注意点
- 响应式布局方法的方法
- 驱动06.触摸屏驱动程序
- 复用:设计模式 反模式 分析模式
- kafka集群 kubernetes_为什么Kubernetes如此受欢迎?
- Mac安装Homebrew教程
- 平面设计计算机基本配置,2017平面设计使用的电脑配置
- 【深度学习】卷积神经网络原理
- Android Audio音频系统之深入浅出
- cd命令远程连接linux服务器,Linux常用命令(5)--SSH访问远程服务器、SCP服务器间文件拷贝...
- android phone 模块分析
- 在线IP到地理位置解析的API接口,IP到地理位置、所属组织名、AS号、域名反查
- cuda性能分析工具
- 英语学习中总结的阅读、段落匹配、选词填空技巧
- re.search与re.findall的区别
- 2018第一次校队集训题解
- 架构设计-支付宝、京东、美团、去哪儿的支付系统架构整体设计详解!!!