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 #include "CurlClient.h"
10
11 #include <iostream>
12 #include <sys/stat.h>
13
14 #include <folly/FileUtil.h>
15 #include <folly/String.h>
16 #include <folly/io/async/SSLContext.h>
17 #include <folly/io/async/SSLOptions.h>
18 #include <folly/portability/GFlags.h>
19 #include <proxygen/lib/http/HTTPMessage.h>
20 #include <proxygen/lib/http/codec/HTTP2Codec.h>
21 #include <proxygen/lib/http/session/HTTPUpstreamSession.h>
22
23 using namespace folly;
24 using namespace proxygen;
25 using namespace std;
26
27 DECLARE_int32(recv_window);
28
29 namespace CurlService {
30
CurlClient(EventBase * evb,HTTPMethod httpMethod,const URL & url,const proxygen::URL * proxy,const HTTPHeaders & headers,const string & inputFilename,bool h2c,unsigned short httpMajor,unsigned short httpMinor)31 CurlClient::CurlClient(EventBase* evb,
32 HTTPMethod httpMethod,
33 const URL& url,
34 const proxygen::URL* proxy,
35 const HTTPHeaders& headers,
36 const string& inputFilename,
37 bool h2c,
38 unsigned short httpMajor,
39 unsigned short httpMinor)
40 : evb_(evb),
41 httpMethod_(httpMethod),
42 url_(url),
43 inputFilename_(inputFilename),
44 h2c_(h2c),
45 httpMajor_(httpMajor),
46 httpMinor_(httpMinor) {
47 if (proxy != nullptr) {
48 proxy_ = std::make_unique<URL>(proxy->getUrl());
49 }
50
51 outputStream_ = std::make_unique<std::ostream>(std::cout.rdbuf());
52 headers.forEach([this](const string& header, const string& val) {
53 request_.getHeaders().add(header, val);
54 });
55 }
56
saveResponseToFile(const std::string & outputFilename)57 bool CurlClient::saveResponseToFile(const std::string& outputFilename) {
58 std::streambuf* buf;
59 if (outputFilename.empty()) {
60 return false;
61 }
62 uint16_t tries = 0;
63 while (tries < std::numeric_limits<uint16_t>::max()) {
64 std::string suffix = (tries == 0) ? "" : folly::to<std::string>("_", tries);
65 auto filename = folly::to<std::string>(outputFilename, suffix);
66 struct stat statBuf;
67 if (stat(filename.c_str(), &statBuf) == -1) {
68 outputFile_ =
69 std::make_unique<ofstream>(filename, ios::out | ios::binary);
70 if (*outputFile_ && outputFile_->good()) {
71 buf = outputFile_->rdbuf();
72 outputStream_ = std::make_unique<std::ostream>(buf);
73 return true;
74 }
75 }
76 tries++;
77 }
78 return false;
79 }
80
parseHeaders(const std::string & headersString)81 HTTPHeaders CurlClient::parseHeaders(const std::string& headersString) {
82 vector<StringPiece> headersList;
83 HTTPHeaders headers;
84 folly::split(",", headersString, headersList);
85 for (const auto& headerPair : headersList) {
86 vector<StringPiece> nv;
87 folly::split('=', headerPair, nv);
88 if (nv.size() > 0) {
89 if (nv[0].empty()) {
90 continue;
91 }
92 std::string value("");
93 for (size_t i = 1; i < nv.size(); i++) {
94 value += folly::to<std::string>(nv[i], '=');
95 }
96 if (nv.size() > 1) {
97 value.pop_back();
98 } // trim anything else
99 headers.add(nv[0], value);
100 }
101 }
102 return headers;
103 }
104
initializeSsl(const string & caPath,const string & nextProtos,const string & certPath,const string & keyPath)105 void CurlClient::initializeSsl(const string& caPath,
106 const string& nextProtos,
107 const string& certPath,
108 const string& keyPath) {
109 sslContext_ = std::make_shared<folly::SSLContext>();
110 sslContext_->setOptions(SSL_OP_NO_COMPRESSION);
111 folly::ssl::setCipherSuites<folly::ssl::SSLCommonOptions>(*sslContext_);
112 if (!caPath.empty()) {
113 sslContext_->loadTrustedCertificates(caPath.c_str());
114 }
115 if (!certPath.empty() && !keyPath.empty()) {
116 sslContext_->loadCertKeyPairFromFiles(certPath.c_str(), keyPath.c_str());
117 }
118 list<string> nextProtoList;
119 folly::splitTo<string>(
120 ',', nextProtos, std::inserter(nextProtoList, nextProtoList.begin()));
121 sslContext_->setAdvertisedNextProtocols(nextProtoList);
122 h2c_ = false;
123 }
124
sslHandshakeFollowup(HTTPUpstreamSession * session)125 void CurlClient::sslHandshakeFollowup(HTTPUpstreamSession* session) noexcept {
126 AsyncSSLSocket* sslSocket =
127 dynamic_cast<AsyncSSLSocket*>(session->getTransport());
128
129 const unsigned char* nextProto = nullptr;
130 unsigned nextProtoLength = 0;
131 sslSocket->getSelectedNextProtocol(&nextProto, &nextProtoLength);
132 if (nextProto) {
133 VLOG(1) << "Client selected next protocol "
134 << string((const char*)nextProto, nextProtoLength);
135 } else {
136 VLOG(1) << "Client did not select a next protocol";
137 }
138
139 // Note: This ssl session can be used by defining a member and setting
140 // something like sslSession_ = sslSocket->getSSLSession() and then
141 // passing it to the connector::connectSSL() method
142 }
143
setFlowControlSettings(int32_t recvWindow)144 void CurlClient::setFlowControlSettings(int32_t recvWindow) {
145 recvWindow_ = recvWindow;
146 }
147
connectSuccess(HTTPUpstreamSession * session)148 void CurlClient::connectSuccess(HTTPUpstreamSession* session) {
149
150 if (url_.isSecure()) {
151 sslHandshakeFollowup(session);
152 }
153
154 session->setFlowControl(recvWindow_, recvWindow_, recvWindow_);
155 sendRequest(session->newTransaction(this));
156 session->closeWhenIdle();
157 }
158
setupHeaders()159 void CurlClient::setupHeaders() {
160 request_.setMethod(httpMethod_);
161 request_.setHTTPVersion(httpMajor_, httpMinor_);
162 if (proxy_) {
163 request_.setURL(url_.getUrl());
164 } else {
165 request_.setURL(url_.makeRelativeURL());
166 }
167 request_.setSecure(url_.isSecure());
168 if (h2c_) {
169 HTTP2Codec::requestUpgrade(request_);
170 }
171
172 if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_USER_AGENT)) {
173 request_.getHeaders().add(HTTP_HEADER_USER_AGENT, "proxygen_curl");
174 }
175 if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_HOST)) {
176 request_.getHeaders().add(HTTP_HEADER_HOST, url_.getHostAndPort());
177 }
178 if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_ACCEPT)) {
179 request_.getHeaders().add("Accept", "*/*");
180 }
181 if (loggingEnabled_) {
182 request_.dumpMessage(4);
183 }
184 }
185
sendRequest(HTTPTransaction * txn)186 void CurlClient::sendRequest(HTTPTransaction* txn) {
187 txn_ = txn;
188 setupHeaders();
189 txn_->sendHeaders(request_);
190
191 if (httpMethod_ == HTTPMethod::POST) {
192 inputFile_ =
193 std::make_unique<ifstream>(inputFilename_, ios::in | ios::binary);
194 sendBodyFromFile();
195 } else {
196 txn_->sendEOM();
197 }
198 }
199
sendBodyFromFile()200 void CurlClient::sendBodyFromFile() {
201 const uint16_t kReadSize = 4096;
202 CHECK(inputFile_);
203 // Reading from the file by chunks
204 // Important note: It's pretty bad to call a blocking i/o function like
205 // ifstream::read() in an eventloop - but for the sake of this simple
206 // example, we'll do it.
207 // An alternative would be to put this into some folly::AsyncReader
208 // object.
209 while (inputFile_->good() && !egressPaused_) {
210 unique_ptr<IOBuf> buf = IOBuf::createCombined(kReadSize);
211 inputFile_->read((char*)buf->writableData(), kReadSize);
212 buf->append(inputFile_->gcount());
213 txn_->sendBody(move(buf));
214 }
215 if (!egressPaused_) {
216 txn_->sendEOM();
217 }
218 }
219
printMessageImpl(proxygen::HTTPMessage * msg,const std::string & tag)220 void CurlClient::printMessageImpl(proxygen::HTTPMessage* msg,
221 const std::string& tag) {
222 if (!loggingEnabled_) {
223 return;
224 }
225 cout << tag;
226 msg->dumpMessage(10);
227 }
228
connectError(const folly::AsyncSocketException & ex)229 void CurlClient::connectError(const folly::AsyncSocketException& ex) {
230 LOG_IF(ERROR, loggingEnabled_)
231 << "Coudln't connect to " << url_.getHostAndPort() << ":" << ex.what();
232 }
233
setTransaction(HTTPTransaction *)234 void CurlClient::setTransaction(HTTPTransaction*) noexcept {
235 }
236
detachTransaction()237 void CurlClient::detachTransaction() noexcept {
238 }
239
onHeadersComplete(unique_ptr<HTTPMessage> msg)240 void CurlClient::onHeadersComplete(unique_ptr<HTTPMessage> msg) noexcept {
241 response_ = std::move(msg);
242 printMessageImpl(response_.get());
243 if (!headersLoggingEnabled_) {
244 return;
245 }
246 response_->describe(*outputStream_);
247 *outputStream_ << std::endl;
248 }
249
onBody(std::unique_ptr<folly::IOBuf> chain)250 void CurlClient::onBody(std::unique_ptr<folly::IOBuf> chain) noexcept {
251 if (!loggingEnabled_) {
252 return;
253 }
254 CHECK(outputStream_);
255 if (chain) {
256 const IOBuf* p = chain.get();
257 do {
258 outputStream_->write((const char*)p->data(), p->length());
259 outputStream_->flush();
260 p = p->next();
261 } while (p != chain.get());
262 }
263 }
264
onTrailers(std::unique_ptr<HTTPHeaders>)265 void CurlClient::onTrailers(std::unique_ptr<HTTPHeaders>) noexcept {
266 LOG_IF(INFO, loggingEnabled_) << "Discarding trailers";
267 }
268
onEOM()269 void CurlClient::onEOM() noexcept {
270 LOG_IF(INFO, loggingEnabled_) << "Got EOM";
271 if (eomFunc_) {
272 eomFunc_.value()();
273 }
274 }
275
onUpgrade(UpgradeProtocol)276 void CurlClient::onUpgrade(UpgradeProtocol) noexcept {
277 LOG_IF(INFO, loggingEnabled_) << "Discarding upgrade protocol";
278 }
279
onError(const HTTPException & error)280 void CurlClient::onError(const HTTPException& error) noexcept {
281 LOG_IF(ERROR, loggingEnabled_) << "An error occurred: " << error.what();
282 }
283
onEgressPaused()284 void CurlClient::onEgressPaused() noexcept {
285 VLOG_IF(1, loggingEnabled_) << "Egress paused";
286 egressPaused_ = true;
287 }
288
onEgressResumed()289 void CurlClient::onEgressResumed() noexcept {
290 VLOG_IF(1, loggingEnabled_) << "Egress resumed";
291 egressPaused_ = false;
292 if (inputFile_) {
293 sendBodyFromFile();
294 }
295 }
296
onPushedTransaction(proxygen::HTTPTransaction * pushedTxn)297 void CurlClient::onPushedTransaction(
298 proxygen::HTTPTransaction* pushedTxn) noexcept {
299 //
300 pushTxnHandlers_.emplace_back(std::make_unique<CurlPushHandler>(this));
301 pushedTxn->setHandler(pushTxnHandlers_.back().get());
302 // Add implementation of the push transaction reception here
303 }
304
getServerName() const305 const string& CurlClient::getServerName() const {
306 const string& res = request_.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST);
307 if (res.empty()) {
308 return url_.getHost();
309 }
310 return res;
311 }
312
313 // CurlPushHandler methods
setTransaction(proxygen::HTTPTransaction * txn)314 void CurlClient::CurlPushHandler::setTransaction(
315 proxygen::HTTPTransaction* txn) noexcept {
316 LOG_IF(INFO, parent_->loggingEnabled_) << "Received pushed transaction";
317 pushedTxn_ = txn;
318 }
319
detachTransaction()320 void CurlClient::CurlPushHandler::detachTransaction() noexcept {
321 LOG_IF(INFO, parent_->loggingEnabled_) << "Detached pushed transaction";
322 }
323
onHeadersComplete(std::unique_ptr<proxygen::HTTPMessage> msg)324 void CurlClient::CurlPushHandler::onHeadersComplete(
325 std::unique_ptr<proxygen::HTTPMessage> msg) noexcept {
326 if (!seenOnHeadersComplete_) {
327 seenOnHeadersComplete_ = true;
328 promise_ = std::move(msg);
329 parent_->printMessageImpl(promise_.get(), "[PP] ");
330 } else {
331 response_ = std::move(msg);
332 parent_->printMessageImpl(response_.get(), "[PR] ");
333 }
334 }
335
onBody(std::unique_ptr<folly::IOBuf> chain)336 void CurlClient::CurlPushHandler::onBody(
337 std::unique_ptr<folly::IOBuf> chain) noexcept {
338 parent_->onBody(std::move(chain));
339 }
340
onEOM()341 void CurlClient::CurlPushHandler::onEOM() noexcept {
342 LOG_IF(INFO, parent_->loggingEnabled_) << "Got PushTxn EOM";
343 }
344
onError(const proxygen::HTTPException & error)345 void CurlClient::CurlPushHandler::onError(
346 const proxygen::HTTPException& error) noexcept {
347 parent_->onError(error);
348 }
349
350 } // namespace CurlService
351