proxygen/httpserver/ResponseBuilder.h (145 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <folly/ScopeGuard.h> #include <proxygen/httpserver/ResponseHandler.h> namespace proxygen { /** * Helps you make responses and send them on demand. * * NOTE: We don't do any correctness checks here, we depend on * state machine in HTTPTransaction to tell us when an * error occurs * * Three expected use cases are * * 1. Send all response at once. If this is an error * response, most probably you also want 'closeConnection'. * * ResponseBuilder(handler) * .status(200, "OK") * .body(...) * .sendWithEOM(); * * 2. Sending back response in chunks. * * ResponseBuilder(handler) * .status(200, "OK") * .body(...) * .send(); // Without `WithEOM` we make it chunked * * 1 or more time * * ResponseBuilder(handler) * .body(...) * .send(); * * At last * * ResponseBuilder(handler) * .body(...) * .sendWithEOM(); * * 3. Accept or reject Upgrade Requests * * ResponseBuilder(handler) * .acceptUpgradeRequest() // send '200 OK' without EOM * * or * * ResponseBuilder(handler) * .rejectUpgradeRequest() // send '400 Bad Request' * */ class ResponseBuilder { public: explicit ResponseBuilder(ResponseHandler* txn) : txn_(txn) { } ResponseBuilder& promise(const std::string& url, const std::string& host) { headers_ = std::make_unique<HTTPMessage>(); headers_->setHTTPVersion(1, 1); headers_->setURL(url); headers_->getHeaders().set(HTTP_HEADER_HOST, host); return *this; } ResponseBuilder& promise(const std::string& url, const std::string& host, HTTPMethod method) { promise(url, host); headers_->setMethod(method); return *this; } ResponseBuilder& status(uint16_t code, const std::string& message) { headers_ = std::make_unique<HTTPMessage>(); headers_->setHTTPVersion(1, 1); headers_->setStatusCode(code); headers_->setStatusMessage(message); return *this; } template <typename T> ResponseBuilder& header(const std::string& headerIn, const T& value) { CHECK(headers_) << "You need to call `status` before adding headers"; headers_->getHeaders().add(headerIn, value); return *this; } template <typename T> ResponseBuilder& header(HTTPHeaderCode code, const T& value) { CHECK(headers_) << "You need to call `status` before adding headers"; headers_->getHeaders().add(code, value); return *this; } ResponseBuilder& body(std::unique_ptr<folly::IOBuf> bodyIn) { if (bodyIn) { if (body_) { body_->prependChain(std::move(bodyIn)); } else { body_ = std::move(bodyIn); } } return *this; } template <typename T> ResponseBuilder& body(T&& t) { return body(folly::IOBuf::maybeCopyBuffer( folly::to<std::string>(std::forward<T>(t)))); } ResponseBuilder& closeConnection() { return header(HTTP_HEADER_CONNECTION, "close"); } ResponseBuilder& trailers(const HTTPHeaders& trailers) { trailers_.reset(new HTTPHeaders(trailers)); return *this; } void sendWithEOM() { sendEOM_ = true; send(); } void send() { // Once we send them, we don't want to send them again SCOPE_EXIT { headers_.reset(); }; // By default, chunked bool chunked = true; // If we have complete response, we can use Content-Length and get done if (headers_ && sendEOM_) { chunked = false; } if (headers_) { // We don't need to add Content-Length or Encoding for 1xx responses if (headers_->isResponse() && headers_->getStatusCode() >= 200) { if (chunked) { headers_->setIsChunked(true); } else { const auto len = body_ ? body_->computeChainDataLength() : 0; headers_->getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, folly::to<std::string>(len)); } } txn_->sendHeaders(*headers_); } if (body_) { if (chunked) { txn_->sendChunkHeader(body_->computeChainDataLength()); txn_->sendBody(std::move(body_)); txn_->sendChunkTerminator(); } else { txn_->sendBody(std::move(body_)); } } if (sendEOM_) { if (trailers_) { auto txn = txn_->getTransaction(); if (txn) { txn->sendTrailers(*trailers_); } trailers_.reset(); } txn_->sendEOM(); } } enum class UpgradeType { CONNECT_REQUEST = 0, HTTP_UPGRADE, }; void acceptUpgradeRequest(UpgradeType upgradeType, const std::string upgradeProtocol = "") { headers_ = std::make_unique<HTTPMessage>(); if (upgradeType == UpgradeType::CONNECT_REQUEST) { headers_->constructDirectResponse({1, 1}, 200, "OK"); } else { CHECK(!upgradeProtocol.empty()); headers_->constructDirectResponse({1, 1}, 101, "Switching Protocols"); headers_->getHeaders().add(HTTP_HEADER_UPGRADE, upgradeProtocol); headers_->getHeaders().add(HTTP_HEADER_CONNECTION, "Upgrade"); } txn_->sendHeaders(*headers_); } void rejectUpgradeRequest() { headers_ = std::make_unique<HTTPMessage>(); headers_->constructDirectResponse({1, 1}, 400, "Bad Request"); txn_->sendHeaders(*headers_); txn_->sendEOM(); } ResponseBuilder& setEgressWebsocketHeaders() { headers_->setEgressWebsocketUpgrade(); return *this; } const HTTPMessage* getHeaders() const { return headers_.get(); } private: ResponseHandler* const txn_{nullptr}; std::unique_ptr<HTTPMessage> headers_; std::unique_ptr<folly::IOBuf> body_; std::unique_ptr<HTTPHeaders> trailers_; // If true, sends EOM. bool sendEOM_{false}; }; } // namespace proxygen