1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "perfetto/ext/base/http/http_server.h"
17
18 #include <cinttypes>
19
20 #include <vector>
21
22 #include "perfetto/ext/base/base64.h"
23 #include "perfetto/ext/base/endian.h"
24 #include "perfetto/ext/base/http/sha1.h"
25 #include "perfetto/ext/base/string_utils.h"
26 #include "perfetto/ext/base/string_view.h"
27
28 namespace perfetto {
29 namespace base {
30
31 namespace {
32 constexpr size_t kMaxPayloadSize = 64 * 1024 * 1024;
33 constexpr size_t kMaxRequestSize = kMaxPayloadSize + 4096;
34
35 enum WebsocketOpcode : uint8_t {
36 kOpcodeContinuation = 0x0,
37 kOpcodeText = 0x1,
38 kOpcodeBinary = 0x2,
39 kOpcodeDataUnused = 0x3,
40 kOpcodeClose = 0x8,
41 kOpcodePing = 0x9,
42 kOpcodePong = 0xA,
43 kOpcodeControlUnused = 0xB,
44 };
45
46 // From https://datatracker.ietf.org/doc/html/rfc6455#section-1.3.
47 constexpr char kWebsocketGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
48
49 } // namespace
50
HttpServer(TaskRunner * task_runner,HttpRequestHandler * req_handler)51 HttpServer::HttpServer(TaskRunner* task_runner, HttpRequestHandler* req_handler)
52 : task_runner_(task_runner), req_handler_(req_handler) {}
53 HttpServer::~HttpServer() = default;
54
Start(int port)55 void HttpServer::Start(int port) {
56 std::string ipv4_addr = "127.0.0.1:" + std::to_string(port);
57 std::string ipv6_addr = "[::1]:" + std::to_string(port);
58
59 sock4_ = UnixSocket::Listen(ipv4_addr, this, task_runner_, SockFamily::kInet,
60 SockType::kStream);
61 bool ipv4_listening = sock4_ && sock4_->is_listening();
62 if (!ipv4_listening) {
63 PERFETTO_PLOG("Failed to listen on IPv4 socket: \"%s\"", ipv4_addr.c_str());
64 sock4_.reset();
65 }
66
67 sock6_ = UnixSocket::Listen(ipv6_addr, this, task_runner_, SockFamily::kInet6,
68 SockType::kStream);
69 bool ipv6_listening = sock6_ && sock6_->is_listening();
70 if (!ipv6_listening) {
71 PERFETTO_PLOG("Failed to listen on IPv6 socket: \"%s\"", ipv6_addr.c_str());
72 sock6_.reset();
73 }
74 }
75
AddAllowedOrigin(const std::string & origin)76 void HttpServer::AddAllowedOrigin(const std::string& origin) {
77 allowed_origins_.emplace_back(origin);
78 }
79
OnNewIncomingConnection(UnixSocket *,std::unique_ptr<UnixSocket> sock)80 void HttpServer::OnNewIncomingConnection(
81 UnixSocket*, // The listening socket, irrelevant here.
82 std::unique_ptr<UnixSocket> sock) {
83 PERFETTO_LOG("[HTTP] New connection");
84 clients_.emplace_back(std::move(sock));
85 }
86
OnConnect(UnixSocket *,bool)87 void HttpServer::OnConnect(UnixSocket*, bool) {}
88
OnDisconnect(UnixSocket * sock)89 void HttpServer::OnDisconnect(UnixSocket* sock) {
90 PERFETTO_LOG("[HTTP] Client disconnected");
91 for (auto it = clients_.begin(); it != clients_.end(); ++it) {
92 if (it->sock.get() == sock) {
93 req_handler_->OnHttpConnectionClosed(&*it);
94 clients_.erase(it);
95 return;
96 }
97 }
98 PERFETTO_DFATAL("[HTTP] Untracked client in OnDisconnect()");
99 }
100
OnDataAvailable(UnixSocket * sock)101 void HttpServer::OnDataAvailable(UnixSocket* sock) {
102 HttpServerConnection* conn = nullptr;
103 for (auto it = clients_.begin(); it != clients_.end() && !conn; ++it)
104 conn = (it->sock.get() == sock) ? &*it : nullptr;
105 PERFETTO_CHECK(conn);
106
107 char* rxbuf = reinterpret_cast<char*>(conn->rxbuf.Get());
108 for (;;) {
109 size_t avail = conn->rxbuf_avail();
110 PERFETTO_CHECK(avail <= kMaxRequestSize);
111 if (avail == 0) {
112 conn->SendResponseAndClose("413 Payload Too Large");
113 return;
114 }
115 size_t rsize = sock->Receive(&rxbuf[conn->rxbuf_used], avail);
116 conn->rxbuf_used += rsize;
117 if (rsize == 0 || conn->rxbuf_avail() == 0)
118 break;
119 }
120
121 // At this point |rxbuf| can contain a partial HTTP request, a full one or
122 // more (in case of HTTP Keepalive pipelining).
123 for (;;) {
124 size_t bytes_consumed;
125
126 if (conn->is_websocket()) {
127 bytes_consumed = ParseOneWebsocketFrame(conn);
128 } else {
129 bytes_consumed = ParseOneHttpRequest(conn);
130 }
131
132 if (bytes_consumed == 0)
133 break;
134 memmove(rxbuf, &rxbuf[bytes_consumed], conn->rxbuf_used - bytes_consumed);
135 conn->rxbuf_used -= bytes_consumed;
136 }
137 }
138
139 // Parses the HTTP request and invokes HandleRequest(). It returns the size of
140 // the HTTP header + body that has been processed or 0 if there isn't enough
141 // data for a full HTTP request in the buffer.
ParseOneHttpRequest(HttpServerConnection * conn)142 size_t HttpServer::ParseOneHttpRequest(HttpServerConnection* conn) {
143 auto* rxbuf = reinterpret_cast<char*>(conn->rxbuf.Get());
144 StringView buf_view(rxbuf, conn->rxbuf_used);
145 bool has_parsed_first_line = false;
146 bool all_headers_received = false;
147 HttpRequest http_req(conn);
148 size_t body_size = 0;
149
150 // This loop parses the HTTP request headers and sets the |body_offset|.
151 while (!buf_view.empty()) {
152 size_t next = buf_view.find('\n');
153 if (next == StringView::npos)
154 break;
155 StringView line = buf_view.substr(0, next);
156 buf_view = buf_view.substr(next + 1); // Eat the current line.
157 while (!line.empty() && (line.at(line.size() - 1) == '\r' ||
158 line.at(line.size() - 1) == '\n')) {
159 line = line.substr(0, line.size() - 1);
160 }
161
162 if (!has_parsed_first_line) {
163 // Parse the "GET /xxx HTTP/1.1" line.
164 has_parsed_first_line = true;
165 size_t space = line.find(' ');
166 if (space == std::string::npos || space + 2 >= line.size()) {
167 conn->SendResponseAndClose("400 Bad Request");
168 return 0;
169 }
170 http_req.method = line.substr(0, space);
171 size_t uri_size = line.find(' ', space + 1) - (space + 1);
172 http_req.uri = line.substr(space + 1, uri_size);
173 } else if (line.empty()) {
174 all_headers_received = true;
175 // The CR-LF marker that separates headers from body.
176 break;
177 } else {
178 // Parse HTTP headers, e.g. "Content-Length: 1234".
179 size_t col = line.find(':');
180 if (col == StringView::npos) {
181 PERFETTO_DLOG("[HTTP] Malformed HTTP header: \"%s\"",
182 line.ToStdString().c_str());
183 conn->SendResponseAndClose("400 Bad Request", {}, "Bad HTTP header");
184 return 0;
185 }
186 auto hdr_name = line.substr(0, col);
187 auto hdr_value = line.substr(col + 2);
188 if (http_req.num_headers < http_req.headers.size()) {
189 http_req.headers[http_req.num_headers++] = {hdr_name, hdr_value};
190 } else {
191 conn->SendResponseAndClose("400 Bad Request", {},
192 "Too many HTTP headers");
193 }
194
195 if (hdr_name.CaseInsensitiveEq("content-length")) {
196 body_size = static_cast<size_t>(atoi(hdr_value.ToStdString().c_str()));
197 } else if (hdr_name.CaseInsensitiveEq("origin")) {
198 http_req.origin = hdr_value;
199 if (IsOriginAllowed(hdr_value))
200 conn->origin_allowed_ = hdr_value.ToStdString();
201 } else if (hdr_name.CaseInsensitiveEq("connection")) {
202 conn->keepalive_ = hdr_value.CaseInsensitiveEq("keep-alive");
203 http_req.is_websocket_handshake =
204 hdr_value.CaseInsensitiveEq("upgrade");
205 }
206 }
207 }
208
209 // At this point |buf_view| has been stripped of the header and contains the
210 // request body. We don't know yet if we have all the bytes for it or not.
211 PERFETTO_CHECK(buf_view.size() <= conn->rxbuf_used);
212 const size_t headers_size = conn->rxbuf_used - buf_view.size();
213
214 if (body_size + headers_size >= kMaxRequestSize ||
215 body_size > kMaxPayloadSize) {
216 conn->SendResponseAndClose("413 Payload Too Large");
217 return 0;
218 }
219
220 // If we can't read the full request return and try again next time with more
221 // data.
222 if (!all_headers_received || buf_view.size() < body_size)
223 return 0;
224
225 http_req.body = buf_view.substr(0, body_size);
226
227 PERFETTO_LOG("[HTTP] %.*s %.*s [body=%zuB, origin=\"%.*s\"]",
228 static_cast<int>(http_req.method.size()), http_req.method.data(),
229 static_cast<int>(http_req.uri.size()), http_req.uri.data(),
230 http_req.body.size(), static_cast<int>(http_req.origin.size()),
231 http_req.origin.data());
232
233 if (http_req.method == "OPTIONS") {
234 HandleCorsPreflightRequest(http_req);
235 } else {
236 // Let the HttpHandler handle the request.
237 req_handler_->OnHttpRequest(http_req);
238 }
239
240 // The handler is expected to send a response. If not, bail with a HTTP 500.
241 if (!conn->headers_sent_)
242 conn->SendResponseAndClose("500 Internal Server Error");
243
244 // Allow chaining multiple responses in the same HTTP-Keepalive connection.
245 conn->headers_sent_ = false;
246
247 return headers_size + body_size;
248 }
249
HandleCorsPreflightRequest(const HttpRequest & req)250 void HttpServer::HandleCorsPreflightRequest(const HttpRequest& req) {
251 req.conn->SendResponseAndClose(
252 "204 No Content",
253 {
254 "Access-Control-Allow-Methods: POST, GET, OPTIONS", //
255 "Access-Control-Allow-Headers: *", //
256 "Access-Control-Max-Age: 86400", //
257 "Access-Control-Allow-Private-Network: true", //
258 });
259 }
260
IsOriginAllowed(StringView origin)261 bool HttpServer::IsOriginAllowed(StringView origin) {
262 for (const std::string& allowed_origin : allowed_origins_) {
263 if (origin.CaseInsensitiveEq(StringView(allowed_origin))) {
264 return true;
265 }
266 }
267 if (!origin_error_logged_ && !origin.empty()) {
268 origin_error_logged_ = true;
269 PERFETTO_ELOG(
270 "[HTTP] The origin \"%.*s\" is not allowed, Access-Control-Allow-Origin"
271 " won't be emitted. If this request comes from a browser it will fail.",
272 static_cast<int>(origin.size()), origin.data());
273 }
274 return false;
275 }
276
UpgradeToWebsocket(const HttpRequest & req)277 void HttpServerConnection::UpgradeToWebsocket(const HttpRequest& req) {
278 PERFETTO_CHECK(req.is_websocket_handshake);
279
280 // |origin_allowed_| is set to the req.origin only if it's in the allowlist.
281 if (origin_allowed_.empty())
282 return SendResponseAndClose("403 Forbidden", {}, "Origin not allowed");
283
284 auto ws_ver = req.GetHeader("sec-webSocket-version").value_or(StringView());
285 auto ws_key = req.GetHeader("sec-webSocket-key").value_or(StringView());
286
287 if (!ws_ver.CaseInsensitiveEq("13"))
288 return SendResponseAndClose("505 HTTP Version Not Supported", {});
289
290 if (ws_key.size() != 24) {
291 // The nonce must be a base64 encoded 16 bytes value (24 after base64).
292 return SendResponseAndClose("400 Bad Request", {});
293 }
294
295 // From https://datatracker.ietf.org/doc/html/rfc6455#section-1.3 :
296 // For this header field, the server has to take the value (as present
297 // in the header field, e.g., the base64-encoded [RFC4648] version minus
298 // any leading and trailing whitespace) and concatenate this with the
299 // Globally Unique Identifier (GUID, [RFC4122]) "258EAFA5-E914-47DA-
300 // 95CA-C5AB0DC85B11" in string form, which is unlikely to be used by
301 // network endpoints that do not understand the WebSocket Protocol. A
302 // SHA-1 hash (160 bits) [FIPS.180-3], base64-encoded (see Section 4 of
303 // [RFC4648]), of this concatenation is then returned in the server's
304 // handshake.
305 StackString<128> signed_nonce("%.*s%s", static_cast<int>(ws_key.size()),
306 ws_key.data(), kWebsocketGuid);
307 auto digest = SHA1Hash(signed_nonce.c_str(), signed_nonce.len());
308 std::string digest_b64 = Base64Encode(digest.data(), digest.size());
309
310 StackString<128> accept_hdr("Sec-WebSocket-Accept: %s", digest_b64.c_str());
311
312 std::initializer_list<const char*> headers = {
313 "Upgrade: websocket", //
314 "Connection: Upgrade", //
315 accept_hdr.c_str(), //
316 };
317 PERFETTO_DLOG("[HTTP] Handshaking WebSocket for %.*s",
318 static_cast<int>(req.uri.size()), req.uri.data());
319 for (const char* hdr : headers)
320 PERFETTO_DLOG("> %s", hdr);
321
322 SendResponseHeaders("101 Switching Protocols", headers,
323 HttpServerConnection::kOmitContentLength);
324
325 is_websocket_ = true;
326 }
327
ParseOneWebsocketFrame(HttpServerConnection * conn)328 size_t HttpServer::ParseOneWebsocketFrame(HttpServerConnection* conn) {
329 auto* rxbuf = reinterpret_cast<uint8_t*>(conn->rxbuf.Get());
330 const size_t frame_size = conn->rxbuf_used;
331 uint8_t* rd = rxbuf;
332 uint8_t* const end = rxbuf + frame_size;
333
334 auto avail = [&] {
335 PERFETTO_CHECK(rd <= end);
336 return static_cast<size_t>(end - rd);
337 };
338
339 // From https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 :
340 // 0 1 2 3
341 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
342 // +-+-+-+-+-------+-+-------------+-------------------------------+
343 // |F|R|R|R| opcode|M| Payload len | Extended payload length |
344 // |I|S|S|S| (4) |A| (7) | (16/64) |
345 // |N|V|V|V| |S| | (if payload len==126/127) |
346 // | |1|2|3| |K| | |
347 // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
348 // | Extended payload length continued, if payload len == 127 |
349 // + - - - - - - - - - - - - - - - +-------------------------------+
350 // | |Masking-key, if MASK set to 1 |
351 // +-------------------------------+-------------------------------+
352 // | Masking-key (continued) | Payload Data |
353 // +-------------------------------- - - - - - - - - - - - - - - - +
354 // : Payload Data continued ... :
355 // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
356 // | Payload Data continued ... |
357 // +---------------------------------------------------------------+
358
359 if (avail() < 2)
360 return 0; // Can't even decode the frame header. Wait for more data.
361
362 uint8_t h0 = *(rd++);
363 uint8_t h1 = *(rd++);
364 const uint8_t opcode = h0 & 0x0F;
365
366 const bool has_mask = !!(h1 & 0x80);
367 uint64_t payload_len_u64 = (h1 & 0x7F);
368 uint8_t extended_payload_size = 0;
369 if (payload_len_u64 == 126) {
370 extended_payload_size = 2;
371 } else if (payload_len_u64 == 127) {
372 extended_payload_size = 8;
373 }
374
375 if (extended_payload_size > 0) {
376 if (avail() < extended_payload_size)
377 return 0; // Not enough data to read the extended header.
378 payload_len_u64 = 0;
379 for (uint8_t i = 0; i < extended_payload_size; ++i) {
380 payload_len_u64 <<= 8;
381 payload_len_u64 |= *(rd++);
382 }
383 }
384
385 if (payload_len_u64 >= kMaxPayloadSize) {
386 PERFETTO_ELOG("[HTTP] Websocket payload too big (%" PRIu64 " > %zu)",
387 payload_len_u64, kMaxPayloadSize);
388 conn->Close();
389 return 0;
390 }
391 const size_t payload_len = static_cast<size_t>(payload_len_u64);
392
393 if (!has_mask) {
394 // https://datatracker.ietf.org/doc/html/rfc6455#section-5.1
395 // The server MUST close the connection upon receiving a frame that is
396 // not masked.
397 PERFETTO_ELOG("[HTTP] Websocket inbound frames must be masked");
398 conn->Close();
399 return 0;
400 }
401
402 uint8_t mask[4];
403 if (avail() < sizeof(mask))
404 return 0; // Not enough data to read the masking key.
405 memcpy(mask, rd, sizeof(mask));
406 rd += sizeof(mask);
407
408 if (avail() < payload_len)
409 return 0; // Not enouh data to read the payload.
410 uint8_t* const payload_start = rd;
411
412 // Unmask the payload.
413 for (uint32_t i = 0; i < payload_len; ++i)
414 payload_start[i] ^= mask[i % sizeof(mask)];
415
416 if (opcode == kOpcodePing) {
417 PERFETTO_DLOG("[HTTP] Websocket PING");
418 conn->SendWebsocketFrame(kOpcodePong, payload_start, payload_len);
419 } else if (opcode == kOpcodeBinary || opcode == kOpcodeText ||
420 opcode == kOpcodeContinuation) {
421 // We do NOT handle fragmentation. We propagate all fragments as individual
422 // messages, breaking the message-oriented nature of websockets. We do this
423 // because in all our use cases we need only a byte stream without caring
424 // about message boundaries.
425 // If we wanted to support fragmentation, we'd have to stash
426 // kOpcodeContinuation messages in a buffer, until we FIN bit is set.
427 // When loading traces with trace processor, the messages can be up to
428 // 32MB big (SLICE_SIZE in trace_stream.ts). The double-buffering would
429 // slow down significantly trace loading with no benefits.
430 WebsocketMessage msg(conn);
431 msg.data =
432 StringView(reinterpret_cast<const char*>(payload_start), payload_len);
433 msg.is_text = opcode == kOpcodeText;
434 req_handler_->OnWebsocketMessage(msg);
435 } else if (opcode == kOpcodeClose) {
436 conn->Close();
437 } else {
438 PERFETTO_LOG("Unsupported WebSocket opcode: %d", opcode);
439 }
440 return static_cast<size_t>(rd - rxbuf) + payload_len;
441 }
442
SendResponseHeaders(const char * http_code,std::initializer_list<const char * > headers,size_t content_length)443 void HttpServerConnection::SendResponseHeaders(
444 const char* http_code,
445 std::initializer_list<const char*> headers,
446 size_t content_length) {
447 PERFETTO_CHECK(!headers_sent_);
448 PERFETTO_CHECK(!is_websocket_);
449 headers_sent_ = true;
450 std::vector<char> resp_hdr;
451 resp_hdr.reserve(512);
452 bool has_connection_header = false;
453
454 auto append = [&resp_hdr](const char* str) {
455 resp_hdr.insert(resp_hdr.end(), str, str + strlen(str));
456 };
457
458 append("HTTP/1.1 ");
459 append(http_code);
460 append("\r\n");
461 for (const char* hdr_cstr : headers) {
462 StringView hdr = (hdr_cstr);
463 if (hdr.empty())
464 continue;
465 has_connection_header |= hdr.substr(0, 11).CaseInsensitiveEq("connection:");
466 append(hdr_cstr);
467 append("\r\n");
468 }
469 content_len_actual_ = 0;
470 content_len_headers_ = content_length;
471 if (content_length != kOmitContentLength) {
472 append("Content-Length: ");
473 append(std::to_string(content_length).c_str());
474 append("\r\n");
475 }
476 if (!has_connection_header) {
477 // Various clients (e.g., python's http.client) assume that a HTTP
478 // connection is keep-alive if the server says nothing, even when they do
479 // NOT ask for it. Hence we must be explicit. If we are about to close the
480 // connection, we must say so.
481 append(keepalive_ ? "Connection: keep-alive\r\n" : "Connection: close\r\n");
482 }
483 if (!origin_allowed_.empty()) {
484 append("Access-Control-Allow-Origin: ");
485 append(origin_allowed_.c_str());
486 append("\r\n");
487 append("Vary: Origin\r\n");
488 }
489 append("\r\n"); // End-of-headers marker.
490 sock->Send(resp_hdr.data(),
491 resp_hdr.size()); // Send response headers.
492 }
493
SendResponseBody(const void * data,size_t len)494 void HttpServerConnection::SendResponseBody(const void* data, size_t len) {
495 PERFETTO_CHECK(!is_websocket_);
496 if (data == nullptr) {
497 PERFETTO_DCHECK(len == 0);
498 return;
499 }
500 content_len_actual_ += len;
501 PERFETTO_CHECK(content_len_actual_ <= content_len_headers_ ||
502 content_len_headers_ == kOmitContentLength);
503 sock->Send(data, len);
504 }
505
Close()506 void HttpServerConnection::Close() {
507 sock->Shutdown(/*notify=*/true);
508 }
509
SendResponse(const char * http_code,std::initializer_list<const char * > headers,StringView content,bool force_close)510 void HttpServerConnection::SendResponse(
511 const char* http_code,
512 std::initializer_list<const char*> headers,
513 StringView content,
514 bool force_close) {
515 if (force_close)
516 keepalive_ = false;
517 SendResponseHeaders(http_code, headers, content.size());
518 SendResponseBody(content.data(), content.size());
519 if (!keepalive_)
520 Close();
521 }
522
SendWebsocketMessage(const void * data,size_t len)523 void HttpServerConnection::SendWebsocketMessage(const void* data, size_t len) {
524 SendWebsocketFrame(kOpcodeBinary, data, len);
525 }
526
SendWebsocketFrame(uint8_t opcode,const void * payload,size_t payload_len)527 void HttpServerConnection::SendWebsocketFrame(uint8_t opcode,
528 const void* payload,
529 size_t payload_len) {
530 PERFETTO_CHECK(is_websocket_);
531
532 uint8_t hdr[10]{};
533 uint32_t hdr_len = 0;
534
535 hdr[0] = opcode | 0x80 /* FIN=1, no fragmentation */;
536 if (payload_len < 126) {
537 hdr_len = 2;
538 hdr[1] = static_cast<uint8_t>(payload_len);
539 } else if (payload_len < 0xffff) {
540 hdr_len = 4;
541 hdr[1] = 126; // Special value: Header extends for 2 bytes.
542 uint16_t len_be = HostToBE16(static_cast<uint16_t>(payload_len));
543 memcpy(&hdr[2], &len_be, sizeof(len_be));
544 } else {
545 hdr_len = 10;
546 hdr[1] = 127; // Special value: Header extends for 4 bytes.
547 uint64_t len_be = HostToBE64(payload_len);
548 memcpy(&hdr[2], &len_be, sizeof(len_be));
549 }
550
551 sock->Send(hdr, hdr_len);
552 if (payload && payload_len > 0)
553 sock->Send(payload, payload_len);
554 }
555
HttpServerConnection(std::unique_ptr<UnixSocket> s)556 HttpServerConnection::HttpServerConnection(std::unique_ptr<UnixSocket> s)
557 : sock(std::move(s)), rxbuf(PagedMemory::Allocate(kMaxRequestSize)) {}
558
559 HttpServerConnection::~HttpServerConnection() = default;
560
GetHeader(StringView name) const561 std::optional<StringView> HttpRequest::GetHeader(StringView name) const {
562 for (size_t i = 0; i < num_headers; i++) {
563 if (headers[i].name.CaseInsensitiveEq(name))
564 return headers[i].value;
565 }
566 return std::nullopt;
567 }
568
569 HttpRequestHandler::~HttpRequestHandler() = default;
OnWebsocketMessage(const WebsocketMessage &)570 void HttpRequestHandler::OnWebsocketMessage(const WebsocketMessage&) {}
OnHttpConnectionClosed(HttpServerConnection *)571 void HttpRequestHandler::OnHttpConnectionClosed(HttpServerConnection*) {}
572
573 } // namespace base
574 } // namespace perfetto
575