| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949 |
- #include "gtest/gtest.h"
- #include <algorithm>
- #include <atomic>
- #include <chrono>
- #include <cstring>
- #include <fcntl.h>
- #include <iostream>
- #include <poll.h>
- #include <set>
- #include <string>
- #include <sys/socket.h>
- #include <thread>
- #include <unistd.h>
- #include <vector>
- #include "smartdns/http2.h"
- class LIBHTTP2 : public ::testing::Test
- {
- protected:
- void SetUp() override
- {
- // Create socketpair for communication
- if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) < 0) {
- perror("socketpair");
- FAIL() << "Failed to create socketpair";
- }
- client_sock = socks[0];
- server_sock = socks[1];
- // Set non-blocking
- fcntl(client_sock, F_SETFL, O_NONBLOCK);
- fcntl(server_sock, F_SETFL, O_NONBLOCK);
- }
- void TearDown() override
- {
- if (client_sock != -1)
- close(client_sock);
- if (server_sock != -1)
- close(server_sock);
- }
- int socks[2];
- int client_sock = -1;
- int server_sock = -1;
- // BIO callbacks
- static int bio_read(void *private_data, uint8_t *buf, int len)
- {
- int fd = *(int *)private_data;
- int ret = read(fd, buf, len);
- if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
- errno = EAGAIN;
- return -1;
- }
- return ret;
- }
- static int bio_write(void *private_data, const uint8_t *buf, int len)
- {
- int fd = *(int *)private_data;
- int ret = write(fd, buf, len);
- if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
- errno = EAGAIN;
- return -1;
- }
- return ret;
- }
- };
- TEST_F(LIBHTTP2, Integrated)
- {
- std::thread server_thread([this]() {
- // Server logic
- struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- int poll_ret = poll(&pfd, 1, 10);
- if (poll_ret == 0) {
- continue;
- }
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Server handshake failed";
- // Accept stream
- struct http2_stream *stream = nullptr;
- int max_attempts = 200;
- while (max_attempts-- > 0 && !stream) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream == nullptr && items[i].readable) {
- stream = http2_ctx_accept_stream(ctx);
- if (stream)
- break;
- }
- }
- usleep(20000);
- }
- if (!stream) {
- std::cout << "Server failed to accept stream after timeout" << std::endl;
- }
- ASSERT_NE(stream, nullptr) << "Server failed to accept stream";
- // Read request body
- uint8_t request_body[4096];
- int request_body_len = 0;
- while (!http2_stream_is_end(stream) && request_body_len < (int)sizeof(request_body)) {
- int read_len = http2_stream_read_body(stream, request_body + request_body_len,
- sizeof(request_body) - request_body_len);
- if (read_len > 0) {
- request_body_len += read_len;
- } else {
- usleep(10000);
- }
- }
- // Send response
- char response[8192];
- int response_len = snprintf(response, sizeof(response), "Echo Response: %.*s", request_body_len, request_body);
- char content_length[32];
- snprintf(content_length, sizeof(content_length), "%d", response_len);
- struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {"content-length", content_length}};
- http2_stream_set_response(stream, 200, headers, 2);
- http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);
- http2_stream_close(stream);
- http2_ctx_close(ctx);
- });
- std::thread client_thread([this]() {
- usleep(500000); // Wait for server start
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Client handshake failed";
- // Create stream
- struct http2_stream *stream = http2_stream_new(ctx);
- ASSERT_NE(stream, nullptr);
- // Send request
- struct http2_header_pair headers[] = {
- {"content-type", "application/json"}, {"content-length", "27"}, {NULL, NULL}};
- http2_stream_set_request(stream, "POST", "/echo", NULL, headers);
- const char *request_body = "{\"message\":\"Hello Echo!\"}";
- http2_stream_write_body(stream, (const uint8_t *)request_body, strlen(request_body), 1);
- // Wait for response
- int max_attempts = 200;
- while (max_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- if (http2_stream_get_status(stream) > 0)
- break;
- usleep(20000);
- }
- EXPECT_EQ(http2_stream_get_status(stream), 200);
- // Read response
- uint8_t response_body[4096];
- int response_body_len = 0;
- while (!http2_stream_is_end(stream) && response_body_len < (int)sizeof(response_body)) {
- int read_len = http2_stream_read_body(stream, response_body + response_body_len,
- sizeof(response_body) - response_body_len);
- if (read_len > 0) {
- response_body_len += read_len;
- } else {
- usleep(10000);
- }
- }
- std::string resp((char *)response_body, response_body_len);
- EXPECT_NE(resp.find("Echo Response"), std::string::npos);
- http2_stream_close(stream);
- http2_ctx_close(ctx);
- });
- server_thread.join();
- client_thread.join();
- }
- TEST_F(LIBHTTP2, MultiStream)
- {
- const int NUM_STREAMS = 3;
- std::thread server_thread([this, NUM_STREAMS]() {
- struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Server handshake failed";
- int streams_completed = 0;
- int max_iterations = 500;
- std::set<struct http2_stream *> processed_streams;
- while (streams_completed < NUM_STREAMS && max_iterations-- > 0) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream == nullptr && items[i].readable) {
- struct http2_stream *s = http2_ctx_accept_stream(ctx);
- } else if (items[i].stream && items[i].readable) {
- struct http2_stream *stream = items[i].stream;
- uint8_t buf[1024];
- http2_stream_read_body(stream, buf, sizeof(buf));
- if (http2_stream_is_end(stream)) {
- if (processed_streams.find(stream) == processed_streams.end()) {
- char response[256];
- int response_len = snprintf(response, sizeof(response), "Echo from stream %d",
- http2_stream_get_id(stream));
- char content_length[32];
- snprintf(content_length, sizeof(content_length), "%d", response_len);
- struct http2_header_pair headers[] = {{"content-type", "text/plain"},
- {"content-length", content_length}};
- http2_stream_set_response(stream, 200, headers, 2);
- http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);
- streams_completed++;
- processed_streams.insert(stream);
- }
- }
- }
- }
- usleep(2000);
- }
- for (auto stream : processed_streams) {
- http2_stream_close(stream);
- }
- http2_ctx_close(ctx);
- });
- std::thread client_thread([this, NUM_STREAMS]() {
- usleep(50000);
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Client handshake failed";
- struct http2_stream *streams[NUM_STREAMS];
- for (int i = 0; i < NUM_STREAMS; i++) {
- streams[i] = http2_stream_new(ctx);
- ASSERT_NE(streams[i], nullptr);
- char path[64];
- snprintf(path, sizeof(path), "/stream%d", i);
- char body[128];
- int body_len = snprintf(body, sizeof(body), "Request from stream %d", i);
- char content_length[32];
- snprintf(content_length, sizeof(content_length), "%d", body_len);
- struct http2_header_pair headers[] = {
- {"content-type", "text/plain"}, {"content-length", content_length}, {NULL, NULL}};
- http2_stream_set_request(streams[i], "POST", path, NULL, headers);
- http2_stream_write_body(streams[i], (const uint8_t *)body, body_len, 1);
- }
- int streams_completed = 0;
- int max_iterations = 500;
- std::set<int> completed_stream_ids;
- while (streams_completed < NUM_STREAMS && max_iterations-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream && items[i].readable) {
- struct http2_stream *stream = items[i].stream;
- uint8_t buf[1024];
- http2_stream_read_body(stream, buf, sizeof(buf));
- if (http2_stream_is_end(stream)) {
- int stream_id = http2_stream_get_id(stream);
- if (completed_stream_ids.find(stream_id) == completed_stream_ids.end()) {
- completed_stream_ids.insert(stream_id);
- streams_completed++;
- }
- }
- }
- }
- usleep(2000);
- }
- EXPECT_EQ(streams_completed, NUM_STREAMS);
- for (int i = 0; i < NUM_STREAMS; i++) {
- http2_stream_close(streams[i]);
- }
- http2_ctx_close(ctx);
- });
- server_thread.join();
- client_thread.join();
- }
- TEST_F(LIBHTTP2, EarlyStreamCreation)
- {
- std::thread server_thread([this]() {
- // Server logic
- struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- int poll_ret = poll(&pfd, 1, 10);
- if (poll_ret == 0) {
- continue;
- }
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Server handshake failed";
- // Accept stream
- struct http2_stream *stream = nullptr;
- int max_attempts = 200;
- while (max_attempts-- > 0 && !stream) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream == nullptr && items[i].readable) {
- stream = http2_ctx_accept_stream(ctx);
- if (stream)
- break;
- }
- }
- usleep(20000);
- }
- ASSERT_NE(stream, nullptr) << "Server failed to accept stream";
- // Verify we received the request
- const char *method = http2_stream_get_method(stream);
- const char *path = http2_stream_get_path(stream);
- EXPECT_STREQ(method, "POST");
- EXPECT_STREQ(path, "/early-test");
- // Read request body (should be empty for GET)
- uint8_t request_body[4096];
- int request_body_len = 0;
- while (!http2_stream_is_end(stream) && request_body_len < (int)sizeof(request_body)) {
- int read_len = http2_stream_read_body(stream, request_body + request_body_len,
- sizeof(request_body) - request_body_len);
- if (read_len > 0) {
- request_body_len += read_len;
- } else {
- usleep(10000);
- }
- }
- // Send response
- char response[8192];
- int response_len = snprintf(response, sizeof(response), "Echo Response: %.*s", request_body_len, request_body);
- char content_length[32];
- snprintf(content_length, sizeof(content_length), "%d", response_len);
- struct http2_header_pair headers[] = {
- {"content-type", "text/plain"}, {"content-length", content_length}, {NULL, NULL}};
- http2_stream_set_response(stream, 200, headers, 2);
- http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);
- http2_stream_close(stream);
- http2_ctx_close(ctx);
- });
- std::thread client_thread([this]() {
- usleep(50000); // Wait for server start
- // Create client context
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // IMPORTANT: Create stream and send request BEFORE handshake completes
- // This tests that the HEADERS frame is buffered and sent after handshake
- struct http2_stream *stream = http2_stream_new(ctx);
- ASSERT_NE(stream, nullptr);
- // Send request immediately (before handshake)
- struct http2_header_pair headers[] = {{"user-agent", "test-client"}, {NULL, NULL}};
- int ret = http2_stream_set_request(stream, "POST", "/early-test", NULL, headers);
- EXPECT_EQ(ret, 0) << "Failed to set request";
- const char *request_body = "test echo";
- http2_stream_write_body(stream, (const uint8_t *)request_body, strlen(request_body), 1);
- // Now complete handshake
- int handshake_attempts = 200;
- ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Client handshake failed";
- // Wait for response
- int max_attempts = 200;
- while (max_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- if (http2_stream_get_status(stream) > 0)
- break;
- usleep(20000);
- }
- EXPECT_EQ(http2_stream_get_status(stream), 200);
- // Read response
- uint8_t response_body[4096];
- int response_body_len = 0;
- while (!http2_stream_is_end(stream) && response_body_len < (int)sizeof(response_body)) {
- int read_len = http2_stream_read_body(stream, response_body + response_body_len,
- sizeof(response_body) - response_body_len);
- if (read_len > 0) {
- response_body_len += read_len;
- } else {
- usleep(10000);
- }
- }
- std::string resp((char *)response_body, response_body_len);
- EXPECT_NE(resp.find("Echo Response"), std::string::npos);
- EXPECT_NE(resp.find("test echo"), std::string::npos);
- http2_stream_close(stream);
- http2_ctx_close(ctx);
- });
- server_thread.join();
- client_thread.join();
- }
- TEST_F(LIBHTTP2, ServerLoopTerminationOnDisconnect)
- {
- std::thread server_thread([this]() {
- struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- int poll_ret = poll(&pfd, 1, 10);
- if (poll_ret == 0) {
- continue;
- }
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Server handshake failed";
- // Accept stream
- struct http2_stream *stream = nullptr;
- int max_attempts = 200;
- while (max_attempts-- > 0 && !stream) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream == nullptr && items[i].readable) {
- stream = http2_ctx_accept_stream(ctx);
- if (stream)
- break;
- }
- }
- usleep(20000);
- }
- ASSERT_NE(stream, nullptr) << "Server failed to accept stream";
- // Read request body until EOF
- uint8_t buf[1024];
- int loop_count = 0;
- while (loop_count++ < 100) {
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- int data_read = 0;
- for (int i = 0; i < count; i++) {
- if (items[i].stream == stream && items[i].readable) {
- int ret = http2_stream_read_body(stream, buf, sizeof(buf));
- if (ret > 0) {
- data_read = 1;
- } else if (ret == 0) {
- // EOF received
- data_read = 1;
- }
- }
- }
- if (!data_read && http2_stream_is_end(stream)) {
- // If we are here, it means poll returned 0 items (or stream not readable),
- // which is correct behavior after EOF is consumed.
- // If the bug exists, poll would keep returning readable stream, and we would keep reading 0 bytes.
- break;
- }
- usleep(10000);
- }
- EXPECT_LT(loop_count, 100) << "Server loop did not terminate (infinite loop detected)";
- http2_ctx_close(ctx);
- });
- std::thread client_thread([this]() {
- usleep(50000);
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret != 0) {
- break;
- }
- }
- ASSERT_EQ(ret, 1);
- struct http2_stream *stream = http2_stream_new(ctx);
- ASSERT_NE(stream, nullptr);
- struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {NULL, NULL}};
- http2_stream_set_request(stream, "POST", "/test", NULL, headers);
- http2_stream_write_body(stream, (const uint8_t *)"test", 4, 1);
- http2_stream_close(stream);
- http2_ctx_close(ctx);
- });
- server_thread.join();
- client_thread.join();
- }
- TEST_F(LIBHTTP2, StreamClose)
- {
- std::thread server_thread([this]() {
- struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- int poll_ret = poll(&pfd, 1, 10);
- if (poll_ret == 0) {
- continue;
- }
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Server handshake failed";
- // Accept stream
- struct http2_stream *stream = nullptr;
- int max_attempts = 200;
- while (max_attempts-- > 0 && !stream) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream == nullptr && items[i].readable) {
- stream = http2_ctx_accept_stream(ctx);
- if (stream)
- break;
- }
- }
- usleep(20000);
- }
- ASSERT_NE(stream, nullptr) << "Server failed to accept stream";
- // Read request and send response
- uint8_t buf[1024];
- http2_stream_read_body(stream, buf, sizeof(buf));
- http2_stream_set_response(stream, 200, NULL, 0);
- http2_stream_write_body(stream, (const uint8_t *)"OK", 2, 1);
- http2_stream_close(stream);
- http2_ctx_close(ctx);
- });
- std::thread client_thread([this]() {
- usleep(50000);
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- int handshake_attempts = 200;
- int ret = 0;
- while (handshake_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Client handshake failed";
- // Create stream
- struct http2_stream *stream = http2_stream_new(ctx);
- ASSERT_NE(stream, nullptr);
- // Send request
- http2_stream_set_request(stream, "GET", "/test", NULL, NULL);
- http2_stream_write_body(stream, NULL, 0, 1);
- // Wait for response
- int max_attempts = 200;
- while (max_attempts-- > 0) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 100);
- struct http2_poll_item items[10];
- int count = 0;
- http2_ctx_poll(ctx, items, 10, &count);
- if (http2_stream_get_status(stream) > 0)
- break;
- usleep(20000);
- }
- // Close the stream explicitly
- http2_stream_get(stream); // Keep reference for reading after close
- http2_stream_close(stream);
- // Verify stream is marked as closed (should still be able to read)
- // After close, the stream should still be readable until all data is consumed
- EXPECT_FALSE(http2_stream_is_end(stream)); // Should not be end yet since we haven't read response
- // Read response (should still work after close)
- uint8_t buf[1024];
- int read_len = http2_stream_read_body(stream, buf, sizeof(buf));
- EXPECT_GE(read_len, 0); // Should be able to read
- // After reading all data, stream should be end
- while (!http2_stream_is_end(stream)) {
- read_len = http2_stream_read_body(stream, buf, sizeof(buf));
- if (read_len <= 0) {
- break;
- }
- }
- EXPECT_TRUE(http2_stream_is_end(stream)); // Should be end after reading all data
- http2_stream_put(stream);
- http2_ctx_put(ctx);
- });
- server_thread.join();
- client_thread.join();
- }
- TEST_F(LIBHTTP2, ReferenceCountingNormal)
- {
- // Test normal reference counting: ctx normal, stream released by business
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Create a stream (already has refcount = 1)
- struct http2_stream *stream = http2_stream_new(ctx);
- ASSERT_NE(stream, nullptr);
- // Close context (should not free stream because business still holds reference)
- http2_ctx_close(ctx);
- // Business releases reference
- http2_stream_close(stream);
- // Now stream should be freed
- // We can't directly check, but no crash should occur
- }
- TEST_F(LIBHTTP2, ReferenceCountingContextError)
- {
- // Test reference counting when ctx has error but stream is still referenced by business
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Create a stream
- struct http2_stream *stream = http2_stream_new(ctx);
- ASSERT_NE(stream, nullptr);
- // Simulate context error by closing the socket (connection broken)
- close(client_sock);
- client_sock = -1;
- // Close context (should handle error gracefully)
- http2_ctx_close(ctx);
- // Business still holds reference, should be able to release it
- http2_stream_close(stream);
- // No crash should occur
- }
- TEST_F(LIBHTTP2, StressTest)
- {
- const int NUM_STREAMS = 1024;
- std::atomic<int> server_processed(0);
- std::atomic<int> client_completed(0);
- std::atomic<bool> test_completed(false);
- std::thread server_thread([this, NUM_STREAMS, &server_processed, &test_completed]() {
- struct http2_ctx *ctx = http2_ctx_server_new("test-server", bio_read, bio_write, &server_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- auto start_time = std::chrono::steady_clock::now();
- int ret = 0;
- while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(5)) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- int poll_ret = poll(&pfd, 1, 10);
- if (poll_ret == 0) {
- continue;
- }
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Server handshake failed";
- std::vector<struct http2_stream *> streams;
- start_time = std::chrono::steady_clock::now();
- while (!test_completed && std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30)) {
- struct pollfd pfd = {server_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- struct http2_poll_item items[64];
- int count = 0;
- http2_ctx_poll(ctx, items, 64, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream == nullptr && items[i].readable) {
- struct http2_stream *stream = http2_ctx_accept_stream(ctx);
- if (stream) {
- streams.push_back(stream);
- }
- } else if (items[i].stream && items[i].readable) {
- struct http2_stream *stream = items[i].stream;
- uint8_t buf[1024];
- while (http2_stream_read_body(stream, buf, sizeof(buf)) > 0)
- ;
- if (http2_stream_is_end(stream)) {
- char response[256];
- int response_len = snprintf(response, sizeof(response), "Echo %d", http2_stream_get_id(stream));
- char content_length[32];
- snprintf(content_length, sizeof(content_length), "%d", response_len);
- struct http2_header_pair headers[] = {{"content-type", "text/plain"},
- {"content-length", content_length}};
- http2_stream_set_response(stream, 200, headers, 2);
- http2_stream_write_body(stream, (const uint8_t *)response, response_len, 1);
- server_processed++;
- }
- }
- }
- }
- for (auto stream : streams) {
- http2_stream_close(stream);
- }
- http2_ctx_close(ctx);
- });
- std::thread client_thread([this, NUM_STREAMS, &client_completed, &test_completed]() {
- usleep(50000);
- struct http2_ctx *ctx = http2_ctx_client_new("test-client", bio_read, bio_write, &client_sock, NULL);
- ASSERT_NE(ctx, nullptr);
- // Handshake
- auto start_time = std::chrono::steady_clock::now();
- int ret = 0;
- while (std::chrono::steady_clock::now() - start_time < std::chrono::seconds(5)) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, 10);
- ret = http2_ctx_handshake(ctx);
- if (ret == 1)
- break;
- if (ret < 0)
- break;
- }
- ASSERT_EQ(ret, 1) << "Client handshake failed";
- std::vector<struct http2_stream *> streams;
- streams.reserve(NUM_STREAMS);
- std::set<int> completed_ids;
- auto process_events = [&](int timeout_ms) {
- struct pollfd pfd = {client_sock, POLLIN, 0};
- poll(&pfd, 1, timeout_ms);
- struct http2_poll_item items[64];
- int count = 0;
- http2_ctx_poll(ctx, items, 64, &count);
- for (int i = 0; i < count; i++) {
- if (items[i].stream && items[i].readable) {
- struct http2_stream *stream = items[i].stream;
- uint8_t buf[1024];
- while (http2_stream_read_body(stream, buf, sizeof(buf)) > 0)
- ;
- if (http2_stream_is_end(stream)) {
- int id = http2_stream_get_id(stream);
- if (completed_ids.find(id) == completed_ids.end()) {
- completed_ids.insert(id);
- client_completed++;
- }
- }
- }
- }
- };
- for (int i = 0; i < NUM_STREAMS; i++) {
- struct http2_stream *stream = http2_stream_new(ctx);
- if (stream) {
- streams.push_back(stream);
- char path[64];
- snprintf(path, sizeof(path), "/stream%d", i);
- char body[64];
- int body_len = snprintf(body, sizeof(body), "Req %d", i);
- struct http2_header_pair headers[] = {{"content-type", "text/plain"}, {NULL, NULL}};
- http2_stream_set_request(stream, "POST", path, NULL, headers);
- http2_stream_write_body(stream, (const uint8_t *)body, body_len, 1);
- }
- // Process events periodically to prevent deadlock/buffer overflow
- if (i % 10 == 0) {
- process_events(0);
- }
- }
- ASSERT_EQ(streams.size(), NUM_STREAMS);
- start_time = std::chrono::steady_clock::now();
- while (client_completed < NUM_STREAMS &&
- std::chrono::steady_clock::now() - start_time < std::chrono::seconds(30)) {
- process_events(10);
- }
- EXPECT_EQ(client_completed, NUM_STREAMS);
- for (auto stream : streams) {
- http2_stream_close(stream);
- }
- http2_ctx_close(ctx);
- test_completed = true;
- });
- server_thread.join();
- client_thread.join();
- }
|