in java/org/apache/coyote/http11/Http11Processor.java [864:1044]
protected final void prepareResponse() throws IOException {
boolean entityBody = true;
contentDelimitation = false;
OutputFilter[] outputFilters = outputBuffer.getFilters();
if (http09) {
// HTTP/0.9
outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
outputBuffer.commit();
return;
}
int statusCode = response.getStatus();
if (statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304) {
// No entity body
outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
entityBody = false;
contentDelimitation = true;
if (statusCode == 205) {
// RFC 7231 requires the server to explicitly signal an empty
// response in this case
response.setContentLength(0);
} else {
response.setContentLength(-1);
}
}
boolean head = request.method().equals("HEAD");
if (head) {
// Any entity body, if present, should not be sent
outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
contentDelimitation = true;
}
// Sendfile support
if (protocol.getUseSendfile()) {
prepareSendfile(outputFilters);
}
// Check for compression
boolean useCompression = false;
if (entityBody && sendfileData == null) {
useCompression = protocol.useCompression(request, response);
}
MimeHeaders headers = response.getMimeHeaders();
// A SC_NO_CONTENT response may include entity headers
if (entityBody || statusCode == HttpServletResponse.SC_NO_CONTENT) {
String contentType = response.getContentType();
if (contentType != null) {
headers.setValue("Content-Type").setString(contentType);
}
String contentLanguage = response.getContentLanguage();
if (contentLanguage != null) {
headers.setValue("Content-Language").setString(contentLanguage);
}
}
long contentLength = response.getContentLengthLong();
boolean connectionClosePresent = isConnectionToken(headers, Constants.CLOSE);
if (http11 && response.getTrailerFields() != null) {
// If trailer fields are set, always use chunking
outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
contentDelimitation = true;
headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
} else if (contentLength != -1) {
headers.setValue("Content-Length").setLong(contentLength);
outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
contentDelimitation = true;
} else if (head) {
/*
* The OutputBuffer can't differentiate between a zero length response because GET would have produced a
* zero length body and a zero length response because HEAD didn't write any content. Therefore skip setting
* the "transfer-encoding: chunked" header as that may not be consistent with what would be seen with the
* equivalent GET.
*/
} 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) {
outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
contentDelimitation = true;
headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
} else {
outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
}
}
if (useCompression) {
outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
}
// Add date header unless application has already set one (e.g. in a
// Caching Filter)
if (headers.getValue("Date") == null) {
headers.addValue("Date").setString(FastHttpDateFormat.getCurrentDate());
}
// Although using transfer-encoding for gzip would be doable and was
// the original intent (which means the compression would be from an
// endpoint to the next, so only for the current transmission), it
// has been found that using content-encoding (which is end to end
// compression) is more efficient and more reliable.
if ((entityBody) && (!contentDelimitation) || connectionClosePresent) {
// Disable keep-alive if:
// - there is a response body but way for the client to determine
// the content length information; or
// - there is a "connection: close" header present
// This will cause the "connection: close" header to be added if it
// is not already present.
keepAlive = false;
}
// This may disable keep-alive so check before working out the Connection header
checkExpectationAndResponseStatus();
// This may disable keep-alive if there is more body to swallow
// than the configuration allows
checkMaxSwallowSize();
// If we know that the request is bad this early, add the
// Connection: close header.
if (keepAlive && statusDropsConnection(statusCode)) {
keepAlive = false;
}
if (!keepAlive) {
// Avoid adding the close header twice
if (!connectionClosePresent) {
headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
}
} else if (!getErrorState().isError()) {
if (!http11) {
headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
}
if (protocol.getUseKeepAliveResponseHeader()) {
boolean connectionKeepAlivePresent =
isConnectionToken(request.getMimeHeaders(), Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
if (connectionKeepAlivePresent) {
int keepAliveTimeout = protocol.getKeepAliveTimeout();
if (keepAliveTimeout > 0) {
String value = "timeout=" + keepAliveTimeout / 1000L;
headers.setValue(Constants.KEEP_ALIVE_HEADER_NAME).setString(value);
if (http11) {
// Append if there is already a Connection header,
// else create the header
MessageBytes connectionHeaderValue = headers.getValue(Constants.CONNECTION);
if (connectionHeaderValue == null) {
headers.addValue(Constants.CONNECTION)
.setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
} else {
connectionHeaderValue.setString(connectionHeaderValue.getString() + ", " +
Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
}
}
}
}
}
}
// Add server header
String server = protocol.getServer();
if (server == null) {
if (protocol.getServerRemoveAppProvidedValues()) {
headers.removeHeader("server");
}
} else {
// server always overrides anything the app might set
headers.setValue("Server").setString(server);
}
writeHeaders(response.getStatus(), headers);
outputBuffer.commit();
}