认知 URL 上的加号

工作中有遇到一个问题,简单的例子就是如下:

  • 访问链接:

    http://localhost:8010/?again=1+2&amnhh=true

  • koa 查看 ctx 上的 query 的结果:

我们发现,我们的 query.again 本来应该为 1+2, 但是从 query 中拿下来却变成了 1 2, 加号被替换成了空格

导致本来加号 encode 后应该变成 %2B, 成为了空格进行 encode,结果得到了 %20

探索过程

1. 查找 koaquery 这个属性的封装

首先看这一块的代码,代码地址位于 /lib/request.js 中:

/**
  * Get parsed query-string.
  *
  * @return {Object}
  * @api public
  */

get query() {
  const str = this.querystring;
  const c = this._querycache = this._querycache || {};
  return c[str] || (c[str] = qs.parse(str));
},

/**
  * Set query-string as an object.
  *
  * @param {Object} obj
  * @api public
  */

set query(obj) {
  this.querystring = qs.stringify(obj);
},

可以看到,ctx.query 为一个存取器属性,在 set 时操作的是 ctx.querystring, get 的时候通过序列化 ctx.querystring 得到这个对象

通过图上可以看到,querystringagain=1+2&amnhh=true, 而序列化之后 query.again1 2

也就是说转换的步骤发生在 qs.parse() 的过程里,这个方法出自 nodejs 的自带包 querystring

2. querystring 包里的 parse 方法

先大概的看下代码:

function parse (qs, sep, eq, options) {
  // ......
  const plusChar = (customDecode ? '%20' : ' ');
  for (let i = 0; i < qs.length; ++i) {
    const code = qs.charCodeAt(i);

    // Try matching key/value pair separator (e.g. '&')
    if (code === sepCodes[sepIdx]) {
      if (++sepIdx === sepLen) {} else {
      sepIdx = 0;
      // Try matching key/value separator (e.g. '=') if we haven't already
      if (eqIdx < eqLen) {
          // ...
        } else {
        if (code === 43/* + */) {
          if (lastPos < i)
            key += qs.slice(lastPos, i);
          key += plusChar;
          lastPos = i + 1;
          continue;
        }
      }
      if (code === 43/* + */) {
        if (lastPos < i)
          value += qs.slice(lastPos, i);
        value += plusChar;
        lastPos = i + 1;
      } else if (!valEncoded) {
        // ...
      }
    }
  }
}

我们可以看到,对待 + 对应的 ASCII 码: 43 的处理过程中,总是将其赋值为 plusChar,也就是空格或者 %20, 而 %20 刚好就是空格的 encode 结果,所以就是说,加号总会被替换为空格

因为涉及到知识的盲区,所以去查了下 rfc 的文档

3. rfc 文档关于 querystring 的说明
QUERY STRINGS

The question mark ("?", ASCII 3F hex) is used to delimit the
boundary between the URI of a queryable object, and a set of words
used to express a query on that object.  When this form is used,
the combined URI stands for the object which results from the
query being applied to the original object.

Within the query string, the plus sign is reserved as shorthand
notation for a space.  Therefore, real plus signs must be encoded.
This method was used to make query URIs easier to pass in systems
which did not allow spaces.

The query string represents some operation applied to the object,
but this specification gives no common syntax or semantics for it.
In practice the syntax and sematics may depend on the scheme and
may even on the base URI.

可以着重看一下句:

Within the query string, the plus sign is reserved as shorthand notation for a space. Therefore, real plus signs must be encoded.

也就是说,rfc规定 url 里的 + 号,就是一个空格的替换符,我们如果想要真实的加号的话,必须讲加号转义

到此,破案

解决方案

我们遇到的问题其实就是加号被转义成了空格,到了后端就解析成了空格的问题,所以在 rfc 里也给到了解决方案:

我们 uri 里真的想要加号的时候,需要使用时将加号转成 %2B

问题反思

  1. httpuri 认知不够,后续需要整理一篇 uri 相关的博客
  2. 遇事需要先查文档,当然一级一级查过来能学到的更多,但是先查文档可能效率更高,可以快速定位到问题
posted @ 2020-02-24 21:32  叁歌~  阅读(598)  评论(0)    收藏  举报