Servlet系列(二):常用接口

HttpServletRequest接口

由本系列前一篇文章我们可以发现,Servlet的开发就是针对不同的url编写对应的Servlet,这个Servlet一般只需要继承自HttpServlet这个抽象类。这个抽象类针对不同的Http请求提供了不同的函数进行处理,如doGet()doPost()等。这些函数都有HttpServletRequestHttpServletResponse类型的两个参数,通过前者我们能够获取用户请求相关的信息,通过后者我们能够将请求的处理结果或数据库内容进行返回。

HttpServletRequest 接口继承自 ServletRequest 接口。HttpServletRequest 对象专门用于封装 HTTP 请求消息,简称 request 对象。

  • 常用方法
返回类型 方法名 描述
String getMethod() 获取Http请求方式
String getRemoteAddr() 获取客户端的 IP 地址。
String getQueryString() 获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。
String getHeader(String name) 该方法用于获取一个指定头字段的值。如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。
Enumeration getHeaderNames() 返回请求头中所有头字段的枚举集合。
String getParameter(String name) 返回指定参数名的参数值。
Enumeration getParameterNames() 以枚举集合的形式返回请求中所有参数名。
Map getParameterMap() 将请求中的所有参数名和参数值装入一个 Map 对象中返回。
HttpSession getSession() 获取一个HttpSession对象,当请求不存在Session对象时创建一个并返回
Cookie[] getCookies() 返回该请求包含的所有Cookie对象的一个数组

以上只列出了常用方法,所有的方法参见文档:https://docs.oracle.com/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/javax/servlet/http/HttpServletRequest.html

HttpServletResponse接口

HttpServletResponse 接口继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应消息,简称 response 对象。

  • 常用方法
返回类型 方法名 描述
void setStatus(int status) 设置 HTTP 响应消息的状态码,并生成响应状态行。
void sendError(int sc) 使用指定的状态码发送错误响应
void addHeader(String name,String value) 增加响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
void addIntHeader(String name,int value) 同上,但增加的响应头字段值为int类型
void setHeader (String name,String value) 设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
void setIntHeader(String name, int value) 同上,但设置的响应头字段值为int类型
void setContentType(String type) 设置 Servlet 输出内容的 MIME 类型以及编码格式。
void setCharacterEncoding(String charset) 设置输出内容使用的字符编码。
PrintWriter getWriter() 获取字符输出流对象
ServletOutputStream getOutputStream() 获取字节输出流对象
void addCookie(Cookie cookie) 添加cookie对象

中文乱码问题

request请求中文乱码

为了对request请求乱码进行测试,编写如下Servlet,在Post请求和Get请求中均读取名为parameter1parameter2的两个参数并在控制台输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HelloServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String parameter1 = req.getParameter("parameter1");
String parameter2 = req.getParameter("parameter2");
System.out.println( "接收到Get请求参数parameter1:" + parameter1);
System.out.println("接收到Get请求参数parameter2:" + parameter2);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String parameter1 = req.getParameter("parameter1");
String parameter2 = req.getParameter("parameter2");
System.out.println("接收到Post请求参数parameter1:" + parameter1);
System.out.println("接收到Post请求参数parameter2:" + parameter2);
}
}
  • Post请求

我们构建如下表单向服务器发送带有中文参数的Post请求进行验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/hello" method="post">
<input type="text" name="parameter1">
<input type="text" name="parameter2">
<input type="submit" value="提交">
</form>
</body>
</html>

当输入参数为「中文」和「Hello」时,控制台输出如下:
输出1

果然产生了乱码,这是因为用户浏览器默认编码为utf-8,当用户通过Post请求提交的参数中包含中文时,Tomcat服务器采用默认编码方式ISO-8859-1进行解码,这时控制台接收到的数据为乱码数据。

解决的办法就是在接收参数前设置参数的编码方式为utf-8

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
// 设置请求的编码方式
req.setCharacterEncoding("utf-8");

String parameter1 = req.getParameter("parameter1");
String parameter2 = req.getParameter("parameter2");
System.out.println("接收到Post请求参数parameter1:" + parameter1);
System.out.println("接收到Post请求参数parameter2:" + parameter2);
}
  • Get请求

Get请求的参数出现在请求的url中,即?后面的部分,我们很容易构造出如下带有中文的Get请求:

1
http://localhost:8080/HelloServlet_war_exploded/hello?parameter1=中文&parameter2=hello

结果是并没有出现乱码:
输出2

这是因为我电脑上的Tomcat版本为9,而Tomcat开发团队在8.x版本时就在底层设计上解决了Get请求中文乱码问题,即使请求参数中有中文也不会乱码了!

response中文乱码

  • 通过字符流输出中文

尝试直接向PrintWriter中写入中文输出:

1
2
3
4
5
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getWriter().println("你好世界");
}

出现乱码:

输出3

出现乱码的原因为:字符流输出内容存放在response的缓存区,而这个缓存区的默认字符编码为ISO-8859-1,因此在用户浏览器使用utf-8解码时出现乱码。

解决方法很简单,让response编码方式为utf-8并通知浏览器用utf-8方式解码即可:

1
2
3
4
5
6
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().println("你好世界");
}
  • 通过字节流输出中文
1
2
3
4
5
6
7
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
// 不一定乱码
OutputStream outputStream = resp.getOutputStream();
outputStream.write("你好世界".getBytes());
}

使用字节流输出中文并不一定会乱码,这取决于中文转为字节数组时与浏览器打开采用的字符集是否一致。更加稳健的解决方式是采用utf-8编码,并通知浏览器以该方式解码:

1
2
3
4
5
6
7
8
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setHeader("Content-Type", "text/html;charset=UTF-8");
OutputStream os = resp.getOutputStream();
byte[] str = "你好世界".getBytes("UTF-8");
os.write(str);
}

请求转发与重定向

请求转发

处理客户端请求时,有时需要多个资源相互配合,Servlet可以对请求进行一些处理之后将其传递给其它Web资源进行后续处理和响应,这种发生在服务器内部的传递行为称为「请求转发」。

请求转发需要通过RequestDispatcher接口,该接口的实例可以通过调用ServletRequest getRequestDispatcher(String path)方法获得,参数用于指定目标资源的路径。

RequestDispatcher提供了以下两个方法用于处理转发,通常调用forward方法:

1
2
3
4
5
// 将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常
void forward(ServletRequest request,ServletResponse response);

// 将其他的资源作为当前响应内容包含进来
void include(ServletRequest request,ServletResponse response);

需要注意:

  • 请求转发不支持跨域访问,只能访问当前应用的资源。
  • 请求转发浏览器地址栏URL不会发生变化,即用户无法知道服务器的转发行为。
  • 参与请求转发的Web资源共享同一request 对象和 response 对象。
  • 由于 forward() 方法会先清空 response 缓冲区,因此只有转发到最后一个 Web 资源时,生成的响应才会被发送到客户端。

实现转发很简单:

1
2
3
4
5
6
7
8
9
10
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
// ....这里做一些请求的处理工作...

// 转发至下一个Servlet进行进一步处理
// 共用req和resq
req.getRequestDispatcher("/DoServlet").forward(req, resp);
}

实际上request对象是Servlet三大域对象之一(这些域对象将在后续进行总结),这意味着在request对象上绑定一些属性,即在request对象上携带一些对象,配合转发能够实现数据的传递。这主要通过以下request对象上的方法实现:

1
2
3
4
5
6
7
8
// 给request对象绑定一个属性,名称为name,属性值为对象o
void setAttribute(String name, Object o);

// 从request对象上获取名为name的属性
Object getAttribute(String name);

// 移除request对象上名为name的属性
void removeAttribute(String name);

重定向

相比于请求转发,重定向是客户端的行为,服务器接收到客户端请求之后,告诉客户端重新向另一个URL发送请求,这个过程为重定向。重定向本质上是两次HTTP请求,故浏览器地址栏的URL会发生变化。

通过response.sendRedirect(String location)方法实现重定向:

1
2
3
4
5
6
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
// 重定向
resp.sendRedirect("https://www.baidu.com");
}

总结

本文首先介绍了request对象和response对象上的一些方法,然后归纳了Servlet请求和响应时容易出现的中文乱码问题,最后总结了转发和重定向之间的区别和实现方法。

参考文献

  1. C语言中文网Servlet教程
  2. Servlet文档