目录
一般情况下,浏览器(客户端)通过 HTTP 协议来访问服务器的资源,Servlet 主要用来处理 HTTP 请求。Servlet 处理 HTTP 请求的流程如下:
- Servlet 容器接收到来自客户端的 HTTP 请求后,容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletResponse 对象。
- 容器将 HttpServletRequest 对象和 HttpServletResponse 对象以参数的形式传入 service() 方法内,并调用该方法。
- 在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息。
- 对 HTTP 请求进行处理。
- 请求处理完成后,将响应信息封装到 HttpServletResponse 对象中。
- Servlet 容器将响应信息返回给客户端。
- 当 Servlet 容器将响应信息返回给客户端后,HttpServletRequest 对象和 HttpServletResponse 对象被销毁。
通过以上流程可以看出, HttpServletRequest 和 HttpServletResponse 是 Servlet 处理 HTTP 请求过程中最重要的两个对象。前者用于封装 HTTP 请求信息,后者用于封装 HTTP 响应信息。
一、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 地址。 |
下面通过示例演示这些方法的使用,代码如下所示:
package com.hoperun.www;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/ReadRequestLine")
public class HttpServletRequestDemo extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write(
"请求方式:" + request.getMethod() + "<br/>" +
"URI:" + request.getRequestURI() + "<br/>" +
"请求字符串:" + request.getQueryString() + "<br/>" +
"应用名称(上下文):" + request.getContextPath() + "<br/>" +
"Servlet所映射的路径:" + request.getServletPath() + "<br/>" +
"客户端的 IP 地址:" + request.getRemoteAddr() + "<br/>" +
"客户端的完整主机名:" + request.getRemoteHost());
writer.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入http://localhost:8080/servletDemo/ReadRequestLine?a=1&b=2,访问 ReadRequestLine,结果如下图:
2、获取请求头信息
当浏览器发送请求时,需要通过请求头向服务器传递一些附加信息。例如客户端可以接收的数据类型、压缩方式、语言等。为了获取请求头中的信息, HttpServletRequest 接口定义了一系列用于获取 HTTP 请求头字段的方法,如下表所示:
返回值类型 | 方法 | 描述 |
String | getHeader(String name) | 该方法用于获取一个指定头字段的值。 如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。 |
Enumeration | getHeaders(String name) | 该方法返回指定头字段的所有值的枚举集合, 在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。 |
Enumeration | getHeaderNames() | 该方法返回请求头中所有头字段的枚举集合。 |
String | getContentType() | 该方法用于获取 Content-Type 头字段的值。 |
int | getContentLength() | 该方法用于获取 Content-Length 头字段的值。 |
String | getCharacterEncoding() | 该方法用于返回请求消息的字符集编码 。 |
下面通过示例演示这些方法的使用,代码如下所示:
package com.hoperun.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/ReadRequestHeader")
public class HttpServletRequestDemo extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
// 获取所有请求头字段的枚举集合
Enumeration<String> headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
// 获取请求头字段的值
String value = request.getHeader(headers.nextElement());
writer.write(headers.nextElement() + ":" + value + "<br/>");
}
writer.write("Content-Type:" + request.getContentType() + "<br/>");
writer.write("Content-Length:" + request.getContentLength() + "<br/>");
writer.write("CharacterEncoding:" + request.getCharacterEncoding() + "<br/>");
writer.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入http://localhost:8080/servletDemo/ReadRequestHeader,访问 ReadRequestHeader ,结果如下图:
3、获取 form 表单的数据
在实际开发中,我们经常需要获取用户提交的表单数据。例如用户名、密码等。为了方便获取表单中的请求参数,ServletRequest 定义了一系列获取请求参数的方法,如下表所示:
返回值类型 | 方法 | 描述 |
String | getParameter(String name) | 返回指定参数名的参数值。 |
String[] | getParameterValues (String name) | 以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。 |
Enumeration<String> | getParameterNames() | 以枚举集合的形式返回请求中所有参数名。 |
Map<String, String[]> | getParameterMap() | 用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。 |
示例:
1)在 servletDemo 的 Webapp 目录下,创建 form.html,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>收集个人信息</title>
</head>
<body>
<form action="/servletDemo/ReadRequestParam" method="post">
<table border="1">
<tr>
<td colspan="2" align="center">个人信息</td>
</tr>
<tr>
<td>输入姓名</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>输入密码</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td>选择性别</td>
<td>
<input type="radio" name="sex" value="male" />男
<input type="radio" name="sex" value="female" />女
</td>
</tr>
<tr>
<td>选择语言</td>
<td>
<input type="checkbox" name="language" value="JAVA" />JAVA
<input type="checkbox" name="language" value="C" />C语言
<input type="checkbox" name="language" value="PHP" />PHP
<input type="checkbox" name="language" value="Python" />Python
</td>
</tr>
<tr>
<td>选择城市</td>
<td>
<select name="city">
<option value="none">--请选择--</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
</select>
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交" />
</td>
</tr>
</table>
</form>
</body>
</html>
2)创建一个名为 ReadRequestParam 的 Servlet 类,代码如下:
package com.hoperun.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/ReadRequestParam")
public class HttpServletRequestDemo extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
// 获取姓名
String username = request.getParameter("username");
// 获取密码
String password = request.getParameter("password");
// 获取性别
String sex = request.getParameter("sex");
// 获取城市
String city = request.getParameter("city");
// 获取语言
String[] languages = request.getParameterValues("language");
writer.write(
"用户名:" + username + "<br/>" +
"密码:" + password + "<br/>" +
"性别:" + sex + "<br/>" +
"城市:" + city + "<br/>" +
"使用过的语言:" + Arrays.toString(languages));
writer.close();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入http://localhost:8080/servletDemo/form.html,即可访问 form.html,结果如下图:
在表单中填写信息后,点击提交,结果如下图:
4、request 中文乱码问题
如果在 form 表单输入框中输入中文。例如:输入姓名时,填写"用户名",点击提交后就会出现乱码问题。对于GET 请求和 POST 请求,这两种请求方式都可能会产生中文乱码问题,下面我们分别对它们产生乱码的原因及其解决方案进行介绍:
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 种解决方案。
- 修改 tomcat/conf/server.xml 中的配置,代码如下:
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>
- 使用 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");
- 使用 String 的构造方法:String(byte[] bytes, String charset) ,对字节数组(bytes)按照指定的字符集(charset)进行解码,返回解码后的字符串,解决乱码问题(推荐使用),代码如下:
// 获取姓名
String username = request.getParameter("username");
// 使用String的构造方法解决乱码的问题
username = new String(username.getBytes("ISO-8859-1"), "UTF-8");
二、HttpServletResponse接口详解
在 Servlet API 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应信息,简称 response 对象。
Servlet 容器会针对每次请求创建一个 response 对象,并把它作为参数传递给 Servlet 的 service 方法。Servlet 处理请求后,会将响应信息封装到 response 对象中,并由容器解析后返回给客户端。
HTTP 响应消息由响应行、响应头、消息体三部分组成,所以 HttpServletResponse 接口中定义了向客户端发送响应状态码、响应头、响应体的方法,下面我们对这些方法进行介绍。
1、响应行相关的方法
当 Servlet 返回响应消息时,需要在响应消息中设置状态码。因此,HttpServletResponse 接口定义了发送状态码的方法,如下表:
返回值类型 | 方法 | 描述 |
void | setStatus(int sc) | 用于设置 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 异常。
启动 Tomat 服务器,在客户端浏览器地址栏输入http://localhost:8080/servletDemo/testHttpServletResponse,访问 testHttpServletResponse,结果如下图:
在 doGet() 方法中调用 output2() 方法,使用字符流输出响应内容到浏览器,再次访问 testHttpServletResponse,结果如下图:
4、response 中文乱码问题
response 对象向页面输出时有两种方式:字节流、字符流,这两种方式输出中文时都有可能出现乱码。
1)使用字节流输出中文
使用字节流向页面输出中文是否会出现乱码问题?不一定。
ServletOutputStream outptuStream = response.getOutputStream();
outputStream.write("你好 www.csdn.net".getBytes());
乱码原因:
字节流输出中文是否出现乱码,取决于中文转成字节数组时与浏览器打开时采用的字符集是否一致。若两者保持一致,则不会出现乱码问题,若不一致就会出现乱码问题。
解决方案:
将中文转成字节数组时和浏览器默认采用的字符集保持一致即可,代码如下:
response.setHeader("Content-Type", "text/html;charset=UTF-8");
// 获取字节输出流
OutputStream os = response.getOutputStream();
byte[] str = "你好 www.csdn.net".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.csdn.net");
第二种方式:
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write("你好 www.csdn.net");