以前遇到一个关于URL encoding的一个问题,很tricky,这里把这个问题的root cause以及对这个问题的一些思考记录下来,分享给你们。html
首先,抽象这个问题的原型以下:java
有一个电商平台,咱们须要调用其暴露的一个API来建立电商信息,API要求咱们把电商名做为URL参数传过去,而后建立相应的一条电商记录。这个API示例以下:node
POST http://localhost:8080/stores/{storeName}
返回结果:算法
{ "storeName": "xxx", "otherInfo": "" }
在测试这个API的过程当中发现,有一个电商名(abc{d)包含字符“{”,出现了一个问题:用postman发送请求过去可以成功;可是经过java代码发送请求则报错,提示说URL syntax出错,URL不容许包含“{”字符。apache
POST http://localhost:8080/stores/abc{d
后来发现,postman能成功的缘由是因为postman自动帮忙作了个URL encoding,经过建立出来的那条记录能够验证,建立出来的记录以下。后端
{ "storeName": "abc%7Bd", "otherInfo": "" }
而java代码没有作encoding,因此就报错了。由于根据URL的RFC规范,“{”字符确实不容许,必须作encoding。See https://tools.ietf.org/html/rfc3986#section-2。服务器
那么,怎么解决这个问题呢,如何容许传入包含“{”字符的电商名呢?当时想到的解决方案是,在代码中显式的把电商名都作一个URL encoding,而后再做为URL参数传过去。很好,用这个方案把这个问题解决了。请求URL参数电商名是encoded过的(abc%7Bd),建立出来也是encoded过的(abc%7Bd)。mvc
没过多久,又出现了另一个问题。在测试过程当中,有一个电商名(abc:d)包含字符“:”,按照以前的逻辑,咱们把“:”作一个encoding(abc%3Ad)发送过去,指望建立的记录应该是encoded的名字,可是结果却不是,结果是一个decoded的名字(abc:d),即包含“:”自己。ide
结果由于这个,程序出错了。同时测试还发现,经过postman发送请求过去没有问题,发送带字符“:”的名字(abc:d)过去,建立出来的就是带“:”(abc:d);发送字符“:”的encoded串(abc%3Ad)过去,获得的就是encoded的串(abc%3Ad)。可是,经过代码发送字符“:”(abc:d)或者字符“:”(abc%3Ad)的encoded的串,建立出来的都是“:”(abc:d)。到这里就有点思路了,发现字符“:”好像在哪里被自动decode了。post
通过调试发现,问题出在一个底层依赖的library的版本上,java发送http请求的方法最终依赖于一个library叫作org.apache.httpcomponents:httpclient,高版本(4.5.8)的library会自动decode一些字符(包括“:”),低版本(4.5.5)的则不会。
那么到这里,问题就明白了,问题的root cause便是虽然咱们显式把字符“:”作了一个encoding,可是在请求被真正发出去以前被Httpclient自动decode了,因此建立出来的记录都是“:”。
对这个问题的一些思考:
第一,为啥字符“:”的encoded串会被自动decode,而字符“{”却没有?
缘由是,“:”是URL规范容许的字符,尽管其是保留字符。而“{”是不容许字符,必须作URL encoding,中文汉字也是同样,必须作URL encoding。
第二,若是传入的是一个中文字符的encoded串,建立出来的就是encoded的串,很显然这个不易读,因此咱们须要把这个encoded的串作一个decode。那么设想一下,服务端每次都须要显示作decode吗?记得以前在Spring mvc项目中没有显示decode啊?
缘由是Spring mvc自动把URL参数都作了一个decoding,因此咱们不用显示decode。而这个问题中的API在实现端没有利用自动decode功能,即拿的是原生的参数值,因此一些时候会存在不易读。自动decoding会有什么问题吗?在大多数状况下应该是没问题,即输入“{”的encoded串,后端获得的是就是“{”。那么若是自己参数值就是“{”的encoded串呢,也没问题,发请求方本身须要对这个串作一次encoding做为输入。
第三,发现自动decoding在不一样技术栈平台(Spring boot / mvc, .net core / mvc, .net framework / mvc, Nodejs)实现不同 ,有时候也会出现不一致的状况。好比说,当请求的URL参数包含%3F(字符“?”的encoded串),在Spring boot和 .net core都可以正常拿到字符“?”;在.net framework里却会报错。而当请求的URL参数包含%2F(字符“/”的encoded串),在Spring boot, .net core和 .net framework里都不工做;在Nodejs里,用相对比较原生的方式,就能够工做而且获取到这个URL参数。以下:
P1:Spring boot中字符“?”是work的
P2:Spring boot中字符“/”不work,报404
P3:Nodejs中能够拿到包含字符“/”的参数
因此有时候利用平台的自动decoding可能会出现一些问题,这时候你可能须要考虑利用平台相对比较原生的方式操做httprequest对象,好比上面nodejs的方式。
最后,其实关于编码,以前也写过一篇关于utf8编码的文章(关于编码的那些事),这里讨论的是URL encoding。编码,我的理解其本质就是把一种表现形式的内容经过某种方式转换成另一种形式,以达到某些目的,好比URL encoding把字符“{”转成“%7B”,这样才符合http规范,URL才能被正确解析及转发到服务器;好比utf8编码把字符“汉”转成“e6b189”,这样才能被计算机存储,由于计算机只能识别一个字节一个字节。除了utf8编码、URL encoding,咱们经常使用到的还有另一种编码方式:base64编码,这个编码主要用于混淆易读的一些信息,好比jwt token。
注意,咱们在不少地方还用到哈希算法,好比SHA1等,编码和哈希最大的区别就是:编码是可逆的,哈希不可逆。
References