Url 编码以及在 Nginx 中的实现

最近在看编码方面的东西, 这里对url编码(Percent-encoding)做一个简单的总结. 然后再看一下nginx中的url编码的实现方式.

url 编码

好吧, 有关url编码, 貌似这个东西被题了N+1遍了, 但是我比较关心的问题有三个:

  1. 为什么要进行url编码
  2. 怎么编码
  3. 有哪些细节需要注意

url编码是一种传输url的机制, 个人认为最主要的作用是区分字符是否有特殊意义. 我们都知道有许多字符在url中是有特殊含义的, 比如?作为参数开始的标志, 又比如&作为参数切分标志. 因此如果想要让这些 字符保留字符原本含义就需要对它们进行url编码. 另外一个作用是用来传递非可视字符. url编码的主要内容是: 0-9A-Za-z以及-, ~, ., ~无需通过编码即可传输(普通字符), 而以下字符(保留字符):

! * ( ) ; : @ & = + $ , / ? # [ ]

如果要当作字符常量对待则需要经过编码. 另外其他的字符都需要进过url编码来进行传输. 编码形式是%后跟对应的十六进制形式.

这里需要注意的有两点:

  1. 编码并非是强制性, 即使是普通字符我们也可以进行编码然后进行传输
  2. 在参数部分, 空格会被编码成+而非%25, 参见这里The application/x-www-form-urlencoded type一节, 所以会造成经过base64编码的内容直接传输时, 加号在经过解码之后变为空格. 针对这个问题衍生出了其他的base64方式(主要是替换了/, +)

这里再推荐一下阮一峰大哥的这篇文章, 写的挺好的~

nginx 的实现

从原理上讲url编码还是蛮容易的, 编码就是遇到非普通字符就开始转换, 解码就是遇到百分号就开始转换(这里可能还需要考虑到各种情况, 诸如上面提到的+号的情况).

如果现在让我来实现, 我可能就直接按字符的asc码比较来进行转换了. 学编程的进步方法之一就是看优秀的代码, 那我们不妨来看看nginx中是如何实现url编码的(因为nginx考虑的url编码情况比较多, 甚至涵盖了memcached的情况, 这里就对最普通的url编码进行解析):

ngx_string.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
uintptr_t
ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type)
{
    ngx_uint_t      n;
    uint32_t       *escape;
    static u_char   hex[] = "0123456789abcdef";

    /* " ", "#", "%", "?", %00-%1F, %7F-%FF */

    /* 位图法判断是否需要进行编码 */
    static uint32_t   uri[] = {
        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */

                    /* ?>=< ;:98 7654 3210  /.-, +*)( '&%$ #"!  */
        0x80000029, /* 1000 0000 0000 0000  0000 0000 0010 1001 */

                    /* _^]\ [ZYX WVUT SRQP  ONML KJIH GFED CBA@ */
        0x00000000, /* 0000 0000 0000 0000  0000 0000 0000 0000 */

                    /*  ~}| {zyx wvut srqp  onml kjih gfed cba` */
        0x80000000, /* 1000 0000 0000 0000  0000 0000 0000 0000 */

        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
        0xffffffff, /* 1111 1111 1111 1111  1111 1111 1111 1111 */
        0xffffffff  /* 1111 1111 1111 1111  1111 1111 1111 1111 */
    };

    /* 省略其他type */

    static uint32_t  *map[] =
                { uri, args, uri_component, html, refresh, memcached, memcached };


    escape = map[type];

    if (dst == NULL) {

        /* find the number of the characters to be escaped */

        n = 0;

        while (size) {
            if (escape[*src >> 5] & (1 << (*src & 0x1f))) {    /* 判断 */
                n++;
            }
            src++;
            size--;
        }

        return (uintptr_t) n;
    }

    while (size) {
        if (escape[*src >> 5] & (1 << (*src & 0x1f))) {    /* 判断 */
            *dst++ = '%';
            *dst++ = hex[*src >> 4];
            *dst++ = hex[*src & 0xf];
            src++;

        } else {
            *dst++ = *src++;
        }
        size--;
    }

    return (uintptr_t) dst;

代码很精简, 同时也很高效. 这里利用bitmap法来判断字符是否需要进行url编码(赞). 最关键的一句是:

1
2
3
4
    /* 取組, 做除法 */      /* 取偏移, 求余 */
if (escape[*src >> 5] & (1 << (*src & 0x1f))) {

}

如果我利用位图法我会将字符先分组(除以32), 然后再与上取其偏移量(模32)来取得bitmap的值. 上面就是在用位运算做这个事情, 真心学习了. 而转十六进制的时候同样使用了这个方法. 强大的位运算~

PPPPSSSS

《屌丝男士》第二季结束了, 还是非常有意思的. 友情出演的同学(邓超, 汤唯, 李晨, 波多野结衣, 温兆伦等)也都很给力~ 希望能出第三季~

Comments