xref: /aosp_15_r20/external/ot-br-posix/third_party/Simple-web-server/repo/tests/io_test.cpp (revision 4a64e381480ef79f0532b2421e44e6ee336b8e0d)
1 #include "assert.hpp"
2 #include "client_http.hpp"
3 #include "server_http.hpp"
4 #include <future>
5 
6 using namespace std;
7 
8 using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;
9 using HttpClient = SimpleWeb::Client<SimpleWeb::HTTP>;
10 
main()11 int main() {
12   // Test ScopeRunner
13   {
14     SimpleWeb::ScopeRunner scope_runner;
15     std::thread cancel_thread;
16     {
17       ASSERT(scope_runner.count == 0);
18       auto lock = scope_runner.continue_lock();
19       ASSERT(lock);
20       ASSERT(scope_runner.count == 1);
21       {
22         auto lock = scope_runner.continue_lock();
23         ASSERT(lock);
24         ASSERT(scope_runner.count == 2);
25       }
26       ASSERT(scope_runner.count == 1);
27       cancel_thread = thread([&scope_runner] {
28         scope_runner.stop();
29         ASSERT(scope_runner.count == -1);
30       });
31       this_thread::sleep_for(chrono::milliseconds(500));
32       ASSERT(scope_runner.count == 1);
33     }
34     cancel_thread.join();
35     ASSERT(scope_runner.count == -1);
36     auto lock = scope_runner.continue_lock();
37     ASSERT(!lock);
38     scope_runner.stop();
39     ASSERT(scope_runner.count == -1);
40 
41     scope_runner.count = 0;
42 
43     vector<thread> threads;
44     for(size_t c = 0; c < 100; ++c) {
45       threads.emplace_back([&scope_runner] {
46         auto lock = scope_runner.continue_lock();
47         ASSERT(scope_runner.count > 0);
48       });
49     }
50     for(auto &thread : threads)
51       thread.join();
52     ASSERT(scope_runner.count == 0);
53   }
54 
55   HttpServer server;
56   server.config.port = 8080;
57 
58   server.resource["^/string$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
59     auto content = request->content.string();
60     ASSERT(content == request->content.string());
61 
62     *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
63               << content;
64 
65     ASSERT(!request->remote_endpoint().address().to_string().empty());
66     ASSERT(request->remote_endpoint().port() != 0);
67   };
68 
69   server.resource["^/string/dup$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
70     auto content = request->content.string();
71 
72     // Send content twice, before it has a chance to be written to the socket.
73     *response << "HTTP/1.1 200 OK\r\nContent-Length: " << (content.length() * 2) << "\r\n\r\n"
74               << content;
75     response->send();
76     *response << content;
77     response->send();
78 
79     ASSERT(!request->remote_endpoint().address().to_string().empty());
80     ASSERT(request->remote_endpoint().port() != 0);
81   };
82 
83   server.resource["^/string2$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
84     response->write(request->content.string());
85   };
86 
87   server.resource["^/string3$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
88     stringstream stream;
89     stream << request->content.rdbuf();
90     response->write(stream);
91   };
92 
93   server.resource["^/string4$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
94     response->write(SimpleWeb::StatusCode::client_error_forbidden, {{"Test1", "test2"}, {"tesT3", "test4"}});
95   };
96 
97   server.resource["^/info$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
98     stringstream content_stream;
99     content_stream << request->method << " " << request->path << " " << request->http_version << " ";
100     content_stream << request->header.find("test parameter")->second;
101 
102     content_stream.seekp(0, ios::end);
103 
104     *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content_stream.tellp() << "\r\n\r\n"
105               << content_stream.rdbuf();
106   };
107 
108   server.resource["^/work$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
109     thread work_thread([response] {
110       this_thread::sleep_for(chrono::seconds(5));
111       response->write("Work done");
112     });
113     work_thread.detach();
114   };
115 
116   server.resource["^/match/([0-9]+)$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
117     string number = request->path_match[1];
118     *response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n"
119               << number;
120   };
121 
122   server.resource["^/header$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
123     auto content = request->header.find("test1")->second + request->header.find("test2")->second;
124 
125     *response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n"
126               << content;
127   };
128 
129   server.resource["^/query_string$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
130     ASSERT(request->path == "/query_string");
131     ASSERT(request->query_string == "testing");
132     auto queries = request->parse_query_string();
133     auto it = queries.find("Testing");
134     ASSERT(it != queries.end() && it->first == "testing" && it->second == "");
135     response->write(request->query_string);
136   };
137 
138   server.resource["^/chunked$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
139     ASSERT(request->path == "/chunked");
140 
141     ASSERT(request->content.string() == "SimpleWeb in\r\n\r\nchunks.");
142 
143     response->write("6\r\nSimple\r\n3\r\nWeb\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n", {{"Transfer-Encoding", "chunked"}});
144   };
145 
146   server.resource["^/chunked2$"]["POST"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
147     ASSERT(request->path == "/chunked2");
148 
149     ASSERT(request->content.string() == "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld");
150 
151     response->write("258\r\nHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld\r\n0\r\n\r\n", {{"Transfer-Encoding", "chunked"}});
152   };
153 
154   server.resource["^/event-stream1$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
155     thread work_thread([response] {
156       response->close_connection_after_response = true; // Unspecified content length
157 
158       // Send header
159       promise<bool> header_error;
160       response->write({{"Content-Type", "text/event-stream"}});
161       response->send([&header_error](const SimpleWeb::error_code &ec) {
162         header_error.set_value(static_cast<bool>(ec));
163       });
164       ASSERT(!header_error.get_future().get());
165 
166       *response << "data: 1\n\n";
167       promise<bool> error;
168       response->send([&error](const SimpleWeb::error_code &ec) {
169         error.set_value(static_cast<bool>(ec));
170       });
171       ASSERT(!error.get_future().get());
172 
173       // Write result
174       *response << "data: 2\n\n";
175     });
176     work_thread.detach();
177   };
178 
179   server.resource["^/event-stream2$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
180     thread work_thread([response] {
181       response->close_connection_after_response = true; // Unspecified content length
182 
183       // Send header
184       promise<bool> header_error;
185       response->write({{"Content-Type", "text/event-stream"}});
186       response->send([&header_error](const SimpleWeb::error_code &ec) {
187         header_error.set_value(static_cast<bool>(ec));
188       });
189       ASSERT(!header_error.get_future().get());
190 
191       *response << "data: 1\r\n\r\n";
192       promise<bool> error;
193       response->send([&error](const SimpleWeb::error_code &ec) {
194         error.set_value(static_cast<bool>(ec));
195       });
196       ASSERT(!error.get_future().get());
197 
198       // Write result
199       *response << "data: 2\r\n\r\n";
200     });
201     work_thread.detach();
202   };
203 
204   server.resource["^/session-close$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
205     response->close_connection_after_response = true; // Unspecified content length
206     response->write("test", {{"Session", "close"}});
207   };
208   server.resource["^/session-close-without-correct-header$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
209     response->close_connection_after_response = true; // Unspecified content length
210     response->write("test");
211   };
212 
213   server.resource["^/non-standard-line-endings1$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
214     *response << "HTTP/1.1 200 OK\r\nname: value\n\n";
215   };
216 
217   server.resource["^/non-standard-line-endings2$"]["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
218     *response << "HTTP/1.1 200 OK\nname: value\n\n";
219   };
220 
221   std::string long_response;
222   for(int c = 0; c < 1000; ++c)
223     long_response += to_string(c);
224   server.resource["^/long-response$"]["GET"] = [&long_response](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
225     response->write(long_response, {{"name", "value"}});
226   };
227 
228   thread server_thread([&server]() {
229     // Start server
230     server.start();
231   });
232 
233   this_thread::sleep_for(chrono::seconds(1));
234 
235   server.stop();
236   server_thread.join();
237 
238   server_thread = thread([&server]() {
239     // Start server
240     server.start();
241   });
242 
243   this_thread::sleep_for(chrono::seconds(1));
244 
245   // Test various request types
246   {
247     HttpClient client("localhost:8080");
248     {
249       stringstream output;
250       auto r = client.request("POST", "/string", "A string");
251       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
252       output << r->content.rdbuf();
253       ASSERT(output.str() == "A string");
254     }
255 
256     {
257       auto r = client.request("POST", "/string", "A string");
258       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
259       ASSERT(r->content.string() == "A string");
260       ASSERT(r->content.string() == "A string");
261     }
262 
263     {
264       stringstream output;
265       auto r = client.request("POST", "/string2", "A string");
266       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
267       output << r->content.rdbuf();
268       ASSERT(output.str() == "A string");
269     }
270 
271     {
272       stringstream output;
273       auto r = client.request("POST", "/string3", "A string");
274       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
275       output << r->content.rdbuf();
276       ASSERT(output.str() == "A string");
277     }
278 
279     {
280       stringstream output;
281       auto r = client.request("POST", "/string4", "A string");
282       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::client_error_forbidden);
283       ASSERT(r->header.size() == 3);
284       ASSERT(r->header.find("test1")->second == "test2");
285       ASSERT(r->header.find("tEst3")->second == "test4");
286       ASSERT(r->header.find("content-length")->second == "0");
287       output << r->content.rdbuf();
288       ASSERT(output.str() == "");
289     }
290 
291     {
292       stringstream output;
293       stringstream content("A string");
294       auto r = client.request("POST", "/string", content);
295       output << r->content.rdbuf();
296       ASSERT(output.str() == "A string");
297     }
298 
299     {
300       // Test rapid calls to Response::send
301       stringstream output;
302       stringstream content("A string\n");
303       auto r = client.request("POST", "/string/dup", content);
304       output << r->content.rdbuf();
305       ASSERT(output.str() == "A string\nA string\n");
306     }
307 
308     {
309       stringstream output;
310       auto r = client.request("GET", "/info", "", {{"Test Parameter", "test value"}});
311       output << r->content.rdbuf();
312       ASSERT(output.str() == "GET /info 1.1 test value");
313     }
314 
315     {
316       stringstream output;
317       auto r = client.request("GET", "/match/123");
318       output << r->content.rdbuf();
319       ASSERT(output.str() == "123");
320     }
321     {
322       auto r = client.request("POST", "/chunked", "6\r\nSimple\r\n3\r\nWeb\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n", {{"Transfer-Encoding", "chunked"}});
323       ASSERT(r->content.string() == "SimpleWeb in\r\n\r\nchunks.");
324     }
325     {
326       auto r = client.request("POST", "/chunked2", "258\r\nHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld\r\n0\r\n\r\n", {{"Transfer-Encoding", "chunked"}});
327       ASSERT(r->content.string() == "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld");
328     }
329 
330     // Test reconnecting
331     for(int c = 0; c < 20; ++c) {
332       auto r = client.request("GET", "/session-close");
333       ASSERT(r->content.string() == "test");
334     }
335     for(int c = 0; c < 20; ++c) {
336       auto r = client.request("GET", "/session-close-without-correct-header");
337       ASSERT(r->content.string() == "test");
338     }
339 
340     // Test non-standard line endings
341     {
342       auto r = client.request("GET", "/non-standard-line-endings1");
343       ASSERT(r->http_version == "1.1");
344       ASSERT(r->status_code == "200 OK");
345       ASSERT(r->header.size() == 1);
346       ASSERT(r->header.begin()->first == "name");
347       ASSERT(r->header.begin()->second == "value");
348       ASSERT(r->content.string().empty());
349     }
350     {
351       auto r = client.request("GET", "/non-standard-line-endings2");
352       ASSERT(r->http_version == "1.1");
353       ASSERT(r->status_code == "200 OK");
354       ASSERT(r->header.size() == 1);
355       ASSERT(r->header.begin()->first == "name");
356       ASSERT(r->header.begin()->second == "value");
357       ASSERT(r->content.string().empty());
358     }
359   }
360   {
361     HttpClient client("localhost:8080");
362 
363     HttpClient::Connection *connection;
364     {
365       // test performing the stream version of the request methods first
366       stringstream output;
367       stringstream content("A string");
368       auto r = client.request("POST", "/string", content);
369       output << r->content.rdbuf();
370       ASSERT(output.str() == "A string");
371       ASSERT(client.connections.size() == 1);
372       connection = client.connections.begin()->get();
373     }
374 
375     {
376       stringstream output;
377       auto r = client.request("POST", "/string", "A string");
378       output << r->content.rdbuf();
379       ASSERT(output.str() == "A string");
380       ASSERT(client.connections.size() == 1);
381       ASSERT(connection == client.connections.begin()->get());
382     }
383 
384     {
385       stringstream output;
386       auto r = client.request("GET", "/header", "", {{"test1", "test"}, {"test2", "ing"}});
387       output << r->content.rdbuf();
388       ASSERT(output.str() == "testing");
389       ASSERT(client.connections.size() == 1);
390       ASSERT(connection == client.connections.begin()->get());
391     }
392 
393     {
394       stringstream output;
395       auto r = client.request("GET", "/query_string?testing");
396       ASSERT(r->content.string() == "testing");
397       ASSERT(client.connections.size() == 1);
398       ASSERT(connection == client.connections.begin()->get());
399     }
400   }
401 
402   // Test large responses
403   {
404     {
405       HttpClient client("localhost:8080");
406       client.config.max_response_streambuf_size = 400;
407       bool thrown = false;
408       try {
409         auto r = client.request("GET", "/long-response");
410       }
411       catch(...) {
412         thrown = true;
413       }
414       ASSERT(thrown);
415     }
416     HttpClient client("localhost:8080");
417     client.config.max_response_streambuf_size = 400;
418     {
419       size_t calls = 0;
420       bool end = false;
421       std::string content;
422       client.request("GET", "/long-response", [&calls, &content, &end](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
423         ASSERT(!ec);
424         content += response->content.string();
425         calls++;
426         if(calls == 1)
427           ASSERT(response->content.end == false);
428         end = response->content.end;
429       });
430       client.io_service->run();
431       ASSERT(content == long_response);
432       ASSERT(calls > 2);
433       ASSERT(end == true);
434     }
435     {
436       size_t calls = 0;
437       std::string content;
438       client.request("GET", "/long-response", [&calls, &content](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
439         if(calls == 0)
440           ASSERT(!ec);
441         content += response->content.string();
442         calls++;
443         response->close();
444       });
445       SimpleWeb::restart(*client.io_service);
446       client.io_service->run();
447       ASSERT(!content.empty());
448       ASSERT(calls >= 2);
449     }
450   }
451 
452   // Test client timeout
453   {
454     HttpClient client("localhost:8080");
455     client.config.timeout = 2;
456     bool thrown = false;
457     try {
458       auto r = client.request("GET", "/work");
459     }
460     catch(...) {
461       thrown = true;
462     }
463     ASSERT(thrown);
464   }
465   {
466     HttpClient client("localhost:8080");
467     client.config.timeout = 2;
468     bool call = false;
469     client.request("GET", "/work", [&call](shared_ptr<HttpClient::Response> /*response*/, const SimpleWeb::error_code &ec) {
470       ASSERT(ec);
471       call = true;
472     });
473     SimpleWeb::restart(*client.io_service);
474     client.io_service->run();
475     ASSERT(call);
476   }
477 
478   // Test asynchronous requests
479   {
480     HttpClient client("localhost:8080");
481     bool call = false;
482     client.request("GET", "/match/123", [&call](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
483       ASSERT(!ec);
484       stringstream output;
485       output << response->content.rdbuf();
486       ASSERT(output.str() == "123");
487       call = true;
488     });
489     client.io_service->run();
490     ASSERT(call);
491 
492     // Test event-stream
493     {
494       vector<int> calls(4, 0);
495       std::size_t call_num = 0;
496       client.request("GET", "/event-stream1", [&calls, &call_num](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
497         calls.at(call_num) = 1;
498         if(call_num == 0) {
499           ASSERT(response->content.string().empty());
500           ASSERT(!ec);
501         }
502         else if(call_num == 1) {
503           ASSERT(response->content.string() == "data: 1\n");
504           ASSERT(!ec);
505         }
506         else if(call_num == 2) {
507           ASSERT(response->content.string() == "data: 2\n");
508           ASSERT(!ec);
509         }
510         else if(call_num == 3) {
511           ASSERT(response->content.string().empty());
512           ASSERT(ec == SimpleWeb::error::eof);
513         }
514         ++call_num;
515       });
516       SimpleWeb::restart(*client.io_service);
517       client.io_service->run();
518       for(auto call : calls)
519         ASSERT(call);
520     }
521     {
522       vector<int> calls(4, 0);
523       std::size_t call_num = 0;
524       client.request("GET", "/event-stream2", [&calls, &call_num](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
525         calls.at(call_num) = 1;
526         if(call_num == 0) {
527           ASSERT(response->content.string().empty());
528           ASSERT(!ec);
529         }
530         else if(call_num == 1) {
531           ASSERT(response->content.string() == "data: 1\n");
532           ASSERT(!ec);
533         }
534         else if(call_num == 2) {
535           ASSERT(response->content.string() == "data: 2\n");
536           ASSERT(!ec);
537         }
538         else if(call_num == 3) {
539           ASSERT(response->content.string().empty());
540           ASSERT(ec == SimpleWeb::error::eof);
541         }
542         ++call_num;
543       });
544       SimpleWeb::restart(*client.io_service);
545       client.io_service->run();
546       for(auto call : calls)
547         ASSERT(call);
548     }
549 
550     // Test concurrent requests from same client
551     {
552       vector<int> calls(100, 0);
553       vector<thread> threads;
554       for(size_t c = 0; c < 100; ++c) {
555         threads.emplace_back([c, &client, &calls] {
556           client.request("GET", "/match/123", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
557             ASSERT(!ec);
558             stringstream output;
559             output << response->content.rdbuf();
560             ASSERT(output.str() == "123");
561             calls[c] = 1;
562           });
563         });
564       }
565       for(auto &thread : threads)
566         thread.join();
567       ASSERT(client.connections.size() == 100);
568       SimpleWeb::restart(*client.io_service);
569       client.io_service->run();
570       ASSERT(client.connections.size() == 1);
571       for(auto call : calls)
572         ASSERT(call);
573     }
574 
575     // Test concurrent synchronous request calls from same client
576     {
577       HttpClient client("localhost:8080");
578       {
579         vector<int> calls(5, 0);
580         vector<thread> threads;
581         for(size_t c = 0; c < 5; ++c) {
582           threads.emplace_back([c, &client, &calls] {
583             try {
584               auto r = client.request("GET", "/match/123");
585               ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
586               ASSERT(r->content.string() == "123");
587               calls[c] = 1;
588             }
589             catch(...) {
590               ASSERT(false);
591             }
592           });
593         }
594         for(auto &thread : threads)
595           thread.join();
596         ASSERT(client.connections.size() == 1);
597         for(auto call : calls)
598           ASSERT(call);
599       }
600     }
601 
602     // Test concurrent requests from different clients
603     {
604       vector<int> calls(10, 0);
605       vector<thread> threads;
606       for(size_t c = 0; c < 10; ++c) {
607         threads.emplace_back([c, &calls] {
608           HttpClient client("localhost:8080");
609           client.request("POST", "/string", "A string", [c, &calls](shared_ptr<HttpClient::Response> response, const SimpleWeb::error_code &ec) {
610             ASSERT(!ec);
611             ASSERT(response->content.string() == "A string");
612             calls[c] = 1;
613           });
614           client.io_service->run();
615         });
616       }
617       for(auto &thread : threads)
618         thread.join();
619       for(auto call : calls)
620         ASSERT(call);
621     }
622   }
623 
624   // Test multiple requests through a persistent connection
625   {
626     HttpClient client("localhost:8080");
627     ASSERT(client.connections.size() == 0);
628     for(size_t c = 0; c < 5000; ++c) {
629       auto r1 = client.request("POST", "/string", "A string");
630       ASSERT(SimpleWeb::status_code(r1->status_code) == SimpleWeb::StatusCode::success_ok);
631       ASSERT(r1->content.string() == "A string");
632       ASSERT(client.connections.size() == 1);
633 
634       stringstream content("A string");
635       auto r2 = client.request("POST", "/string", content);
636       ASSERT(SimpleWeb::status_code(r2->status_code) == SimpleWeb::StatusCode::success_ok);
637       ASSERT(r2->content.string() == "A string");
638       ASSERT(client.connections.size() == 1);
639     }
640   }
641 
642   // Test multiple requests through new several client objects
643   for(size_t c = 0; c < 100; ++c) {
644     {
645       HttpClient client("localhost:8080");
646       auto r = client.request("POST", "/string", "A string");
647       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
648       ASSERT(r->content.string() == "A string");
649       ASSERT(client.connections.size() == 1);
650     }
651 
652     {
653       HttpClient client("localhost:8080");
654       stringstream content("A string");
655       auto r = client.request("POST", "/string", content);
656       ASSERT(SimpleWeb::status_code(r->status_code) == SimpleWeb::StatusCode::success_ok);
657       ASSERT(r->content.string() == "A string");
658       ASSERT(client.connections.size() == 1);
659     }
660   }
661 
662   // Test Client client's stop()
663   for(size_t c = 0; c < 40; ++c) {
664     auto io_service = make_shared<SimpleWeb::io_context>();
665     bool call = false;
666     HttpClient client("localhost:8080");
667     client.io_service = io_service;
668     client.request("GET", "/work", [&call](shared_ptr<HttpClient::Response> /*response*/, const SimpleWeb::error_code &ec) {
669       call = true;
670       ASSERT(ec);
671     });
672     thread thread([io_service] {
673       io_service->run();
674     });
675     this_thread::sleep_for(chrono::milliseconds(100));
676     client.stop();
677     this_thread::sleep_for(chrono::milliseconds(100));
678     thread.join();
679     ASSERT(call);
680   }
681 
682   // Test Client destructor that should cancel the client's request
683   for(size_t c = 0; c < 40; ++c) {
684     auto io_service = make_shared<SimpleWeb::io_context>();
685     {
686       HttpClient client("localhost:8080");
687       client.io_service = io_service;
688       client.request("GET", "/work", [](shared_ptr<HttpClient::Response> /*response*/, const SimpleWeb::error_code & /*ec*/) {
689         ASSERT(false);
690       });
691       thread thread([io_service] {
692         io_service->run();
693       });
694       thread.detach();
695       this_thread::sleep_for(chrono::milliseconds(100));
696     }
697     this_thread::sleep_for(chrono::milliseconds(100));
698   }
699 
700   server.stop();
701   server_thread.join();
702 
703   // Test server destructor
704   {
705     auto io_service = make_shared<SimpleWeb::io_context>();
706     bool call = false;
707     bool client_catch = false;
708     {
709       HttpServer server;
710       server.config.port = 8081;
711       server.io_service = io_service;
712       server.resource["^/test$"]["GET"] = [&call](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> /*request*/) {
713         call = true;
714         thread sleep_thread([response] {
715           this_thread::sleep_for(chrono::seconds(5));
716           response->write(SimpleWeb::StatusCode::success_ok, "test");
717           response->send([](const SimpleWeb::error_code & /*ec*/) {
718             ASSERT(false);
719           });
720         });
721         sleep_thread.detach();
722       };
723       server.start();
724       thread server_thread([io_service] {
725         io_service->run();
726       });
727       server_thread.detach();
728       this_thread::sleep_for(chrono::seconds(1));
729       thread client_thread([&client_catch] {
730         HttpClient client("localhost:8081");
731         try {
732           auto r = client.request("GET", "/test");
733           ASSERT(false);
734         }
735         catch(...) {
736           client_catch = true;
737         }
738       });
739       client_thread.detach();
740       this_thread::sleep_for(chrono::seconds(1));
741     }
742     this_thread::sleep_for(chrono::seconds(5));
743     ASSERT(call);
744     ASSERT(client_catch);
745     io_service->stop();
746   }
747 }
748