这篇讨论几点问题:
- request的parameter的一些七里八里
- urlencoder到底是干嘛的
- 指定request的head的意义
- 解析request的一些事
这几个问题不必要分割,首先,简单过一下概念,最后串联起来理解一下。
http request是什么
request本质是基于tcp协议的数据传输,数据以字节流形式传递。那剩下的问题:这些字节流,怎么去组织和分割内容?怎么去设定编码?这些就是高级的协议比如http/https,需要解决的一部分问题了。
request的parameter
parameter,义为参数。动态request会传递参数,服务器拿到后方知道客户端想要什么,就给什么。参数,我们理解就是一对对的:参数名=参数值
,简单说k=v
。
urlencoder干什么的
url简单点讲类似于浏览器里输入的地址,这个地址里有一些保留字符,比如:
/
?
&
等等,如果你的k
或v
里含有这些保留字符,那就没有办法分割出正确的参数了。所以,v
需要encode,即转义,k
不需要是因为不会去使用这些特殊字符。
request head有什么用
head里都是一些属性,即表示此request的一些特性和规格,有了这些设定,服务器才能知道此request的高矮胖瘦等。head除了告知服务器,还有一种情况需要特别注意:
有的客户端程序或API会根据设定的head,去编码传输的数据,比如fiddler,假如body的parameter里含有中文,如果指定charset=iso-8859-1去编码,结果parameter内容肯定会丢失,服务器将取不到正确的原文,一切将失去意义。如果不对fiddler指定charset,服务器能取到内容,揣测此时fiddler没进行转码
因此有时head具有双重意义的。
request怎么解析
通常有几种方法,也没有什么深度,但弄懂了并灵活运用,也能解决不少繁琐的问题,将在下文中结合讲叙。
work pattern
现,将以上关键词串联讲叙,关键字内容用粗体表示。
基于前文分析,客户端传递过来的内容提倡进行url encode。客户端可以是浏览器、程序,或中间件,不管是谁,都该做这个工作,做多次跟做一次效果是一样的,没必要重复做。
这样,客户端就将k=v
一对对的用&连接起来发送给了服务器,比如:
a1=353588880000255&b1=contact&avatar=D%3A%5CProgram+Files%5CNetease%5C%CD%F8%D2%D7%C9%C1%B5%E7%D3%CA%5Cpaper%5Cimages%5Ccustom_v_bot1.jpg
以上是服务器收到的原文,但服务器的web容器可能自带有解析的api,为了让api更好地干它的工作,提倡设定内容的编码,即指定head属性:Content-Type: charset=utf-8
,这样,web容器会直接将v
转换成对应编码,比如上例中的avatar它其实是一个中文路径,转换成utf-8字符才能直接被使用,不然服务端程序需要再绕一下。
回头看一下,上例是一个web表单提交注册用户的请求,avatar应该传上传头像文件,怎么传的一个文件路径?
这是一个mime type的设定问题,浏览器form标签有一个属性enctype
指定mime,普通表单(默认)为application/x-www-form-urlencoded
,但要上传文件需设定为multipart/form-data
。在request的head里,都体现在Content-Type
属性中,与字符集指定是放在一起的,完整的可以是Content-Type: application/x-www-form-urlencoded;charset=utf-8
。
application/x-www-form-urlencoded
和multipart/form-data
有什么区别?
- 区别就在于怎么组织参数,前者组织参数的形式前文已经叙述,后者服务器收到的原文如下。显然,它们用一串特殊字符来分隔参数,且每个参数的都有自己的属性:
-----------------------------7e01731f10b054c
Content-Disposition: form-data; name="a1"
353588880000255
-----------------------------7e01731f10b054c
Content-Disposition: form-data; name="b1"
contact
-----------------------------7e01731f10b054c
Content-Disposition: form-data; name="avatar"; filename="D:\kuaipan\md\md_in\article\travel\109\109_0.md"
Content-Type: text/plain
...
-----------------------------7e01731f10b054c
- 区别还在于服务器的web容器需要不同对待,前者需要url decode(未encoded的内容也可以进行decode啊,还是原文),后者,需要借助一些特殊的解析包,比如apache的fileupload包。
最后,服务器怎么解析request?
前文已经提过,multipart/form-data
提倡用apache的fileupload包;另外常用的一种就是很直接的request.getParameter(k),web容器已经将内容decode,并且转换为charset的编码(未指定则为iso-8859-1)的字符形式;还有一种是request.getInputStream(),将原文用字节读出来,剩下就是自己去拆分和decode参数了,这种情况request body可以完全地自定义,可以为json,或其它自定义格式,mime type也可以使用其它类型,比如text/plain,application/octet-stream。
在使用tomcat servlet的api中request.getParameter(k)和request.getInputStream()有几个小注意事项:
- request.getInputStream()无法获取到url中的parameter
- request.getParameter(k)肯定能取到url中的parameter
- 放在url中的参数,比如汉字,不管有无url encode,通过request.getParameter(k)获得的内容都是iso-8859-1的编码
- mime为
application/x-www-form-urlencoded
,request.getParameter(k)能取到参数,使用它就不能再使用request.getInputStream(),只能读一次。当url和body中同时含有同名parameter时,取到的是url中的值。 - mime为
multipart/form-data
,request.getParameter(k)取不到值,同时可以使用request.getInputStream()读到原版的request body,转化提倡用apache的fileupload包
下面举一个具体的例子说明这几个注意事项,比如用fiddler(相当于客户端)构造一个post
请求:
url:
https://tl/v3/app/fence_list_sub_acc?imei=好
message body:
imei=啊
指定head:
Content-Type: application/x-www-form-urlencoded;charset=iso-8859-1
服务器端的运行情况是:
- 同时开启getParameter和getInputStream,getParameter能取到
imei=好
,但是需要从iso-8859-1还原字符值并转码为utf-8才能还原汉字内容。这时getInputStream空。 - 仅开启getInputStream,getInputStream能取
imei=?
,不管如何转码无法还原,原文内容有丢失。将charset改为utf-8就没有问题 - 将url中的
imei=好
的parameter改名,imei2=好
,用getParameter则取message body中的imei,charset的问题同样存在。
注:
fiddler有一个现象,构造一个post请求,但没有指定head属性
Content-Type: application/x-www-form-urlencoded;charset=iso-8859-1
,请求的WebForms里没有内容(设置了head即有),服务端通过getParameter取不到内容,通过getInputStream能取到。
所以基于以上情况,汉字放在url中传输将产生一个固定情况,脱离于charset的设置,这样如果charset设置为utf-8,用getParameter方式,那么url中的参数需要转码,body的参数不需要转码,而servelet的api并不会区分parameter是通过url还是body传过来的。因此,客户端传参方式统一,编码方式统一还是很有必要。