这篇讨论几点问题:

  1. request的parameter的一些七里八里
  2. urlencoder到底是干嘛的
  3. 指定request的head的意义
  4. 解析request的一些事

这几个问题不必要分割,首先,简单过一下概念,最后串联起来理解一下。

http request是什么

request本质是基于tcp协议的数据传输,数据以字节流形式传递。那剩下的问题:这些字节流,怎么去组织和分割内容?怎么去设定编码?这些就是高级的协议比如http/https,需要解决的一部分问题了。

request的parameter

parameter,义为参数。动态request会传递参数,服务器拿到后方知道客户端想要什么,就给什么。参数,我们理解就是一对对的:参数名=参数值,简单说k=v

urlencoder干什么的

url简单点讲类似于浏览器里输入的地址,这个地址里有一些保留字符,比如: / ? &等等,如果你的kv里含有这些保留字符,那就没有办法分割出正确的参数了。所以,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-urlencodedmultipart/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传过来的。因此,客户端传参方式统一,编码方式统一还是很有必要。