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