1 /* 2 * Copyright (c) Facebook, Inc. and its affiliates. 3 * All rights reserved. 4 * 5 * This source code is licensed under the BSD-style license found in the 6 * LICENSE file in the root directory of this source tree. 7 */ 8 9 #pragma once 10 11 #include <folly/ScopeGuard.h> 12 #include <proxygen/httpserver/ResponseHandler.h> 13 14 namespace proxygen { 15 16 /** 17 * Helps you make responses and send them on demand. 18 * 19 * NOTE: We don't do any correctness checks here, we depend on 20 * state machine in HTTPTransaction to tell us when an 21 * error occurs 22 * 23 * Three expected use cases are 24 * 25 * 1. Send all response at once. If this is an error 26 * response, most probably you also want 'closeConnection'. 27 * 28 * ResponseBuilder(handler) 29 * .status(200, "OK") 30 * .body(...) 31 * .sendWithEOM(); 32 * 33 * 2. Sending back response in chunks. 34 * 35 * ResponseBuilder(handler) 36 * .status(200, "OK") 37 * .body(...) 38 * .send(); // Without `WithEOM` we make it chunked 39 * 40 * 1 or more time 41 * 42 * ResponseBuilder(handler) 43 * .body(...) 44 * .send(); 45 * 46 * At last 47 * 48 * ResponseBuilder(handler) 49 * .body(...) 50 * .sendWithEOM(); 51 * 52 * 3. Accept or reject Upgrade Requests 53 * 54 * ResponseBuilder(handler) 55 * .acceptUpgradeRequest() // send '200 OK' without EOM 56 * 57 * or 58 * 59 * ResponseBuilder(handler) 60 * .rejectUpgradeRequest() // send '400 Bad Request' 61 * 62 */ 63 class ResponseBuilder { 64 public: ResponseBuilder(ResponseHandler * txn)65 explicit ResponseBuilder(ResponseHandler* txn) : txn_(txn) { 66 } 67 promise(const std::string & url,const std::string & host)68 ResponseBuilder& promise(const std::string& url, const std::string& host) { 69 headers_ = std::make_unique<HTTPMessage>(); 70 headers_->setHTTPVersion(1, 1); 71 headers_->setURL(url); 72 headers_->getHeaders().set(HTTP_HEADER_HOST, host); 73 return *this; 74 } 75 promise(const std::string & url,const std::string & host,HTTPMethod method)76 ResponseBuilder& promise(const std::string& url, 77 const std::string& host, 78 HTTPMethod method) { 79 promise(url, host); 80 headers_->setMethod(method); 81 return *this; 82 } 83 status(uint16_t code,const std::string & message)84 ResponseBuilder& status(uint16_t code, const std::string& message) { 85 headers_ = std::make_unique<HTTPMessage>(); 86 headers_->setHTTPVersion(1, 1); 87 headers_->setStatusCode(code); 88 headers_->setStatusMessage(message); 89 return *this; 90 } 91 92 template <typename T> header(const std::string & headerIn,const T & value)93 ResponseBuilder& header(const std::string& headerIn, const T& value) { 94 CHECK(headers_) << "You need to call `status` before adding headers"; 95 headers_->getHeaders().add(headerIn, value); 96 return *this; 97 } 98 99 template <typename T> header(HTTPHeaderCode code,const T & value)100 ResponseBuilder& header(HTTPHeaderCode code, const T& value) { 101 CHECK(headers_) << "You need to call `status` before adding headers"; 102 headers_->getHeaders().add(code, value); 103 return *this; 104 } 105 body(std::unique_ptr<folly::IOBuf> bodyIn)106 ResponseBuilder& body(std::unique_ptr<folly::IOBuf> bodyIn) { 107 if (bodyIn) { 108 if (body_) { 109 body_->prependChain(std::move(bodyIn)); 110 } else { 111 body_ = std::move(bodyIn); 112 } 113 } 114 115 return *this; 116 } 117 118 template <typename T> body(T && t)119 ResponseBuilder& body(T&& t) { 120 return body(folly::IOBuf::maybeCopyBuffer( 121 folly::to<std::string>(std::forward<T>(t)))); 122 } 123 closeConnection()124 ResponseBuilder& closeConnection() { 125 return header(HTTP_HEADER_CONNECTION, "close"); 126 } 127 trailers(const HTTPHeaders & trailers)128 ResponseBuilder& trailers(const HTTPHeaders& trailers) { 129 trailers_.reset(new HTTPHeaders(trailers)); 130 return *this; 131 } 132 sendWithEOM()133 void sendWithEOM() { 134 sendEOM_ = true; 135 send(); 136 } 137 send()138 void send() { 139 // Once we send them, we don't want to send them again 140 SCOPE_EXIT { 141 headers_.reset(); 142 }; 143 144 // By default, chunked 145 bool chunked = true; 146 147 // If we have complete response, we can use Content-Length and get done 148 if (headers_ && sendEOM_) { 149 chunked = false; 150 } 151 152 if (headers_) { 153 // We don't need to add Content-Length or Encoding for 1xx responses 154 if (headers_->isResponse() && headers_->getStatusCode() >= 200) { 155 if (chunked) { 156 headers_->setIsChunked(true); 157 } else { 158 const auto len = body_ ? body_->computeChainDataLength() : 0; 159 headers_->getHeaders().add(HTTP_HEADER_CONTENT_LENGTH, 160 folly::to<std::string>(len)); 161 } 162 } 163 164 txn_->sendHeaders(*headers_); 165 } 166 167 if (body_) { 168 if (chunked) { 169 txn_->sendChunkHeader(body_->computeChainDataLength()); 170 txn_->sendBody(std::move(body_)); 171 txn_->sendChunkTerminator(); 172 } else { 173 txn_->sendBody(std::move(body_)); 174 } 175 } 176 177 if (sendEOM_) { 178 if (trailers_) { 179 auto txn = txn_->getTransaction(); 180 if (txn) { 181 txn->sendTrailers(*trailers_); 182 } 183 trailers_.reset(); 184 } 185 txn_->sendEOM(); 186 } 187 } 188 189 enum class UpgradeType { 190 CONNECT_REQUEST = 0, 191 HTTP_UPGRADE, 192 }; 193 194 void acceptUpgradeRequest(UpgradeType upgradeType, 195 const std::string upgradeProtocol = "") { 196 headers_ = std::make_unique<HTTPMessage>(); 197 if (upgradeType == UpgradeType::CONNECT_REQUEST) { 198 headers_->constructDirectResponse({1, 1}, 200, "OK"); 199 } else { 200 CHECK(!upgradeProtocol.empty()); 201 headers_->constructDirectResponse({1, 1}, 101, "Switching Protocols"); 202 headers_->getHeaders().add(HTTP_HEADER_UPGRADE, upgradeProtocol); 203 headers_->getHeaders().add(HTTP_HEADER_CONNECTION, "Upgrade"); 204 } 205 txn_->sendHeaders(*headers_); 206 } 207 rejectUpgradeRequest()208 void rejectUpgradeRequest() { 209 headers_ = std::make_unique<HTTPMessage>(); 210 headers_->constructDirectResponse({1, 1}, 400, "Bad Request"); 211 txn_->sendHeaders(*headers_); 212 txn_->sendEOM(); 213 } 214 setEgressWebsocketHeaders()215 ResponseBuilder& setEgressWebsocketHeaders() { 216 headers_->setEgressWebsocketUpgrade(); 217 return *this; 218 } 219 getHeaders()220 const HTTPMessage* getHeaders() const { 221 return headers_.get(); 222 } 223 224 private: 225 ResponseHandler* const txn_{nullptr}; 226 227 std::unique_ptr<HTTPMessage> headers_; 228 std::unique_ptr<folly::IOBuf> body_; 229 std::unique_ptr<HTTPHeaders> trailers_; 230 231 // If true, sends EOM. 232 bool sendEOM_{false}; 233 }; 234 235 } // namespace proxygen 236