The Request
Request对象封装了来自客户端请求的所有信息。在HTTP协议中,这个信息是在请求的HTTP头和消息体从客户端传输给服务器的。
1. HTTP协议参数
servlet的请求参数是作为请求的一部分从客户机向servlet容器发送的字符串。当请求是一个HttpServletRequest对象时,在第24页上“当参数可用时”的条件被满足时,容器将从URI查询字符串和POST-ed数据中填充参数。
参数存储为一组名称-值对。对于任何给定的参数名,都可以存在多个参数值。ServletRequest接口的以下方法可以访问参数:
- getParameter
- getParameterNames
- getParameterValues
- getParameterMap
getParameterValues方法返回包含与参数名相关联的所有参数值的字符串对象数组。getParameter方法返回的值必须是getParameterValues返回的字符串对象数组中的第一个值。getParameterMap方法返回一个请求的参数的java.util.Map,其中包含名称作为键和参数值作为映射值。
查询字符串和post主体的数据被聚合到请求参数集中,在post body数据之前显示查询字符串数据。例如,如果一个请求是用一个a=hello的查询字符串和一个a=goodbye&a=world的post体做的,那么结果的参数集将被排序为=(hello, goodbye, world).
GET请求(由HTTP 1.1定义)的一部分的路径参数没有被这些api公开。它们必须从getRequestURI方法或getPathInfo方法返回的字符串值中解析。
1.1 当参数可用时
The following are the conditions that must be met before post form data will be populated to the parameter set:
下面是在将表单数据填充到参数集之前必须满足的条件:
- The request is an HTTP or HTTPS request.
- The HTTP method is POST.
- The content type is application/x-www-form-urlencoded.
- The servlet has made an initial call of any of the getParameter family of methods on the request object.
如果条件没有满足,并且在参数集中不包含post表单数据,那么post数据仍然必须通过请求对象的输入流对servlet可用。如果条件满足,则不再可以从请求对象的输入流直接读取post表单数据。
2.文件上传
Servlet容器允许在数据作为multipart/form-data发送时上传文件。
如果满足以下任一条件,servlet容器将提供multipart/form-data处理。
■servlet处理的请求注释@MultipartConfig 8.1.5节的定义,“@MultipartConfig”8 - 74页。
■部署描述符包含servlet multipart-config元素处理请求。
如何提供类型multipart/form-data的请求数据取决于servlet容器是否提供multipart/form-data处理:
■如果servlet容器提供了multipart/form-data处理,数据是通过以下在HttpServletRequest的方法可用的:
■ public Collection<Part> getParts()
■ public Part getPart(String name)
Each part provides access to the headers, content type related with it and the content via the Part.getInputStream method.
每个部分都提供了对header的访问,内容类型与它相关,而且内容来自Part.getInputStream方法。
对于以表单数据作为内容配置的部分,但是如果没有文件名,该部分的字符串值也可以通过使用该部分名称的HttpServletRequest的etParameter和getParameterValues方法获得。
每个部分都提供了对header的访问,内容类型与它相关,内容通过部分。getInputStream方法。
对于以表单数据作为内容配置但是没有文件名的部分,则该部分的字符串值也将通过HttpServletRequest的getParameter和getParameterValues方法获得,并使用该部分的名称。
■如果servlet容器不提供multi-part/form-data处理,可用的数据将通过HttpServletReuqest.getInputStream。
3.属性
属性是与请求相关联的对象。属性可以由容器设置,以表达通过API无法表达的信息,也可以由servlet设置以与另一个servlet进行信息交流(通过RequestDispatcher)。可使用ServletRequest接口的下列方法访问属性:
■ getAttribute
■ getAttributeNames
■ setAttribute
只有一个属性值可能与属性名相关联。以java和javax的前缀开始的属性名称是本规范的保留定义。类似地,属性名称以sun., com.sun., oracle and com.oracle的前缀开始,是由甲骨文公司保留的。建议将属性集中的所有属性按照Java编程语言规范所建议的反向域名约定命名。
4.信息头
servlet可以通过HttpServletRequest接口的以下方法访问HTTP请求的头:
■ getHeader
■ getHeaders
■ getHeaderNames
getHeader方法返回给定标题名称的标题。在HTTP请求中,可以有多个具有相同名称的头,例如缓存控制头。如果有多个具有相同名称的头,则getHeader方法将返回请求中的第一个头。getheader方法允许访问与特定头名称关联的所有头值,返回字符串对象的枚举。
头可能包含整数或日期数据的字符串表示。HttpServletRequest接口的以下便利方法提供了访问头数据的一种格式:
■getIntHeader
■getDateHeader
如果getIntHeader方法无法将标题值转换为int,则抛出NumberFormatException。如果getDateHeader方法不能将消息头转换为日期对象,则抛出一个IllegalArgumentException。
5.请求路径元素
导致servlet服务请求的请求路径由许多重要的部分组成。以下元素从请求URI路径获得,并通过请求对象公开:
■Context Path:与 ServletContext 相关联的路径前缀是这个 servlet 的一部分。如果这个上下文是基于 Web服务器的 URL 命名空间基础上的“默认”上下文,那么这个路径将是一个空字符串。否则,如果上下文不是
基于服务器的根命名空间,那么这个路径以/字符开始,但不以/字符结束。
■ Servlet Path:路径部分直接对应于激活此请求的映射。此路径以“/”字符开头,除非请求与“/*”或“”模式匹配,在这种情况下,它是一个空字符串。
■ PathInfo: 请求路径的一部分,不是上下文路径或Servlet路径的一部分。如果没有额外的路径,则为null,或者是一个带引导' / '的字符串。
使用 HttpServletRequest 接口中的下面方法来访问这些信息:
■ getContextPath
■ getServletPath
■ getPathInfo
重要的是要注意,除了请求 URI 和路径部分的 URL 编码差异外,下面的等式永远为真:
requestURI = contextPath + servletPath + pathInfo
举几个例子来澄清上述各点,请考虑以下几点:
表 3-1 上下文设置的例子
Context Path | /catalog |
---|---|
Servlet Mapping | Pattern: /lawn/* Servlet: LawnServlet |
Servlet Mapping | Pattern: /garden/* Servlet: GardenServlet |
Servlet Mapping | Pattern: *.jsp Servlet: JSPServlet |
遵守下列行为:
表 3-2 遵守路径元素行为
请求路径 | 路径元素 |
---|---|
/catalog/lawn/index.html | ContextPath: /catalog ServletPath: /lawn PathInfo: /index.html |
/catalog/garden/implements/ | ContextPath: /catalog ServletPath: /garden PathInfo: /implements/ |
/catalog/help/feedback.jsp | ContextPath: /catalog ServletPath: /help/feedback.jsp PathInfo: null |
6. 路径转换方法
开发人员获得与特定路径等效的文件系统路径。这些方法有:
■ServletContext.getRealPath
■HttpServletRequest.getPathTranslated
getRealPath方法接受一个字符串参数,并返回路径对应的本地文件系统上的文件的字符串表示。getpathtransform方法计算请求的pathInfo的实际路径。
在servlet容器无法通过这些方法确定的文件有效路径的情况下,例如,当Web应用程序从存档中执行时,在本地无法访问的远程文件系统上,或者在数据库中,这些方法必须返回null。当容器在调用getRealPath()时从包含的JAR文件中解压它们,必须考虑到JAR文件的META-INF/ Resources目录中的资源,并且在这种情况下必须返回未解压的位置。
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 提供了下面的回调方法:
■ ReadListener
■ onDataAvailable().当可以从传入的请求流中读取数据时 ReadListener 的 onDataAvailable 方法被调用。当数据可读时容器初次调用该方法。当且仅当下面描述的 ServletInputStream 的 isReady 方法返回 false,容器随后将调用 onDataAvailable 方法。
■ onAllDataRead().当读取完注册了此监听器的 ServletRequest 的所有数据时调用 onAllDataRead 方法。
■ onError(Throwable t). 处理请求时如果有任何错误或异常发生时调用 onError 方法。
容器必须线程安全的访问 ReadListener 中的方法。
除了上述 ReadListener 定义的方法外,下列方法已被添加到 ServletInputStream 类中:
■ 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。
8.HTTP/2服务器推送
服务器推送是HTTP/2中出现在servlet API中的最明显的改进。HTTP/2中的所有新特性,包括服务器推送,都是为了提高web浏览体验的性能。服务器推送的贡献在于改进了感知浏览器的性能,从简单的事实来看,在了解额外的资源(例如图像、样式表和脚本)与初始请求的关系上服务器的处境要比客户机好得多。例如,服务器可能知道,无论何时浏览器请求index.html,它不久将请求header.gif,footer.gif和style.css。因为服务器知道这一点,所以他们可以预先伴随发送index.html的字节开始发送这些资源的字节。
要使用服务器推送,可以从HttpServletRequest中获取一个PushBuilder的引用,根据需要将构建器进行mutate,然后调用push()。请参阅方法的javadoc javax.servlet.http.HttpServletRequest.newPushBuilder javax.servlet.http()和类。规范规范的PushBuilder。本节的其余部分将在第6页“其他重要参考”中引用的HTTP/2规范版本中的“服务器推送”一节中对实现要求进行调用。
除非明确排除,Servlet 4.0容器必须支持HTTP/2规范部分“服务器推送”中指定的服务器推送。如果客户端能够使用HTTP/2,那么容器必须启用服务器推送,除非客户端通过发送SETTINGS_ENABLE_PUSH设置值(0)来实现当前连接,从而禁用了服务器推送。在这种情况下,仅针对该连接,不能启用服务器推送。
除了允许客户端使用SETTINGS_ENABLE_PUSH设置禁用服务器推送之外,servlet容器还必须遵守客户机的请求,以避免在更细粒度的基础上接收被推的响应,因为它会注意到引用被推流的流标识符的CANCEL或拒绝流代码。这种交互的一个常见用法是,当浏览器已经拥有缓存中的资源时。
9. Cookies
HttpServletRequest接口提供了getCookies方法,以获得在请求中出现的一组cookie。这些cookie是客户端向服务器发送的数据。通常,作为cookie的一部分,客户端返回的唯一信息是cookie的名称和cookie值。当cookie被发送到浏览器时,可以设置其他cookie属性,比如注释,通常不会返回。该规范还允许cookie仅为HttpOnly cookie。HttpOnly cookie向客户机表明它们不应该暴露在客户端脚本代码中(除非客户机知道要查找这个属性,否则它不会被过滤掉)。使用HttpOnly cookie有助于减轻某些类型的跨站点脚本攻击。
10. SSL 属性
如果在安全协议(如HTTPS)上传输了请求,则必须通过ServletRequest接口的isSecure方法公开该信息。Web容器必须向servlet程序员公开以下属性:
TABLE 3-3 Protocol Attributes
Attribute | Attribute Name | Java Type |
---|---|---|
cipher suite | javax.servlet.request.cipher_suite | String |
bit size of the algorithm | javax.servlet.request.key_size | Integer |
SSL session id | javax.servlet.request.ssl_session_id | String |
如果有与请求相关联的SSL证书,那么它必须由servlet容器公开给servlet程序员作为java.security.cert类型的对象数组。X509Certificate javax.servlet.request.X509Certificate通过ServletRequest属性。
这个数组的顺序被定义为以信任的升序排列。链中的第一个证书是由客户端设置的,第二个证书是用来对第一个证书进行身份验证的,以此类推。
11.国际化
客户端可以选择向Web服务器表明他们希望响应的语言是什么。可以使用Accept-Language头和HTTP/1.1规范中描述的其他机制从客户端传递这些信息。在ServletRequest接口中提供了以下方法来确定发送者的首选语言环境:
- getLocale
- getLocales
getLocale方法将返回客户机想要接受内容的首选语言环境。请参阅RFC 7231 (HTTP/1.1)的第14.4节,了解关于如何解释Accept-Language头的更多信息,以确定客户机的首选语言。
getLocales方法将返回一个Locale对象的枚举,以减少订单开始时的首选语言环境,客户可以接受的区域。
如果客户机没有指定首选语言环境,则getLocale方法返回的语言环境必须是servlet容器的缺省语言环境,而getLocales方法必须包含缺省语言环境的单个语言环境元素的枚举。
12. 请求数据编码
目前,许多浏览器都不发送带有ContentType头的字符编码限定符,将字符编码的确定留给读取HTTP请求。在没有字符编码限定符的情况下,如果内容类型是application/x-www-form-urlencode,则容器用于创建请求读取器和解析POST数据的默认编码必须是US-ASCII。任何%nn编码的值必须被解码为ISO-8859-1。对于任何其他内容类型,如果客户端请求、web应用程序或容器供应商的特定配置(对于容器中的所有web应用程序)都没有指定,则容器用于创建请求读取器和解析POST数据的默认编码必须是ISO-8859-1。但是,为了向开发人员表明没有字符编码限定符,容器必须从getCharacterEncoding()方法返回null。
如果客户端没有设置字符编码,并且请求数据用与上面描述的默认编码不同的编码进行编码,那么就会发生破坏。来补救这种情况,ServletContext上可使用setRequestCharacterEncoding(String enc),web.xml使用的< request-character-encoding >元素以及在ServletRequest接口上可以使用setCharacterEncoding(String enc)。开发人员可以通过调用这个方法重写容器提供的字符编码。必须在解析任何post数据或从请求中读取任何输入之前调用它。一旦数据被读取,调用此方法将不会影响编码。
13.请求对象的生存期
除非为组件启用了异步处理,并且在请求对象上调用startAsync方法,否则每个请求对象仅在servlet的service方法的范围内,或者在过滤器的doFilter方法的范围内有效。在异步处理发生的情况下,请求对象仍然有效,直到在AsyncContext上调用complete为止。容器通常回收请求对象,以避免请求对象创建的性能开销。开发人员必须意识到,不建议使用startAsync在上面描述的范围之外的请求对象的引用,因为它可能有不确定的结果。
在升级的情况下,以上的说法依旧正确。