参考架构 Tomcat 架构设计
Http 中的流式传输

setContentLength:498, Response (org.apache.coyote)
close:256, OutputBuffer (org.apache.catalina.connector)
finishResponse:484, Response (org.apache.catalina.connector)
service:373, CoyoteAdapter (org.apache.catalina.connector)
service:800, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:800, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1471, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

如果之前未触发过 commit, 则会设置 content-length

org.apache.catalina.connector.OutputBuffer#close

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());  
    }  
}

然后触发 org.apache.coyote.ActionCode#COMMIT
准备响应 org.apache.coyote.http11.Http11Processor#prepareResponse

// 判断是否存在 contentlength
long contentLength = response.getContentLengthLong();  
boolean connectionClosePresent = isConnectionToken(headers, Constants.CLOSE);  
if (contentLength != -1) {  
    headers.setValue("Content-Length").setLong(contentLength);  
    outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);  
    contentDelimitation = true;  
} else {  
    // If the response code supports an entity body and we're on  
    // HTTP 1.1 then we chunk unless we have a Connection: close header    if (http11 && entityBody && !connectionClosePresent) {  
    // 不存在默认使用 chunked 
        outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);  
        contentDelimitation = true;  
        headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);  
    } else {  
        outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);  
    }  
}

NOTE

如果存在 content-length, 那么就使用 content-length
否则默认使用 chunked, 如果是 http1.1, 因为 Http2 和 chunked 冲突