void HTTP1xCodec::generateHeader()

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