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