Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ seastar_generate_ragel (
IN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/http/request_parser.rl
OUT_FILE ${Seastar_GEN_BINARY_DIR}/include/seastar/http/request_parser.hh)

seastar_generate_ragel (
TARGET seastar_http2_frame_parser
VAR http2_frame_parser_file
IN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/src/http/http2_frame_parser.rl
OUT_FILE ${Seastar_GEN_BINARY_DIR}/include/seastar/http/http2_frame_parser.hh)

seastar_generate_ragel (
TARGET seastar_http_response_parser
VAR http_response_parser_file
Expand All @@ -489,6 +495,7 @@ seastar_generate_protobuf (
add_library (seastar
${http_chunk_parsers_file}
${http_request_parser_file}
${http2_frame_parser_file}
${proto_metrics2_files}
${seastar_dpdk_obj}
include/seastar/core/abort_source.hh
Expand Down Expand Up @@ -600,6 +607,7 @@ add_library (seastar
include/seastar/http/mime_types.hh
include/seastar/http/reply.hh
include/seastar/http/request.hh
include/seastar/http/http2_connection.hh
include/seastar/http/routes.hh
include/seastar/http/short_streams.hh
include/seastar/http/transformers.hh
Expand Down Expand Up @@ -721,6 +729,7 @@ add_library (seastar
src/http/transformers.cc
src/http/url.cc
src/http/client.cc
src/http/http2_connection.cc
src/http/request.cc
src/json/formatter.cc
src/json/json_elements.cc
Expand Down Expand Up @@ -771,6 +780,7 @@ add_dependencies (seastar
seastar_http_chunk_parsers
seastar_http_request_parser
seastar_http_response_parser
seastar_http2_frame_parser
seastar_proto_metrics2)

target_include_directories (seastar
Expand Down
93 changes: 93 additions & 0 deletions include/seastar/http/http2_connection.hh
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 {
Copy link
Contributor

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

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
54 changes: 54 additions & 0 deletions src/http/http2_connection.cc
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
87 changes: 87 additions & 0 deletions src/http/http2_frame_parser.rl
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
42 changes: 41 additions & 1 deletion tests/unit/httpd_test.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/*
* Copyright 2015 Cloudius Systems
*/


#define SEASTAR_HTTP2_TEST
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>
Expand All @@ -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>
Expand Down Expand Up @@ -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();
});
}
Loading