in proxygen/lib/http/codec/HTTP1xCodec.cpp [960:1191]
int HTTP1xCodec::onHeadersComplete(size_t len) {
if (headerParseState_ == HeaderParseState::kParsingHeaderValue) {
if (!pushHeaderNameAndValue(msg_->getHeaders())) {
return -1;
}
}
// discard messages with folded or multiple valued Transfer-Encoding headers
// ex : "chunked , zorg\r\n" or "\r\n chunked \r\n" (t12767790)
HTTPHeaders& hdrs = msg_->getHeaders();
const std::string& headerVal =
hdrs.getSingleOrEmpty(HTTP_HEADER_TRANSFER_ENCODING);
if (!headerVal.empty() && !caseInsensitiveEqual(headerVal, kChunked)) {
LOG(ERROR) << "Invalid Transfer-Encoding header. Value =" << headerVal;
return -1;
}
// discard messages with multiple content-length headers (t12767790)
if (hdrs.getNumberOfValues(HTTP_HEADER_CONTENT_LENGTH) > 1) {
// Only reject the message if the Content-Length headers have different
// values
folly::Optional<folly::StringPiece> contentLen;
bool error = hdrs.forEachValueOfHeader(
HTTP_HEADER_CONTENT_LENGTH, [&](folly::StringPiece value) -> bool {
if (!contentLen.has_value()) {
contentLen = value;
return false;
}
return (contentLen.value() != value);
});
if (error) {
LOG(ERROR) << "Invalid message, multiple Content-Length headers";
return -1;
}
}
// Update the HTTPMessage with the values parsed from the header
msg_->setHTTPVersion(parser_.http_major, parser_.http_minor);
msg_->setIsChunked((parser_.flags & F_CHUNKED));
if (transportDirection_ == TransportDirection::DOWNSTREAM) {
// Set the method type
msg_->setMethod(http_method_str(static_cast<http_method>(parser_.method)));
connectRequest_ = (msg_->getMethod() == HTTPMethod::CONNECT);
// If this is a headers-only request, we shouldn't send
// an entity-body in the response.
headRequest_ = (msg_->getMethod() == HTTPMethod::HEAD);
ParseURL parseUrl = msg_->setURL(std::move(url_), strictValidation_);
if (strictValidation_ && !parseUrl.valid()) {
LOG(ERROR) << "Invalid URL: " << msg_->getURL();
return -1;
}
url_.clear();
if (parseUrl.hasHost()) {
// RFC 2616 5.2.1 states "If Request-URI is an absoluteURI, the host
// is part of the Request-URI. Any Host header field value in the
// request MUST be ignored."
auto hostAndPort = parseUrl.hostAndPort();
VLOG(4) << "Adding inferred host header: " << hostAndPort;
msg_->getHeaders().set(HTTP_HEADER_HOST, hostAndPort);
}
// If the client sent us an HTTP/1.x with x >= 1, we may send
// chunked responses.
mayChunkEgress_ = ((parser_.http_major == 1) && (parser_.http_minor >= 1));
} else {
msg_->setStatusCode(parser_.status_code);
msg_->setStatusMessage(std::move(reason_));
reason_.clear();
}
auto g = folly::makeGuard([this] {
// Always clear the outbound upgrade header after we receive a response
if (transportDirection_ == TransportDirection::UPSTREAM &&
parser_.status_code != 100) {
upgradeHeader_.clear();
}
});
headerParseState_ = HeaderParseState::kParsingHeadersComplete;
if (transportDirection_ == TransportDirection::UPSTREAM) {
if (connectRequest_ &&
(parser_.status_code >= 200 && parser_.status_code < 300)) {
// Enable upgrade if this is a 200 response to a CONNECT
// request we sent earlier
ingressUpgrade_ = true;
} else if (parser_.status_code == 101) {
// Set the upgrade flags if the server has upgraded.
const std::string& serverUpgrade =
msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_UPGRADE);
if (serverUpgrade.empty() || upgradeHeader_.empty()) {
LOG(ERROR) << "Invalid 101 response, empty upgrade headers";
return -1;
}
auto result = checkForProtocolUpgrade(
upgradeHeader_, serverUpgrade, false /* client mode */);
if (result) {
ingressUpgrade_ = true;
egressUpgrade_ = true;
if (result->first != CodecProtocol::HTTP_1_1) {
bool success = callback_->onNativeProtocolUpgrade(
ingressTxnID_, result->first, result->second, *msg_);
if (success) {
nativeUpgrade_ = true;
msg_->setIsUpgraded(ingressUpgrade_);
return 1; // no message body if successful
}
} else if (result->second == getCodecProtocolString(result->first)) {
// someone upgraded to http/1.1? Reset upgrade flags
ingressUpgrade_ = false;
egressUpgrade_ = false;
}
// else, there's some non-native upgrade
} else {
LOG(ERROR) << "Invalid 101 response, client/server upgrade mismatch "
"client="
<< upgradeHeader_ << " server=" << serverUpgrade;
return -1;
}
} else if (parser_.upgrade || parser_.flags & F_UPGRADE) {
// Ignore upgrade header for upstream response messages with status code
// different from 101 in case if it was not a response to CONNECT.
parser_.upgrade = false;
parser_.flags &= ~F_UPGRADE;
}
} else {
if (connectRequest_) {
// Enable upgrade by default for the CONNECT requests.
// If we locally reject CONNECT, we will disable this flag while
// sending the reject response. If we forward the req to upstream proxy,
// we will start forwarding data to the proxy without waiting for
// the response from the proxy server.
ingressUpgrade_ = true;
} else if (!allowedNativeUpgrades_.empty() && ingressTxnID_ == 1) {
upgradeHeader_ = msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_UPGRADE);
if (!upgradeHeader_.empty() && !allowedNativeUpgrades_.empty()) {
auto result = checkForProtocolUpgrade(
upgradeHeader_, allowedNativeUpgrades_, true /* server mode */);
if (result && result->first != CodecProtocol::HTTP_1_1) {
nativeUpgrade_ = true;
upgradeResult_ = *result;
// unfortunately have to copy because msg_ is passed to
// onHeadersComplete
upgradeRequest_ = std::make_unique<HTTPMessage>(*msg_);
}
}
}
}
msg_->setIsUpgraded(ingressUpgrade_);
const std::string& upgrade = hdrs.getSingleOrEmpty(HTTP_HEADER_UPGRADE);
if (kUpgradeToken.equals(upgrade, folly::AsciiCaseInsensitive())) {
msg_->setIngressWebsocketUpgrade();
if (transportDirection_ == TransportDirection::UPSTREAM) {
// response.
const std::string& accept =
hdrs.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_ACCEPT);
if (accept != websockAcceptKey_) {
LOG(ERROR) << "Mismatch in expected ws accept key: "
<< "upstream: " << accept
<< " expected: " << websockAcceptKey_;
return -1;
}
} else {
// request.
// If the websockAcceptKey is already set, we error out.
// Currently, websockAcceptKey is never cleared, which means
// that only one Websocket upgrade attempt can be made on the
// connection. If that upgrade request is not successful for any
// reason, the connection is no longer usable. At some point, we
// may want to change this to clear the websockAcceptKey if
// the request doesn't succeed keeping the connection usable.
if (!websockAcceptKey_.empty()) {
LOG(ERROR) << "ws accept key already set: '" << websockAcceptKey_
<< "'";
return -1;
}
auto key = hdrs.getSingleOrEmpty(HTTP_HEADER_SEC_WEBSOCKET_KEY);
websockAcceptKey_ = generateWebsocketAccept(key);
}
}
bool msgKeepalive = msg_->computeKeepalive();
if (!msgKeepalive) {
keepalive_ = false;
}
if (transportDirection_ == TransportDirection::DOWNSTREAM) {
// Remember whether this was an HTTP 1.0 request with keepalive enabled
if (msgKeepalive && msg_->isHTTP1_0() &&
(keepaliveRequested_ == KeepaliveRequested::UNSET ||
keepaliveRequested_ == KeepaliveRequested::ENABLED)) {
keepaliveRequested_ = KeepaliveRequested::ENABLED;
} else {
keepaliveRequested_ = KeepaliveRequested::DISABLED;
}
}
// Determine whether the HTTP parser should ignore any headers
// that indicate the presence of a message body. This is needed,
// for example, if the message is a response to a request with
// method==HEAD.
bool ignoreBody;
if (transportDirection_ == TransportDirection::DOWNSTREAM) {
ignoreBody = false;
} else {
is1xxResponse_ = msg_->is1xxResponse();
if (expectNoResponseBody_) {
ignoreBody = true;
} else {
ignoreBody = RFC2616::responseBodyMustBeEmpty(msg_->getStatusCode());
}
}
headersComplete_ = true;
headerSize_.uncompressed += len;
headerSize_.compressed += len;
msg_->setIngressHeaderSize(headerSize_);
if (userAgent_.empty()) {
userAgent_ = msg_->getHeaders().getSingleOrEmpty(HTTP_HEADER_USER_AGENT);
}
callback_->onHeadersComplete(ingressTxnID_, std::move(msg_));
// 1 is a magic value that tells the http_parser not to expect a
// message body even if the message header implied the presence
// of one (e.g., via a Content-Length)
return (ignoreBody) ? 1 : 0;
}