-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add http2 support. Start with http2 frame parsing logic #2886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| /* | ||
| * This file is open source software, licensed to you under the terms | ||
| * of the Apache License, Version 2.0 (the "License"). See the NOTICE file | ||
| * distributed with this work for additional information regarding copyright | ||
| * ownership. You may not use this file except in compliance with the License. | ||
| * | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| /* | ||
| * Copyright (C) 2025 Scylladb, Ltd. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <seastar/core/iostream.hh> | ||
| #include <seastar/core/gate.hh> | ||
| #include <seastar/core/pipe.hh> | ||
| #include <seastar/net/api.hh> | ||
| #include <seastar/http/request.hh> | ||
| #include <seastar/http/reply.hh> | ||
| #include <seastar/http/routes.hh> | ||
| #include <seastar/http/http2_frame_parser.hh> | ||
|
|
||
| namespace seastar { | ||
|
|
||
| namespace http { | ||
|
|
||
| class http2_connection { | ||
| public: | ||
| // Represents a single HTTP/2 stream (a request/response exchange) | ||
| // As per RFC 7540 | ||
| struct h2_stream { | ||
| // A stream has a well-defined lifecycle (RFC 7540, Section 5.1). | ||
| enum class state { | ||
| IDLE, | ||
| RESERVED_LOCAL, | ||
| RESERVED_REMOTE, | ||
| OPEN, | ||
| HALF_CLOSED_REMOTE, | ||
| HALF_CLOSED_LOCAL, | ||
| CLOSED | ||
| }; | ||
| }; | ||
|
|
||
| private: | ||
| httpd::routes& _routes; | ||
| connected_socket _fd; | ||
| input_stream<char> _read_buf; | ||
| output_stream<char> _write_buf; | ||
| http2_frame_header_parser _parser; | ||
| bool _done = false; | ||
|
|
||
| public: | ||
| http2_connection(httpd::routes& routes, connected_socket&& fd, socket_address remote_addr, socket_address local_addr) | ||
| : _routes(routes) | ||
| , _fd(std::move(fd)) | ||
| , _read_buf(_fd.input()) | ||
| , _write_buf(_fd.output()) | ||
| {} | ||
|
|
||
| // Main entry point to start processing the HTTP/2 connection. | ||
| future<> process(); | ||
|
|
||
| #ifdef SEASTAR_HTTP2_TEST | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We try to avoid ifdefs for no special readons. If the intent is just to get a private field out of a class, declare a method in internal or testing namespace that's friend to that class |
||
| http2_frame_header_parser& get_parser_for_testing() { return _parser; } | ||
| #endif | ||
|
|
||
| private: | ||
| // The main loop for reading and dispatching frames. | ||
| future<> read_loop(); | ||
| future<> read_one_frame(); | ||
|
|
||
| // Frame-specific handlers. | ||
| future<> handle_settings_frame(const temporary_buffer<char>& payload); | ||
| future<> handle_headers_frame(uint32_t stream_id, uint8_t flags, const temporary_buffer<char>& payload); | ||
| future<> handle_data_frame(uint32_t stream_id, uint8_t flags, const temporary_buffer<char>& payload); | ||
| // ... other frame handlers (GOAWAY, RST_STREAM, etc.) | ||
| }; | ||
|
|
||
|
|
||
|
|
||
| } // namespace http | ||
|
|
||
| } // namespace seastar | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| #include "seastar/core/future.hh" | ||
| #include <seastar/http/http2_connection.hh> | ||
| #include <seastar/core/loop.hh> | ||
| #include <seastar/core/do_with.hh> | ||
| #include <seastar/core/when_all.hh> | ||
| #include <seastar/core/print.hh> | ||
| #include <seastar/core/future-util.hh> | ||
| #include <seastar/core/temporary_buffer.hh> | ||
| #include <seastar/core/iostream.hh> | ||
| #include <seastar/util/log.hh> | ||
| #include <cstring> | ||
|
|
||
| namespace seastar { | ||
| namespace http { | ||
|
|
||
| static logger h2log("http2"); | ||
|
|
||
| future<> http2_connection::process() { | ||
| h2log.info("Starting HTTP/2 connection processing"); | ||
| return read_loop(); | ||
| } | ||
|
|
||
| future<> http2_connection::read_loop() { | ||
| h2log.info("Entering HTTP/2 read loop"); | ||
| return do_until([this] { return _done; }, [this] { | ||
| return read_one_frame(); | ||
| }); | ||
| } | ||
|
|
||
| future<> http2_connection::read_one_frame() { | ||
| // Read 9-byte HTTP/2 frame header | ||
| // According to the HTTP/2 spec, the frame header is always 9 bytes long. | ||
| // https://datatracker.ietf.org/doc/html/rfc7540#section-4.1 | ||
| return _read_buf.read_exactly(9).then([this](temporary_buffer<char> hdr_buf) { | ||
| if (hdr_buf.size() < 9) { | ||
| h2log.error("Connection closed or incomplete frame header"); | ||
| _done = true; | ||
| return make_ready_future<>(); | ||
| } | ||
| // Parse the frame header | ||
| _parser.init(); | ||
| _parser.parse(hdr_buf.get_write(), hdr_buf.get_write() + 9); | ||
| uint32_t frame_len = _parser._length; | ||
| uint8_t frame_type = _parser._type; | ||
| //TODO: parse frame flags and streamid | ||
| h2log.info("Frame: type={} len={}", frame_type, frame_len); | ||
| _done = true; | ||
| // Depending on the the frame type, call the appropriate handler | ||
| return make_ready_future<>(); | ||
| }); | ||
| } | ||
|
|
||
| } // namespace http | ||
| } // namespace seastar |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /* | ||
| * HTTP/2 Frame Header Parser | ||
| * Parses the 9-byte HTTP/2 frame header into a struct. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <seastar/core/ragel.hh> | ||
| #include <cstdint> | ||
|
|
||
| namespace seastar { | ||
|
|
||
| %%{ | ||
| machine http2_frame_parser; | ||
|
|
||
| access _fsm_; | ||
|
|
||
| action mark_start { | ||
| _frame_start = p; | ||
| } | ||
|
|
||
| action store_length1 { | ||
| _length = (uint32_t)(uint8_t)_frame_start[0] << 16; | ||
| } | ||
| action store_length2 { | ||
| _length |= (uint32_t)(uint8_t)_frame_start[1] << 8; | ||
| } | ||
| action store_length3 { | ||
| _length |= (uint32_t)(uint8_t)_frame_start[2]; | ||
| } | ||
| action store_type { | ||
| _type = (uint8_t)_frame_start[3]; | ||
| } | ||
| action store_flags { | ||
| _flags = (uint8_t)_frame_start[4]; | ||
| } | ||
|
|
||
| main := ( | ||
| any >mark_start | ||
| any @store_length1 | ||
| any @store_length2 | ||
| any @store_length3 | ||
| any @store_type | ||
| any @store_flags | ||
| ); | ||
| }%% | ||
|
|
||
| class http2_frame_header_parser : public ragel_parser_base<http2_frame_header_parser> { | ||
| %% write data nofinal noprefix; | ||
| private: | ||
| const char* _frame_start = nullptr; | ||
|
|
||
| public: | ||
| uint32_t _length = 0; | ||
| uint8_t _type = 0; | ||
| uint8_t _flags = 0; | ||
|
|
||
| void init() { | ||
| init_base(); | ||
| _length = 0; | ||
| _type = 0; | ||
| _flags = 0; | ||
| %% write init; | ||
| } | ||
|
|
||
| // Returns pointer to next byte after header if parsed, nullptr otherwise | ||
| char* parse(char* p, char* pe) { | ||
| char* start = p; | ||
| #ifdef __clang__ | ||
| #pragma clang diagnostic push | ||
| #pragma clang diagnostic ignored "-Wmisleading-indentation" | ||
| #endif | ||
| #pragma GCC diagnostic push | ||
| #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" | ||
| %% write exec; | ||
| #pragma GCC diagnostic pop | ||
| #ifdef __clang__ | ||
| #pragma clang diagnostic pop | ||
| #endif | ||
| if (p - start >= 9) { | ||
| return p; | ||
| } | ||
| return nullptr; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace seastar |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| /* | ||
| * Copyright 2015 Cloudius Systems | ||
| */ | ||
|
|
||
|
|
||
| #define SEASTAR_HTTP2_TEST | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would like a better way to expose private functions for test purposes. |
||
| #include <seastar/http/function_handlers.hh> | ||
| #include <seastar/http/httpd.hh> | ||
| #include <seastar/http/handlers.hh> | ||
|
|
@@ -20,6 +21,7 @@ | |
| #include <seastar/testing/test_case.hh> | ||
| #include <seastar/testing/thread_test_case.hh> | ||
| #include "loopback_socket.hh" | ||
| #include "seastar/http/http2_connection.hh" | ||
| #include <boost/algorithm/string.hpp> | ||
| #include <seastar/core/thread.hh> | ||
| #include <seastar/util/noncopyable_function.hh> | ||
|
|
@@ -2176,5 +2178,43 @@ SEASTAR_TEST_CASE(test_client_close_connection) { | |
|
|
||
| when_all(std::move(server), std::move(client)).discard_result().get(); | ||
| } | ||
|
|
||
| }); | ||
| } | ||
|
|
||
| SEASTAR_TEST_CASE(test_http2_frame_parsing) { | ||
|
|
||
| return seastar::async([] { | ||
| // Set up loopback connection factory and server socket | ||
| loopback_connection_factory lcf(1); | ||
| auto server_sock = lcf.get_server_socket(); | ||
| loopback_socket_impl lsi(lcf); | ||
|
|
||
| // Start server coroutine to handle HTTP/2 connection | ||
| auto server = seastar::async([&] { | ||
| auto ar = server_sock.accept().get(); | ||
| // Create HTTP/2 connection object | ||
| seastar::httpd::routes dummy_routes; | ||
| seastar::http::http2_connection h2conn(dummy_routes, std::move(ar.connection), ar.remote_address, ar.remote_address); | ||
| h2conn.process().get(); | ||
| fmt::print("after http2 connection process, frame type {}\n", h2conn.get_parser_for_testing()._type); | ||
| BOOST_REQUIRE(h2conn.get_parser_for_testing()._type == 0x4); // 0x4 is the SETTINGS frame type | ||
|
|
||
| fmt::print("finished processing http2 frame!!\n"); | ||
| }); | ||
|
|
||
| // Client coroutine: connect and send a SETTINGS frame | ||
| auto client = seastar::async([&] { | ||
| connected_socket sock = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get(); | ||
| auto out = sock.output(); | ||
|
|
||
| // 1. Send a SETTINGS frame (empty payload, just header) | ||
| char settings_frame[9] = {0,0,0, 0x4, 0x0, 0,0,0,0}; // length=0, type=4 (SETTINGS), flags=0, stream_id=0 | ||
| out.write(settings_frame, 9).get(); | ||
| out.flush().get(); | ||
| out.close().get(); | ||
| }); | ||
| // Wait for both client and server to finish | ||
| when_all(std::move(server), std::move(client)).get(); | ||
| }); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This du[licates (at the first glance) the client.hh conneciton class