From 9e21b0c89bc3d81a4e7ef001d1df32ad6ab818c5 Mon Sep 17 00:00:00 2001 From: notaphplover Date: Thu, 20 Nov 2025 14:47:00 +0100 Subject: [PATCH 1/3] feat: update HttpResponse with beginWrite --- src/HttpResponse.h | 19 +++++ tests/HttpResponse.cpp | 184 +++++++++++++++++++++++++++++++++++++++++ tests/Makefile | 2 + 3 files changed, 205 insertions(+) create mode 100644 tests/HttpResponse.cpp diff --git a/src/HttpResponse.h b/src/HttpResponse.h index fb2e3b1a9..9d7d0513e 100644 --- a/src/HttpResponse.h +++ b/src/HttpResponse.h @@ -422,6 +422,25 @@ struct HttpResponse : public AsyncSocket { return this; } + /* Begin writing the response body. Useful for chunked encodings whose first chunk is not yet known */ + void beginWrite() { + /* Write status if not already done */ + writeStatus(HTTP_200_OK); + + HttpResponseData *httpResponseData = getHttpResponseData(); + + if (!(httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED)) { + /* Write mark on first call to write */ + writeMark(); + + writeHeader("Transfer-Encoding", "chunked"); + httpResponseData->state |= HttpResponseData::HTTP_WRITE_CALLED; + + /* Start of the body */ + Super::write("\r\n", 2); + } + } + /* End without a body (no content-length) or end with a spoofed content-length. */ void endWithoutBody(std::optional reportedContentLength = std::nullopt, bool closeConnection = false) { if (reportedContentLength.has_value()) { diff --git a/tests/HttpResponse.cpp b/tests/HttpResponse.cpp new file mode 100644 index 000000000..a9a37cd87 --- /dev/null +++ b/tests/HttpResponse.cpp @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../src/App.h" +#include "../uSockets/src/libusockets.h" + +/* Global test state */ +bool testPassed = false; +std::string receivedResponse; + +void testBeginWrite() { + std::cout << "TestBeginWrite" << std::endl; + + us_listen_socket_t *listenSocket = nullptr; + + uWS::App().get("/test", [](auto *res, auto *req) { + res->beginWrite(); + res->write("First"); + res->write("Second"); + res->write("Third"); + res->end(); + }).listen(9001, [&listenSocket](auto *token) { + if (token) { + listenSocket = token; + std::cout << " Server started on port 9001" << std::endl; + + std::thread([&listenSocket]() { + sleep(1); + + int sock = socket(AF_INET, SOCK_STREAM, 0); + assert(sock >= 0); + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(9001); + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + + int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); + assert(result == 0); + + const char *request = "GET /test HTTP/1.1\r\nHost: localhost\r\n\r\n"; + send(sock, request, strlen(request), 0); + + char buffer[4096] = {0}; + int bytesRead = recv(sock, buffer, sizeof(buffer) - 1, 0); + assert(bytesRead > 0); + + std::string response(buffer, bytesRead); + + assert(response.find("Transfer-Encoding: chunked") != std::string::npos); + assert(response.find("First") != std::string::npos); + assert(response.find("Second") != std::string::npos); + assert(response.find("Third") != std::string::npos); + assert(response.find("\r\n0\r\n\r\n") != std::string::npos); + + close(sock); + + std::cout << " ✓ beginWrite works correctly with chunked encoding" << std::endl; + + us_listen_socket_close(0, listenSocket); + }).detach(); + } else { + std::cerr << "Failed to listen on port 9001" << std::endl; + exit(1); + } + }).run(); +} + +void testBeginWriteIdempotent() { + std::cout << "TestBeginWriteIdempotent" << std::endl; + + us_listen_socket_t *listenSocket = nullptr; + + uWS::App().get("/idempotent", [](auto *res, auto *req) { + res->beginWrite(); + res->beginWrite(); + res->write("Data1"); + res->beginWrite(); + res->write("Data2"); + res->end(); + }).listen(9002, [&listenSocket](auto *token) { + if (token) { + listenSocket = token; + std::thread([&listenSocket]() { + sleep(1); + + int sock = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(9002); + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + + connect(sock, (struct sockaddr*)&addr, sizeof(addr)); + + const char *request = "GET /idempotent HTTP/1.1\r\nHost: localhost\r\n\r\n"; + send(sock, request, strlen(request), 0); + + char buffer[4096] = {0}; + recv(sock, buffer, sizeof(buffer) - 1, 0); + std::string response(buffer); + + assert(response.find("Transfer-Encoding: chunked") != std::string::npos); + assert(response.find("Data1") != std::string::npos); + assert(response.find("Data2") != std::string::npos); + + size_t firstPos = response.find("Transfer-Encoding: chunked"); + size_t secondPos = response.find("Transfer-Encoding: chunked", firstPos + 1); + assert(secondPos == std::string::npos); + + close(sock); + + std::cout << " ✓ beginWrite is idempotent (multiple calls safe)" << std::endl; + + us_listen_socket_close(0, listenSocket); + }).detach(); + } else { + exit(1); + } + }).run(); +} + +void testBeginWriteNoData() { + std::cout << "TestBeginWriteNoData" << std::endl; + + us_listen_socket_t *listenSocket = nullptr; + + uWS::App().get("/nodata", [](auto *res, auto *req) { + res->beginWrite(); + res->end(); + }).listen(9003, [&listenSocket](auto *token) { + if (token) { + listenSocket = token; + std::thread([&listenSocket]() { + sleep(1); + + int sock = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(9003); + inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); + + connect(sock, (struct sockaddr*)&addr, sizeof(addr)); + + const char *request = "GET /nodata HTTP/1.1\r\nHost: localhost\r\n\r\n"; + send(sock, request, strlen(request), 0); + + char buffer[4096] = {0}; + recv(sock, buffer, sizeof(buffer) - 1, 0); + std::string response(buffer); + + assert(response.find("Transfer-Encoding: chunked") != std::string::npos); + assert(response.find("\r\n0\r\n\r\n") != std::string::npos); + + close(sock); + + std::cout << " ✓ beginWrite with no data works correctly" << std::endl; + + us_listen_socket_close(0, listenSocket); + }).detach(); + } else { + exit(1); + } + }).run(); +} + +int main() { + std::cout << "Testing HttpResponse::beginWrite()" << std::endl; + std::cout << std::endl; + + testBeginWrite(); + testBeginWriteIdempotent(); + testBeginWriteNoData(); + + std::cout << std::endl; + std::cout << "HTTP RESPONSE DONE" << std::endl; + + return 0; +} diff --git a/tests/Makefile b/tests/Makefile index 86dc82b9b..f214838b3 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -13,6 +13,8 @@ default: ./ExtensionsNegotiator $(CXX) -std=c++17 -fsanitize=address HttpParser.cpp -o HttpParser ./HttpParser + $(CXX) -std=c++17 -fsanitize=address -I../uSockets/src -pthread HttpResponse.cpp ../uSockets/*.o -lz -o HttpResponse + ./HttpResponse performance: $(CXX) -std=c++17 HttpRouter.cpp -O3 -o HttpRouter From 5b436b3077d226f1d8c1302807a8c7559f6aee68 Mon Sep 17 00:00:00 2001 From: uNetworkingAB <110806833+uNetworkingAB@users.noreply.github.com> Date: Wed, 15 Apr 2026 06:10:10 +0200 Subject: [PATCH 2/3] Delete tests/HttpResponse.cpp --- tests/HttpResponse.cpp | 184 ----------------------------------------- 1 file changed, 184 deletions(-) delete mode 100644 tests/HttpResponse.cpp diff --git a/tests/HttpResponse.cpp b/tests/HttpResponse.cpp deleted file mode 100644 index a9a37cd87..000000000 --- a/tests/HttpResponse.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../src/App.h" -#include "../uSockets/src/libusockets.h" - -/* Global test state */ -bool testPassed = false; -std::string receivedResponse; - -void testBeginWrite() { - std::cout << "TestBeginWrite" << std::endl; - - us_listen_socket_t *listenSocket = nullptr; - - uWS::App().get("/test", [](auto *res, auto *req) { - res->beginWrite(); - res->write("First"); - res->write("Second"); - res->write("Third"); - res->end(); - }).listen(9001, [&listenSocket](auto *token) { - if (token) { - listenSocket = token; - std::cout << " Server started on port 9001" << std::endl; - - std::thread([&listenSocket]() { - sleep(1); - - int sock = socket(AF_INET, SOCK_STREAM, 0); - assert(sock >= 0); - - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(9001); - inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); - - int result = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); - assert(result == 0); - - const char *request = "GET /test HTTP/1.1\r\nHost: localhost\r\n\r\n"; - send(sock, request, strlen(request), 0); - - char buffer[4096] = {0}; - int bytesRead = recv(sock, buffer, sizeof(buffer) - 1, 0); - assert(bytesRead > 0); - - std::string response(buffer, bytesRead); - - assert(response.find("Transfer-Encoding: chunked") != std::string::npos); - assert(response.find("First") != std::string::npos); - assert(response.find("Second") != std::string::npos); - assert(response.find("Third") != std::string::npos); - assert(response.find("\r\n0\r\n\r\n") != std::string::npos); - - close(sock); - - std::cout << " ✓ beginWrite works correctly with chunked encoding" << std::endl; - - us_listen_socket_close(0, listenSocket); - }).detach(); - } else { - std::cerr << "Failed to listen on port 9001" << std::endl; - exit(1); - } - }).run(); -} - -void testBeginWriteIdempotent() { - std::cout << "TestBeginWriteIdempotent" << std::endl; - - us_listen_socket_t *listenSocket = nullptr; - - uWS::App().get("/idempotent", [](auto *res, auto *req) { - res->beginWrite(); - res->beginWrite(); - res->write("Data1"); - res->beginWrite(); - res->write("Data2"); - res->end(); - }).listen(9002, [&listenSocket](auto *token) { - if (token) { - listenSocket = token; - std::thread([&listenSocket]() { - sleep(1); - - int sock = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(9002); - inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); - - connect(sock, (struct sockaddr*)&addr, sizeof(addr)); - - const char *request = "GET /idempotent HTTP/1.1\r\nHost: localhost\r\n\r\n"; - send(sock, request, strlen(request), 0); - - char buffer[4096] = {0}; - recv(sock, buffer, sizeof(buffer) - 1, 0); - std::string response(buffer); - - assert(response.find("Transfer-Encoding: chunked") != std::string::npos); - assert(response.find("Data1") != std::string::npos); - assert(response.find("Data2") != std::string::npos); - - size_t firstPos = response.find("Transfer-Encoding: chunked"); - size_t secondPos = response.find("Transfer-Encoding: chunked", firstPos + 1); - assert(secondPos == std::string::npos); - - close(sock); - - std::cout << " ✓ beginWrite is idempotent (multiple calls safe)" << std::endl; - - us_listen_socket_close(0, listenSocket); - }).detach(); - } else { - exit(1); - } - }).run(); -} - -void testBeginWriteNoData() { - std::cout << "TestBeginWriteNoData" << std::endl; - - us_listen_socket_t *listenSocket = nullptr; - - uWS::App().get("/nodata", [](auto *res, auto *req) { - res->beginWrite(); - res->end(); - }).listen(9003, [&listenSocket](auto *token) { - if (token) { - listenSocket = token; - std::thread([&listenSocket]() { - sleep(1); - - int sock = socket(AF_INET, SOCK_STREAM, 0); - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(9003); - inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); - - connect(sock, (struct sockaddr*)&addr, sizeof(addr)); - - const char *request = "GET /nodata HTTP/1.1\r\nHost: localhost\r\n\r\n"; - send(sock, request, strlen(request), 0); - - char buffer[4096] = {0}; - recv(sock, buffer, sizeof(buffer) - 1, 0); - std::string response(buffer); - - assert(response.find("Transfer-Encoding: chunked") != std::string::npos); - assert(response.find("\r\n0\r\n\r\n") != std::string::npos); - - close(sock); - - std::cout << " ✓ beginWrite with no data works correctly" << std::endl; - - us_listen_socket_close(0, listenSocket); - }).detach(); - } else { - exit(1); - } - }).run(); -} - -int main() { - std::cout << "Testing HttpResponse::beginWrite()" << std::endl; - std::cout << std::endl; - - testBeginWrite(); - testBeginWriteIdempotent(); - testBeginWriteNoData(); - - std::cout << std::endl; - std::cout << "HTTP RESPONSE DONE" << std::endl; - - return 0; -} From 223399b17fd1ed6a32bf2118311982a2fa621365 Mon Sep 17 00:00:00 2001 From: uNetworkingAB <110806833+uNetworkingAB@users.noreply.github.com> Date: Wed, 15 Apr 2026 06:10:30 +0200 Subject: [PATCH 3/3] Delete tests/Makefile --- tests/Makefile | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 tests/Makefile diff --git a/tests/Makefile b/tests/Makefile deleted file mode 100644 index f214838b3..000000000 --- a/tests/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -default: - $(CXX) -std=c++17 -fsanitize=address Query.cpp -o Query - ./Query - $(CXX) -std=c++17 -fsanitize=address ChunkedEncoding.cpp -o ChunkedEncoding - ./ChunkedEncoding - $(CXX) -std=c++17 -fsanitize=address TopicTree.cpp -o TopicTree - ./TopicTree - $(CXX) -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG -std=c++17 -g -fsanitize=address HttpRouter.cpp -o HttpRouter - ./HttpRouter - $(CXX) -std=c++17 -fsanitize=address BloomFilter.cpp -o BloomFilter - ./BloomFilter - $(CXX) -std=c++17 -fsanitize=address ExtensionsNegotiator.cpp -o ExtensionsNegotiator - ./ExtensionsNegotiator - $(CXX) -std=c++17 -fsanitize=address HttpParser.cpp -o HttpParser - ./HttpParser - $(CXX) -std=c++17 -fsanitize=address -I../uSockets/src -pthread HttpResponse.cpp ../uSockets/*.o -lz -o HttpResponse - ./HttpResponse - -performance: - $(CXX) -std=c++17 HttpRouter.cpp -O3 -o HttpRouter - ./HttpRouter - -smoke: - ../Crc32 & - sleep 1 - ~/.deno/bin/deno run --allow-net smoke.mjs - node smoke.mjs - pkill Crc32 - -compliance: - ../EchoBody & - sleep 1 - ~/.deno/bin/deno run --allow-net ../h1spec/http_test.ts localhost 3000 - pkill EchoBody