Skip to content

Commit e8ec254

Browse files
committed
Add http2 support. Start with http2 frame parsing logic
1 parent 7c32d29 commit e8ec254

File tree

5 files changed

+285
-1
lines changed

5 files changed

+285
-1
lines changed

CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ seastar_generate_ragel (
474474
IN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/http/request_parser.rl
475475
OUT_FILE ${Seastar_GEN_BINARY_DIR}/include/seastar/http/request_parser.hh)
476476

477+
seastar_generate_ragel (
478+
TARGET seastar_http2_frame_parser
479+
VAR http2_frame_parser_file
480+
IN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/http/http2_frame_parser.rl
481+
OUT_FILE ${Seastar_GEN_BINARY_DIR}/include/seastar/http/http2_frame_parser.hh)
482+
477483
seastar_generate_ragel (
478484
TARGET seastar_http_response_parser
479485
VAR http_response_parser_file
@@ -489,6 +495,7 @@ seastar_generate_protobuf (
489495
add_library (seastar
490496
${http_chunk_parsers_file}
491497
${http_request_parser_file}
498+
${http2_frame_parser_file}
492499
${proto_metrics2_files}
493500
${seastar_dpdk_obj}
494501
include/seastar/core/abort_source.hh
@@ -600,6 +607,7 @@ add_library (seastar
600607
include/seastar/http/mime_types.hh
601608
include/seastar/http/reply.hh
602609
include/seastar/http/request.hh
610+
include/seastar/http/http2_connection.hh
603611
include/seastar/http/routes.hh
604612
include/seastar/http/short_streams.hh
605613
include/seastar/http/transformers.hh
@@ -721,6 +729,7 @@ add_library (seastar
721729
src/http/transformers.cc
722730
src/http/url.cc
723731
src/http/client.cc
732+
src/http/http2_connection.cc
724733
src/http/request.cc
725734
src/json/formatter.cc
726735
src/json/json_elements.cc
@@ -771,6 +780,7 @@ add_dependencies (seastar
771780
seastar_http_chunk_parsers
772781
seastar_http_request_parser
773782
seastar_http_response_parser
783+
seastar_http2_frame_parser
774784
seastar_proto_metrics2)
775785

776786
target_include_directories (seastar
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* This file is open source software, licensed to you under the terms
3+
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4+
* distributed with this work for additional information regarding copyright
5+
* ownership. You may not use this file except in compliance with the License.
6+
*
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
/*
19+
* Copyright (C) 2025 Scylladb, Ltd.
20+
*/
21+
22+
#pragma once
23+
24+
#include <seastar/core/iostream.hh>
25+
#include <seastar/core/gate.hh>
26+
#include <seastar/core/pipe.hh>
27+
#include <seastar/net/api.hh>
28+
#include <seastar/http/request.hh>
29+
#include <seastar/http/reply.hh>
30+
#include <seastar/http/routes.hh>
31+
#include <seastar/http/http2_frame_parser.hh>
32+
33+
namespace seastar {
34+
35+
namespace http {
36+
37+
class http2_connection {
38+
public:
39+
// Represents a single HTTP/2 stream (a request/response exchange)
40+
// As per RFC 7540
41+
struct h2_stream {
42+
// A stream has a well-defined lifecycle (RFC 7540, Section 5.1).
43+
enum class state {
44+
IDLE,
45+
RESERVED_LOCAL,
46+
RESERVED_REMOTE,
47+
OPEN,
48+
HALF_CLOSED_REMOTE,
49+
HALF_CLOSED_LOCAL,
50+
CLOSED
51+
};
52+
};
53+
54+
private:
55+
httpd::routes& _routes;
56+
connected_socket _fd;
57+
input_stream<char> _read_buf;
58+
output_stream<char> _write_buf;
59+
http2_frame_header_parser _parser;
60+
bool _done = false;
61+
62+
public:
63+
http2_connection(httpd::routes& routes, connected_socket&& fd, socket_address remote_addr, socket_address local_addr)
64+
: _routes(routes)
65+
, _fd(std::move(fd))
66+
, _read_buf(_fd.input())
67+
, _write_buf(_fd.output())
68+
{}
69+
70+
// Main entry point to start processing the HTTP/2 connection.
71+
future<> process();
72+
73+
#ifdef SEASTAR_HTTP2_TEST
74+
http2_frame_header_parser& get_parser_for_testing() { return _parser; }
75+
#endif
76+
77+
private:
78+
// The main loop for reading and dispatching frames.
79+
future<> read_loop();
80+
future<> read_one_frame();
81+
82+
// Frame-specific handlers.
83+
future<> handle_settings_frame(const temporary_buffer<char>& payload);
84+
future<> handle_headers_frame(uint32_t stream_id, uint8_t flags, const temporary_buffer<char>& payload);
85+
future<> handle_data_frame(uint32_t stream_id, uint8_t flags, const temporary_buffer<char>& payload);
86+
// ... other frame handlers (GOAWAY, RST_STREAM, etc.)
87+
};
88+
89+
90+
91+
} // namespace http
92+
93+
} // namespace seastar

src/http/http2_connection.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "seastar/core/future.hh"
2+
#include <seastar/http/http2_connection.hh>
3+
#include <seastar/core/loop.hh>
4+
#include <seastar/core/do_with.hh>
5+
#include <seastar/core/when_all.hh>
6+
#include <seastar/core/print.hh>
7+
#include <seastar/core/future-util.hh>
8+
#include <seastar/core/temporary_buffer.hh>
9+
#include <seastar/core/iostream.hh>
10+
#include <seastar/util/log.hh>
11+
#include <cstring>
12+
13+
namespace seastar {
14+
namespace http {
15+
16+
static logger h2log("http2");
17+
18+
future<> http2_connection::process() {
19+
h2log.info("Starting HTTP/2 connection processing");
20+
return read_loop();
21+
}
22+
23+
future<> http2_connection::read_loop() {
24+
h2log.info("Entering HTTP/2 read loop");
25+
return do_until([this] { return _done; }, [this] {
26+
return read_one_frame();
27+
});
28+
}
29+
30+
future<> http2_connection::read_one_frame() {
31+
// Read 9-byte HTTP/2 frame header
32+
// According to the HTTP/2 spec, the frame header is always 9 bytes long.
33+
// https://datatracker.ietf.org/doc/html/rfc7540#section-4.1
34+
return _read_buf.read_exactly(9).then([this](temporary_buffer<char> hdr_buf) {
35+
if (hdr_buf.size() < 9) {
36+
h2log.error("Connection closed or incomplete frame header");
37+
_done = true;
38+
return make_ready_future<>();
39+
}
40+
// Parse the frame header
41+
_parser.init();
42+
_parser.parse(hdr_buf.get_write(), hdr_buf.get_write() + 9);
43+
uint32_t frame_len = _parser._length;
44+
uint8_t frame_type = _parser._type;
45+
//TODO: parse frame flags and streamid
46+
h2log.info("Frame: type={} len={}", frame_type, frame_len);
47+
_done = true;
48+
// Depending on the the frame type, call the appropriate handler
49+
return make_ready_future<>();
50+
});
51+
}
52+
53+
} // namespace http
54+
} // namespace seastar

src/http/http2_frame_parser.rl

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* HTTP/2 Frame Header Parser
3+
* Parses the 9-byte HTTP/2 frame header into a struct.
4+
*/
5+
6+
#pragma once
7+
8+
#include <seastar/core/ragel.hh>
9+
#include <cstdint>
10+
11+
namespace seastar {
12+
13+
%%{
14+
machine http2_frame_parser;
15+
16+
access _fsm_;
17+
18+
action mark_start {
19+
_frame_start = p;
20+
}
21+
22+
action store_length1 {
23+
_length = (uint32_t)(uint8_t)_frame_start[0] << 16;
24+
}
25+
action store_length2 {
26+
_length |= (uint32_t)(uint8_t)_frame_start[1] << 8;
27+
}
28+
action store_length3 {
29+
_length |= (uint32_t)(uint8_t)_frame_start[2];
30+
}
31+
action store_type {
32+
_type = (uint8_t)_frame_start[3];
33+
}
34+
action store_flags {
35+
_flags = (uint8_t)_frame_start[4];
36+
}
37+
38+
main := (
39+
any >mark_start
40+
any @store_length1
41+
any @store_length2
42+
any @store_length3
43+
any @store_type
44+
any @store_flags
45+
);
46+
}%%
47+
48+
class http2_frame_header_parser : public ragel_parser_base<http2_frame_header_parser> {
49+
%% write data nofinal noprefix;
50+
private:
51+
const char* _frame_start = nullptr;
52+
53+
public:
54+
uint32_t _length = 0;
55+
uint8_t _type = 0;
56+
uint8_t _flags = 0;
57+
58+
void init() {
59+
init_base();
60+
_length = 0;
61+
_type = 0;
62+
_flags = 0;
63+
%% write init;
64+
}
65+
66+
// Returns pointer to next byte after header if parsed, nullptr otherwise
67+
char* parse(char* p, char* pe) {
68+
char* start = p;
69+
#ifdef __clang__
70+
#pragma clang diagnostic push
71+
#pragma clang diagnostic ignored "-Wmisleading-indentation"
72+
#endif
73+
#pragma GCC diagnostic push
74+
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
75+
%% write exec;
76+
#pragma GCC diagnostic pop
77+
#ifdef __clang__
78+
#pragma clang diagnostic pop
79+
#endif
80+
if (p - start >= 9) {
81+
return p;
82+
}
83+
return nullptr;
84+
}
85+
};
86+
87+
} // namespace seastar

tests/unit/httpd_test.cc

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/*
22
* Copyright 2015 Cloudius Systems
33
*/
4-
4+
5+
#define SEASTAR_HTTP2_TEST
56
#include <seastar/http/function_handlers.hh>
67
#include <seastar/http/httpd.hh>
78
#include <seastar/http/handlers.hh>
@@ -20,6 +21,7 @@
2021
#include <seastar/testing/test_case.hh>
2122
#include <seastar/testing/thread_test_case.hh>
2223
#include "loopback_socket.hh"
24+
#include "seastar/http/http2_connection.hh"
2325
#include <boost/algorithm/string.hpp>
2426
#include <seastar/core/thread.hh>
2527
#include <seastar/util/noncopyable_function.hh>
@@ -2176,5 +2178,43 @@ SEASTAR_TEST_CASE(test_client_close_connection) {
21762178

21772179
when_all(std::move(server), std::move(client)).discard_result().get();
21782180
}
2181+
2182+
});
2183+
}
2184+
2185+
SEASTAR_TEST_CASE(test_http2_frame_parsing) {
2186+
2187+
return seastar::async([] {
2188+
// Set up loopback connection factory and server socket
2189+
loopback_connection_factory lcf(1);
2190+
auto server_sock = lcf.get_server_socket();
2191+
loopback_socket_impl lsi(lcf);
2192+
2193+
// Start server coroutine to handle HTTP/2 connection
2194+
auto server = seastar::async([&] {
2195+
auto ar = server_sock.accept().get();
2196+
// Create HTTP/2 connection object
2197+
seastar::httpd::routes dummy_routes;
2198+
seastar::http::http2_connection h2conn(dummy_routes, std::move(ar.connection), ar.remote_address, ar.remote_address);
2199+
h2conn.process().get();
2200+
fmt::print("after http2 connection process, frame type {}\n", h2conn.get_parser_for_testing()._type);
2201+
BOOST_REQUIRE(h2conn.get_parser_for_testing()._type == 0x4); // 0x4 is the SETTINGS frame type
2202+
2203+
fmt::print("finished processing http2 frame!!\n");
2204+
});
2205+
2206+
// Client coroutine: connect and send a SETTINGS frame
2207+
auto client = seastar::async([&] {
2208+
connected_socket sock = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get();
2209+
auto out = sock.output();
2210+
2211+
// 1. Send a SETTINGS frame (empty payload, just header)
2212+
char settings_frame[9] = {0,0,0, 0x4, 0x0, 0,0,0,0}; // length=0, type=4 (SETTINGS), flags=0, stream_id=0
2213+
out.write(settings_frame, 9).get();
2214+
out.flush().get();
2215+
out.close().get();
2216+
});
2217+
// Wait for both client and server to finish
2218+
when_all(std::move(server), std::move(client)).get();
21792219
});
21802220
}

0 commit comments

Comments
 (0)