3. Request
请求对象封装了客户端请求的所有信息。在 HTTP 协议中,这些信息是从客户端发送到服务器请求的 HTTP
头部和消息体。
3.1 HTTP 协议参数
servlet 的 请求参 数 以 字 符 串 的 形 式 作 为 请 求 的 一 部 分 从 客 户 端 发 送 到 servlet 容 器 。当 请 求 是 一 个 HttpServletRequest
对象,且符合 “3.1.1 当参数可用时” 描述的条件时,容器从 URI 查询字符串和 POST 数据中填充参数。参数以一系列的名-值对的形式保存。任何给定的参数的名称可存在多个参数值。
ServletRequest
接口的下列方法可访问这些参数:
getParameter
getParameterNames
getParameterValues
getParameterMap
getParameterValues
方法返回一个 String 对象的数组,包含了与参数名称相关的所有参数值。 getParameter
方法的返回值必须是 getParameterValues
方法返回的 String 对象数组中的第一个值。 getParameterMap
方法
返回请求参数的一个 java.util.Map
对象,其中以参数名称作为 map 键,参数值作为 map 值。
查询字符串和 POST 请求的数据被汇总到请求参数集合中。查询字符串数据在 POST 数据之前发送。例如,
如果请求由查询字符串 a=hello
和 POST 数据 a=goodbye&a=world
组成,得到的参数集合顺序将是
a=(hello,goodbye,world)
。
这些 API 不会暴露 GET 请求( HTTP 1.1 所定义的)的路径参数。他们必须从 getRequestURI
方法或getPathInfo
方法返回的字符串值中解析。
注意:POST 请求时,getParameter
会影响 getInputStream
和 getReader
代码示例
Servlet代码:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String parameter = req.getParameter("a");
String parameterb = req.getParameter("b");
String[] as = req.getParameterValues("a");
Enumeration<String> parameterNames = req.getParameterNames();
Map<String, String[]> parameterMap = req.getParameterMap();
log.info("parameter = {}", parameter);
log.info("parameterb = {}", parameterb);
log.info(Arrays.toString(as));
log.info("-------------------------");
while (parameterNames.hasMoreElements()) {
log.info(parameterNames.nextElement());
}
log.info("-------------------------");
Set<Map.Entry<String, String[]>> entrySet = parameterMap.entrySet();
for (Map.Entry<String, String[]> entry : entrySet) {
log.info(entry.getKey() + " = " + Arrays.toString(entry.getValue()));
}
}
HTTP 请求:
POST http://localhost:8080/MyServlet4/myServlet?a=v1
Content-Type: application/x-www-form-urlencoded
a=v3&a=v4&b=v5
日志结果:
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - parameter = v1
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - parameterb = v5
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - [v1, v3, v4]
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - -------------------------
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - a
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - b
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - -------------------------
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - a = [v1, v3, v4]
19:01:41.835 [http-nio-8080-exec-8] INFO my.servlet.MyServlet - b = [v5]
3.1.1 当参数可用时
以下是在 POST 表单数据填充到参数集前必须满足的条件:
- 该请求是一个 HTTP 或 HTTPS 请求。
- HTTP 方法是 POST。
- 内容类型是
application/x-www-form-urlencoded
。 - 该 servlet 已经对 request 对象的任意
getParameter
方法进行了初始调用。
如果不满足这些条件,而且参数集中不包括 POST 表单数据,那么 servlet 必须可以通过 request 对象的输入流得到 POST 数据。如果满足这些条件,那么从 request 对象的输入流中直接读取 POST 数据将不再有效。
3.2 文件上传
当数据以 multipart/form-data
的格式发送时, servlet 容器支持文件上传。
如果满足以下任何一个条件, servlet 容器提供 multipart/form-data
格式数据的处理。
- servlet 处理第 8.1.5 节, 8-68 页中定义的注解“
@MultipartConfig
”标注的请求。 - 为了 servlet 处理请求,部署描述符包含了一个
multipart-config
元素。
如何使 request 中 multipart/form-data
类型的数据可用,取决于 servlet 容器是否提供 multipart/form-data
格式数据的处理:
如果 servlet 容器提供 multipart/form-data
格式数据的处理,可通过 HttpServletRequest
中的以下方法得到:
public Collection<Part> getParts()
public Part getPart(String name)
译者注: Part
类代表从 multipart/form-data
格式的 POST 请求中接收到的一个部分或表单项。 每个 part 都可通过 Part.getInputStream
方法访问头部,相关的内容类型和内容。
对于表单数据的 Content-Disposition
,即使没有文件名,也可使用 part 的名称通过 HttpServletRequest
的 getParameter
和 getParameterValues
方法得到 part 的字符串值。
如 果 servlet 的 容 器 不 提 供 multi-part/form-data
格 式 数 据 的 处 理 , 这 些 数 据 将 可 通 过
HttpServletReuqest.getInputStream
得到。
3.3 属性 Attribute
属性是与请求相关联的对象。属性可以由容器设置来表达信息,否则无法通过 API 表示,或者由 servlet 设置将信息传达给另一个 servlet(通过 RequestDispatcher
)。属性通过 ServletRequest
接口中下面的方法来访问:
getAttribute
getAttributeNames
setAttribute
只有一个属性值可与一个属性名称相关联。以前缀 java.
和 javax.
开头的属性名称是本规范的保留定义。同样地, 以前缀 sun.
和 com.sun.
,oracle
和 com.oracle
开头的属性名是 Oracle Corporation 的保留定义。建议属性集中所有属性的命名与 Java 编程语言的规范 1 为包命名建议的反向域名约定一致。
3.4 头 Header
servlet 可以通过 HttpServletRequest
接口的下面方法访问 HTTP 请求的头部信息:
getHeader
getHeaders
getHeaderNames
getHeader
方法返回给定头名称的头。多个头可以具有相同的名称,例如 HTTP 请求中的 Cache-Control
头。
如果多个头的名称相同, getHeader
方法返回请求中的第一个头。 getHeaders
方法允许访问所有与特定头名
称相关的头值,返回一个 String
对象的枚举。
头可包含由 String
形式的 int
或 Date
数据。 HttpServletRequest
接口提供如下方便的方法访问这些类型的头数据:
getIntHeader
getDateHeader
如果 getIntHeader
方法不能转换为 int
的头值,则抛出 NumberFormatException
异常。如果 getDateHeader
方法不能把头转换成一个 Date
对象,则抛出 IllegalArgumentException
异常。
代码示例
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String nextElement = headerNames.nextElement();
log.info("{} === {}", nextElement, req.getHeader(nextElement));
}
}
3.5 请求路径元素
引导 servlet 服务请求的请求路径由许多重要部分组成。以下元素从请求 URI 路径得到,并通过 request 对象
公开:
- Context Path:与
ServletContext
相关联的路径前缀是这个 servlet 的一部分。如果这个上下文是基于 Web服务器的 URL 命名空间基础上的“默认”上下文,那么这个路径将是一个空字符串。否则,如果上下文不是基于服务器的命名空间,那么这个路径以/
字符开始,但不以/
字符结束。 - Servlet Path:路径部分直接与激活请求的映射对应。这个路径以
/
字符开头,如果请求与/
或*
模式
匹配,在这种情况下,它是一个空字符串。 - PathInfo:请求路径的一部分,不属于 Context Path 或 Servlet Path。如果没有额外的路径,它要么是
null
,要么是以/
开头的字符串。
使用 HttpServletRequest
接口中的下面方法来访问这些信息:
getContextPath
getServletPath
getPathInfo
重要的是要注意,除了请求 URI 和路径部分的 URL 编码差异外,下面的等式永远为真:
requestURI = contextPath + servletPath + pathInfo
举几个例子来澄清上述各点,请考虑以下几点:
映射路径 | 请求路径 | ContextPath | ServletPath | PathInfo |
---|---|---|---|---|
/lawn/* | /catalog/lawn/index.html | /catalog | /lawn | /index.html |
/garden/* | /catalog/garden/implements/ | /catalog | /garden | /implements/ |
*.jsp | /catalog/help/feedback.jsp /catalog/help/feedback.jsp?k1=v1 |
/catalog | /help/feedback.jsp | null |
代码示例
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
log.info("==========================={}", LocalDate.now());
log.info("ContextPath == {}", req.getContextPath());
log.info("ServletPath == {}", req.getServletPath());
log.info("PathInfo == {}", req.getPathInfo());
}
3.6 路径转换方法
在 API 中有两个方便的方法,允许开发者获得与某个特定的路径等价的文件系统路径。这些方法是:
ServletContext.getRealPath
HttpServletRequest.getPathTranslated
getRealPath
方法需要一个字符串参数,并返回一个字符串形式的路径,这个路径对应一个在本地文件系统上的文件。 getPathTranslated
方法推断出请求的 pathInfo
的实际路径(译者注:把 URL 中 servlet 名称之后,查询字符串之前的路径信息转化成实际的路径)。
这些方法在 servlet 容器无法确定一个有效的文件路径 的情况下,如 Web 应用程序从归档中,在不能访问本地的远程文件系统上,或在一个数据库中执行时,这些方法必须返回 null
。 JAR 文件中 META-INF/resources
目录下的资源,只有当调用 getRealPath()
方法时才认为容器已经从包含它的 JAR 文件中解压,在这种情况下,必须返回解压缩后位置。
3.7 非阻塞 IO
Web 容器中的非阻塞请求处理有助于提高对改善 Web 容器可扩展性不断增加的需求,增加 Web 容器可同时处理请求的连接数量。 servlet 容器的非阻塞 IO 允许开发人员在数据可用时读取数据或在数据可写时写数据。非阻塞 IO 仅对在 Servlet 和 Filter(2.3.3.3 节定义的, “异步处理”)中的异步请求处理和升级处理(2.3.3.5 节定义的,“升级处理”)有效。否则,当调用 ServletInputStream.setReadListener
或ServletOutputStream.setWriteListener
方法时将抛出 IllegalStateException
。
ReadListener
为非阻塞 IO 提供了下面的回调方法:
-
onDataAvailable()
.当可以从传入的请求流中读取数据时ReadListener
的onDataAvailable
方法被调用。当数据可读时容器初次调用该方法。当且仅当下面描述的ServletInputStream
的isReady
方法返回false
,容器随后将调用onDataAvailable
方法。 -
onAllDataRead()
.当读取完注册了此监听器的ServletRequest
的所有数据时调用onAllDataRead
方法。 -
onError(Throwable t).
处理请求时如果有任何错误或异常发生时调用onError
方法。
容器必须线程安全的访问 ReadListener
中的方法。
除了上述 ReadListener
定义的方法外,下列方法已被添加到 ServletInputStream
类中:
boolean isFinished()
. 与ServletReader
/ServletInputStream
相关的请求的所有数据已经读取完时isFinished
方法返回true
。否则返回false
。boolean isReady()
.如果可以无阻塞地读取数据isReady
方法返回true
。如果没有数据可以无阻塞地读取该方法返回false
。 如果isReady
方法返回false
,调用read
方法是非法的,且必须抛出IllegalStateException
。void setReadListener(ReadListener listener)
. 设置上述定义的ReadListener
,调用它以非阻塞的方式读取数据。一旦把监听器与给定的ServletInputStream
关联起来,当数据可以读取,所有的数据都读取完或如果处理请求时发生错误,容器调用ReadListener
的方法。注册一个ReadListener
将启动非阻塞 IO。 在那时切换到传统的阻塞 IO 是非法的,且必须抛出IllegalStateException
。在当前请求范围内,随后调用setReadListener
是非法的且必须抛出IllegalStateException
。
3.8 Cookies
HttpServletRequest
接口提供了 getCookies
方法来获得请求中的 cookie 的一个数组。这些 cookie 是从客户端发送到服务器端的客户端发出的每个请求上的数据。典型地,客户端发送回的作为 cookie 的一部分的唯一
信息是 cookie 的名称和 cookie 值。当 cookie 发送到浏览器时可以设置其他 cookie 属性,诸如注释,这些信
息不会返回到服务器。该规范还允许的 cookies 是 HttpOnly cookie。 HttpOnly cookie 暗示客户端它们不会暴
露给客户端脚本代码(它没有被过滤掉,除非客户端知道如何查找此属性)。使用 HttpOnly cookie 有助于减
少某些类型的跨站点脚本攻击。
3.9 SSL 属性
如果请求已经通过一个安全协议发送过,如 HTTPS,必须通过 ServletRequest
接口的 isSecure
方法公开该信
息。 Web 容器必须公开下列属性给 servlet 程序员:
表 3-3 协议属性
属性 | 属性名称 | Java 类型 |
---|---|---|
密码套件 | javax.servlet.request.cipher_suite |
String |
算法的位大小 | javax.servlet.request.key_size |
Integer |
SSL 会话 id | javax.servlet.request.ssl_session_id |
String |
如果有一个与请求相关的 SSL 证书,它必须由 servlet 容器以 java.security.cert.X509Certificate
类型的对象数组暴露给 servlet 程序员并可通过一个 javax.servlet.request.X509Certificate
类型的 ServletRequest
属性访问。
这个数组的顺序是按照信任的升序顺序。证书链中的第一个证书是由客户端设置的,第二个是用来验证第
一个的,等等。
3.10 国际化
客户可以选择希望 Web 服务器用什么语言来响应。该信息可以和使用 Accept-Language
头与 HTTP/1.1 规范
中描述的其他机制的客户端通信。 ServletRequest
接口提供下面的方法来确定发送者的首选语言环境:
getLocale
getLocales
getLocale
方法将返回客户端要接受内容的首选语言环境。要了解更多关于 Accept-Language
头必须被解
释为确定客户端首选语言的信息,请参阅 RFC 2616( HTTP/1.1) 14.4 节。
getLocales
方法将返回一个 Locale
对象的枚举,从首选语言环境开始顺序递减,这些语言环境是可被客户
接受的语言环境。
如果客户端没有指定首选语言环境, getLocale
方法返回的语言环境必须是 servlet 容器默认的语言环境,而
getLocales
方法必须返回只包含一个默认语言环境的 Locale
元素的枚举。
3.11 请求数据编码
目前,许多浏览器不随着 Content-Type
头一起发送字符编码限定符,而是根据读取 HTTP 请求确定字符编码。如果客户端请求没有指定请求默认的字符编码,容器用来创建请求读取器和解析 POST 数据的编码必须是“ISO-8859-1
”。然而,为了向开发人员说明客户端没有指定请求默认的字符编码,在这种情况下,客户端发送字符编码失败,容器从 getCharacterEncoding
方法返回 null
。
如果客户端没有设置字符编码,并使用不同的编码来编码请求数据,而不是使用上面描述的默认的字符编码 , 那 么 可 能 会 发 生 破 坏 。 为 了 弥 补 这 种 情 况 , ServletRequest
接 口 添 加 了 一 个 新 的 方 法setCharacterEncoding(String enc)
。开发人员可以通过调用此方法来覆盖由容器提供的字符编码。必须在解析任何 post 数据或从请求读取任何输入之前调用此方法。此方法一旦调用,将不会影响已经读取的数据的编
码。
3.12 Request 对象的生命周期
每个 request 对象只在 servlet 的 service
方法的作用域内,或过滤器的 doFilter
方法的作用域内有效,除非该组件启用了异步处理并且调用了 request 对象的 startAsync
方法。在发生异步处理的情况下, request 对象一直有效,直到调用 AsyncContext
的 complete
方法。容器通常会重复利用 request 对象,以避免创建request 对象的性能开销。开发人员必须注意的是,不建议在上述范围之外保持 startAsync
方法还没有被调用的请求对象的引用,因为这样可能产生不确定的结果。在升级情况下,如上描述仍成立。