CCF
Loading...
Searching...
No Matches
http2_parser.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
5#include "ccf/ds/logger.h"
6#include "ccf/ds/nonstd.h"
7#include "enclave/session.h"
8#include "http2_callbacks.h"
9#include "http2_types.h"
10#include "http_proc.h"
11#include "http_rpc_context.h"
12
13namespace http2
14{
15 using DataHandlerCB = std::function<void(std::span<const uint8_t>)>;
16
17 class Parser : public AbstractParser
18 {
19 private:
20 // Keep track of last peer stream id received on this session so that we can
21 // reject new streams ids less than this value.
22 StreamId last_stream_id = 0;
23 DataHandlerCB handle_outgoing_data;
25
26 protected:
27 std::map<StreamId, std::shared_ptr<StreamData>> streams;
28 nghttp2_session* session;
29
30 public:
32 const ccf::http::ParserConfiguration& configuration_,
33 bool is_client = false) :
34 configuration(configuration_)
35 {
36 LOG_TRACE_FMT("Creating HTTP2 parser");
37
38 nghttp2_session_callbacks* callbacks;
39 nghttp2_session_callbacks_new(&callbacks);
40 nghttp2_session_callbacks_set_on_stream_close_callback(
41 callbacks, on_stream_close_callback);
42 nghttp2_session_callbacks_set_data_source_read_length_callback(
43 callbacks, on_data_source_read_length_callback);
44 nghttp2_session_callbacks_set_error_callback2(
45 callbacks, on_error_callback);
46
47 nghttp2_session_callbacks_set_on_begin_frame_callback(
48 callbacks, on_begin_frame_recv_callback);
49 nghttp2_session_callbacks_set_on_frame_recv_callback(
50 callbacks, on_frame_recv_callback);
51 nghttp2_session_callbacks_set_on_begin_headers_callback(
52 callbacks, on_begin_headers_callback);
53 nghttp2_session_callbacks_set_on_header_callback(
54 callbacks, on_header_callback);
55 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
56 callbacks, on_data_callback);
57
58 if (is_client)
59 {
60 if (nghttp2_session_client_new(&session, callbacks, this) != 0)
61 {
62 throw std::logic_error("Could not create new HTTP/2 client session");
63 }
64 }
65 else
66 {
67 if (nghttp2_session_server_new(&session, callbacks, this) != 0)
68 {
69 throw std::logic_error("Could not create new HTTP/2 server session");
70 }
71 }
72
73 // Submit initial settings
74 std::vector<nghttp2_settings_entry> settings;
75 settings.push_back(
76 {NGHTTP2_SETTINGS_MAX_FRAME_SIZE,
77 static_cast<uint32_t>(configuration.max_frame_size.value_or(
78 ccf::http::default_max_frame_size))});
79 settings.push_back(
80 {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
81 static_cast<uint32_t>(
82 configuration.max_concurrent_streams_count.value_or(
83 ccf::http::default_max_concurrent_streams_count))});
84 settings.push_back(
85 {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE,
86 static_cast<uint32_t>(configuration.initial_window_size.value_or(
87 ccf::http::default_initial_window_size))});
88 // NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE is only a hint to client
89 // (https://www.rfc-editor.org/rfc/rfc7540#section-10.5.1)
90 settings.push_back(
91 {NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE,
92 static_cast<uint32_t>(
93 configuration.max_headers_count.value_or(
94 ccf::http::default_max_headers_count) *
95 configuration.max_header_size.value_or(
96 ccf::http::default_max_header_size))});
97
98 auto rv = nghttp2_submit_settings(
99 session, NGHTTP2_FLAG_NONE, settings.data(), settings.size());
100 if (rv != 0)
101 {
102 throw std::logic_error(fmt::format(
103 "Error submitting settings for HTTP2 session: {}",
104 nghttp2_strerror(rv)));
105 }
106
107 nghttp2_session_callbacks_del(callbacks);
108 }
109
110 virtual ~Parser()
111 {
112 nghttp2_session_del(session);
113 }
114
116 {
117 return last_stream_id;
118 }
119
121 {
122 return configuration;
123 }
124
126 {
127 handle_outgoing_data = std::move(cb);
128 }
129
131 StreamId stream_id, const std::shared_ptr<StreamData>& stream_data)
132 {
133 auto it = streams.find(stream_id);
134 if (it != streams.end())
135 {
136 throw std::logic_error(fmt::format(
137 "Cannot store new stream {} as it already exists", stream_id));
138 }
139
140 streams.insert(it, {stream_id, stream_data});
141 last_stream_id = stream_id;
142 }
143
144 std::shared_ptr<StreamData> get_stream(StreamId stream_id) override
145 {
146 auto it = streams.find(stream_id);
147 if (it == streams.end())
148 {
149 return nullptr;
150 }
151 return it->second;
152 }
153
154 std::shared_ptr<StreamData> create_stream(StreamId stream_id) override
155 {
156 auto s = std::make_shared<StreamData>();
157 store_stream(stream_id, s);
158 LOG_TRACE_FMT("Created new stream {}", stream_id);
159 return s;
160 }
161
162 void destroy_stream(StreamId stream_id) override
163 {
164 auto it = streams.find(stream_id);
165 if (it != streams.end())
166 {
167 // Erase _before_ calling close callback as `destroy_stream()` may be
168 // called multiple times (once when the client closes the stream, and
169 // when the server sends the final trailers)
170 auto stream_data = it->second;
171 it = streams.erase(it);
172 LOG_TRACE_FMT("Deleted stream {}", stream_id);
173 if (
174 stream_data->close_callback != nullptr &&
175 stream_data->outgoing.state != http2::StreamResponseState::Closing)
176 {
177 // Close callback is supplied by app so handle eventual exceptions
178 try
179 {
180 stream_data->close_callback();
181 }
182 catch (const std::exception& e)
183 {
184 LOG_DEBUG_FMT("Error closing callback: {}", e.what());
185 }
186 }
187 LOG_TRACE_FMT("Successfully destroyed stream {}", stream_id);
188 }
189 else
190 {
191 LOG_DEBUG_FMT("Cannot destroy unknown stream {}", stream_id);
192 }
193 }
194
195 bool execute(const uint8_t* data, size_t size)
196 {
197 LOG_TRACE_FMT("http2::Parser execute: {}", size);
198 auto readlen = nghttp2_session_mem_recv(session, data, size);
199 if (readlen < 0)
200 {
201 throw std::logic_error(fmt::format(
202 "HTTP/2: Error receiving data: {}", nghttp2_strerror(readlen)));
203 }
204
206
207 // Detects whether server session expects any more data to read/write, and
208 // if not (e.g. goaway frame was handled), closes session gracefully
209 if (
210 nghttp2_session_want_read(session) == 0 &&
211 nghttp2_session_want_write(session) == 0)
212 {
213 LOG_TRACE_FMT("http2::Parser execute: Closing session gracefully");
214 return false;
215 }
216
217 return true;
218 }
219
221 {
222 ssize_t size = 0;
223 const uint8_t* data = nullptr;
224 while ((size = nghttp2_session_mem_send(session, &data)) != 0)
225 {
226 if (size > 0)
227 {
228 handle_outgoing_data({data, static_cast<size_t>(size)});
229 }
230 else
231 {
232 throw std::logic_error(fmt::format(
233 "HTTP/2: Error sending data: {}", nghttp2_strerror(size)));
234 }
235 }
236 }
237 };
238
239 class ServerParser : public Parser
240 {
241 private:
243
244 void submit_trailers(StreamId stream_id, ccf::http::HeaderMap&& trailers)
245 {
246 if (trailers.empty())
247 {
248 return;
249 }
250
251 LOG_TRACE_FMT("Submitting {} trailers", trailers.size());
252 std::vector<nghttp2_nv> trlrs;
253 trlrs.reserve(trailers.size());
254 for (auto& [k, v] : trailers)
255 {
256 trlrs.emplace_back(make_nv(k.data(), v.data()));
257 }
258
259 int rv =
260 nghttp2_submit_trailer(session, stream_id, trlrs.data(), trlrs.size());
261 if (rv != 0)
262 {
263 throw std::logic_error(fmt::format(
264 "nghttp2_submit_trailer error: {}", nghttp2_strerror(rv)));
265 }
266 }
267
268 void submit_response(
269 StreamId stream_id,
270 ccf::http_status status,
271 const ccf::http::HeaderMap& base_headers,
272 const ccf::http::HeaderMap& extra_headers = {})
273 {
274 std::vector<nghttp2_nv> hdrs = {};
275
276 auto status_str = fmt::format(
277 "{}",
278 static_cast<std::underlying_type<ccf::http_status>::type>(status));
279 hdrs.emplace_back(
280 make_nv(ccf::http2::headers::STATUS, status_str.data()));
281
282 for (auto& [k, v] : base_headers)
283 {
284 hdrs.emplace_back(make_nv(k.data(), v.data()));
285 }
286
287 for (auto& [k, v] : extra_headers)
288 {
289 hdrs.emplace_back(make_nv(k.data(), v.data()));
290 }
291
292 nghttp2_data_provider prov;
293 prov.read_callback = read_outgoing_callback;
294
295 int rv = nghttp2_submit_response(
296 session, stream_id, hdrs.data(), hdrs.size(), &prov);
297 if (rv != 0)
298 {
299 throw std::logic_error(fmt::format(
300 "nghttp2_submit_response error: {}", nghttp2_strerror(rv)));
301 }
302 }
303
304 public:
307 const ccf::http::ParserConfiguration& configuration_) :
308 Parser(configuration_, false),
309 proc(proc_)
310 {}
311
313 {
315 "http2::set_on_stream_close_callback: stream {}", stream_id);
316
317 auto* stream_data = get_stream_data(session, stream_id);
318 if (stream_data == nullptr)
319 {
320 throw std::logic_error(
321 fmt::format("Stream {} no longer exists", stream_id));
322 }
323
324 stream_data->close_callback = cb;
325 }
326
328 StreamId stream_id,
329 ccf::http_status status,
330 const ccf::http::HeaderMap& headers,
331 ccf::http::HeaderMap&& trailers,
332 std::span<const uint8_t> body)
333 {
335 "http2::respond: stream {} - {} headers - {} trailers - {} bytes "
336 "body",
337 stream_id,
338 headers.size(),
339 trailers.size(),
340 body.size());
341
342 auto* stream_data = get_stream_data(session, stream_id);
343 if (stream_data == nullptr)
344 {
345 throw std::logic_error(
346 fmt::format("Stream {} no longer exists", stream_id));
347 }
348
349 bool should_submit_response =
350 stream_data->outgoing.state != StreamResponseState::Streaming;
351
352 stream_data->outgoing.state = StreamResponseState::Closing;
353
354 ccf::http::HeaderMap extra_headers = {};
355 extra_headers[ccf::http::headers::CONTENT_LENGTH] =
356 std::to_string(body.size());
357 auto thv = make_trailer_header_value(trailers);
358 if (thv.has_value())
359 {
360 extra_headers[ccf::http::headers::TRAILER] = thv.value();
361 }
362
363 stream_data->outgoing.body = DataSource(body);
364 stream_data->outgoing.has_trailers = !trailers.empty();
365
366 if (should_submit_response)
367 {
368 submit_response(stream_id, status, headers, extra_headers);
370 }
371
372 submit_trailers(stream_id, std::move(trailers));
374 }
375
377 StreamId stream_id,
378 ccf::http_status status,
379 const ccf::http::HeaderMap& headers)
380 {
382 "http2::start_stream: stream {} - {} headers",
383 stream_id,
384 headers.size());
385
386 auto* stream_data = get_stream_data(session, stream_id);
387 if (stream_data == nullptr)
388 {
389 throw std::logic_error(
390 fmt::format("Stream {} no longer exists", stream_id));
391 }
392
393 if (stream_data->outgoing.state != StreamResponseState::Uninitialised)
394 {
395 throw std::logic_error(fmt::format(
396 "Stream {} should be uninitialised to start stream", stream_id));
397 }
398
399 stream_data->outgoing.state = StreamResponseState::Streaming;
400
401 submit_response(stream_id, status, headers);
403 }
404
405 void send_data(StreamId stream_id, std::span<const uint8_t> data)
406 {
408 "http2::send_data: stream {} - {} bytes", stream_id, data.size());
409
410 auto* stream_data = get_stream_data(session, stream_id);
411 if (stream_data == nullptr)
412 {
413 throw std::logic_error(
414 fmt::format("Stream {} no longer exists", stream_id));
415 }
416
417 if (stream_data->outgoing.state != StreamResponseState::Streaming)
418 {
419 throw std::logic_error(
420 fmt::format("Stream {} should be streaming to send data", stream_id));
421 }
422
423 stream_data->outgoing.body = DataSource(data);
424
425 int rv = nghttp2_session_resume_data(session, stream_id);
426 if (rv < 0)
427 {
428 throw std::logic_error(fmt::format(
429 "nghttp2_session_resume_data error: {}", nghttp2_strerror(rv)));
430 }
431
433 }
434
435 void close_stream(StreamId stream_id, ccf::http::HeaderMap&& trailers)
436 {
438 "http2::close: stream {} - {} trailers ", stream_id, trailers.size());
439
440 auto* stream_data = get_stream_data(session, stream_id);
441 if (stream_data == nullptr)
442 {
443 throw std::logic_error(
444 fmt::format("Stream {} no longer exists", stream_id));
445 }
446
447 auto it = streams.find(stream_id);
448 if (it != streams.end())
449 {
450 stream_data->outgoing.state = StreamResponseState::Closing;
451 stream_data->outgoing.has_trailers = !trailers.empty();
452
453 submit_trailers(stream_id, std::move(trailers));
455 }
456 // else this stream was already closed by client, and we shouldn't send
457 // trailers
458 }
459
460 virtual void handle_completed(
461 StreamId stream_id, StreamData* stream_data) override
462 {
463 LOG_TRACE_FMT("http2::ServerParser: handle_completed");
464
465 if (stream_data == nullptr)
466 {
467 LOG_FAIL_FMT("No stream data to handle request");
468 return;
469 }
470
471 auto& headers = stream_data->incoming.headers;
472
473 std::string url = {};
474 {
475 const auto url_it = headers.find(ccf::http2::headers::PATH);
476 if (url_it != headers.end())
477 {
478 url = url_it->second;
479 }
480 }
481
482 llhttp_method method = {};
483 {
484 const auto method_it = headers.find(ccf::http2::headers::METHOD);
485 if (method_it != headers.end())
486 {
487 method = ccf::http_method_from_str(method_it->second.c_str());
488 }
489 }
490
491 proc.handle_request(
492 method,
493 url,
494 std::move(stream_data->incoming.headers),
495 std::move(stream_data->incoming.body),
496 stream_id);
497 }
498 };
499
500 class ClientParser : public Parser
501 {
502 private:
504
505 public:
507 Parser(ccf::http::ParserConfiguration{}, true),
508 proc(proc_)
509 {}
510
512 llhttp_method method,
513 const std::string& route,
514 const ccf::http::HeaderMap& headers,
515 std::span<const uint8_t> body)
516 {
517 std::vector<nghttp2_nv> hdrs;
518 hdrs.emplace_back(
519 make_nv(ccf::http2::headers::METHOD, llhttp_method_name(method)));
520 hdrs.emplace_back(make_nv(ccf::http2::headers::PATH, route.data()));
521 hdrs.emplace_back(make_nv(":scheme", "https"));
522 hdrs.emplace_back(make_nv(":authority", "localhost:8080"));
523 for (auto const& [k, v] : headers)
524 {
525 hdrs.emplace_back(make_nv(k.data(), v.data()));
526 }
527
528 auto stream_data = std::make_shared<StreamData>();
529 stream_data->outgoing.body = DataSource(body);
530
531 nghttp2_data_provider prov;
532 prov.read_callback = read_outgoing_callback;
533
534 stream_data->outgoing.state = StreamResponseState::Closing;
535
536 auto stream_id = nghttp2_submit_request(
537 session, nullptr, hdrs.data(), hdrs.size(), &prov, stream_data.get());
538 if (stream_id < 0)
539 {
541 "Could not submit HTTP request: {}", nghttp2_strerror(stream_id));
542 return;
543 }
544
545 store_stream(stream_id, stream_data);
546
548 LOG_DEBUG_FMT("Successfully sent request with stream id: {}", stream_id);
549 }
550
551 void handle_completed(StreamId stream_id, StreamData* stream_data) override
552 {
553 LOG_TRACE_FMT("http2::ClientParser: handle_completed");
554
555 if (stream_data == nullptr)
556 {
557 LOG_FAIL_FMT("No stream data to handle response");
558 return;
559 }
560
561 auto& headers = stream_data->incoming.headers;
562
563 ccf::http_status status = {};
564 {
565 const auto status_it = headers.find(ccf::http2::headers::STATUS);
566 if (status_it != headers.end())
567 {
568 status = ccf::http_status(std::stoi(status_it->second));
569 }
570 }
571
572 proc.handle_response(
573 status,
574 std::move(stream_data->incoming.headers),
575 std::move(stream_data->incoming.body));
576 }
577 };
578}
Definition http2_types.h:85
Definition http2_parser.h:501
ClientParser(::http::ResponseProcessor &proc_)
Definition http2_parser.h:506
void send_structured_request(llhttp_method method, const std::string &route, const ccf::http::HeaderMap &headers, std::span< const uint8_t > body)
Definition http2_parser.h:511
void handle_completed(StreamId stream_id, StreamData *stream_data) override
Definition http2_parser.h:551
Definition http2_types.h:36
Definition http2_parser.h:18
void set_outgoing_data_handler(DataHandlerCB &&cb)
Definition http2_parser.h:125
std::map< StreamId, std::shared_ptr< StreamData > > streams
Definition http2_parser.h:27
nghttp2_session * session
Definition http2_parser.h:28
StreamId get_last_stream_id() const override
Definition http2_parser.h:115
ccf::http::ParserConfiguration get_configuration() const override
Definition http2_parser.h:120
void send_all_submitted()
Definition http2_parser.h:220
void store_stream(StreamId stream_id, const std::shared_ptr< StreamData > &stream_data)
Definition http2_parser.h:130
bool execute(const uint8_t *data, size_t size)
Definition http2_parser.h:195
std::shared_ptr< StreamData > get_stream(StreamId stream_id) override
Definition http2_parser.h:144
std::shared_ptr< StreamData > create_stream(StreamId stream_id) override
Definition http2_parser.h:154
virtual ~Parser()
Definition http2_parser.h:110
void destroy_stream(StreamId stream_id) override
Definition http2_parser.h:162
Parser(const ccf::http::ParserConfiguration &configuration_, bool is_client=false)
Definition http2_parser.h:31
Definition http2_parser.h:240
void send_data(StreamId stream_id, std::span< const uint8_t > data)
Definition http2_parser.h:405
void start_stream(StreamId stream_id, ccf::http_status status, const ccf::http::HeaderMap &headers)
Definition http2_parser.h:376
void set_on_stream_close_callback(StreamId stream_id, StreamCloseCB cb)
Definition http2_parser.h:312
virtual void handle_completed(StreamId stream_id, StreamData *stream_data) override
Definition http2_parser.h:460
ServerParser(http::RequestProcessor &proc_, const ccf::http::ParserConfiguration &configuration_)
Definition http2_parser.h:305
void respond(StreamId stream_id, ccf::http_status status, const ccf::http::HeaderMap &headers, ccf::http::HeaderMap &&trailers, std::span< const uint8_t > body)
Definition http2_parser.h:327
void close_stream(StreamId stream_id, ccf::http::HeaderMap &&trailers)
Definition http2_parser.h:435
Definition http_proc.h:20
virtual void handle_request(llhttp_method method, const std::string_view &url, ccf::http::HeaderMap &&headers, std::vector< uint8_t > &&body, int32_t stream_id=http2::DEFAULT_STREAM_ID)=0
Definition http_proc.h:31
virtual void handle_response(ccf::http_status status, ccf::http::HeaderMap &&headers, std::vector< uint8_t > &&body)=0
#define LOG_TRACE_FMT
Definition logger.h:356
#define LOG_DEBUG_FMT
Definition logger.h:357
#define LOG_FAIL_FMT
Definition logger.h:363
std::map< std::string, std::string, std::less<> > HeaderMap
Definition http_header_map.h:10
Definition app_interface.h:14
llhttp_status http_status
Definition http_status.h:9
Definition http2_callbacks.h:12
int32_t StreamId
Definition http2_types.h:21
ccf::http::StreamOnCloseCallback StreamCloseCB
Definition http2_types.h:24
std::function< void(std::span< const uint8_t >)> DataHandlerCB
Definition http2_parser.h:15
Definition error_reporter.h:6
Definition http_configuration.h:24
std::optional< ccf::ds::SizeString > max_header_size
Definition http_configuration.h:26
std::optional< size_t > max_concurrent_streams_count
Definition http_configuration.h:30
std::optional< ccf::ds::SizeString > initial_window_size
Definition http_configuration.h:31
std::optional< uint32_t > max_headers_count
Definition http_configuration.h:27
std::optional< ccf::ds::SizeString > max_frame_size
Definition http_configuration.h:34
std::vector< uint8_t > body
Definition http2_types.h:69
ccf::http::HeaderMap headers
Definition http2_types.h:68
Definition http2_types.h:65
Incoming incoming
Definition http2_types.h:71