首页 > 代码库 > 了解URL编码的基本概念,在javascript和java程序中使用内置的API进行编码和解码

了解URL编码的基本概念,在javascript和java程序中使用内置的API进行编码和解码


1.URL编码的基本概念

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格式进行编码。

2.URL字符分类

原义字符:

在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.{}|\^[]`~:某一些网关或者传输代理会篡改这些字符


3.java端进行URL编码/解码

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你 

4.javascript进行URL编码/解码

 javascript中涉及URL编码的函数是:escape、encodeURI、encodeURIComponent。

escape:

该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码:* @ - _ + . / 。其他所有的字符都会被转义序列替换。

encodeURI

该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ‘ ( ) 。
该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#

encodeURIComponent

该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ‘ ( ) 。其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。

5.自己实现URL编码

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();
    }
}

6.参考资料

url 编码(percentcode 百分号编码)
http://www.cnblogs.com/leaven/archive/2012/07/12/2588746.html

关于URL编码
http://www.ruanyifeng.com/blog/2010/02/url_encoding.html

字符编码笔记:ASCII,Unicode和UTF-8
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html


了解URL编码的基本概念,在javascript和java程序中使用内置的API进行编码和解码