序
在写文件下载的时候,遇到了一个问题,就是这个ServletOutputStream到底要不要自己flush以及close。这里以tomcat容易为例,解读一下。
CoyoteAdapter
tomcat-embed-core-8.5.16-sources.jar!/org/apache/catalina/connector/CoyoteAdapter.java
主要看service这段代码:
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response);
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringCharset(connector.getURICharset());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean async = false;
boolean postParseSuccess = false;
req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
// 这里调用具体的servlet方法,使用springmvc的话,就是调用mvc的方法
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
if (request.isAsync()) {
async = true;
ReadListener readListener = req.getReadListener();
if (readListener != null && request.isFinished()) {
// Possible the all data may have been read during service()
// method so this needs to be checked here
ClassLoader oldCL = null;
try {
oldCL = request.getContext().bind(false, null);
if (req.sendAllDataReadEvent()) {
req.getReadListener().onAllDataRead();
}
} finally {
request.getContext().unbind(false, oldCL);
}
}
Throwable throwable =
(Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// If an async request was started, is not going to end once
// this container thread finishes and an error occurred, trigger
// the async error process
if (!request.isAsyncCompleting() && throwable != null) {
request.getAsyncContextInternal().setErrorState(throwable, true);
}
} else {
request.finishRequest();
//这里去关闭response
response.finishResponse();
}
} catch (IOException e) {
// Ignore
} finally {
AtomicBoolean error = new AtomicBoolean(false);
res.action(ActionCode.IS_ERROR, error);
if (request.isAsyncCompleting() && error.get()) {
// Connection will be forcibly closed which will prevent
// completion happening at the usual point. Need to trigger
// call to onComplete() here.
res.action(ActionCode.ASYNC_POST_PROCESS, null);
async = false;
}
// Access log
if (!async && postParseSuccess) {
// Log only if processing was invoked.
// If postParseRequest() failed, it has already logged it.
Context context = request.getContext();
// If the context is null, it is likely that the endpoint was
// shutdown, this connection closed and the request recycled in
// a different thread. That thread will have updated the access
// log so it is OK not to update the access log here in that
// case.
if (context != null) {
context.logAccess(request, response,
System.currentTimeMillis() - req.getStartTime(), false);
}
}
req.getRequestProcessor().setWorkerThreadName(null);
// Recycle the wrapper request and response
if (!async) {
request.recycle();
response.recycle();
}
}
}
Response.finishResponse
tomcat-embed-core-8.5.16-sources.jar!/org/apache/catalina/connector/Response.java
public class Response implements HttpServletResponse {
//......
/**
* The associated output buffer.
*/
protected OutputBuffer outputBuffer;
/**
* The associated output stream.
*/
protected CoyoteOutputStream outputStream;
//......
/**
* Perform whatever actions are required to flush and close the output
* stream or writer, in a single operation.
*
* @exception IOException if an input/output error occurs
*/
public void finishResponse() throws IOException {
// Writing leftover bytes
outputBuffer.close();
}
}
OutputBuffer.close
tomcat-embed-core-8.5.16-sources.jar!/org/apache/catalina/connector/OutputBuffer.java
/**
* Close the output buffer. This tries to calculate the response size if
* the response has not been committed yet.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
if (closed) {
return;
}
if (suspended) {
return;
}
// If there are chars, flush all of them to the byte buffer now as bytes are used to
// calculate the content-length (if everything fits into the byte buffer, of course).
if (cb.remaining() > 0) {
flushCharBuffer();
}
if ((!coyoteResponse.isCommitted()) && (coyoteResponse.getContentLengthLong() == -1)
&& !coyoteResponse.getRequest().method().equals("HEAD")) {
// If this didn't cause a commit of the response, the final content
// length can be calculated. Only do this if this is not a HEAD
// request since in that case no body should have been written and
// setting a value of zero here will result in an explicit content
// length of zero being set on the response.
if (!coyoteResponse.isCommitted()) {
coyoteResponse.setContentLength(bb.remaining());
}
}
if (coyoteResponse.getStatus() == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
doFlush(true);
} else {
doFlush(false);
}
closed = true;
// The request should have been completely read by the time the response
// is closed. Further reads of the input a) are pointless and b) really
// confuse AJP (bug 50189) so close the input buffer to prevent them.
Request req = (Request) coyoteResponse.getRequest().getNote(CoyoteAdapter.ADAPTER_NOTES);
req.inputBuffer.close();
coyoteResponse.action(ActionCode.CLOSE, null);
}
这里flush了一下
private void flushCharBuffer() throws IOException {
realWriteChars(cb.slice());
clear(cb);
}
/**
* Flush bytes or chars contained in the buffer.
*
* @param realFlush true
if this should also cause a real network flush
* @throws IOException An underlying IOException occurred
*/
protected void doFlush(boolean realFlush) throws IOException {
if (suspended) {
return;
}
try {
doFlush = true;
if (initial) {
coyoteResponse.sendHeaders();
initial = false;
}
if (cb.remaining() > 0) {
flushCharBuffer();
}
if (bb.remaining() > 0) {
flushByteBuffer();
}
} finally {
doFlush = false;
}
if (realFlush) {
coyoteResponse.action(ActionCode.CLIENT_FLUSH, null);
// If some exception occurred earlier, or if some IOE occurred
// here, notify the servlet with an IOE
if (coyoteResponse.isExceptionPresent()) {
throw new ClientAbortException(coyoteResponse.getErrorException());
}
}
}
这个正好印证了servlet3.1规范里头说的(5.6 Closure of Response Object
)
When a response is closed, the container must immediately flush all remaining content in the response buffer to the client.
Response.getOutputStream
/**
* @return the servlet output stream associated with this Response.
*
* @exception IllegalStateException if getWriter
has
* already been called for this response
* @exception IOException if an input/output error occurs
*/
@Override
public ServletOutputStream getOutputStream()
throws IOException {
if (usingWriter) {
throw new IllegalStateException
(sm.getString("coyoteResponse.getOutputStream.ise"));
}
usingOutputStream = true;
if (outputStream == null) {
outputStream = new CoyoteOutputStream(outputBuffer);
}
return outputStream;
}
CoyoteOutputStream的flush和close方法
tomcat-embed-core-8.5.16-sources.jar!/org/apache/catalina/connector/CoyoteOutputStream.java
/**
* Will send the buffer to the client.
*/
@Override
public void flush() throws IOException {
boolean nonBlocking = checkNonBlockingWrite();
ob.flush();
if (nonBlocking) {
checkRegisterForWrite();
}
}
@Override
public void close() throws IOException {
ob.close();
}
也都是调用OutputBuffer的flush和close方法。因此手工调用貌似没有必要。
doc
servlet-3_1-fr-eval-spec
Response.getWriter() should it be closed / flushed
Do I need to flush the servlet outputstream?