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