5. Response
响应( response)对象封装了从服务器返回到客户端的所有信息。在 HTTP 协议中,从服务器传输到客户端
的信息通过 HTTP 头信息或响应的消息体。
5.1 缓冲
Servlet 容器允许但不必为了提高效率而缓冲到客户端的输出。典型的服务器默认都是缓冲的,但允许 servlet
指定缓冲参数。
ServletResponse
接口的如下方法允许 servlet 访问和设置缓冲信息:
getBufferSize
setBufferSize
isCommitted
reset
resetBuffer
flushBuffer
不管 Servlet 使用的是一个 ServletOutputStream
还是一个 Writer, ServletResponse
接口提供的这些方法允许执行缓冲操作。
getBufferSize
方法返回使用的底层缓冲区大小。如果没有使用缓冲,该方法必须返回一个 int 值 0
。
Servlet 可以请求 setBufferSize
方法设置一个最佳的缓冲大小。不一定分配 servlet 请求大小的缓冲区,但至
少与请求的大小一样大。这允许容器重用一组固定大小的缓冲区,如果合适,可以提供一个比请求时更大的缓冲区。该方法必须在使用 ServletOutputStream
或 Writer 写任何内容之前调用。如果已经写了内容或响应对象已经提交,则该方法必须抛出 IllegalStateException
。
isCommitted
方法返回一个表示是否有任何响应字节已经返回到客户端的 boolean 值。 flushBuffer
方法强制
刷出缓冲区的内容到客户端。
当响应没有提交时, reset
方法清空缓冲区的数据。头信息,状态码和在调用 reset
之前 servlet 调用 getWriter
或 getOutputStream
设置的状态也必须被清空。如果响应没有被提交, resetBuffer
方法将清空缓冲区中的内容,但不清空请求头和状态码。
如果响应已经提交并且 reset
或 resetBuffer
方法已被调用,则必须抛出 IllegalStateException
,响应及它关联的缓冲区将保持不变。
当使用缓冲区时,容器必须立即刷出填满的缓冲区内容到客户端。如果这是最早发送到客户端的数据,且认为响应被提交了。
5.2 头信息
Servlet 可以使用如下 HttpServletResponse
接口中的方法设置 HTTP 响应头:
setHeader
addHeader
setHeader
方法设置一个给定名字和值的 header。之前的 header 将被新的 header 替换。如果已经存在同名的
header 值的 set, set 中的值会被清空并用新的值替换。
addHeader
方法使用给定的名字添加一个 header 值到 set。如果没有 header 与给定的名字关联,则创建一个
新的 set。
Header 可能包含表示 int
或 Date
对象的数据。以下 HttpServletResponse
接口提供的便利方法允许 Servlet 对适当的数据类型用正确的格式设置一个 header:
setIntHeader
setDateHeader
addIntHeader
addDateHeader
为了成功的传回给客户端, header 必须在响应提交前设置。响应提交后的 Header 设置将被 servlet 容器忽略。
Servlet 程序员负责保证为 Servlet 生成的内容设置合适的 response 对象的 Content-Type
header。 HTTP 1.1 规范中没有要求在 HTTP 响应中设置此 header。当 Servlet 程序员没有设置该类型时, Servlet 容器也不能设置
默认的内容类型。
建议容器使用 X-Powered-By
HTTP header 公布它的实现信息。该字段值应考虑一个或多个实现类型,如
"Servlet/3.1"。容器应该可以配置来隐藏该 header。可选的容器补充的信息和底层 Java 平台可以被放在括号
内并添加到实现类型之后。
X-Powered-By: Servlet/3.1
X-Powered-By: Servlet/3.1 JSP/2.2 (GlassFish Open Source Edition 4.0 Java/Oracle Corporation/1.7)
5.3 非阻塞 IO
非阻塞 IO 仅对在 Servlet 和 Filter(2.3.3.3 节定义的, “异步处理”)中的异步请求处理和升级处理
(2.3.3.5 节定义的,“升级处理”)有效。否则,当调用 ServletInputStream.setReadListener
或
ServletOutputStream.setWriteListener
方法时将抛出 IllegalStateException
。 为了支持在 Servlet 容器中的非阻塞写,除了在 3-28 页 3.7 节描述的“非阻塞 IO” 对 ServletRequest
做的更改之外,下面做出的更
改以便于处理响应相关的类/接口。
WriteListener
提供了如下适用于容器调用的回调方法。
-
void onWritePossible()
. 当一个WriteListener
注册到ServletOutputStream
时,当可以写数据时该方法将被容器首次调用。当且仅当下边描述的ServletOutputStream
的isReady
方法返回false
,容器随后将调用该方法。 -
onError(Throwable t)
. 当处理响应过程中出现错误时回调。
除了 WriteListener
外,还有如下方法被添加到 ServletOutputStream
类并允许开发人员运行时检查是否可以写数据发送到客户端。
-
boolean isReady()
. 如果往ServletOutputStream
写会成功,则该方法返回true
,其他情况会返回false
。如果该方法返回true
,可以在ServletOutputStream
上执行写操作。如果没有后续的数据能写到
ServletOutputStream
,那么直到底层的数据被刷出之前该方法将一直返回false
。且在此时容器将调用
WriteListener
的onWritePossible
方法。随后调用该方法将返回true
。 -
void setWriteListener(WriteListener listener)
. 关联WriteListener
和当前的ServletOutputStream
,当ServletOutputStream
可以写入数据时容器会调用WriteListener
的回调方法。注册了WriteListener
将开始非阻塞 IO。此时再切换到传统的阻塞 IO 是非法的。
容器必须线程安全的访问 WriteListener
中的方法。
5.4 简便方法
HttpServletResponse
提供了如下简便方法:
sendRedirect
sendError
sendRedirect
方法将设置适当的 header 和内容体将客户端重定向到另一个地址。使用相对 URL 路径调用该
方法是合法的,但是底层的容器必须将传回到客户端的相对地址转换为全路径 URL。无论出于什么原因,如 果 给 定 的 URL 是 不 完 整 的 , 且 不 能 转 换 为 一 个 有 效 的 URL , 那 么 该 方 法 必 须 抛 出
IllegalArgumentException
。
sendError
方法将设置适当的 header 和内容体用于返回给客户端返回错误消息。可以 sendError
方法提供一
个可选的 String
参数用于指定错误的内容体。
如果响应已经提交并终止,这两个方法将对提交的响应产生负作用。这两个方法调用后 servlet 将不会产生
到客户端的后续的输出。这两个方法调用后如果有数据继续写到响应,这些数据被忽略。 如果数据已经写
到响应的缓冲区,但没有返回到客户端(例如,响应没有提交),则响应缓冲区中的数据必须被清空并使用
这两个方法设置的数据替换。如果想要已提交,这两个方法必须抛出 IllegalStateException
。
5.5 国际化
Servlet 应设置 response 的 locale 和字符集。使用 ServletResponse.setLocale
方法设置 locale。该方法可以重复的调用;但响应被提交后调用该方法不会产生任何作用。如果在页面被提交之前 Servlet 没有设置 locale,
容器的默认 locale 将用来确定响应的 locale,但是没有制定与客户端通信的规范,例如使用 HTTP 情况下的
Content-Language
header。
<locale-encoding-mapping-list>
<locale-encoding-mapping>
<locale>ja</locale>
<encoding>Shift_JIS</encoding>
</locale-encoding-mapping>
</locale-encoding-mapping-list>
如果该元素不存在或没有提供 mapping, setLocale 使用容器依赖的 mapping。 setCharacterEncoding
,
setContentType
和 setLocale
方法可以被重复的调用来改变字符编码。如果在 servlet 响应的 getWriter
方法已经调用之后或响应被提交之后,调用相关方法设置字符编码将没有任何作用。只有当给定的 content type 字
符 串 提 供 了 一 个 charset 属 性 值 , 调 用 setContentType
可 以 设 置 字 符 编 码 。 只 有 当 既 没 有 调 用
setCharacterEncoding
也没有调用 setContentType
去设置字符编码之前调用 setLocale
可以设置字符编码。
在 ServletResponse
接口的 getWriter
方法被调用或响应被提交之前,如果 servlet 没有指定字符编码,默认使用 ISO-8859-1
。
如果使用的协议提供了一种这样做的方式,容器必须传递 servlet 响应的 writer 使用的 locale 和字符编码到
客户端。使用 HTTP 的情况下, locale 可以使用 Content-Language
header 传递,字符编码可以作为用于指定
文本媒体类型的 Content-Type
header 的一部分传递。注意,如果没有指定 content type,字符编码不能通过
HTTP header 传递;但是仍使用它来编码通过 servlet 响应的 writer 写的文本。
5.6 结束响应对象
当响应被关闭时,容器必须立即刷出响应缓冲区中的所有剩余的内容到客户端。
以下事件表明 servlet 满足了请求且响应对象即将关闭:
- servlet 的
service
方法终止。 - 响应的
setContentLength
或setContentLengthLong
方法指定了大于零的内容量,且已经写入到响应。 sendError
方法已调用。sendRedirect
方法已调用。AsyncContext
的complete
方法已调用。
5.7 Response 的生命周期
每个响应对象是只有当在 servlet 的 service
方法的范围内或在 filter 的 doFilter
方法范围内是有效的,除非该组件关联的请求对象已经开启异步处理。如果相关的请求已经启动异步处理,那么直到 AsyncContext
的
complete
方法被调用,请求对象一直有效。为了避免响应对象创建的性能开销,容器通常回收响应对象。
在相关的请求的 startAsync
还没有调用时,开发人员必须意识到保持到响应对象引用,超出之上描述的范
围可能导致不确定的行为。