tomcat如何关闭response的outputStream

序 在写文件下载的时候,遇到了一个问题,就是这个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()); } }}

【tomcat如何关闭response的outputStream】这个正好印证了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?

    推荐阅读