1 //===-- llvm/unittest/Support/HTTPServer.cpp - unit tests -------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "llvm/Debuginfod/HTTPClient.h" 10 #include "llvm/Debuginfod/HTTPServer.h" 11 #include "llvm/Support/Error.h" 12 #include "llvm/Support/ThreadPool.h" 13 #include "llvm/Testing/Support/Error.h" 14 #include "gmock/gmock.h" 15 #include "gtest/gtest.h" 16 17 using namespace llvm; 18 19 #ifdef LLVM_ENABLE_HTTPLIB 20 21 TEST(HTTPServer, IsAvailable) { EXPECT_TRUE(HTTPServer::isAvailable()); } 22 23 HTTPResponse Response = {200u, "text/plain", "hello, world\n"}; 24 std::string UrlPathPattern = R"(/(.*))"; 25 std::string InvalidUrlPathPattern = R"(/(.*)"; 26 27 HTTPRequestHandler Handler = [](HTTPServerRequest &Request) { 28 Request.setResponse(Response); 29 }; 30 31 HTTPRequestHandler DelayHandler = [](HTTPServerRequest &Request) { 32 std::this_thread::sleep_for(std::chrono::milliseconds(50)); 33 Request.setResponse(Response); 34 }; 35 36 HTTPRequestHandler StreamingHandler = [](HTTPServerRequest &Request) { 37 Request.setResponse({200, "text/plain", Response.Body.size(), 38 [=](size_t Offset, size_t Length) -> StringRef { 39 return Response.Body.substr(Offset, Length); 40 }}); 41 }; 42 43 TEST(HTTPServer, InvalidUrlPath) { 44 // test that we can bind to any address 45 HTTPServer Server; 46 EXPECT_THAT_ERROR(Server.get(InvalidUrlPathPattern, Handler), 47 Failed<StringError>()); 48 EXPECT_THAT_EXPECTED(Server.bind(), Succeeded()); 49 } 50 51 TEST(HTTPServer, bind) { 52 // test that we can bind to any address 53 HTTPServer Server; 54 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded()); 55 EXPECT_THAT_EXPECTED(Server.bind(), Succeeded()); 56 } 57 58 TEST(HTTPServer, ListenBeforeBind) { 59 // test that we can bind to any address 60 HTTPServer Server; 61 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded()); 62 EXPECT_THAT_ERROR(Server.listen(), Failed<StringError>()); 63 } 64 65 #ifdef LLVM_ENABLE_CURL 66 // Test the client and server against each other. 67 68 // Test fixture to initialize and teardown the HTTP client for each 69 // client-server test 70 class HTTPClientServerTest : public ::testing::Test { 71 protected: 72 void SetUp() override { HTTPClient::initialize(); } 73 void TearDown() override { HTTPClient::cleanup(); } 74 }; 75 76 /// A simple handler which writes returned data to a string. 77 struct StringHTTPResponseHandler final : public HTTPResponseHandler { 78 std::string ResponseBody = ""; 79 /// These callbacks store the body and status code in an HTTPResponseBuffer 80 /// allocated based on Content-Length. The Content-Length header must be 81 /// handled by handleHeaderLine before any calls to handleBodyChunk. 82 Error handleBodyChunk(StringRef BodyChunk) override { 83 ResponseBody = ResponseBody + BodyChunk.str(); 84 return Error::success(); 85 } 86 }; 87 88 TEST_F(HTTPClientServerTest, Hello) { 89 HTTPServer Server; 90 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded()); 91 Expected<unsigned> PortOrErr = Server.bind(); 92 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 93 unsigned Port = *PortOrErr; 94 ThreadPool Pool(hardware_concurrency(1)); 95 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 96 std::string Url = "http://localhost:" + utostr(Port); 97 HTTPRequest Request(Url); 98 StringHTTPResponseHandler Handler; 99 HTTPClient Client; 100 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 101 EXPECT_EQ(Handler.ResponseBody, Response.Body); 102 EXPECT_EQ(Client.responseCode(), Response.Code); 103 Server.stop(); 104 } 105 106 TEST_F(HTTPClientServerTest, LambdaHandlerHello) { 107 HTTPServer Server; 108 HTTPResponse LambdaResponse = {200u, "text/plain", 109 "hello, world from a lambda\n"}; 110 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, 111 [LambdaResponse](HTTPServerRequest &Request) { 112 Request.setResponse(LambdaResponse); 113 }), 114 Succeeded()); 115 Expected<unsigned> PortOrErr = Server.bind(); 116 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 117 unsigned Port = *PortOrErr; 118 ThreadPool Pool(hardware_concurrency(1)); 119 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 120 std::string Url = "http://localhost:" + utostr(Port); 121 HTTPRequest Request(Url); 122 StringHTTPResponseHandler Handler; 123 HTTPClient Client; 124 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 125 EXPECT_EQ(Handler.ResponseBody, LambdaResponse.Body); 126 EXPECT_EQ(Client.responseCode(), LambdaResponse.Code); 127 Server.stop(); 128 } 129 130 // Test the streaming response. 131 TEST_F(HTTPClientServerTest, StreamingHello) { 132 HTTPServer Server; 133 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, StreamingHandler), Succeeded()); 134 Expected<unsigned> PortOrErr = Server.bind(); 135 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 136 unsigned Port = *PortOrErr; 137 ThreadPool Pool(hardware_concurrency(1)); 138 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 139 std::string Url = "http://localhost:" + utostr(Port); 140 HTTPRequest Request(Url); 141 StringHTTPResponseHandler Handler; 142 HTTPClient Client; 143 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 144 EXPECT_EQ(Handler.ResponseBody, Response.Body); 145 EXPECT_EQ(Client.responseCode(), Response.Code); 146 Server.stop(); 147 } 148 149 // Writes a temporary file and streams it back using streamFile. 150 HTTPRequestHandler TempFileStreamingHandler = [](HTTPServerRequest Request) { 151 int FD; 152 SmallString<64> TempFilePath; 153 sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD, 154 TempFilePath); 155 raw_fd_ostream OS(FD, true, /*unbuffered=*/true); 156 OS << Response.Body; 157 OS.close(); 158 streamFile(Request, TempFilePath); 159 }; 160 161 // Test streaming back chunks of a file. 162 TEST_F(HTTPClientServerTest, StreamingFileResponse) { 163 HTTPServer Server; 164 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, TempFileStreamingHandler), 165 Succeeded()); 166 Expected<unsigned> PortOrErr = Server.bind(); 167 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 168 unsigned Port = *PortOrErr; 169 ThreadPool Pool(hardware_concurrency(1)); 170 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 171 std::string Url = "http://localhost:" + utostr(Port); 172 HTTPRequest Request(Url); 173 StringHTTPResponseHandler Handler; 174 HTTPClient Client; 175 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 176 EXPECT_EQ(Handler.ResponseBody, Response.Body); 177 EXPECT_EQ(Client.responseCode(), Response.Code); 178 Server.stop(); 179 } 180 181 // Deletes the temporary file before streaming it back, should give a 404 not 182 // found status code. 183 HTTPRequestHandler MissingTempFileStreamingHandler = 184 [](HTTPServerRequest Request) { 185 int FD; 186 SmallString<64> TempFilePath; 187 sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD, 188 TempFilePath); 189 raw_fd_ostream OS(FD, true, /*unbuffered=*/true); 190 OS << Response.Body; 191 OS.close(); 192 // delete the file 193 sys::fs::remove(TempFilePath); 194 streamFile(Request, TempFilePath); 195 }; 196 197 // Streaming a missing file should give a 404. 198 TEST_F(HTTPClientServerTest, StreamingMissingFileResponse) { 199 HTTPServer Server; 200 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, MissingTempFileStreamingHandler), 201 Succeeded()); 202 Expected<unsigned> PortOrErr = Server.bind(); 203 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 204 unsigned Port = *PortOrErr; 205 ThreadPool Pool(hardware_concurrency(1)); 206 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 207 std::string Url = "http://localhost:" + utostr(Port); 208 HTTPRequest Request(Url); 209 StringHTTPResponseHandler Handler; 210 HTTPClient Client; 211 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 212 EXPECT_EQ(Client.responseCode(), 404u); 213 Server.stop(); 214 } 215 216 TEST_F(HTTPClientServerTest, ClientTimeout) { 217 HTTPServer Server; 218 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, DelayHandler), Succeeded()); 219 Expected<unsigned> PortOrErr = Server.bind(); 220 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 221 unsigned Port = *PortOrErr; 222 ThreadPool Pool(hardware_concurrency(1)); 223 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 224 std::string Url = "http://localhost:" + utostr(Port); 225 HTTPClient Client; 226 // Timeout below 50ms, request should fail 227 Client.setTimeout(std::chrono::milliseconds(40)); 228 HTTPRequest Request(Url); 229 StringHTTPResponseHandler Handler; 230 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Failed<StringError>()); 231 Server.stop(); 232 } 233 234 // Check that Url paths are dispatched to the first matching handler and provide 235 // the correct path pattern match components. 236 TEST_F(HTTPClientServerTest, PathMatching) { 237 HTTPServer Server; 238 239 EXPECT_THAT_ERROR( 240 Server.get(R"(/abc/(.*)/(.*))", 241 [&](HTTPServerRequest &Request) { 242 EXPECT_EQ(Request.UrlPath, "/abc/1/2"); 243 ASSERT_THAT(Request.UrlPathMatches, 244 testing::ElementsAre("1", "2")); 245 Request.setResponse({200u, "text/plain", Request.UrlPath}); 246 }), 247 Succeeded()); 248 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, 249 [&](HTTPServerRequest &Request) { 250 llvm_unreachable( 251 "Should not reach this handler"); 252 Handler(Request); 253 }), 254 Succeeded()); 255 256 Expected<unsigned> PortOrErr = Server.bind(); 257 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 258 unsigned Port = *PortOrErr; 259 ThreadPool Pool(hardware_concurrency(1)); 260 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 261 std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2"; 262 HTTPRequest Request(Url); 263 StringHTTPResponseHandler Handler; 264 HTTPClient Client; 265 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 266 EXPECT_EQ(Handler.ResponseBody, "/abc/1/2"); 267 EXPECT_EQ(Client.responseCode(), 200u); 268 Server.stop(); 269 } 270 271 TEST_F(HTTPClientServerTest, FirstPathMatched) { 272 HTTPServer Server; 273 274 EXPECT_THAT_ERROR( 275 Server.get(UrlPathPattern, 276 [&](HTTPServerRequest Request) { Handler(Request); }), 277 Succeeded()); 278 279 EXPECT_THAT_ERROR( 280 Server.get(R"(/abc/(.*)/(.*))", 281 [&](HTTPServerRequest Request) { 282 EXPECT_EQ(Request.UrlPathMatches.size(), 2u); 283 llvm_unreachable("Should not reach this handler"); 284 Request.setResponse({200u, "text/plain", Request.UrlPath}); 285 }), 286 Succeeded()); 287 288 Expected<unsigned> PortOrErr = Server.bind(); 289 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); 290 unsigned Port = *PortOrErr; 291 ThreadPool Pool(hardware_concurrency(1)); 292 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); 293 std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2"; 294 HTTPRequest Request(Url); 295 StringHTTPResponseHandler Handler; 296 HTTPClient Client; 297 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); 298 EXPECT_EQ(Handler.ResponseBody, Response.Body); 299 EXPECT_EQ(Client.responseCode(), Response.Code); 300 Server.stop(); 301 } 302 303 #endif 304 305 #else 306 307 TEST(HTTPServer, IsAvailable) { EXPECT_FALSE(HTTPServer::isAvailable()); } 308 309 #endif // LLVM_ENABLE_HTTPLIB 310