in proxygen/lib/http/codec/HTTP1xCodec.cpp [361:667]
void HTTP1xCodec::generateHeader(
IOBufQueue& writeBuf,
StreamID txn,
const HTTPMessage& msg,
bool eom,
HTTPHeaderSize* size,
const folly::Optional<HTTPHeaders>& extraHeaders) {
if (keepalive_ && disableKeepalivePending_) {
keepalive_ = false;
}
const bool upstream = (transportDirection_ == TransportDirection::UPSTREAM);
const bool downstream = !upstream;
if (upstream) {
DCHECK_EQ(txn, egressTxnID_);
requestPending_ = true;
responsePending_ = true;
connectRequest_ = (msg.getMethod() == HTTPMethod::CONNECT);
headRequest_ = (msg.getMethod() == HTTPMethod::HEAD);
expectNoResponseBody_ = connectRequest_ || headRequest_;
} else {
// In HTTP, transactions must be egressed sequentially -- no out of order
// responses. So txn must be egressTxnID_ + 1. Furthermore, we shouldn't
// ever egress a response before we see a request, so txn can't
// be > ingressTxnID_
if ((txn != egressTxnID_ + 1 && !(txn == egressTxnID_ && is1xxResponse_)) ||
(txn > ingressTxnID_)) {
LOG(DFATAL) << "Out of order, duplicate or premature HTTP response";
}
if (!is1xxResponse_) {
++egressTxnID_;
}
is1xxResponse_ = msg.is1xxResponse() || msg.isEgressWebsocketUpgrade();
expectNoResponseBody_ =
connectRequest_ || headRequest_ ||
RFC2616::responseBodyMustBeEmpty(msg.getStatusCode());
}
int statusCode = 0;
StringPiece statusMessage;
if (downstream) {
statusCode = msg.getStatusCode();
statusMessage = msg.getStatusMessage();
// If a response to a websocket upgrade is being sent out, it must be 101.
// This is required since the application may not have changed the status,
// particularly when proxying between a H2 hop and a H1 hop.
if (msg.isEgressWebsocketUpgrade()) {
statusCode = 101;
statusMessage = HTTPMessage::getDefaultReason(101);
}
if (connectRequest_ && (statusCode >= 200 && statusCode < 300)) {
// Set egress upgrade flag if we are sending a 200 response
// to a CONNECT request we received earlier.
egressUpgrade_ = true;
} else if (statusCode == 101) {
// Set the upgrade flags if we upgraded after the request from client.
ingressUpgrade_ = true;
egressUpgrade_ = true;
} else if (connectRequest_ && ingressUpgrade_) {
// Disable upgrade when rejecting CONNECT request
ingressUpgrade_ = false;
// This codec/session is no longer useful as we might have
// forwarded some data before receiving the 200.
keepalive_ = false;
}
} else {
if (connectRequest_ || msg.isEgressWebsocketUpgrade()) {
// Sending a CONNECT request or a websocket upgrade request to an upstream
// server. This is used to determine the chunked setting below.
egressUpgrade_ = true;
}
}
egressChunked_ = msg.getIsChunked() && !egressUpgrade_;
lastChunkWritten_ = false;
std::pair<uint8_t, uint8_t> version = msg.getHTTPVersion();
if (version > HTTPMessage::kHTTPVersion11) {
version = HTTPMessage::kHTTPVersion11;
}
size_t len = 0;
switch (transportDirection_) {
case TransportDirection::DOWNSTREAM:
DCHECK_NE(statusCode, 0);
if (version == HTTPMessage::kHTTPVersion09) {
return;
}
if (force1_1_ && version < HTTPMessage::kHTTPVersion11) {
version = HTTPMessage::kHTTPVersion11;
}
appendLiteral(writeBuf, len, "HTTP/");
appendUint(writeBuf, len, version.first);
appendLiteral(writeBuf, len, ".");
appendUint(writeBuf, len, version.second);
appendLiteral(writeBuf, len, " ");
appendUint(writeBuf, len, statusCode);
appendLiteral(writeBuf, len, " ");
appendString(writeBuf, len, statusMessage);
break;
case TransportDirection::UPSTREAM:
if (force1_1_ && version < HTTPMessage::kHTTPVersion11) {
version = HTTPMessage::kHTTPVersion11;
}
if (msg.isEgressWebsocketUpgrade()) {
appendString(writeBuf, len, methodToString(HTTPMethod::GET));
} else {
appendString(writeBuf, len, msg.getMethodString());
}
appendLiteral(writeBuf, len, " ");
appendString(writeBuf, len, msg.getURL());
if (version != HTTPMessage::kHTTPVersion09) {
appendLiteral(writeBuf, len, " HTTP/");
appendUint(writeBuf, len, version.first);
appendLiteral(writeBuf, len, ".");
appendUint(writeBuf, len, version.second);
}
mayChunkEgress_ = (version.first == 1) && (version.second >= 1);
if (!upgradeHeader_.empty()) {
LOG(DFATAL)
<< "Attempted to pipeline HTTP request with pending upgrade";
upgradeHeader_.clear();
}
break;
}
appendLiteral(writeBuf, len, CRLF);
if (keepalive_ && (!msg.wantsKeepalive() || version.first < 1 ||
(downstream && version == HTTPMessage::kHTTPVersion10 &&
keepaliveRequested_ != KeepaliveRequested::ENABLED))) {
// Disable keepalive if
// - the message asked to turn it off
// - it's HTTP/0.9
// - this is a response to a 1.0 request that didn't say keep-alive
keepalive_ = false;
}
egressChunked_ &= mayChunkEgress_;
if (version == HTTPMessage::kHTTPVersion09) {
parser_.http_major = 0;
parser_.http_minor = 9;
return;
}
folly::StringPiece deferredContentLength;
bool hasTransferEncodingChunked = false;
bool hasDateHeader = false;
bool hasUpgradeHeader = false;
std::vector<StringPiece> connectionTokens;
size_t lastConnectionToken = 0;
bool egressWebsocketUpgrade = msg.isEgressWebsocketUpgrade();
bool hasUpgradeTokeninConnection = false;
auto headerEncoder = [&](HTTPHeaderCode code,
folly::StringPiece header,
folly::StringPiece value) {
if (code == HTTP_HEADER_CONTENT_LENGTH) {
// Write the Content-Length last (t1071703)
deferredContentLength = value;
return; // continue
} else if (code == HTTP_HEADER_CONNECTION &&
(!is1xxResponse_ || egressWebsocketUpgrade)) {
static const string kClose = "close";
static const string kKeepAlive = "keep-alive";
folly::split(',', value, connectionTokens);
for (auto curConnectionToken = lastConnectionToken;
curConnectionToken < connectionTokens.size();
curConnectionToken++) {
auto token = trimWhitespace(connectionTokens[curConnectionToken]);
if (caseInsensitiveEqual(token, "upgrade")) {
hasUpgradeTokeninConnection = true;
}
if (caseInsensitiveEqual(token, kClose)) {
keepalive_ = false;
} else if (!caseInsensitiveEqual(token, kKeepAlive)) {
connectionTokens[lastConnectionToken++] = token;
} // else eat the keep-alive token
}
connectionTokens.resize(lastConnectionToken);
// We'll generate a new Connection header based on the keepalive_
// state
return;
} else if (code == HTTP_HEADER_UPGRADE && txn == 1) {
hasUpgradeHeader = true;
if (upstream) {
// save in case we get a 101 Switching Protocols
upgradeHeader_ = value.str();
}
} else if (!hasTransferEncodingChunked &&
code == HTTP_HEADER_TRANSFER_ENCODING) {
if (!caseInsensitiveEqual(value, kChunked)) {
return;
}
hasTransferEncodingChunked = true;
if (!mayChunkEgress_) {
return;
}
} else if (!hasDateHeader && code == HTTP_HEADER_DATE) {
hasDateHeader = true;
} else if (egressWebsocketUpgrade &&
code == HTTP_HEADER_SEC_WEBSOCKET_KEY) {
// will generate our own key per hop, not client's.
return;
} else if (egressWebsocketUpgrade &&
code == HTTP_HEADER_SEC_WEBSOCKET_ACCEPT) {
// will generate our own accept per hop, not client's.
return;
}
if (value.find_first_of("\r\n") != std::string::npos) {
return;
}
size_t lineLen = header.size() + value.size() + 4; // 4 for ": " + CRLF
auto writable =
writeBuf.preallocate(lineLen, std::max(lineLen, size_t(2000)));
char* dst = (char*)writable.first;
memcpy(dst, header.data(), header.size());
dst += header.size();
*dst++ = ':';
*dst++ = ' ';
memcpy(dst, value.data(), value.size());
dst += value.size();
*dst++ = '\r';
*dst = '\n';
DCHECK_EQ(size_t(++dst - (char*)writable.first), lineLen);
writeBuf.postallocate(lineLen);
len += lineLen;
};
msg.getHeaders().forEachWithCode(headerEncoder);
if (extraHeaders) {
extraHeaders->forEachWithCode(headerEncoder);
}
bool bodyCheck =
(downstream && keepalive_ && !expectNoResponseBody_ && !egressUpgrade_) ||
// auto chunk POSTs and any request that came to us chunked
(upstream && ((msg.getMethod() == HTTPMethod::POST) || egressChunked_));
// TODO: 400 a 1.0 POST with no content-length
// clear egressChunked_ if the header wasn't actually set
egressChunked_ &= hasTransferEncodingChunked;
if (bodyCheck && !egressChunked_ && deferredContentLength.empty()) {
// On a connection that would otherwise be eligible for keep-alive,
// we're being asked to send a response message with no Content-Length,
// no chunked encoding, and no special circumstances that would eliminate
// the need for a response body. If the client supports chunking, turn
// on chunked encoding now. Otherwise, turn off keepalives on this
// connection.
if (!hasTransferEncodingChunked && mayChunkEgress_) {
appendLiteral(writeBuf, len, "Transfer-Encoding: chunked\r\n");
egressChunked_ = true;
} else {
keepalive_ = false;
}
}
if (downstream && !hasDateHeader) {
addDateHeader(writeBuf, len);
}
// websocket headers
if (msg.isEgressWebsocketUpgrade()) {
if (!hasUpgradeHeader && txn == 1) {
// upgradeHeader_ is set in serializeWwebsocketHeader for requests.
serializeWebsocketHeader(writeBuf, len, upstream);
if (!hasUpgradeTokeninConnection) {
connectionTokens.push_back(kUpgradeConnectionToken);
lastConnectionToken++;
}
} else {
LOG(ERROR) << folly::to<string>(
"Not serializing headers. "
"Upgrade headers present/txn: ",
hasUpgradeHeader,
txn);
}
}
if (!is1xxResponse_ || upstream || !connectionTokens.empty()) {
// We don't seem to add keep-alive/close and let the application add any
// for 1xx responses.
appendLiteral(writeBuf, len, "Connection: ");
if (connectionTokens.size() > 0) {
appendString(writeBuf, len, folly::join(", ", connectionTokens));
}
if (!is1xxResponse_) {
if (connectionTokens.size() > 0) {
appendString(writeBuf, len, ", ");
}
if (keepalive_) {
appendLiteral(writeBuf, len, "keep-alive");
} else {
appendLiteral(writeBuf, len, "close");
}
}
appendLiteral(writeBuf, len, "\r\n");
}
if (!deferredContentLength.empty()) {
appendLiteral(writeBuf, len, "Content-Length: ");
appendString(writeBuf, len, deferredContentLength);
appendLiteral(writeBuf, len, CRLF);
}
appendLiteral(writeBuf, len, CRLF);
if (eom) {
len += generateEOM(writeBuf, txn);
}
if (size) {
size->compressed = 0;
size->uncompressed = len;
}
}