easyui datagrid url不请求请求_Go Web编程--深入学习解析HTTP请求

22d4467239d2358d49f4c2bd6180b635.png

之前这个系列的文章一直在讲用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

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

Header

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

type Header map[string][]string

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

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-LengthConnection会在需要时自动写入,并且标头中的值可能会被忽略。

Body

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

GetBody

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

GetBody func() (io.ReadCloser, error)

ContentLength

ContentLength记录请求关联内容的长度。值-1表示长度未知。值>=0表示从Body 中读取到的字节数。对于客户请求,值为0且非nilBody也会被视为长度未知。

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字段的查询参数以及PATCHPOSTPUT表单数据。此字段仅在调用Request.ParseForm之后可用。HTTP客户端会忽略Form并改用BodyForm字段的类型是url.Values类型的指针。url.Values类型的声明如下:

type Values map[string][]string

也是map[string][]string类型的别名。url.Values类型实现了GETSETAddDel等方法用于存取表单数据。

PostForm

PostForm类型与Form字段一样,包含来自PATCHPOST的已解析表单数据或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并更改同一请求的调用方所拥有的上下文。

读取请求头

上面分析了GoHTTP请求头存储在Request结构体对象的Header字段里,Header字段实质上是一个Map,请求头的名称为MapkeyMap Value的类型为字符串切片,有的请求头像Accept会有多个值,在切片中就对应多个元素。

Header类型的Get方法可以获取请求头的第一个值,

func exampleHandler(w http.ResponseWriter, r *http.Request) {ua := r.Header.Get("User-Agent")...
}

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

ua := r.Header["User-Agent"]

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

package handlerimport ("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 %qn", k, v)}fmt.Fprintf(w, "Host = %qn", r.Host)fmt.Fprintf(w, "RemoteAddr= %qn", r.RemoteAddr)// 通过 Key 获取指定请求头的值fmt.Fprintf(w, "nnFinding value of "Accept" %q", r.Header["Accept"])
}

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

indexRouter.HandleFunc("/display_headers", handler.DisplayHeadersHandler)

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

http://localhost:8000/index/display_headers

可以看到如下输出:

18f3b4fc094db227c38b4ea806d658d1.png
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类型的别名,实现了GETSETAddDel等方法用于存取数据。

所以我们可以使用r.URL.Query().Get("ParamName")获取参数值,也可以使用r.URL.Query()["ParamName"]。两者的区别是Get只返回切片中的第一个值,如果参数对应多个值时(比如复选框表单那种请求就是一个name对应多个值),记住要使用第二种方式。

我们通过运行一个示例程序display_url_params.go来看一下两种获取URL参数的区别

package handlerimport (
"fmt"
"net/http"
)func DisplayUrlParamsHandler(w http.ResponseWriter, r *http.Request) {for k, v := range r.URL.Query() {fmt.Fprintf(w, "ParamName %q, Value %qn", k, v)fmt.Fprintf(w, "ParamName %q, Get Value %qn", 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字段的查询参数以及PATCHPOSTPUT表单数据。此字段仅在调用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 handlerimport ("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 %qn", key, values)fmt.Fprintf(w, "Form field %q, Value %qn", 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类型的值以及errorCookie类型的定义如下:

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 handlerimport ("fmt""net/http"
)func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {for _, cookie := range r.Cookies() {fmt.Fprintf(w, "Cookie field %q, Value %qn", 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 handlerimport ("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 %sn", 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

cc8dbc938cef37a28f9ada830cb18cab.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/522450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MaxCompute JOIN优化小结

摘要&#xff1a; Join是MaxCompute中最基本的语法&#xff0c;但由于数据量和倾斜问题&#xff0c;非常容易出现性能问题。一般情况下&#xff0c;join产生的问题有两大类&#xff1a; 数据倾斜问题&#xff1a;join会将key相同的数据分发到同一个instance上处理&#xff0c;如…

李锐:金龙客车DMS上云实践以及对网络、混合云、弹性、运维、安全的思考

摘要&#xff1a; 金龙客车CIO李锐的深度分享。DMS是大金龙管理轻客经销、售后服务、售后备件销售、三包索赔的信息子系统。精细管控国内88家轻客经销商活动&#xff0c;集中轻客订单、在制、收发车、库存、核销、回款等全程通业务&#xff0c;具有金龙整合-创新-增效的特色&am…

不小心执行 rm -f,该如何恢复?

戳蓝字“CSDN云计算”关注我们哦&#xff01;源 / 程序员的那些事前言每当我们在生产环境服务器上执行rm命令时&#xff0c;总是提心吊胆的&#xff0c;因为一不小心执行了误删&#xff0c;然后就要准备跑路了&#xff0c;毕竟人不是机器&#xff0c;更何况机器也有 bug&#x…

flowable 启动流程到完成所有任务之间的数据库变化

先给出流程图&#xff0c;很简单的流程&#xff0c;就是3个UserTask ProcessDefinition pdrepositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult(); ProcessInstance piruntimeService.startProcessInstanceById(pd.getId());或…

如何使用dll ip转换_如何使用多功能转换插座更安全

作为专业的转换插头厂家&#xff0c;在制造多功能转换插座的时候都会做好了全面的安全考虑&#xff0c;包括但不仅限于以下安全设计&#xff1a;1. 高阻燃外壳材料&#xff1a;万浦电器的多功能转换插座的外壳大多以进口PC材料为主&#xff0c;阻燃温度高达750℃。在高温中不容…

2020计算机考研难度排名,2020年考研难度排名:100所高校上榜,中国人民大学排名第7...

据数据显示&#xff0c;2021年全国硕士研究生报考人数为341万左右&#xff0c;相对于2020年的290万&#xff0c;人数增加了近51万左右&#xff0c;而相对于100万左右考研计划录取人数&#xff0c;这也就意味着每3.5个人只有一个人能够被录取为硕士研究生&#xff0c;当然就考上…

玩转短视频?守护视频安全?AI智能提速?一分钱体验? 阿里云视频点播大招盘点...

摘要&#xff1a; 前言 随着近几年在线视频市场规模不断扩大&#xff0c;内容不断创新&#xff0c;用户粘性增加&#xff0c;在线视频市场的商业价值不断增长&#xff0c;各垂直行业纷纷引入视频能力&#xff0c;一时之间&#xff0c;视频已经成为了众多移动APP和在线平台沉淀用…

美女主播变大妈:在bug翻车现场说测试策略

戳蓝字“CSDN云计算”关注我们哦&#xff01;美女主播变大妈&#xff1a;在bug翻车现场说测试策略文 | 珍妮兔这两天直播圈发生了一起严重的翻车事故。一个一直以“颜值主播”自称的网红女主播“乔碧萝殿下”&#xff0c;因为平台bug&#xff0c;露出了自己的真容&#xff0c;上…

一张图看懂阿里云网络产品[十一]云托付

摘要&#xff1a; 云托付&#xff08;Cloud Hosting&#xff09;是以阿里云的标准&#xff0c;提供给企业优质的机房托管资源、云专线网络以及增值服务&#xff0c;并与阿里云公有云产品结合&#xff0c;为企业搭建混合云提供基础资源。 原文地址&#xff1a;http://click.aliy…

springboot dubbo引入包_spring boot 集成 dubbo 企业完整版

一、什么是Spring Boot &#xff1f;现阶段的 Spring Boot 可谓是太火了&#xff0c;为什么呢&#xff1f;因为使用方便、配置简洁、上手快速&#xff0c;那么它是什么&#xff1f;从官网上我们可以看到&#xff0c;它是 Spring 开源组织下的一个子项目&#xff0c;主要简化了 …

MaxCompute助力ofo实现精细化运营:日订单超3200万、整体运行效率提升76%

摘要&#xff1a;ofo小黄车大数据BI系统负责人龙利民为大家分享了ofo的上云体验&#xff0c;重点分享了MaxCompute的应用实践&#xff0c;最后对阿里云提出了自己的建议需求。 关于ofo小黄车 共享经济不仅与技术相关&#xff0c;它还关乎人类共同命运&#xff0c;关乎可持续发展…

微服务精华问答 | 为什么需要微服务?

戳蓝字“CSDN云计算”关注我们哦&#xff01;过去几年来&#xff0c;“微服务架构”这个术语出现了&#xff0c;它描述了一种将软件应用程序设计为可独立部署的服务套件的特定方式。尽管这种架构风格没有确切的定义&#xff0c;但围绕业务能力&#xff0c;自动化部署&#xff0…

3个点让你彻底明白,为什么要使用MQ消息中间件?

3个点让你彻底明白&#xff0c;为什么要使用MQ消息中间件? 前言 一个用消息队列的人&#xff0c;不知道为啥用&#xff0c;有点尴尬。没有复习这点&#xff0c;很容易被问蒙&#xff0c;然后就开始胡扯了。 回答&#xff1a;这个问题,咱只答三个最主要的应用场景&#xff0c;不…

五位专家跟你讲讲为啥Python更适合做AI/机器学习

摘要&#xff1a; 为什么Python会在这股深度学习浪潮中成为编程语言的头牌&#xff1f;听听大牛如何解释吧&#xff01; 原文地址&#xff1a;http://click.aliyun.com/m/43988/1.Python网络编程框架Twisted的创始人Glyph Lefkowitz&#xff08;glyph&#xff09;&#xff1a;编…

c++如何显示图片_Vue+laravel后端添加商品后图片如何显示?

知识点&#xff1a;利用laravel的php artisan storage:link实现软连接php artisan storage:link命令执行完毕后&#xff0c;就会在项目里多出一个 public/storage&#xff0c;这个 storage 就是一个软链接&#xff0c;它指向 storage/app/public 目录。这样的话我们就可以直接访…

微软发布 Azure 物联网安全中心;阿里巴巴在美申请专利,以实现跨区块链统一管理;Google利用足球训练下一代人工智能……...

关注并标星星CSDN云计算极客头条&#xff1a;速递、最新、绝对有料。这里有企业新动、这里有业界要闻&#xff0c;打起十二分精神&#xff0c;紧跟fashion你可以的&#xff01;每周三次&#xff0c;打卡即read更快、更全了解泛云圈精彩newsgo go go 合众汽车新能源哪吒N01 2020…

手把手教您解决90%的自然语言处理问题

摘要&#xff1a; 本文将讲解如何从零开始&#xff0c;有效地处理自然语言问题的指南和技巧&#xff1a;首先解释如何构建机器学习解决方案来解决上面提到的问题。然后转向更细致的解决方案。如何将5W和H应用于文本数据&#xff01;文本数据无处不在从文本数据中提取有意义和值…

Spring Boot整合swagger2(生成有左右菜单式的api文档界面)

一、pom.xml内容&#xff1a; <!--依赖swagger2 zhongzk 2019.7.7 --> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version> </dependency> <depend…

你会感觉容器使用起来很痛苦吗?

摘要&#xff1a; 容器问世4年了&#xff0c;它终于逐渐褪去神秘光环。不过&#xff0c;真正运用自如并不是件容易的事情。 在将容器用于生产环境中的过程中&#xff0c;你是否有很多痛苦&#xff0c;是否踩过很多坑&#xff0c;要吐好多苦水&#xff1f; 其实很大的一个问题在…

如何证明你不是在开发垃圾?

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 | 珍妮兔 来源| 轻松做软件需要懂垃圾分类的不仅仅只有家庭主妇和煮夫&#xff0c;还有程序员。如果你开发的功能跑不通&#xff0c;你是在开发干垃圾&#xff1b;如果你的代码害得别人的功能跑不通&#xff0c;你是在开发湿垃…