bool test_http1_1_keepalive() { Socket acceptSocket; SocketError socketErr; HttpServer server; HttpError serverError; std::mutex serverMutex; std::condition_variable serverCond; bool isShutdown = false; std::tie(acceptSocket, socketErr) = getAcceptingSocket(Addrinfo::getLoopback(INet::Protocol::Ipv6, 0)); if (socketErr != SocketError::Ok) { testf("Failed to bind socket for accept with: %d", socketErr); return false; } uint16_t boundPort = acceptSocket.getLocalAddress().getPort(); StreamSourceSocket streamSource(std::move(acceptSocket)); server.addHandler("/index.html", [](HttpRequest& request, HttpResponse& response) { response.setStatus(200, "OK"); response.addHeader("Content-Length", "11"); HttpOutputStream* outputStream; std::tie(outputStream, std::ignore) = response.getOutputStream(); outputStream->write("Hello World", 11); outputStream->close(); }); Async::runAsync([&streamSource, &server, &serverError, &serverMutex, &serverCond, &isShutdown] { HttpError err = server.start(&streamSource); std::unique_lock<std::mutex> lock(serverMutex); isShutdown = true; serverError = err; serverCond.notify_one(); }); std::vector<StringRef> requests = { "GET /index.html HTTP/1.1\r\n" "Connection: keep-alive\r\n" "Host: localhost\r\n" "\r\n" "GET /index.html HTTP/1.1\r\n" "Host: localhost\r\n" "\r\n" "GET /index.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: close\r\n" "\r\n", "GET /index.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: close\r\n" "\r\n" "GET /index.html HTTP/1.1\r\n" "Host: localhost\r\n" "\r\n", }; std::vector<StringRef> expectedResponses = { "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello World" "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello World" "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello World", "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello World", }; for (size_t i = 0; i < requests.size(); i++) { StringRef request = requests[i]; StringRef expectedResponse = expectedResponses[i]; char responseBuffer[1024]; Socket requestSocket; std::tie(requestSocket, socketErr) = getConnectedSocket(Addrinfo::getLoopback(INet::Protocol::Ipv6, boundPort)); if (socketErr != SocketError::Ok) { testf("Failed to connect to HTTP socket with: %d", socketErr); return false; } socketErr = requestSocket.write(request.data(), request.length()); if (socketErr != SocketError::Ok) { testf("Failed to write to HTTP socket with: %d", socketErr); return false; } uint32_t totalBytesRead = 0; std::tie(totalBytesRead, socketErr) = readFully(&requestSocket, responseBuffer, sizeof(responseBuffer)); if (socketErr != SocketError::Ok) { testf("Failed to read from HTTP socket with: %d", socketErr); return false; } if (StringRef(responseBuffer, totalBytesRead) != expectedResponse) { testf("Did not receive expected response. Got:\n%.*s", (size_t)totalBytesRead, responseBuffer); return false; } } server.shutdown(); // Wait for server shutdown and check its error std::unique_lock<std::mutex> lock(serverMutex); serverCond.wait(lock, [&isShutdown] {return isShutdown; }); if (serverError != HttpError::Ok) { testf("Failed to start HTTP server with: %d", serverError); return false; } return true; }
bool test_http1_1_chunked_request() { Socket acceptSocket; SocketError socketErr; HttpServer server; HttpError serverError; std::mutex serverMutex; std::condition_variable serverCond; bool isShutdown = false; bool gotExpectedPost = false; HttpError postError = HttpError::Ok; bool hadCorrectHeader = false; std::tie(acceptSocket, socketErr) = ::getAcceptingSocket(Addrinfo::getLoopback(INet::Protocol::Ipv6, 0)); if (socketErr != SocketError::Ok) { testf("Failed to bind socket for accept with: %d", socketErr); return false; } uint16_t boundPort = acceptSocket.getLocalAddress().getPort(); StreamSourceSocket streamSource(std::move(acceptSocket)); server.addHandler("/formthingy", [&gotExpectedPost, &postError, &hadCorrectHeader](HttpRequest& request, HttpResponse& response) { char readBuffer[1024]; HttpInputStream& httpInputStream = request.getInputStream(); HttpError readErr; uint32_t readBytes; uint32_t totalRead = 0; do { std::tie(readBytes, readErr) = httpInputStream.read(readBuffer + totalRead, 3); // Intentionally small reads totalRead += readBytes; } while (readErr == HttpError::Ok); if (readErr != HttpError::Eof) { postError = readErr; return; } postError = httpInputStream.close(); const StringRef postData = "Some form post data"; if (totalRead != postData.length()) { gotExpectedPost = false; } else { gotExpectedPost = std::memcmp(postData.data(), readBuffer, postData.length()) == 0; } StringRef transferEncodingHeader; bool hasHeader; std::tie(transferEncodingHeader, hasHeader) = request.getHeader("Transfer-Encoding"); if (!hasHeader) { hadCorrectHeader = false; } else { hadCorrectHeader = transferEncodingHeader.equals("chunked"); } response.setStatus(200, "OK"); }); Async::runAsync([&streamSource, &server, &serverError, &serverMutex, &serverCond, &isShutdown] { HttpError err = server.start(&streamSource); std::unique_lock<std::mutex> lock(serverMutex); isShutdown = true; serverError = err; serverCond.notify_one(); }); StringRef request = "POST /formthingy HTTP/1.1\r\n" "Host: localhost\r\n" "Transfer-Encoding: chunked\r\n" "Connection: close\r\n" "\r\n" "5\r\n" "Some \r\n" "e\r\n" "form post data\r\n" "0\r\n" "\r\n"; StringRef expectedResponse = "HTTP/1.1 200 OK\r\n\r\n"; char responseBuffer[1024]; Socket requestSocket; std::tie(requestSocket, socketErr) = getConnectedSocket(Addrinfo::getLoopback(INet::Protocol::Ipv6, boundPort)); if (socketErr != SocketError::Ok) { testf("Failed to connect to HTTP socket with: %d", socketErr); return false; } socketErr = requestSocket.write(request.data(), request.length()); if (socketErr != SocketError::Ok) { testf("Failed to write to HTTP socket with: %d", socketErr); return false; } uint32_t totalBytesRead = 0; std::tie(totalBytesRead, socketErr) = readFully(&requestSocket, responseBuffer, sizeof(responseBuffer)); if (socketErr != SocketError::Ok) { testf("Failed to read from HTTP socket with: %d", socketErr); return false; } if (StringRef(responseBuffer, totalBytesRead) != expectedResponse) { testf("Did not receive expected response. Got:\n%.*s", totalBytesRead, responseBuffer); return false; } server.shutdown(); // Wait for server shutdown and check its error std::unique_lock<std::mutex> lock(serverMutex); serverCond.wait(lock, [&isShutdown] {return isShutdown; }); if (serverError != HttpError::Ok) { testf("Failed to start HTTP server with: %d", serverError); return false; } if (!gotExpectedPost) { testf("Did not read back expected post data"); return false; } if (postError != HttpError::Ok) { testf("Received error when reading post data: %d", postError); return false; } if (!hadCorrectHeader) { testf("Http request did not have expected Transfer-Encoding header"); return false; } return true; }
// Tests that a chunked header is automatically added, and chunked mode is used, // if neither Transfer-Encoding or Content-Length are specified. bool test_http1_1_auto_chunked_response() { Socket acceptSocket; SocketError socketErr; HttpServer server; HttpError serverError = HttpError::Ok; HttpError responseError = HttpError::Ok; std::mutex serverMutex; std::condition_variable serverCond; bool isShutdown = false; std::tie(acceptSocket, socketErr) = getAcceptingSocket(Addrinfo::getLoopback(INet::Protocol::Ipv6, 0)); if (socketErr != SocketError::Ok) { testf("Failed to bind socket for accept with: %d", socketErr); return false; } uint16_t boundPort = acceptSocket.getLocalAddress().getPort(); StreamSourceSocket streamSource(std::move(acceptSocket)); server.addHandler("/index.html", [&responseError](HttpRequest& request, HttpResponse& response) { response.setStatus(200, "OK"); HttpOutputStream* outputStream; std::tie(outputStream, responseError) = response.getOutputStream(); if (responseError != HttpError::Ok) { return; } responseError = outputStream->write("0123456789", 10); if (responseError != HttpError::Ok) { return; } responseError = outputStream->write("abc", 3); }); Async::runAsync([&streamSource, &server, &serverError, &serverMutex, &serverCond, &isShutdown] { HttpError err = server.start(&streamSource); std::unique_lock<std::mutex> lock(serverMutex); isShutdown = true; serverError = err; serverCond.notify_one(); }); StringRef request = "GET /index.html HTTP/1.1\r\n" "Host: localhost\r\n" "Connection: close\r\n" "\r\n"; StringRef expectedResponse = "HTTP/1.1 200 OK\r\n" "Transfer-Encoding: chunked\r\n" "\r\n" "A\r\n" "0123456789\r\n" "3\r\n" "abc\r\n" "0\r\n" "\r\n"; char responseBuffer[1024]; Socket requestSocket; std::tie(requestSocket, socketErr) = getConnectedSocket(Addrinfo::getLoopback(INet::Protocol::Ipv6, boundPort)); if (socketErr != SocketError::Ok) { testf("Failed to connect to HTTP socket with: %d", socketErr); return false; } socketErr = requestSocket.write(request.data(), request.length()); if (socketErr != SocketError::Ok) { testf("Failed to write to HTTP socket with: %d", socketErr); return false; } uint32_t totalBytesRead = 0; std::tie(totalBytesRead, socketErr) = readFully(&requestSocket, responseBuffer, sizeof(responseBuffer)); if (socketErr != SocketError::Ok) { testf("Failed to read from HTTP socket with: %d", socketErr); return false; } server.shutdown(); // Wait for server shutdown and check its error std::unique_lock<std::mutex> lock(serverMutex); serverCond.wait(lock, [&isShutdown] {return isShutdown; }); if (serverError != HttpError::Ok) { testf("Failed to start HTTP server with: %d", serverError); return false; } if (responseError != HttpError::Ok) { testf("Error when generating response: %d", responseError); return false; } if (StringRef(responseBuffer, totalBytesRead) != expectedResponse) { testf("Did not receive expected response. Got:\n%.*s", (size_t)totalBytesRead, responseBuffer); return false; } return true; }