之前这个系列的文章一直在讲用 Go 语言怎么编写HTTP服务器来提供服务,如何给服务器配置路由来匹配请求到对应的处理程序,如何添加中间件把一些通用的处理任务从具体的Handler中解耦出来,以及如何更规范地在项目中应用数据库。不过一直漏掉了一个环节是服务器接收到请求后如何解析请求拿到想要的数据, Go 语言使用 net/http 包中的 Request 结构体对象来表示 HTTP 请求,通过 Request 结构对象上定义的方法和数据字段,应用程序能够便捷地访问和设置 HTTP 请求中的数据。

一般服务端解析请求的需求有如下几种

  • HTTP请求头中的字段值
  • URL 查询字符串中的字段值
  • 请求体中的 Form 表单数据
  • 请求体中的 JSON 格式数据
  • 读取客户端的上传的文件

今天这篇文章我们就按照这几种常见的服务端对 HTTP 请求的操作来说一下服务器应用程序如何通过 Request 对象解析请求头和请求体。

Request 结构定义

在说具体操作的使用方法之前我们先来看看 net/http 包中 Request 结构体的定义,了解一下 Request 拥有什么样的数据结构。 Request 结构在源码中的定义如下。

我们快速地了解一下每个字段大致的含义,了解了每个字段的含义在不同的应用场景下需要读取访问 HTTP 请求的不同部分时就能够有的放矢了。

Method

指定HTTP方法(GET,POST,PUT等)。

URL

URL指定要请求的URI(对于服务器请求)或要访问的URL(用于客户请求)。它是一个表示 URL 的类型 url.URL 的指针, url.URL 的结构定义如下:

Proto

Proto , ProtoMajor , ProtoMinor 三个字段表示传入服务器请求的协议版本。对于客户请求,这些字段将被忽略。 HTTP 客户端代码始终使用 HTTP/1.1 或 HTTP/2 。

Header

Header 包含服务端收到或者由客户端发送的 HTTP 请求头,该字段是一个 http.Header 类型的指针, http.Header 类型的声明如下:

是 map[string][]string 类型的别名, http.Header 类型实现了 GET , SET , Add 等方法用于存取请求头。如果服务端收到带有如下请求头的请求:

那么 Header 的值为:

对于传入的请求, Host 标头被提升为 Request.Host 字段,并将其从 Header 对象中删除。 HTTP 定义头部的名称是不区分大小写的。 Go 使用 CanonicalHeaderKey 实现的请求解析器使得请求头名称第一个字母以及跟随在短横线后的第一个字母大写其他都为小写形式,比如: Content-Length 。对于客户端请求,某些标头,例如 Content-Length 和 Connection 会在需要时自动写入,并且标头中的值可能会被忽略。

Body

这个字段的类型是 io.ReadCloser , Body 是请求的主体。对于客户端发出的请求, nil 主体表示该请求没有 Body ,例如 GET 请求。 HTTP 客户端的传输会负责调用 Close 方法。对于服务器接收的请求,请求主体始终为非 nil ,但如果请求没有主体,则将立即返回 EOF 。服务器将自动关闭请求主体。服务器端的处理程序不需要关心此操作。

GetBody

客户端使用的方法的类型,其声明为:

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 类型的声明如下:

也是 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 方法可以获取请求头的第一个值,

或者是获取值时直接通过 key 获取对应的切片值就好,比如将上面的改为:

下面我们写个遍历请求头信息的示例程序,同时也会通上面介绍的 Request 结构中定义的 Method , URL , Host , RemoteAddr 等字段把请求的通用信息打印出来。在我们一直使用的 http_demo 项目中增加一个 DisplayHeadersHandler ,其源码如下:

将其处理程序绑定到 /index/display_headers 路由上:

然后启动项目,打开浏览器访问:

可以看到如下输出:

http_demo 项目中已经添加了本文中所有示例的源码,公众号内回复 gohttp06 可以获取源码的下载链接。

获取URL参数值

GET 请求中的 URL 查询字符串中的参数可以通过 url.Query() ,我们来看一下啊 url.Query() 函数的源码:

它通过 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 参数的区别

将其处理程序绑定到 /index/display_url_params 路由上:

打开浏览器访问

浏览器会输出:

我们为参数 a 传递了两个参数值,可以看到通过 url.Query.Get() 只能读取到第一个参数值。

获取表单中的参数值

Request 结构的 Form 字段包含已解析的表单数据,包括 URL 字段的查询参数以及 PATCH , POST 或 PUT 表单数据。此字段仅在调用 Request.ParseForm 之后可用。不过 Request 对象提供一个 FormValue 方法来获取指定名称的表单数据, FormValue 方法会根据 Form 字段是否有设置来自动执行 ParseForm 方法。

可以看到 FormValue 方法也是只返回切片中的第一个值。如果需要获取字段对应的所有值,那么需要通过字段名访问 Form 字段。如下:

获取表单字段的单个值

获取表单字段的多个值

下面是我们的示例程序,以及对应的路由:

我们在命令行中使用 cURL 命令发送表单数据到处理程序,看看效果。

返回的响应如下:

获取Cookie

Request 对象专门提供了一个 Cookie 方法用来访问请求中携带的 Cookie 数据,方法会返回一个 *Cookie 类型的值以及 error 。 Cookie 类型的定义如下:

所以要读取请求中指定名称的 Cookie 值,只需要

Request.Cookies() 方法会返回 []*Cookie 切片,其中会包含请求中所有的 Cookie

下面的示例程序,会打印请求中所有的 Cookie

我们通过 cURL 在命令行请求 http://localhost:8000/index/read_cookie

执行命令后会返回:

解析请求体中的JSON数据

现在前端都倾向于把请求数据以 JSON 格式放到请求主体中传给服务器,针对这个使用场景,我们需要把请求体作为 json.NewDecoder() 的输入流,然后将请求体中携带的 JSON 格式的数据解析到声明的结构体变量中

在命令行里用 cURL 命令测试我们的程序:

返回响应如下:

读取上传文件

服务器接收客户端上传的文件,使用 Request 定义的 FormFile() 方法。该方法会自动调用 r.ParseMultipartForm(32<<20) 方法解析请求多部表单中的上传文件,并把文件可读入内存的大小设置为 32M (32向左位移20位),如果内存大小需要单独设置,就要在程序里单独调用 ParseMultipartForm() 方法才行。

Go语言解析 HTTP 请求比较常用的方法我们都介绍的差不多了。因为想总结全一点,篇幅还是有点长,不过整体不难懂,而且也可以下载程序中的源码自己运行调试,动手实践一下更有助于理解吸收。 HTTP 客户端发送请求要设置的内容也只今天讲的 Request 结构体的字段, Request 对象也提供了一些设置相关的方法供开发人员使用,今天就先说这么多了。

get请求可以传body吗_详解用 Go 语言解析各种 HTTP 请求的方法相关推荐

  1. linux系统rc路由配置_详解CentOS 6.4 添加永久静态路由所有方法汇总

    CentOS添加永久静态路由,具体如下: 在使用双网卡,同时使用2个网关的时候就需要加一条静态路由了.当然还有很多时候会需要加路由. 操作系统版本centos-6.4 64bit 一:使用route ...

  2. mysql mgr 三节点_详解MySQL 5.7 MGR单主确定主节点方法

    我们行MGR年底要上线了,每天都要看官方文档学习,做测试,坚持每天写一个小知识点,有想一起学习的么~ MySQL 5.7 MGR单主确定主节点是哪个,我们可以通过成员ID来判断,然后结合read_on ...

  3. Java API源码在哪里找_详解查看JAVA API及JAVA源码的方法

    在java的日常学习中,我们有时候会需要看java的api说明,或者是查看java的源码,使我们更好的了解java,接下来我就来说说如何查看java的api以及java源码 对于java的api,一般 ...

  4. 0编译器详解_详解Java枚举类型(Enum)中的方法

    文章前记 程序员工作久了便可能整日忙碌于"增删改查"中,迷失方向,毫无进步. 该公众号致力于分享软件开发相关的原创干货,助你完成从程序员到架构师的进阶之路! 努力!做一个NB的Co ...

  5. oclick vue 传参 函数_详解Vue计算属性和侦听属性

    关注[搜狐技术产品]公众号,第一时间获取技术干货 作者介绍: 本期特邀作者:浪里行舟 Github博客2600 star作者,专注于前端领域.个人公众号:「前端工匠」,致力于打造适合初中级工程师能够快 ...

  6. okhttp的应用详解与源码解析--android网络请求框架发展史

    乘5G之势,借物联网之风,Android未来亦可期,Android优势在于开放,手机.平板.车载设备.智能家居等都是Android的舞台,Google不倒,Android不灭,本专栏的同步视频教程已经 ...

  7. python until语句_详解Lua中repeat...until循环语句的使用方法

    与for和while循环不同,在循环的顶部测试循环条件,Lua编程语言的repeat...until 循环检查循环底部的状态. repeat...until 循环类似于while循环,不同的是do . ...

  8. python语言format用法_详解Python中的format格式化函数的使用方法

    format函数实现字符串格式化的功能 基本语法为: 通过 : 和 {} 来控制字符串的操作 一.对字符串进行操作 1. 不设置指定位置,按默认顺序插入 ①当参数个数等于{}个数的时候 str_1 = ...

  9. jupyternotebook虚拟环境无法连接服务_详解pycharm连接远程linux服务器的虚拟环境的方法_python...

    这篇文章主要介绍了pycharm连接远程linux服务器的虚拟环境的详细教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 一.前提条件 ...

最新文章

  1. 马斯克员工参与新冠研究,论文登上Nature子刊
  2. Ext2.0框架的Grid使用介绍(转)
  3. mongodb 只查询某个字段
  4. Unity--------------------万向锁的概念
  5. 【Android】开源图表库MPAndroidChart的学习
  6. BZOJ 2716: [Violet 3]天使玩偶
  7. 学习:MOSS2007 实现单点登陆(转)
  8. 网页设计基础知识总结
  9. 十、Net6 Core Api发布到IIS
  10. JSP程序设计课后习题答案
  11. 2022显卡、CPU天梯图
  12. Linux应用开发自学之路
  13. 腾创秒会达MHD-CHD40A 20倍光学变焦摄像机
  14. Photoshop中27种图层混合模式原理图文详解
  15. 实验2 运算器的编程实现
  16. python 发邮件 带附件
  17. 一个啥都不懂但还不知天高地厚的我
  18. 简述远程视频监控项目方案
  19. Thumbnails 压缩后反而变大
  20. 录制宏,理解编程的方法

热门文章

  1. 在matlab中安装命令窗口,安装完后发现命令窗口有这个?怎么回事?
  2. python二次开发odoo_odoo二次开发 - 战鹏的Blog - OSCHINA - 中文开源技术交流社区
  3. jquery ajax json传递数组,jQuery ajax 传递JSON数组到Spring Controller
  4. linux 装jdk出错,redhat linux 9.0安装jdk出错,该如何解决
  5. matlab视频旋转振动,基于MATLAB的振动合成及左旋与右旋的动态模拟演示
  6. mysql1000w数据怎么加索引_给mysql一百万条数据的表添加索引
  7. OpenShift 4 - 基于Memory的HPA
  8. OpenShift 4 - 用自定义的TLS证书对访问OpenShift的用户认证身份
  9. OpenShift 4 Hands-on Lab (8) 基于Gogs+Nexus+Sonarqube的Jenkins CI/CD Pipeline
  10. 新的恶意软件将后门植入微软 SQL Server 中