URL只能使用US-ASCII 字符集来通过因特网进行发送。由于URL常常会包含 ASCII 集合之外的字符,URL必须转换为有效的 ASCII 格式。URL 编码使用 "%" 其后跟随两位的十六进制数来替换非 ASCII 字符。URL 不能包含空格,URL 编码通常使用 + 来替换空格。所谓URL编码,就是将非US-ASCII字符和US-ASCII中的特殊字符,用相应的字符集编码来表示。比如,汉字”你”,如果用UTF-8编码,出现在URL中是%E4%BD%A0,如果用GBK编码出现URL中是%C4%E3。
RFC3986文档规定,URL中只允许包含英文字母(a-zA-Z)、数字(0-9)、4个特殊字符("-" / "." / "_" / "~")以及所有保留字符。但是由于历史原因,目前尚存在一些不标准的编码实现。例如对于"~"符号,虽然RFC3986文档规定,对于波浪符号~,不需要进行Url编码,但是还是有很多老的网关或者传输代理会将该字符进行URL编码。URL将字符分为以下4类:
1、原义字符。英文字母和阿拉伯数字、4个特殊字符可以直接出现在URL的任何地方,不需要编码,因为这些是原义字符,不具有特殊意义。鉴于历史原因,最好不要直接将"~"符号未经编码地出现在URL中,即将"~"排除在原义字符之外。
2、保留字符。往往具有特殊的含义,在URL中有着固定的作用。这些字符如果是代表特殊意义而出现,则不需要进行URL编码;如果不是作为特殊含义出现,则必须经过编码。
3、不安全字符。这类字符虽然不会引起URL的歧义,但是当他们直接放在URL中的时候,可能会引起解析程序的歧义,因此也必须进行URL编码。
4、非US-ASCII字符。 对于其他语言的字符,必须按照某个字符集,将其转换成US-ASCII字符。对于URL,一般都是用UTF-8格式进行编码。
在URL中不代表特殊意义,只是作为普通字符串出现。RFC3986文档规定如下:
These includeuppercase and lowercase letters, decimal digits, hyphen, period, underscore,and tilde.
原义字符 = ALPHA / DIGIT / "-" / "."/ "_" / "~"
保留字符:
url可以划分成若干个组件,协议、主机、路径等。有一些字符(:/?#[]@)是用作分隔不同组件的。例如:冒号用于分隔协议和主机,/用于分隔主机和路径,?用于分隔路径和查询参数,等等。还有一些字符(!$&'()*+,;=)用于在每个组件中起到分隔作用的,如=用于表示查询参数中的键值对,&符号用于分隔查询多个键值对。当组件中的普通数据包含这些特殊字符时,需要对其进行编码,防止引起url歧义。
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" /"[" / "]" / "@"
sub-delims ="!" / "$" / "&" / " ' " / "(" / ")" /"*" / "+" / "," / ";" / "="
RFC3986中指定了以下字符为保留字符:! * ' ( ) ; : @ & = + $ , / ? # [ ]
不安全字符:
当他们直接放在Url中的时候,可能会引起解析程序的歧义。例如双引号(“”)在标签中用于限定URL的属性值。如果您想要在URL中直接包括双引号,那么可能会令浏览器感到困惑。因此,应该使用双引号的编码%22,以避免任何可能的冲突。其他保留字符和不安全字符也应该始终使用它们的编码。
不安全字符如下:< > " # % { } | \ ^ ~ [ ] ` 空格
a.空格:url在传输的过程,或者用户在排版的过程,或者文本处理程序在处理Url的过程,都有可能引入无关紧要的空格,或者将那些有意义的空格给去掉。
b.引号以及<>:引号和尖括号通常用于在普通文本中起到分隔Url的作用
c.#:通常用于表示书签或者锚点
d.%:百分号本身用作对不安全字符进行编码时使用的特殊字符,因此本身需要编码
e.{}|\^[]`~:某一些网关或者传输代理会篡改这些字符
JDK内置了URLEncoder和URLDecoder,用来在java端对字符进行编码和解码,具体的使用方式可以参考javadoc文档。编码和解码规则如下:
1、字母数字字符 "a" 到 "z"、"A" 到 "Z" 和 "0" 到 "9" 保持不变
2、特殊字符 "."、"-"、"*" 和 "_" 保持不变
3、空格字符 " " 转换为一个加号 "+"
4、所有其他字符都是不安全的,因此首先使用一些编码机制将它们转换为一个或多个字节。然后每个字节用一个包含 3 个字符的字符串 "%xy" 表示,其中 xy 为该字节的两位十六进制表示形式。推荐的编码机制是 UTF-8
//空格的ascii码是%20,十进制的32 System.out.println(URLDecoder.decode("1%201", "UTF-8")); //1 1 //+号被视为空格 System.out.println(URLDecoder.decode("1+1你", "UTF-8")); //1 1你
javascript中涉及URL编码的函数是:escape、encodeURI、encodeURIComponent。
escape:
该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码:* @ - _ + . / 。其他所有的字符都会被转义序列替换。
encodeURI:
该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。
该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#
encodeURIComponent:
该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。
JDK自带的URLEncoder,不会对特殊字符"."、"-"、"*" 和 "_" 进行编码,而这些字符可能会被js中的escapse()/encodeURI()/encodeURIComponent()函数会对某些字符进行编码,这样容易造成客户端和服务端的不一致。为了达到统一性,我们可以约定:除了英文大小写字母和阿拉伯数字外,其他所有字符都是不安全字符,需要进行URL编码。下面是我参考tomcat源码包下的org.apache.catalina.util.URLEncoder,对字符串进行URL编码工具类:
package org.apache.catalina.util; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.util.BitSet; public class URLEncoder { protected static final char[] hexadecimal = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; // Array containing the safe characters set. protected BitSet safeCharacters = new BitSet(256); public URLEncoder() { for (char i = 'a'; i <= 'z'; i++) { safeCharacters.set(i); } for (char i = 'A'; i <= 'Z'; i++) { safeCharacters.set(i); } for (char i = '0'; i <= '9'; i++) { safeCharacters.set(i); } } public String encode(String path) throws Exception { // path经过URL编码后的字符串 StringBuilder rewrittenPath = new StringBuilder(path.length()); // 字节输出流,数据被写入一个 byte数组 int maxBytesPerChar = 10; ByteArrayOutputStream byteBuff = new ByteArrayOutputStream(maxBytesPerChar); // 按照特定的charset,在字节流和字符流之间进行转换 OutputStreamWriter writer = new OutputStreamWriter(byteBuff, "UTF-8"); for (int i = 0; i < path.length(); i++) { int c = path.charAt(i); if (safeCharacters.get(c)) { rewrittenPath.append((char) c); } else { writer.write((char) c); writer.flush(); // 得到字符对应的字节数组 byte[] ba = byteBuff.toByteArray(); for (int j = 0; j < ba.length; j++) { byte toEncode = ba[j]; rewrittenPath.append('%'); // 字节低4位的值 int low = toEncode & 0x0f; // 字节高4位的值 int high = (toEncode & 0xf0) >> 4; rewrittenPath.append(hexadecimal[high]); rewrittenPath.append(hexadecimal[low]); } // 字节流数组清空 byteBuff.reset(); } } return rewrittenPath.toString(); } }