Web基础知识(5)- Java Servlet (五) | 静态资源、HttpServletRequest、HttpServletResponse、请求转发和重定向
1. 处理静态资源的Servlet
通过链接来访问应用内的资源文件,例如 *.jpg、*.html、*.js 这类的静态文件。这就需要用到 DefaultServlet,它在tomat 的安装目录下的 conf/web.xml 中的定义,如下:
1 <servlet> 2 <servlet-name>default</servlet-name> 3 <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> 4 <init-param> 5 <param-name>debug</param-name> 6 <param-value>0</param-value> 7 </init-param> 8 <init-param> 9 <param-name>listings</param-name> 10 <param-value>false</param-value> 11 </init-param> 12 <load-on-startup>1</load-on-startup> 13 </servlet>
修改 webapp/WEB-INF/web.xml,添加如下配置:
1 <web-app> 2 3 <!-- 处理 html/js/css/jpg/png 文件 --> 4 <servlet-mapping> 5 <servlet-name>default</servlet-name> 6 <url-pattern>*.html</url-pattern> 7 <url-pattern>*.js</url-pattern> 8 <url-pattern>*.css</url-pattern> 9 <url-pattern>*.jpg</url-pattern> 10 <url-pattern>*.png</url-pattern> 11 </servlet-mapping> 12 13 </web-app>
把 *.html、*.js、*.css、*.jpg 和 *.png 等文件放到 webapp 目录或其子目录下,即可通过浏览器访问这些静态资源。
2. HttpServletRequest接口
在 Servlet API 中,定义了一个 HttpServletRequest 接口,它继承自 ServletRequest 接口。HttpServletRequest 对象专门用于封装 HTTP 请求消息,简称 request 对象。
HTTP 请求消息分为请求行、请求消息头和请求消息体三部分,所以 HttpServletRequest 接口中定义了获取请求行、请求头和请求消息体的相关方法。
1) 获取请求行信息
HTTP 请求的请求行中包含请求方法、请求资源名、请求路径等信息,HttpServletRequest 接口定义了一系列获取请求行信息的方法,如下:
方法 | 描述 |
String getMethod() | 该方法用于获取 HTTP 请求方式(如 GET、POST 等)。 |
String getRequestURI() | 该方法用于获取请求行中的资源名称部分,即位于 URL 的主机和端口之后,参数部分之前的部分。 |
String getQueryString() | 该方法用于获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。 |
String getContextPath() | 返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""。 |
String getServletPath() | 该方法用于获取 Servlet 所映射的路径。 |
String getRemoteAddr() | 该方法用于获取客户端的 IP 地址。 |
String getRemoteHost() | 该方法用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址。 |
2) 获取请求头信息
当浏览器发送请求时,需要通过请求头向服务器传递一些附加信息,例如客户端可以接收的数据类型、压缩方式、语言等。为了获取请求头中的信息, HttpServletRequest 接口定义了一系列用于获取 HTTP 请求头字段的方法,如下:
方法 | 描述 |
String getHeader(String name) | 该方法用于获取一个指定头字段的值。如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。 |
Enumeration getHeaders(String name) | 该方法返回指定头字段的所有值的枚举集合,在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。 |
Enumeration getHeaderNames() | 该方法返回请求头中所有头字段的枚举集合。 |
String getContentType() | 该方法用于获取 Content-Type 头字段的值。 |
int getContentLength() | 该方法用于获取 Content-Length 头字段的值 。 |
String getCharacterEncoding() | 该方法用于返回请求消息的字符集编码 。 |
3) 获取 form 表单的数据
在实际开发中,我们经常需要获取用户提交的表单数据,例如用户名和密码等。为了方便获取表单中的请求参数,ServletRequest 定义了一系列获取请求参数的方法,如下:
方法 | 描述 |
String getParameter(String name) | 返回指定参数名的参数值。 |
String[] getParameterValues(String name) | 以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。 |
Enumeration getParameterNames() | 以枚举集合的形式返回请求中所有参数名。 |
Map getParameterMap() | 用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。 |
在 webapp 目录或子目录下,新建 test.html 文件:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test Form</title> 5 </head> 6 <body> 7 <h3>Test Form</h3> 8 <p> </p> 9 10 <form action="/test" method="post"> 11 <p>Username: <input type="text" name="username" /></p> 12 <p>Email: <input type="text" name="email" /></p> 13 <p><input type="submit" value="Submit" /></p> 14 </form> 15 16 </body> 17 </html>
4) 中文乱码问题
(1) POST 请求
乱码的原因:POST 提交的数据在请求体中,其所使用的编码格式时页面一致(即 utf-8)。request 对象接收到数据之后,会将数据放到 request 缓冲区,缓冲区的默认字符集是 ISO-8859-1(该字符集不支持中文),两者使用的字符集不一致导致乱码。
解决方案:在获取请求参数之前设置 request 缓冲区字符集为 utf-8 ,代码如下。
// 修改request缓冲区的字符集为UTF-8
request.setCharacterEncoding("utf-8");
String username = request.getParameter("username");
(2) GET 请求
乱码的原因:Get 请求将请求数据附加到 URL 后面作为参数,浏览器发送文字时采用的编码格式与页面编码保持一致(utf-8)。如果 Tomcat 没有设置字符集,接收 URL 时默认使用 ISO-8859-1 进行解码,ISO-8859-1 不兼容中文,无法正确解码,导致出现乱码。
需要注意的是,在 Tomcat 8 中已解决了 get 方式提交请求中文乱码的问题,使用 Tomcat 8 及以上版本的同学不必再考虑此问题了,如果您使用的是 Tomcat 7 或更早的版本,出现乱码问题可以使用如下的方案解决。
解决方案:解决 GET 请求中文乱码问题,有以下 3 种解决方案。
a) 修改 tomcat/conf/server.xml 中的配置,代码如下。
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>
b) 使用 URLEncoder 和 URLDecoder 进行编码和解码的操作(逆向编解码)。
// 得到TOMCAT通过ISO8859-1解码的字符串
String username = request.getParameter("username");
// 对字符串使用ISO8859-1进行编码,得到最初浏览器使用UTF-8编码的字符串
username = URLEncoder.encode(username, "ISO8859-1");
// 将使用UTF-8编码的字符串使用UTF-8进行解码,得到正确的字符串
username = URLDecoder.decode(username, "UTF-8");
c) 使用 String 的构造方法:String(byte[] bytes, String charset) ,对字节数组(bytes)按照指定的字符集(charset)进行解码,返回解码后的字符串,解决乱码问题(推荐使用)。
String username = request.getParameter("username");
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");
3. HttpServletResponse接口
在 Servlet API 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应消息,简称 response 对象。
Servlet 容器会针对每次请求创建一个 response 对象,并把它作为参数传递给 Servlet 的 service 方法。Servlet 处理请求后,会将响应信息封装到 response 对象中,并由容器解析后返回给客户端。
由于 HTTP 响应消息由响应行、响应头、消息体三部分组成,所以 HttpServletResponse 接口中定义了向客户端发送响应状态码、响应头、响应体的方法,下面我们将针对这些方法进行介绍。
1) 响应行相关的方法
当 Servlet 返回响应消息时,需要在响应消息中设置状态码。因此,HttpServletResponse 接口定义了发送状态码的方法,如下表。
方法 | 描述 |
void setStatus(int status) | 用于设置 HTTP 响应消息的状态码,并生成响应状态行。 |
void sendError(int sc) | 用于发送表示错误信息的状态码。 |
2) 响应头相关的方法
HttpServletResponse 接口中定义了一系列设置 HTTP 响应头字段的方法,如下表所示。
方法 | 描述 |
void addHeader(String name,String value) | 用于增加响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。 |
void setHeader (String name,String value) | 用于设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。 |
void addIntHeader(String name,int value) | 用于增加值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。 |
void setIntHeader(String name, int value) | 用于设置值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。 |
void setContentType(String type) | 用于设置 Servlet 输出内容的 MIME 类型以及编码格式。 |
void setCharacterEncoding(String charset) | 用于设置输出内容使用的字符编码。 |
3) 响应体相关的方法
由于在 HTTP 响应消息中,大量的数据都是通过响应消息体传递的。因此 ServletResponse 遵循以 I/O 流传递大量数据的设计理念,在发送响应消息体时,定义了两个与输出流相关的方法。
方法 | 描述 |
ServletOutputStream getOutputStream() | 用于获取字节输出流对象。 |
PrintWriter getWriter() | 用于获取字符输出流对象。 |
注意:getOutputStream() 和 getWriter() 方法互相排斥,不可同时使用,否则会发生 IllegalStateException 异常。
4) 中文乱码问题
(1) 使用字节流输出中文
字节流输出格式:
ServletOutputStream outptuStream = response.getOutputStream();
outputStream.write(“中文乱码问题 www.test.com”.getBytes());
乱码原因:字节流输出中文是否出现乱码,取决于中文转成字节数组时与浏览器打开时采用的字符集是否一致。若两者保持一致,则不会出现乱码问题,若不一致就会出现乱码问题。
解决方案:将中文转成字节数组时和浏览器默认采用的字符集保持一致即可,代码如下。
response.setHeader("Content-Type", "text/html;charset=UTF-8");
// 获取字节输出流
OutputStream os = response.getOutputStream();
byte[] str = "中文乱码问题 www.test.com".getBytes("UTF-8");
// 输出中文
os.write(str);
(2)使用字符流输出中文
乱码原因:通过字符流输出的内容是存放在 response 缓冲区的,response 缓冲区的默认字符集是 ISO-8859-1,该字符集不支持中文。
解决方案:将 response 缓冲区和浏览器采用的字符集保持一致即可,有如下 2 种的方式。
第一种方式:
// 设置response缓冲区的编码
response.setCharacterEncoding("UTF-8");
// 设置浏览器打开文件所采用的编码
response.setHeader("Content-Type", "text/html;charset=UTF-8");
// 输出中文
response.getWriter().write("中文乱码问题 www.test.com");
第二种方式:
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("中文乱码问题 www.test.com");
4. Servlet请求转发
Web 应用在处理客户端的请求时,经常需要多个 Web 资源共同协作才能生成响应结果。但由于 Serlvet 对象无法直接调用其他 Servlet 的 service() 方法,所以 Servlet 规范提供了 2 种解决方案:
请求转发
请求包含(了解即可)
1)请求转发
请求转发属于服务器行为。容器接收请求后,Servlet 会先对请求做一些预处理,然后将请求传递给其他 Web 资源,来完成包括生成响应在内的后续工作。
(1)RequestDispatcher 接口
javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。
Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:
调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。
绝对路径是指以符号“/”开头的路径,“/”表示当前 Web 应用的根目录。相对路径是指相对当前 Web 资源的路径,不以符号“/”开头。
RequestDispatcher 接口中提供了以下方法。
方法 | 描述 |
void forward(ServletRequest request,ServletResponse response) | 用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常 |
void include(ServletRequest request,ServletResponse response) | 用于将其他的资源作为当前响应内容包含进来 |
使用方式:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/next").forward(request, response);
}
(2)请求转发的工作原理
在 Servlet 中,通常使用 forward() 方法将当前请求转发给其他的 Web 资源进行处理。请求转发的工作原理如下图所示。
请求转发具有以下特点:
a) 请求转发不支持跨域访问,只能跳转到当前应用中的资源。
b) 请求转发之后,浏览器地址栏中的 URL 不会发生变化,因此浏览器不知道在服务器内部发生了转发行为,更无法得知转发的次数。
c) 参与请求转发的 Web 资源之间共享同一 request 对象和 response 对象。
d) 由于 forward() 方法会先清空 response 缓冲区,因此只有转发到最后一个 Web 资源时,生成的响应才会被发送到客户端。
2) request 域对象
request 是 Servlet 的三大域对象之一,它需要与请求转发配合使用,才可以实现动态资源间的数据传递。
在 ServletRequest 接口 (HttpServletRequest 继承了 ServletRequest)中定义了一系列操作属性的方法,如下表。
方法 | 描述 |
void setAttribute(String name, Object o) | 将 Java 对象与属性名绑定,并将它作为一个属性存放到 request 对象中。参数 name 为属性名,参数 object 为属性值。 |
Object getAttribute(String name) | 根据属性名 name,返回 request 中对应的属性值。 |
void removeAttribute(String name) | 用于移除 request 对象中指定的属性。 |
Enumeration getAttributeNames() | 用于返回 request 对象中的所有属性名的枚举集合。 |
Context 域对象和 request 域对象对比,具有以下 4 点差异:
(1) 生命周期不同
Context 域对象的生命周期从容器启动开始,到容器关闭或者 Web 应用被移除时结束;
request 域对象的生命周期从客户端向容器发送请求开始,到对这次请求做出响应后结束。
(2) 作用域不同
Context 域对象对整个 Web 应用内的所有Servlet都有效;
request 域对象只对本次请求涉及的 Servlet 有效。
(3) Web 应用中数量不同
整个 Web 应用中只有一个 Context 域对象;
由于 Servlet 能处理多个请求,因此 Web 应用中的每个 Servlet 实例都可以有多个 request 域对象。
(4) 实现数据共享的方式不同
Context 域对象可以独立完成动态资源之间的数据共享;
Request 域对象需要与请求转发配合使用才能实现动态资源之间的数据共享。
5. Servlet重定向
重定向属于客户端行为。服务器在收到客户端请求后,会通知客户端浏览器重新向另外一个 URL 发送请求,这称为请求重定向。它本质上是两次 HTTP 请求,对应两个 request 对象和两个 response 对象。
1) 重定向的工作流程
(1) 用户在浏览器中输入 URL,请求访问服务器端的 Web 资源。
(2) 服务器端的 Web 资源返回一个状态码为 302 的响应信息,该响应的含义为:通知浏览器再次发送请求,访问另一个 Web 资源(在响应信息中提供了另一个资源的 URL)。
(3) 当浏览器接收到响应后,立即自动访问另一个指定的 Web 资源。
(4) 另一 Web 资源将请求处理完成后,由容器把响应信息返回给浏览器进行展示。
2) 转发和重定向的区别
转发和重定向都能实现页面的跳转,但是两者也存在以下区别。
区别 | 转发 | 重定向 |
浏览器地址栏 URL 是否发生改变 | 否 | 是 |
是否支持跨域跳转 | 否 | 是 |
请求与响应的次数 | 一次请求和一次响应 | 两次请求和两次响应 |
是否共享 request 和 response 对象 | 是 | 否 |
是否能通过 request 域对象传递数据 | 是 | 否 |
速度 | 相对快 | 相对慢 |
行为类型 | 服务器行为 | 客户端行为 |
3) response.sendRedirect()
HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向。
void sendRedirect(String location);
向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。
使用方式:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.sendRedirect("/next");
}
示例:
1 package com.example; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import java.io.OutputStream; 6 import java.util.Enumeration; 7 8 import javax.servlet.ServletConfig; 9 import javax.servlet.ServletException; 10 import javax.servlet.http.HttpServlet; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 14 public class TestServlet extends HttpServlet { 15 16 protected void doGet(HttpServletRequest request, HttpServletResponse response) 17 throws ServletException, IOException { 18 19 response.setContentType("text/html;charset=UTF-8"); 20 PrintWriter writer = response.getWriter(); 21 22 ServletConfig config = getServletConfig(); 23 writer.write(config.getServletName() + " -> doGet()<br/>"); 24 writer.println("<br/>"); 25 26 writer.println("Method: " + request.getMethod() + "<br/>" + 27 "Remote IP: " + request.getRemoteAddr() + "<br/>" + 28 "Context Path: " + request.getContextPath() + "<br/>" + 29 "URI: " + request.getRequestURI() + "<br/>" + 30 "Query String: " + request.getQueryString() + "<br/>" + 31 "Servlet Path: " + request.getServletPath() + "<br/>" + 32 "Remote Host: " + request.getRemoteHost() + "<br/>" 33 ); 34 writer.println("<br/>"); 35 36 Enumeration<String> headers = request.getHeaderNames(); 37 while (headers.hasMoreElements()) { 38 String value = request.getHeader(headers.nextElement()); 39 writer.write(headers.nextElement() + ":" + value + "<br/>"); 40 } 41 writer.println("<br/>"); 42 writer.close(); 43 44 } 45 46 protected void doPost(HttpServletRequest request, HttpServletResponse response) 47 throws ServletException, IOException { 48 request.setCharacterEncoding("utf-8"); 49 response.setContentType("text/html;charset=UTF-8"); 50 51 String username = request.getParameter("username"); 52 String email = request.getParameter("email"); 53 54 String str = "Username(用户名):" + username + "<br/>" + "Email(电子邮件):" + email + "<br/>"; 55 OutputStream os = response.getOutputStream(); 56 os.write(str.getBytes("UTF-8")); 57 } 58 }