HttpServletRequest
获取请求行的相关信息
HTTP请求消息可以分为三个部分:请求行、请求消息头、消息实体。
请求行可分为三个部分:请求方式、资源路径、HTTP协议版本,如:
GET /test.html HTTP/1.0
getMethod
返回HTTP请求消息中的请求方式,如GET、POST、HEAD等,即请求行的第一部分。
getRequestURI
返回请求行中的资源名部分,即URL主机和端口之后、参数部分之前的部分(注,返回的结果没有被容器进行URL解码)。如:
状态行 返回值
POST /some/path.html HTTP/1.1 /some/path.html
GET http://foo.bar/a.html HTTP/1.0 /a.html
HEAD /xyz?a=b HTTP/1.1 /xyz
getQueryString
返回请求行中资源的参数部分,即资源路径后面的问号以后的内容(注,返回的结果没有被容器进行URL解码),如:
http://localhost:8080/myapp/testservlet?name=jzj&age=30
则返回name=jzj&age=30,如果URL后没有参参数则返回null。
getProtocol
返回请求行中的协议名和版本,如 HTTP/1.0 或HTTP/1.0。
getContextPath
返回当前应用路径,这个路径以“/”开头,表示相对于整个Web站点根目录,且结尾不含“/”。如果请求URL属于站点的根目录,则返回空字符串。返回的路径没有经过容器URL解码。
http://localhost:8080/myapp/testservlet?name=jzj&age=30 返回 “/myapp”
http://localhost:8080/index.jsp 返回 “”
getPathInfo
返回URL中额外路径信息,即指请求URL中的位于Servlet的路径之后和查询参数之前的内容,它以“/”开头,如果URL中没有额外路径信息,则返回null。
额外路径可用于向Servlet传递信息,但它传递的信息通常指服务器上其他资源名,如其他Servlet、Jsp页面。在MVC设计模式中,客户端的请求都要通过中央控制器的Servlet,其他各个JSP页面则作为请求URL中路径部分,则这个Servlet根据额外路径信息去调用其他各具体的JSP页面。只要在web.xml文件中将某个Servlet映射成一个通配符形式的路径,如,“/controller/*”,然后就可以使用“/controller/one.jsp”、 “/controller/two.jsp”等多个路径来访问这个Servlet,其中的“/one.jsp”与“two.jsp”等即为额外路径。
简单地说就是 web.xml配置的Servlet映射路径中通配符所对匹配的部分。
getPathTranslated
getPathTranslated方法返回URL中的额外路径信息所对应的资源的真实文件系统路径。假设“/controller/one.jsp”中的“one.jsp”为额外路径信息,则真实路径为:Z:\eclipse_workspace\servlet_jsp_exercise\myapp\one.jsp(感觉没什么用)
getServletPath
返回请求URL中Servlet所映射路径,不包括应用目录。以“/”开头。如果web.xml配置为“/*”,则返回为空字符串“”,因为Servlet路径不包括通配符所匹配的部分,那是额外路径。
<servlet-mapping>
<servlet-name>servletRequestTest</servlet-name>
<url-pattern>/servletRequestTest/*</url-pattern>
</servlet-mapping>
http://localhost:8080/myapp/servletRequestTest/one.jsp 返回 /servletRequestTest。
如果请求的是资源直接是JSP页面,如http://localhost:8080/myapp/one.jsp,则返回 /one.jsp
获取网络连接信息
getRemoteAddr
返回客户端的IP
getRemoteHost
返回发出天主教堂的客户机完整主机名,如“www.xxx.com”,如果Servlet引擎不能解析出客户机的完整主机名,那么该方法就返回客户机的IP地址。
getRemotePort
返回发出请求的客户机所使用的网络接口的端口号。
getLocalAddr
返回Web服务器上接收当前请求的网络接口的IP地址。
getLocalName
返回Web服务器上接收当前请求的网络接口的IP地址所对应的主机名(也可是虚拟主机名)。
getLocalPort
返回Web服务器上接收当前请求的网络接口的端口号。
getServerName
返回当前请求所指向的主机名,如果HTTP请求消息的Host头包含有主机名部分,那么返回值即为该主机名部分,如果没有Host头,则返回主机名,或IP。如:Host: localhost:8080
返回localhost
getServerPort
返回当前请求所连接的服务器端口号,如果HTTP请求消息的Host头包含端口信息,那么返回值即为该端口号,如:Host: localhost:8080
返回 8080
getScheme
返回请求的协议名,如http、https或ftp。
getRequestURL
返回客户端发出请求时的完整URL,包括协议、服务器名、端口号、资源路径等信息,但不包括后面查询参数部分。
获取请求头信息
getHeader方法是一个通用的方法,可用于读取所有的头字段。除了getHeader方法外,HttpServletRequest还定义了一些便利的方法,如,getContentType、getContentLength、getIntHeader、getDateHeader等方法,用来方便地读取那些常用的请求的头和读取包含有日期、整数数据的请求头。
getHeader
String getHeader(java.lang.String name)
如果消息中包含多个该指定名称的头字段,则返回第一个头的值。传入的参数不区分大小写。
getHeaders
Enumeration getHeaders(java.lang.String name)
返回某个指定的头的所有值,因为有些头是可以设置多个的。
getHeaderNames
Enumeration getHeaderNames()
返回所有头字段名的Enumeration。
注,返回的Enumeration中的字段名不重复,所以如果同一头出现多次,则需要使用 getHeaders 来读取,如:
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
//System.out.println(headerName +" = " + request.getHeader(headerName));
Enumeration values = request.getHeaders(headerName);
while (values.hasMoreElements()) {
System.out.println(headerName + " = " + values.nextElement());
}
}
getIntHeader
int getIntHeader(java.lang.String name)
以整型类型返回指定的头值,如果不存在返回-1。
getDateHeader
long getDateHeader(java.lang.String name)
将GMT时间格式的头的值以整型毫秒数返回。如果不存在返回-1。
getContentType
String getContentType()
直接返回Content-Type头字段的值。
getContentLength
int getContentLength()
直接返回Content-Length头字段的值。
getCharacterEncoding
String getCharacterEncoding()
返回请求消息的实体部分的字符集编码,通常从Content-Type头字段中进行提取。
盗链应用
使用 referer 头防止“盗链”:
//referer头是HTTP创始人拼写错误 ,正确应该为 referrer
String referrer = request.getHeader("referer");
String sitePart = "http://" + request.getServerName();
if(referrer != null && referrer.startsWith(sitePart)){
}else{//盗链
}
例如,不想让另人看到重要的 js 代码,我们可以先将重要的 js 写在一个 js 文件里,然后使用Serlvet输出到浏览器(但也可以将js放置在WEB-INF下,使用Servlet转发机制也可,这样就不用使用流输出了),如:
<script type=text/javascript src=servlet/hidejsservlet></script>
但此时还是可以直接通过 来访问得到,所以就可以使用上面盗链的方式来禁止非法访问,但此时还需要注意的是要在服务器上给响应头设置禁止缓存的头。当然这样做还是不是很安全的,因为还可以直接使用telnet或相关的软件来自己创建假的头再发送。
BASE64编码及客户端身份认证
当客户端访问时,服务器发送 401(Unauthorized)响应状态码和 WWW-Authenticate 响应头来要求客户端进行身份认证,而客户端则可以再次发送带有 Authorization 请求头(提供用户名与密码信息),服务器再进行身份信息认证。
WWW-Authenticate 响应头可以指定两种认证方式:BASIC和DIGEST。BASIC验证要求客户端对用户名和密码进行BASE64编码后传送给服务器,属于一种未加密的明文传送方式,容易被解码。DIGEST认证方式则是以MD5的方式对用户名与密码进行加密码再进行传送,由于MD5不可逆,所以一般不会被破解。
BASE64算法就是将二进制数据转换成可打印的ASCII字符的一种最常见的编码方式。规则:将一组连续的字节数据按6个bit位进行分组,然后对每组数据用一个ASCII字符来表示。6个bit位最多能表示 2^6=64个数值,因此可以使用64个ASCII字符来对应这64个数值,这64个ASCII字符为:
索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 | 索引 | 对应字符 |
0 | A | 17 | R | 34 | i | 51 | z |
1 | B | 18 | S | 35 | j | 52 | 0 |
2 | C | 19 | T | 36 | k | 53 | 1 |
3 | D | 20 | U | 37 | l | 54 | 2 |
4 | E | 21 | V | 38 | m | 55 | 3 |
5 | F | 22 | W | 39 | n | 56 | 4 |
6 | G | 23 | X | 40 | o | 57 | 5 |
7 | H | 24 | Y | 41 | p | 58 | 6 |
8 | I | 25 | Z | 42 | q | 59 | 7 |
9 | J | 26 | a | 43 | r | 60 | 8 |
10 | K | 27 | b | 44 | s | 61 | 9 |
11 | L | 28 | c | 45 | t | 62 | + |
12 | M | 29 | d | 46 | u | 63 | / |
13 | N | 30 | e | 47 | v |
|
|
14 | O | 31 | f | 48 | w |
|
|
15 | P | 32 | g | 49 | x |
|
|
16 | Q | 33 | h | 50 | y |
|
|
假设在内存中有如下三个连续的字节数据:
【0110 ,0001】【0110 ,0010】【0110 ,0011】
将它们按6个bit 位进行分组后如下:
【011000】【010110】【001001】【100011】
分组后得到四组数组,每组数据对应的十进制值为 24 22 6 35 ,它们分别对应Y W J j这四个字符,所以对上面三个字节的数据进行BASE64编码后结果为“YWJj”。注,如果不足6位,则在最后补零,分组是从左到右,并且规定对于编码结果不是4的倍数的结果,需要在结果后加上“=”来凑成4的倍数,比如:
【01100001】
【011000】【010000】
“YQ”->“YQ==”
经过BASE64编码后的结果所占用的字节个数大约是原始内容的4/3倍。
Sun公司在JDK的核心包rt.jar里提供了BASE64编码和解码的类:sun.misc.BASE64Encoder与sum.misc.BASE64Decoder
因为人们只要掌握BASE64的规则后,就可以对BASE64编码的数据进行解码,所以,严格地说,BASE64只是一种编码方式,而不是一种加密方式。
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=GB2312");
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
// System.out.println(headerName +" = " + request.getHeader(headerName));
Enumeration values = request.getHeaders(headerName);
while (values.hasMoreElements()) {
System.out.println(headerName + " = " + values.nextElement());
}
}
System.out.println("----------------------------------------------");
PrintWriter out = response.getWriter();
String encodeAuth = request.getHeader("Authorization");
//要求客户端发送身份认证信息,并且只能是BASIC认证方式
if (encodeAuth == null || !encodeAuth.toUpperCase().startsWith("BASIC")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "BASIC realm=\"myapp\"");
out.print("没有传递用户身份!");
return;
}
BASE64Decoder decoder = new BASE64Decoder();
//Authorization: Basic evh40jEyMZOlNg== ,注,其中用户名与密码规定是使用冒号相连的
byte[] decodeBytes = decoder.decodeBuffer(encodeAuth.substring(6));
String decodedInfo = new String(decodeBytes);
int idx = decodedInfo.indexOf(":");
if (idx < 0) {
out.println("信息格式不完整");
return;
}
String user = decodedInfo.substring(0, idx);
String password = decodedInfo.substring(idx + 1);
if ("jzj".equals(user) && "123456".equals(password)) {
out.println("认证通过!");
out.println("<a href=/myapp/test.jsp>test.jsp</a>");
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "BASIC realm=\"myapp\"");
out.println("认证不通过!");
}
}
同一站点只需认证一次通过,则后面访问别的资源不再受限,只要浏览器不关闭。因为浏览器在内存中保存了用户上次输入的用户名和密码,并且在以后访问相同域的资源时都会主动使用Authorization头将用户名和密码传递给服务器(即每次都会传递这个头信息),这样服务器端程序(比如过滤器)就可以直接从中获取用户名和密码进行检验,而不必让浏览器再次提示用户输入用户和密码。
参数传递细节及码流分析
参数传递总结
GET方式传递的参数不能超过1024字节,即1K
<form>表单元素的enctype 属性用于指定浏览器使用哪种编码方式将表单中的数据传递给服务器,该属性有两种取值:
application/x-www-form-urlencoded
multipart/form-data
enctype属性默认的设置值为“application/x-www-form-urlencoded”
POST方式提交表单时,在头部会有 Content-Type与Content-Length这两个头信息,而GET方式提交的表单是不会有,也不会有HTTP请求实体。
<form>表单默认就是“GET”方式。
以POST方式提交表单,并且enctype设置为multipart/form-data时,表单里的参数在服务器端不能使用request.getParameter方法来获取,这时属性文件上传方式,会以流的方式存放在消息实体中发送。
根据HTML标准,如果提交的表单不需要修改服务器上的数据,则应采用GET方式(如果查询操作),如果需要修改,则采用POST方式提交。
如果URL后面附有参数,而<form>表单里有也表单元素,则要以POST方式传递,如果以GET方式的话,URL后面的参数不会传递到服务器,它只会传递表单里的元素。
不管表单是以POST还是GET方式提交,表单里的元素都会先以浏览器的编码方式进行统一编码并以“%xx”形式组织后传递到服务器,如果以GET方式提交,则编码后的参数会附在URL后面传递到服务器,如果是以POST方式提交,则编码后的参数会放在HTTP消息体里传递到服务器。
如果直接在浏览器地址栏里拼接参数,则还是会以浏览器编码,只不过不再会以“%xx”形式组织,且地址栏还是原样显示,并且也会以原样显示在HTTP消息头的请求行里(即第一行请求行)。
如果在javaScript里使用encodeURIComponent(param)函数来拼接参数,则该参数固定以“UTF-8”来编码,此时与浏览器编码没有关系。且经过encodeURIComponent()函数编码后的附加参数内容也会以%xx形式串显示在地址栏中。
按钮传递规则:
如果一个表单中有多个Button元素(提交、重置、普通),只有被点击的且点击的是提交按钮时才会将submit按钮本身的信息(名与值)一并提交到服务器。表单中的普通按键与重置按钮的名称和值不会作为参数传递到服务器。没有设置name属性的提交按钮信息不会作为参数传递,即使你点击的是它。
单选按钮与复选框传递规则:
l 只有被选中的复选框和单选按键的信息才会作为参数传递,未被被选中的复选框和单选按钮的信息不会作为作为参数传递,就像表单中没有这些字段元素一样,但文本框与文本域不管你输出不输出文本都会传递。
l 对于多个名称相同的复选框,它们可以同时被选中;对于多个名称相同的单选按钮,只能同时选中其中的任意一个。
l 对于被选中的多个同名复选框,它们的信息将以多个名称相同的参数进行传递,即参数列表中会出现多个名称相同的参数。
l 对于没有设置value属性的单选按钮和复选框,当它们被选中时,它们传递的默认参数值为“one”。
不仅submit按钮可以提交表单,图像按钮也可以提交:<input type=image name=image1 src=/myapp/logo.jpg>
并且图像的点击坐标信息也会一并提交,如image1.x=19&image1.y=19
列表框参数递规则:
<select name=select1 multiple>
<option value=>--不选--</option>
<option >java</option>
<option value=1>c-</option>
<option value=2>jsp</option>
</select>
选中java、c、jsp三项后提交,浏览器地址栏中出现路径如下:
http://localhost:8080/myapp/select.html?select1=java&select1=1&select1=2
没有做出选择的列表框信息不会作为参数传递(注,单选下拉框一样),就像没有这个复选列表框元素一样。
对于多选列表框,对于选中的每个选项,它们都会与列表框名称分别组合成单独的参数后进行传递,这样,参数列表中会出现多个名称为列表框名同名的参数。
当选中没有设置value属性的列表选项时,浏览器使用该选项的标题作为参数值。
GET方式HTTP码流
GET /gb2312rs.jsp?textParam1=%D6%D0a+%7E%21@%23%24%25%5E%26*%28%29_%2B%7B%7D%7C%3A%5C%22+%3C%3E%3F%60-%3D%5B%5D%5C%5C%3B%27%2C.%2F&fileParamhttp://localhost:8080/HttpStream/gb2312.jsp Accept-Language: zh-CN User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0) Accept-Encoding: gzip, deflate Host: localhost:8088 Connection: Keep-Alive=file1.txt HTTP/1.1 Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer:
|
POST方式HTTP码流
POST /gb2312rs.jsp?qryParam1=%E4%B8%ADa%20~!%40%23%24%25%5E%26*()_%2B%7B%7D%7C%3A%22%20%3C%3E%3F%60-%3D%5B%5D%5C%3B'%2C.%2F&qryParam2=%E4%B8%ADa%20~!%40%23%24%25%5E%26*()_%2B%7B%7D%7C%3A%5C%22%20%3C%3E%3F%60-%3D%5B%5D%5C%5C%3B'%2C.%2F HTTP/1.1 Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer: http://localhost:8080/HttpStream/gb2312.jsp Accept-Language: zh-CN User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0) Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate Host: localhost:8088 Content-Length: 132 Connection: Keep-Alive Cache-Control: no-cache
textParam1=%D6%D0a+%7E%21@%23%24%25%5E%26*%28%29_%2B%7B%7D%7C%3A%5C%22+%3C%3E%3F%60-%3D%5B%5D%5C%5C%3B%27%2C.%2F&fileParam=file1.txt
|
文件上传码流
<form name=form1 action="" method="post" enctype="multipart/form-data">
POST /gb2312rs.jsp?qryParam1=%E4%B8%ADa%20~!%40%23%24%25%5E%26*()_%2B%7B%7D%7C%3A%22%20%3C%3E%3F%60-%3D%5B%5D%5C%3B'%2C.%2F&qryParam2=%E4%B8%ADa%20~!%40%23%24%25%5E%26*()_%2B%7B%7D%7C%3A%5C%22%20%3C%3E%3F%60-%3D%5B%5D%5C%5C%3B'%2C.%2F HTTP/1.1 Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer: http://localhost:8080/HttpStream/gb2312.jsp Accept-Language: zh-CN User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0) Content-Type: multipart/form-data; boundary=---------------------------7d9165750396 Accept-Encoding: gzip, deflate Host: localhost:8088 Content-Length: 595 Connection: Keep-Alive Cache-Control: no-cache
-----------------------------7d9165750396 Content-Disposition: form-data; name="textParam1"
中a ~!@#$%^&*()_+{}|:\" <>?`-=[]\\;',./ -----------------------------7d9165750396 Content-Disposition: form-data; name="fileParam1"; filename="file1.txt" Content-Type: text/plain
这是第一个测试文件的内容: 1111111111111 aaaaaaaaaaaaa -----------------------------7d9165750396 Content-Disposition: form-data; name="fileParam2"; filename="file2.txt" Content-Type: text/plain
这是第二个测试文件的内容: 中a ~!@#$%^&*()_+{}|:\" <>?`-=[]\\;',./ -----------------------------7d9165750396--
|
windows浏览器会将finalname设置为上传文件的完整路径,而其他的只有文件名。
上传文件的内容,不会受浏览器编码的影响,浏览器在上传时将其原封不支地上传给Web服务器,没有进行任何编码转换处理,服务器只需要将接收到的文件内容数据原封不动地读取出来(以字节流的形式读取)并存储到本地文件系统中,就得到了客户端上传的文件。
注意实体内容中的字段分隔界线与content-type并没有中指定的字段分隔线有一点细微的差别,前者(29个减号)是在后者(27个减号)前面增加了两个减号字符而形成的。
文件下载码流
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=328097D70C625E8A9279FF9472319A5D; Path=/HttpStream Content-Type: text/html;charset=GB2312 Content-Length: 60 Date: Mon, 09 Nov 2009 13:19:22 GMT
这是测试文件的内容: 中a ~!@#$%^&*()_+{}|:\" <>?`-=[]\\;',./
|
获取请求参数
getParameter
String getParameter(java.lang.String name)
如果指定名称的参数存在但没有设置值,则返回一个空串。如果有多个同名参数,则返回第一个出现的参数值。
getParameterValues
String[] getParameterValues(java.lang.String name)
获取同一参数名所对应的所有参数值,比如获取复选框与多选下拉框可能会传递多个值。
getParameterNames
Enumeration<java.lang.String> getParameterNames()
返回一个包含请求消息中的所有参数名的Enumeration对象。
getParameterMap
Map<java.lang.String,java.lang.String[]> getParameterMap()
将请求消息中的所有参数名和值装入一个Map对象中。
获取请求消息的实体内容
getInputStream与getReader
getInputStream()返回为ServletInputStream字节流。
getReader()返回为BufferedReader的字符流。在调用getReader方法之前,可以调用ServletRequest的setCharacterEncoding方法指定其返回BufferedReader对象所使用的字符集编码方式。如果请求消息中没有指定实体内容的字符集编码,并且也没有在调用getReader方法之前没有调用ServletRequest的setCharacterEncoding方法,那么,getReader方法返回的BufferedReader对应将使用ISO8859-1作为默认字符编码。
调用了ServletRequest对象的getReader或getInputStreamm中的任何一个方法后,就不能再调用另一方法,这两个方法是相互排斥的。虽然Web容器能够在Servlet的service方法返回后自劝关闭getReader或geInputStream返回的流对象,但是最好是在Servlet程序读取完实体内容后,接着在其内部调用流对象的close方法,以便服务器尽快释放相关的资源。
获取请求里的属性
setAttribute
setAttribute(java.lang.String name, java.lang.Object o)
name一般要遵循以包命开头,但要避免以 java.*、javax.*、com.sun.*等开头的名称。如果传递的属性值o为null,则删除指定名称的属性,效果等同于removeAttribute方法
getAttributeNames
Enumeration<java.lang.String> getAttributeNames()
返回一个ServletRequest对象中所有属性名的Enumeration
请求参数的中文读取问题
如果URL中参数含有特殊字符,则一定要先对这些字符进行URL编码后再传递。服务器收到客户端传递的整个参数信息后,首先从中分离出每个参数的名称和值部分,接着对单个名称和值部分进行URL解码,然后将URL解码得到的字节数组按照某种字符集编码转换成Unicode字符串。
URL编码并不对字符进行编码,而是对代表这个字符的数值(真真编码值)进行编码。
Java中的URL编码
java.net.URLEncoder:String encode(String s, String enc) throws UnsupportedEncodingException
java.net.URLDecoder:String decode(String s, String enc) throws UnsupportedEncodingException
其中第一个参数指定了要进行URL编码或解码的字符串,第二个参数指定对字符串以哪种字符集编码进行URL编码或解码。encode方法首先将要URL编码的Unicode字符串转换成某种字符集编码的字节数组,然后对字节数组中的内容进行URL编码;decode方法首先将要进行URL解码的字符串解码成一个字节数组,然后将这个字节数组按照某种字符集编码转换成Unicode字符串。
浏览器中的URL编码
浏览器对FORM表单中输入的中文字符都会先进行URL编码,再传送给服务器。
对于页面中的FORM表单中输入的内容,浏览器将按照当前显示页面时所采用的字符集编码来进行URL编码。即浏览器将FORM表单中输入的内容转换成浏览器当前显示页面时所选择的字符集编码后,再对这些内容进行URL编码后传送给服务器。
getCharacterEncoding
返回请求消息中褓内容的字符集编码名称,如果请求消息中没有指定实体内容的字符编码名称,则返回null。
setCharacterEncoding
setCharacterEncoding(java.lang.String env) throws java.io.UnsupportedEncodingException
该方法用于覆盖请求消息中的实体内容的字符集编码名称的设置。getParameter和getReader方法将读取到的实体内容从字节数组形态转换成字符串返回时,都要参照请求消息中的实体内容的字符集编码名称,所以,setCharacterEncoding方法应早于getParameter和getReader方法之前进行调用。
getParameter方法的中文问题
对于HTTP请求消息的请求行中的URL地址后的参数,getParameter等方法进行URL解码时所采用的字符集编码在Servlet规范中没有明确规定,它由各个Servlet引擎厂商自行决定。对于这种情况,Tomcat中的ServletRequest对象的getParameter等方法默认采用ISO8859-1字符集编码进行URL解码,因此无法返回正确的中文参效信息。
对于POST方式下的“application/x-www-form-urlencoded”编码格式的实体内容,getPmemeter等方法以ServletRequest对象的getCharecterEncoding()方法返回的字符集编码对其进行URL解码。由于浏览器产生的HTTP请求消息中没有通过任何方式指定浏览器对实体内容进行URL编码时所采用的字符集编码,因此getCharacterEncoding()方法的返回值为null,所在在没有向request里设置字符编码时,ServletRequest对象的getParameter等方法将使用默认的IS08859-1字符集编码对实体内容中的参数进行URL解码,此时会出现乱码。
ServletRequest.seCharacterEncoding方法来设置请求消息中的实体内容的字符集偏码名称,getParameter方法将以该方法设置的字符集编码对实体内容进行URL解码。但是,应该注意一点:ServletRequest.setCharacterEncoding方法设置的是请求消息中的实体内容的字符集编码方式,它只对POST方式下的实体内容起作用,而对URL地址后绑定的参数不起效果。
Tomcat中URL编码问题
如果URL后附带有中文字符的参数,在服务器收到后它getParameter读取这个参数值时默认用ISO8859-1来解码,所以我们要想得到正常的字符,则应该如下做:
String param = new String(request.getParameter("parma").getBytes("iso8859-1"), "GB2312");
但在Tomcat中,我们也可以不必像上面那样麻烦地转来转去的。conf/server.xml中的<Connector>元素的URIEncoding属性用于指定URL后的附加参数的字符集编码,useBodyEncodingForURI属性则表示是否采用实体内容的字符集编码设置来代替URIEncoding的设置,也就是说,当useBodyEncodingForURI属性设置为true时,ServletRequest.setCharacterEncoding方法设置的字符集编码也将影响getParameter等方法对URL地址后的参数进行URL解码的结果。
<Connector port="8080" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" useBodyEncodingForURI="true" />
URL附带参数一定要使用URL编码
HTTP协议规定浏览器不能向服务器直接传递某些特殊字符,必须是这些字符进行URL编码后再传递。这有的人可以说“我试过,不进行URL编码与进行URL编码后的运行效果是一样的,对中文字符没有必要进行URL编码!”。如,现以如下Servlet:
public class ChinaeseServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String param = new String(request.getParameter("param").getBytes(
"iso8859-1"), "GB2312");
System.out.println("param = " + param);
}
}
我们在浏览器地址栏中直接输入如下地址(注,发送前请将浏览器的编码方式设置成GB2312):
http://localhost:8080/myapp/ChinaeseServlet?parma=中国
可以看见命令窗口显示正常。但上面的URL没有经过
但如果不象上面那亲直接从地址栏访问 /myapp/ChinaeseServlet ,比如是从另一个Servlet跳转到这个ChinaeseServlet,如下:
public class ChinaeseServletDispatcher extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/ChinaeseServlet?param=中国").forward(
request, response);
}
}
则从命令行可以看到显示的是乱码。因为在跳转之前,Servlet引擎会对URL进行URL编码,而URL编码前会将内存中的Unicode编码的“中国”转换成某种编码的Byte字节流,然后再在此字节流的基础之上进行URL编码(转换成%xx的形式),最后再跳转到ChinaeseServlet。而Servlet引擎在跳转前如果发现URL没有进行URL编码,则会先以ISO8859-1 编码,所以上面跳前就丢失信息了。下面使用java.net.URLEncoder来明确指定URL编码所使用的字符集:
public class ChinaeseServletDispatcher extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher(
"/ChinaeseServlet?param="
+ java.net.URLEncoder.encode("中国", "GB2312")).forward(
request, response);
}
}
再次运行时发现命令窗口又显示正常,所以最好使用URL编码。
对于浏览器,不管是以POST还是GET方式,表单里的元素都会经过浏览器URL编码后才传递给浏览器,但是如果在地址栏中输入URL并直接附上参数(或在js中直接在URL后拼接参数),则浏览器不会对它进行URL编码,而是直接对参数进行浏览器编码后直接传递给服务器,但可以通过JavaScript的encodeURIComponent函数进行URL编码。另外,经过上面的Servlet,可以看出Servlet在跳转前URL经过了Servlet引擎的URL编码。