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