乐趣区

关于urlencode:url的编码之家-urlQueryEscapeencodeURIComponenturlencodeRFC3986

RFC3986

首先,理解一下 RFC 3986 规范,简略讲就是规定了如下:除了 数字 + 字母 + -_.~ 不会被本义,其余字符都会被以 百分号(%)后跟两位十六进制数 %{hex} 的形式进行本义。

再者,理解下 wwwpost form data 也就是 x-www-form-urlencode 的编码规定:除 -_.(没有 ~)之外的所有 非字母 非数字 的字符都将被替换成 百分号(%)后跟两位十六进制数 %{hex}空格(留神)则编码为加号 +

二者的区别如下:

1、rfc3986~ 不做转码,x-www-form-urlencode~ 做转码 %7E
2、rfc3986空格 转为 %20x-www-form-urlencode 空格 转为 +

接下来看几个高级语言的 url 编码方式。

js encodeURIComponent
php urlencode/rawurlencode
go url.QueryEscape

js

encodeURIComponent

console.log(encodeURIComponent("hello233 ~-_."))
hello233%20~-_.

能够看到 js 齐全遵循 rfc3986,保留了 ~-_.空格 被转码为 %20,正规。

php

urlencode

<?php
echo urlencode("hello233 ~-_.");
hello233+%7E-_.

空格+,只保留 -_. 没保留 ~,典型的 x-www-form-urlencode 规定。

rawurlencode

<?php
echo rawurlencode("hello233 ~-_.");
hello233%20~-_.

rfc3986 模式。

http_build_query

这里要分明,http_build_query 只对 keyval 做了 urlencode 解决,=& 符号没有解决(gourl.Values.Encode 能分明的看到如述的处理过程)。

<?php
echo http_build_query(["msg" => "hello233 ~-_.", "name" => "sqrtCat"]);
msg=hello233+%7E-_.&name=sqrtCat

go

go 的编码方式 … 就比拟有意思了,这也是我写这篇文章的起因。
go 提供的 url.Values.Encode(相当于 phpksort+http_build_query)、url.QueryEscape(相当于 phpurlencode/rawurlencode)。

url.Values.Encode

相似 phphttp_query_builder, 只对 keyval 做本义解决,=& 不做解决,看下实现就明了了。

func (v Values) Encode() string {
    if v == nil {return ""}
    var buf strings.Builder
    keys := make([]string, 0, len(v))
    for k := range v {keys = append(keys, k)
    }
    sort.Strings(keys)
    for _, k := range keys {vs := v[k]
        keyEscaped := QueryEscape(k)
        for _, v := range vs {if buf.Len() > 0 {buf.WriteByte('&')// 拼接
            }
            buf.WriteString(keyEscaped)// 已本义
            buf.WriteByte('=')// 拼接
            buf.WriteString(QueryEscape(v))// 本义
        }
    }
    return buf.String()}

能够看到 keyval 是应用 url.QueryEscape 做的本义,那咱们持续看它的本义规范。

url.QueryEscape

func main() {fmt.Println(url.QueryEscape("hello233 ~-_."))
}
hello233+~-_.

是不是有些懵逼?
~ 被保留了,rfc3986?但 空格 却转为了 + 而不是 %20,这不是 x-www-form-urlencode 才会做的么,但 ~ 也没转为 %70 呀。

总结

  1. 如果你的数据里没有 空格~ 能够间接略过了。
  2. 其实无论是 rfc3986 还是 x-www-form-urlencode 还是 go 的混合编码,浏览器的地址栏都能够正确解析解决的,大家能够尝试打印一下 GET 参数,三项都能够正确获取到数据的。(但拿 rfc3986 编码的后果,让遵循 x-www-form-urlencode 模式的解码是解不出正确数据的,你就简略认为这是浏览器地址栏的个性好了)。
    http://0.0.0.0:8888/?rfc3986=hello233%20~-_.&urlencode=hello233+%7E-_.&go=hello233+~-_.

    array(3) {["rfc3986"]=>
      string(12) "hello233 ~-_"
      ["urlencode"]=>
      string(13) "hello233 ~-_."
      ["go"]=>
      string(13) "hello233 ~-_."
    }
  3. 我掉坑的次要起因是一些历史遗留的服务还在应用 参数字典排序签名验证 的模式,碰巧数据中含有 空格 ~gourl.Values.Encode 空格 +,对 ~ 保留,phphttp_build_query空格+,但对 ~%7E,这就导致两端签名始终不匹配。

解决方案

  1. 遵循 x-www-form-urlencode 模式。
    1.1 gourl.Values.Encode~ 替换为 %7E 的解决。
    1.2 php 端间接 ksort + http_build_query 即可。
  2. 遵循 rfc3986 规范。
    2.1 前提保证数据中没有 空格 go 在没有 空格 时也变为了 rfc3986 模式。
    2.2 phprawurlencode(urldecode(http_build_query($params)))
    2.3 gourl.QueryEscape(url.QueryUnescape(url.Values.Encode()))
    2.4 http_build_query / url.Values.Encode() 帮你疾速构建 queryString 但对 keyval 已转码,所以 urldecode / url.QueryUnescape 失去原始的 key=val&key=val 后再编码。
退出移动版