目录
(2)从HttpServletRequest 获取请求头信息
(3)从HttpServletRequest获取 form 表单的数据
5、Tomcat 中创建 HttpServletRequest 对象的源码分析
6、Servlet 容器在接收到客户端请求时会创建哪些对象?
1、Servlet 处理 HTTP 请求的流程
一般情况下,浏览器(客户端)通过 HTTP 协议来访问服务器的资源,Servlet 主要用来处理 HTTP 请求。Servlet 处理 HTTP 请求的流程如下:
- Servlet 容器接收到来自客户端的 HTTP 请求后,Servlet容器会针对该请求分别创建一个 HttpServletRequest 对象和 HttpServletReponse 对象。
- 容器将 HttpServletRequest 对象和 HttpServletReponse 对象以参数的形式传入 service() 方法内,并调用该方法。
- 在 service() 方法中 Servlet 通过 HttpServletRequest 对象获取客户端信息以及请求的相关信息。
- 对 HTTP 请求进行处理。
- 请求处理完成后,将响应信息封装到 HttpServletReponse 对象中。
- Servlet 容器将响应信息返回给客户端。
- 当 Servlet 容器将响应信息返回给客户端后,HttpServletRequest 对象和 HttpServletReponse 对象被销毁。
通过以上流程可以看出, HttpServletRequest 和 HttpServletReponse 是 Servlet 处理 HTTP 请求流程中最重要的两个对象。HttpServletRequest 对象用于封装 HTTP 请求信息,HttpServletReponse 对象用于封装 HTTP 响应信息。
下面我们将对 HttpServletRequest 和 ServletRequest 接口进行详细介绍。
2、ServletRequest 接口
ServletRequest接口是Java Servlet API中的一个接口,用于访问Servlet容器接收到的HTTP请求的信息。ServletRequest接口定义了一系列方法,用于获取HTTP请求的参数、属性、输入流等信息,以及对这些信息的操作。以下是ServletRequest接口中的一些具体方法:
getProtocol()
:返回请求使用的协议的名称和版本号。例如,HTTP/1.1。getScheme()
:返回请求的协议名称。例如,http、https。getServerName()
:返回接收请求的服务器的名称。getServerPort()
:返回接收请求的服务器的端口号。getRemoteAddr()
:返回客户端的IP地址。getRemoteHost()
:返回客户端的主机名。getRemotePort()
:返回客户端的端口号。getLocalAddr()
:返回服务器的IP地址。getLocalName()
:返回服务器的主机名。getLocalPort()
:返回服务器的端口号。getParameter(String name)
:返回请求参数的值,如果请求参数不存在,则返回null。getParameterNames()
:返回请求参数的名称的枚举。getParameterValues(String name)
:返回请求参数的值的数组,如果请求参数不存在,则返回null。getAttribute(String name)
:返回指定属性名称的属性值,如果属性不存在,则返回null。getAttributeNames()
:返回所有属性名称的枚举。setAttribute(String name, Object value)
:将指定属性名称的属性值设置为指定的值。removeAttribute(String name)
:从请求中删除指定名称的属性。getLocale()
:返回客户端的首选语言环境。getLocales()
:返回客户端的所有语言环境。getCharacterEncoding()
:返回请求字符编码的名称,如果字符编码未指定,则返回null。getContentLength()
:返回请求的正文的长度,以字节为单位。getContentType()
:返回请求正文的MIME类型。getInputStream()
:返回请求正文的InputStream。getReader()
:返回请求正文的Reader。getProtocol()
:返回请求使用的协议的名称和版本号。getRemoteUser()
:返回发出请求的用户的登录名,如果用户未经过身份验证,则返回null。isSecure()
:返回请求是否通过安全连接传输。getRequestDispatcher(String path)
:返回用于将请求转发到另一个资源的RequestDispatcher对象。getRealPath(String path)
:返回指定虚拟路径的真实路径。getRequestURI()
:返回请求的URI,不包括查询字符串。getRequestURL()
:返回请求的URL,包括协议,服务器名称,端口号和请求URI,但不包括查询字符串。getServletPath()
:返回Servlet的路径。getSession()
:返回与此请求相关联的会话,如果请求没有会话,则创建一个新会话。getSession(boolean create)
:返回与此请求相关联的会话,如果请求没有会话,则根据指定的create参数创建一个新会话或不创建。isRequestedSessionIdValid()
:返回请求的会话ID是否仍然有效。isRequestedSessionIdFromCookie()
:返回请求是否使用Cookie来检索会话ID。isRequestedSessionIdFromURL()
:返回请求是否将会话ID附加到URL中。isRequestedSessionIdFromUrl()
:已过时的方法,等效于isRequestedSessionIdFromURL()。
ServletRequest接口提供了访问HTTP请求的各种信息和操作的方法,方便在开发中获取HTTP请求的相关参数和属性。在实际开发中,我们通常会使用HttpServletRequest接口,它继承了ServletRequest接口,并增加了更多HTTP特定的请求和响应信息的方法。
// ServletRequest 主要用于封装HTTP请求的信息,所以想要从请求中获取信息,得从ServletRequest 中去寻找。这点很重要
3、HttpServletRequest 接口
在 Servlet API 中,定义了一个 HttpServletRequest 接口,它继承自 ServletRequest 接口。HttpServletRequest 专门用于封装 HTTP 请求消息,该接口在ServletRequest接口的基础上增加了许多HTTP特定的方法,以提供更多的HTTP请求和响应相关的信息。
以下是HttpServletRequest相对于ServletRequest新增的一些方法:
getMethod()
:返回HTTP请求的方法类型(GET、POST等)。getPathInfo()
:返回HTTP请求的路径信息。getPathTranslated()
:返回HTTP请求的路径翻译后的信息。getQueryString()
:返回HTTP请求的查询字符串。getContentLength()
:返回HTTP请求的主体内容长度。getContentType()
:返回HTTP请求的主体内容类型。getServletPath()
:返回HTTP请求的Servlet路径。getRequestURI()
:返回HTTP请求的统一资源标识符(URI)。getRequestURL()
:返回HTTP请求的统一资源定位器(URL)。getProtocol()
:返回HTTP请求的协议名称和版本号。getRemoteAddr()
:返回HTTP请求的客户端IP地址。getRemoteHost()
:返回HTTP请求的客户端主机名。getServerName()
:返回HTTP请求的服务器名称。getServerPort()
:返回HTTP请求的服务器端口号。getScheme()
:返回HTTP请求使用的协议名称(http、https等)。isSecure()
:返回HTTP请求是否通过安全套接字层(SSL)进行传输。
ServletRequest接口提供了通用的请求信息的访问方法,而HttpServletRequest接口主要是为了提供更多HTTP特定的请求信息,方便在开发中处理HTTP请求和响应。在实际开发中,我们一般会使用HttpServletRequest接口来访问HTTP请求的相关信息。
HTTP 请求消息分为请求行、请求消息头和请求消息体三部分,所以 HttpServletRequest 接口中定义了获取请求行、请求头和请求消息体的相关方法。
(1)从HttpServletRequest获取请求行信息
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 地址。 |
为了更好地理解这些方法,下面通过案例演示这些方法的使用。创建一个名为 RequestLine 的 Servlet 类,代码如下:
@WebServlet("/RequestLine")
public class RequestLine 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.println("请求方式:" + request.getMethod() + "<br/>" +
"客户端的 IP 地址:" + request.getRemoteAddr() + "<br/>" +
"应用名字(上下文):" + request.getContextPath() + "<br/>" +
"URI:" + request.getRequestURI() + "<br/>" +
"请求字符串:" + request.getQueryString() + "<br/>" +
"Servlet所映射的路径:" + request.getServletPath() + "<br/>" +
"客户端的完整主机名:" + request.getRemoteHost() + "<br/>"
);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/web/RequestLine?a=1&b=2”,访问 RequestLine,结果如下图。
(2)从HttpServletRequest 获取请求头信息
当浏览器发送请求时,需要通过请求头向服务器传递一些附加信息,例如客户端可以接收的数据类型、压缩方式、语言等。为了获取请求头中的信息, HttpServletRequest 接口定义了一系列用于获取 HTTP 请求头字段的方法,如下表所示。
返回值类型 | 方法声明 | 描述 |
---|---|---|
String | getHeader(String name) | 该方法用于获取一个指定头字段的值。 如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。 |
Enumeration | getHeaders(String name) | 该方法返回指定头字段的所有值的枚举集合, 在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。 |
Enumeration | getHeaderNames() | 该方法返回请求头中所有头字段的枚举集合。 |
String | getContentType() | 该方法用于获取 Content-Type 头字段的值。 |
int | getContentLength() | 该方法用于获取 Content-Length 头字段的值 。 |
String | getCharacterEncoding() | 该方法用于返回请求消息的字符集编码 。 |
为了更好地理解这些方法,下面通过案例演示这些方法的使用。创建一个名为 RequestHeader 的 Servlet 类,代码如下:
@WebServlet("/RequestHeader")
public class RequestHeader 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/>");
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/web/RequestHeader”,访问 RequestHeader ,结果如下图。
(3)从HttpServletRequest获取 form 表单的数据
在实际开发中,我们经常需要获取用户提交的表单数据,例如用户名和密码等。为了方便获取表单中的请求参数,ServletRequest 定义了一系列获取请求参数的方法,如下表所示。
返回值类型 | 方法声明 | 功能描述 |
---|---|---|
String | getParameter(String name) | 返回指定参数名的参数值。 |
String [ ] | getParameterValues (String name) | 以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。 |
Enumeration | getParameterNames() | 以枚举集合的形式返回请求中所有参数名。 |
Map | getParameterMap() | 用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。 |
为了更好地理解这些方法,下面通过案例演示这些方法的使用。在webapp目录下,创建 web_form.html,代码如下。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/web/RequestParam" method="post">
<table border="1" width="50%">
<tr>
<td colspan="2" align="center">welcome to RequestForm!</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>
创建一个名为 RequestParam 的 Servlet 类,代码如下:
@WebServlet("/RequestParam")
public class RequestParam extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws 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) + "<br/>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/web/web_form.html”,即可访问 form.html,结果如下图。
在表单中填写信息后,点击提交,结果如下图:
(4)附:URL和URI的区别
URL和URI都是用于表示资源定位的字符串,但它们在概念上有一些区别。
URI(Uniform Resource Identifier,统一资源标识符)是一个标识某个资源的字符串,可以用来唯一地标识一个资源,包括但不限于Web页面、图片、文本文件等。URI包括两个部分:URL和URN。URN(Uniform Resource Name,统一资源名称)是通过名字来标识资源的,而URL(Uniform Resource Locator,统一资源定位符)则是通过地址来标识资源的,即URL是一种特定类型的URI,它通过指定地址来标识特定的资源。例如,"https://www.example.com/index.html"是一个URL,它指定了要访问的资源的地址和协议。
因此,URL是URI的一个子集,所有的URL都是URI,但不是所有的URI都是URL。URI还可以表示一些没有直接关联到网络资源的抽象概念,例如一个人的名字或者一份文档的版本号。而URL则是用来指定如何访问Web资源的字符串,包括协议、主机名、端口号、路径和查询参数等。
4、HttpServletRequest 中文乱码问题
如果在 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 种解决方案。
1) 修改 tomcat/conf/server.xml 中的配置,代码如下。
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/>
2)使用 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");
3)使用 String 的构造方法:String(byte[] bytes, String charset) ,对字节数组(bytes)按照指定的字符集(charset)进行解码,返回解码后的字符串,解决乱码问题(推荐使用)。
//获取姓名
String username = request.getParameter("username");
//使用String的构造方法解决乱码的问题
username = new String(username.getBytes("ISO-8859-1"),"UTF-8");
测试程序,接收路径参数为中国时,对参数进行重新转码。
@WebServlet("/RequestParam")
public class RequestParam 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");
//获取参数
String name = request.getParameter("a");
String value = new String(name.getBytes("ISO-8859-1"), "UTF-8");
System.out.println(value);
PrintWriter writer = response.getWriter();
writer.println("请求方式:" + request.getMethod() + "<br/>" +
"客户端的 IP 地址:" + request.getRemoteAddr() + "<br/>" +
"应用名字(上下文):" + request.getContextPath() + "<br/>" +
"URI:" + request.getRequestURI() + "<br/>" +
"请求字符串:" + request.getQueryString() + "<br/>" +
"Servlet所映射的路径:" + request.getServletPath() + "<br/>" +
"客户端的完整主机名:" + request.getRemoteHost() + "<br/>"
);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
启动 Tomcat 服务器,在地址栏中输入“http://localhost:8080/web/RequestParam?a=中国&b=日本”,结果如下图。
控制台打印如下:
5、Tomcat 中创建 HttpServletRequest 对象的源码分析
在 Tomcat 中,HttpServletRequest 对象的创建是由 Catalina 处理请求的核心组件 org.apache.catalina.connector.Connector 负责的。具体来说,当 Connector 接收到客户端请求时,会创建 Request 对象(该对象封装了 HttpServletRequest 对象)并将其传递给 Tomcat 的管道处理器 Pipeline 进行处理。Pipeline 中会将 Request 对象传递给多个阶段的处理器(Valve),在每个阶段中对 Request 对象进行处理并执行相应的操作(如安全认证、过滤器等),最终将 Request 对象传递给对应的 Servlet 进行业务逻辑处理。
具体创建 HttpServletRequest 对象的源码如下所示:
@Override
public void service(SocketWrapperBase<?> socket) throws IOException {
// ...
// 创建 Request 对象并将其封装到 Processor 实例中
Http11Processor processor = createProcessor();
processor.assign(socket);
if (proto.isSSLEnabled() && (socket instanceof SecureNio2Channel)) {
SecureNio2Channel channel = (SecureNio2Channel) socket;
SSLEngine engine = proto.getSSLEngine();
if (engine != null) {
channel.reset(engine);
}
}
// 将请求交给 Pipeline 处理器进行处理
pipeline.getFirst().invoke(processor.getRequest(), processor.getResponse());
// ...
}
从上述代码中可以看出,当接收到客户端请求时,Tomcat 会通过 createProcessor 方法创建 Http11Processor 实例,并将 SocketWrapperBase 作为参数传递给该实例。在 Http11Processor 中,会调用 createRequest 方法创建 Request 对象,并将 SocketWrapperBase 和 Http11InputBuffer 封装到 Request 对象中。最终,Tomcat 将 Request 对象作为参数传递给 Pipeline 处理器进行处理。在 Pipeline 中,会将 Request 对象传递给多个 Valve 进行处理,最终将 Request 对象传递给对应的 Servlet 进行业务逻辑处理。
6、Servlet 容器在接收到客户端请求时会创建哪些对象?
在接收到客户端请求时,Servlet 容器会创建以下对象:
- HttpServletRequest 对象:封装了客户端请求的信息,包括请求参数、请求头、请求体等信息。
- HttpServletResponse 对象:封装了响应信息,包括响应状态码、响应头、响应体等信息。
- ServletContext 对象:代表 Servlet 上下文,可以用于在不同 Servlet 之间共享数据。
- HttpSession 对象:代表客户端的会话,可以用于在同一客户端多次请求之间共享数据。
- ServletConfig 对象:代表 Servlet 的配置信息,包括 Servlet 的初始化参数等信息。
- ServletRequest 对象:是 HttpServletRequest 的父接口,定义了通用的请求方法,如获取请求参数等。
- ServletResponse 对象:是 HttpServletResponse 的父接口,定义了通用的响应方法,如设置响应头等。
以上对象都是由 Servlet 容器在接收到客户端请求时创建的,可以在 Servlet 中直接使用。其中,HttpServletRequest 和 HttpServletResponse 对象是每个请求独有的,而 ServletContext 和 HttpSession 对象则是在多个请求之间共享的。
需要注意的是,具体创建对象的方式可能因 Servlet 容器而异。例如,在 Tomcat 中,HttpServletRequest 和 HttpServletResponse 对象是由 Catalina 处理请求的核心组件 org.apache.catalina.connector.Connector 负责创建的,而 ServletContext 和 HttpSession 对象则是由 org.apache.catalina.core.StandardContext 创建的。