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 <proxygen/httpserver/samples/hq/HQClient.h>
10 
11 #include <fstream>
12 #include <ostream>
13 #include <string>
14 #include <thread>
15 
16 #include <folly/io/async/AsyncTimeout.h>
17 #include <folly/io/async/EventBaseManager.h>
18 #include <folly/io/async/ScopedEventBaseThread.h>
19 #include <folly/json.h>
20 
21 #include <proxygen/httpserver/samples/hq/FizzContext.h>
22 #include <proxygen/httpserver/samples/hq/HQLoggerHelper.h>
23 #include <proxygen/httpserver/samples/hq/InsecureVerifierDangerousDoNotUseInProduction.h>
24 #include <proxygen/lib/http/codec/HTTP1xCodec.h>
25 #include <proxygen/lib/utils/UtilInl.h>
26 #include <quic/api/QuicSocket.h>
27 #include <quic/client/QuicClientTransport.h>
28 #include <quic/congestion_control/CongestionControllerFactory.h>
29 #include <quic/fizz/client/handshake/FizzClientQuicHandshakeContext.h>
30 #include <quic/logging/FileQLogger.h>
31 
32 namespace quic { namespace samples {
33 
HQClient(const HQParams & params)34 HQClient::HQClient(const HQParams& params) : params_(params) {
35   if (params_.transportSettings.pacingEnabled) {
36     pacingTimer_ = TimerHighRes::newTimer(
37         &evb_, params_.transportSettings.pacingTimerTickInterval);
38   }
39 }
40 
start()41 int HQClient::start() {
42 
43   initializeQuicClient();
44   initializeQLogger();
45 
46   // TODO: turn on cert verification
47   wangle::TransportInfo tinfo;
48   session_ = new proxygen::HQUpstreamSession(params_.txnTimeout,
49                                              params_.connectTimeout,
50                                              nullptr, // controller
51                                              tinfo,
52                                              nullptr); // codecfiltercallback
53 
54   // Need this for Interop since we use HTTP0.9
55   session_->setForceUpstream1_1(false);
56 
57   // TODO: this could now be moved back in the ctor
58   session_->setSocket(quicClient_);
59   session_->setConnectCallback(this);
60 
61   LOG(INFO) << "HQClient connecting to " << params_.remoteAddress->describe();
62   session_->startNow();
63   quicClient_->start(session_);
64 
65   // This is to flush the CFIN out so the server will see the handshake as
66   // complete.
67   evb_.loopForever();
68   if (params_.migrateClient) {
69     quicClient_->onNetworkSwitch(
70         std::make_unique<folly::AsyncUDPSocket>(&evb_));
71     sendRequests(true, quicClient_->getNumOpenableBidirectionalStreams());
72   }
73   evb_.loop();
74 
75   return failed_ ? -1 : 0;
76 }
77 
78 proxygen::HTTPTransaction* FOLLY_NULLABLE
sendRequest(const proxygen::URL & requestUrl)79 HQClient::sendRequest(const proxygen::URL& requestUrl) {
80   std::unique_ptr<CurlService::CurlClient> client =
81       std::make_unique<CurlService::CurlClient>(&evb_,
82                                                 params_.httpMethod,
83                                                 requestUrl,
84                                                 nullptr,
85                                                 params_.httpHeaders,
86                                                 params_.httpBody,
87                                                 false,
88                                                 params_.httpVersion.major,
89                                                 params_.httpVersion.minor);
90 
91   client->setLogging(params_.logResponse);
92   client->setHeadersLogging(params_.logResponseHeaders);
93   auto txn = session_->newTransaction(client.get());
94   if (!txn) {
95     return nullptr;
96   }
97   if (!params_.outdir.empty()) {
98     bool canWrite = false;
99     // default output file name
100     std::string filename = "hq.out";
101     // try to get the name from the path
102     folly::StringPiece path = requestUrl.getPath();
103     size_t offset = proxygen::findLastOf(path, '/');
104     if (offset != std::string::npos && (offset + 1) != path.size()) {
105       filename = std::string(path.subpiece(offset + 1));
106     }
107     filename = folly::to<std::string>(params_.outdir, "/", filename);
108     canWrite = client->saveResponseToFile(filename);
109     if (!canWrite) {
110       LOG(ERROR) << "Can not write output to file '" << filename
111                  << "' printing to stdout instead";
112     }
113   }
114   client->sendRequest(txn);
115   curls_.emplace_back(std::move(client));
116   return txn;
117 }
118 
sendRequests(bool closeSession,uint64_t numOpenableStreams)119 void HQClient::sendRequests(bool closeSession, uint64_t numOpenableStreams) {
120   VLOG(10) << "http-version:" << params_.httpVersion;
121   while (!httpPaths_.empty() && numOpenableStreams > 0) {
122     proxygen::URL requestUrl(httpPaths_.front().str(), /*secure=*/true);
123     sendRequest(requestUrl);
124     httpPaths_.pop_front();
125     numOpenableStreams--;
126   }
127   if (closeSession && httpPaths_.empty()) {
128     session_->drain();
129     session_->closeWhenIdle();
130   }
131 }
132 static std::function<void()> selfSchedulingRequestRunner;
133 
connectSuccess()134 void HQClient::connectSuccess() {
135   if (params_.sendKnobFrame) {
136     sendKnobFrame("Hello, World from Client!");
137   }
138   uint64_t numOpenableStreams =
139       quicClient_->getNumOpenableBidirectionalStreams();
140   CHECK_GT(numOpenableStreams, 0);
141   httpPaths_.insert(
142       httpPaths_.end(), params_.httpPaths.begin(), params_.httpPaths.end());
143   sendRequests(!params_.migrateClient, numOpenableStreams);
144   // If there are still pending requests, schedule a callback on the first EOM
145   // to try to make some more. That callback will keep scheduling itself until
146   // there are no more requests.
147   if (!httpPaths_.empty()) {
148     selfSchedulingRequestRunner = [&]() {
149       uint64_t numOpenable = quicClient_->getNumOpenableBidirectionalStreams();
150       if (numOpenable > 0) {
151         sendRequests(true, numOpenable);
152       };
153       if (!httpPaths_.empty()) {
154         auto rtt = std::chrono::duration_cast<std::chrono::milliseconds>(
155             quicClient_->getTransportInfo().srtt);
156         evb_.timer().scheduleTimeoutFn(
157             selfSchedulingRequestRunner,
158             std::max(rtt, std::chrono::milliseconds(1)));
159       }
160     };
161     CHECK(!curls_.empty());
162     curls_.back()->setEOMFunc(selfSchedulingRequestRunner);
163   }
164 }
165 
sendKnobFrame(const folly::StringPiece str)166 void HQClient::sendKnobFrame(const folly::StringPiece str) {
167   if (str.empty()) {
168     return;
169   }
170   uint64_t knobSpace = 0xfaceb00c;
171   uint64_t knobId = 100;
172   Buf buf(folly::IOBuf::create(str.size()));
173   memcpy(buf->writableData(), str.data(), str.size());
174   buf->append(str.size());
175   VLOG(10) << "Sending Knob Frame to peer. KnobSpace: " << std::hex << knobSpace
176            << " KnobId: " << std::dec << knobId << " Knob Blob" << str;
177   const auto knobSent = quicClient_->setKnob(0xfaceb00c, 100, std::move(buf));
178   if (knobSent.hasError()) {
179     LOG(ERROR) << "Failed to send Knob frame to peer. Received error: "
180                << knobSent.error();
181   }
182 }
183 
onReplaySafe()184 void HQClient::onReplaySafe() {
185   VLOG(10) << "Transport replay safe";
186   evb_.terminateLoopSoon();
187 }
188 
connectError(std::pair<quic::QuicErrorCode,std::string> error)189 void HQClient::connectError(std::pair<quic::QuicErrorCode, std::string> error) {
190   LOG(ERROR) << "HQClient failed to connect, error=" << toString(error.first)
191              << ", msg=" << error.second;
192   failed_ = true;
193   evb_.terminateLoopSoon();
194 }
195 
initializeQuicClient()196 void HQClient::initializeQuicClient() {
197   auto sock = std::make_unique<folly::AsyncUDPSocket>(&evb_);
198   auto client = std::make_shared<quic::QuicClientTransport>(
199       &evb_,
200       std::move(sock),
201       quic::FizzClientQuicHandshakeContext::Builder()
202           .setFizzClientContext(createFizzClientContext(params_))
203           .setCertificateVerifier(
204               std::make_unique<
205                   proxygen::InsecureVerifierDangerousDoNotUseInProduction>())
206           .setPskCache(params_.pskCache)
207           .build());
208   client->setPacingTimer(pacingTimer_);
209   client->setHostname(params_.host);
210   client->addNewPeerAddress(params_.remoteAddress.value());
211   if (params_.localAddress.has_value()) {
212     client->setLocalAddress(*params_.localAddress);
213   }
214   client->setCongestionControllerFactory(
215       std::make_shared<quic::DefaultCongestionControllerFactory>());
216   client->setTransportSettings(params_.transportSettings);
217   client->setSupportedVersions(params_.quicVersions);
218 
219   quicClient_ = std::move(client);
220 }
221 
initializeQLogger()222 void HQClient::initializeQLogger() {
223   if (!quicClient_) {
224     return;
225   }
226   // Not used immediately, but if not set
227   // the qlogger wont be able to report. Checking early
228   if (params_.qLoggerPath.empty()) {
229     return;
230   }
231 
232   auto qLogger = std::make_shared<HQLoggerHelper>(
233       params_.qLoggerPath, params_.prettyJson, quic::VantagePoint::Client);
234   quicClient_->setQLogger(std::move(qLogger));
235 }
236 
startClient(const HQParams & params)237 int startClient(const HQParams& params) {
238   HQClient client(params);
239   return client.start();
240 }
241 
242 }} // namespace quic::samples
243