]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/tests/unit/httpd_test.cc
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / seastar / tests / unit / httpd_test.cc
CommitLineData
11fdf7f2
TL
1/*
2 * Copyright 2015 Cloudius Systems
3 */
4
5#include <seastar/http/httpd.hh>
6#include <seastar/http/handlers.hh>
7#include <seastar/http/matcher.hh>
8#include <seastar/http/matchrules.hh>
9#include <seastar/json/formatter.hh>
10#include <seastar/http/routes.hh>
11#include <seastar/http/exception.hh>
12#include <seastar/http/transformers.hh>
f67539c2
TL
13#include <seastar/core/do_with.hh>
14#include <seastar/core/loop.hh>
15#include <seastar/core/when_all.hh>
11fdf7f2 16#include <seastar/testing/test_case.hh>
f67539c2 17#include <seastar/testing/thread_test_case.hh>
11fdf7f2
TL
18#include "loopback_socket.hh"
19#include <boost/algorithm/string.hpp>
20#include <seastar/core/thread.hh>
21#include <seastar/util/noncopyable_function.hh>
22#include <seastar/http/json_path.hh>
23#include <sstream>
24
25using namespace seastar;
26using namespace httpd;
27
28class handl : public httpd::handler_base {
29public:
30 virtual future<std::unique_ptr<reply> > handle(const sstring& path,
31 std::unique_ptr<request> req, std::unique_ptr<reply> rep) {
32 rep->done("html");
33 return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
34 }
35};
36
37SEASTAR_TEST_CASE(test_reply)
38{
39 reply r;
40 r.set_content_type("txt");
41 BOOST_REQUIRE_EQUAL(r._headers["Content-Type"], sstring("text/plain"));
42 return make_ready_future<>();
43}
44
45SEASTAR_TEST_CASE(test_str_matcher)
46{
47
48 str_matcher m("/hello");
49 parameters param;
50 BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
51 return make_ready_future<>();
52}
53
54SEASTAR_TEST_CASE(test_param_matcher)
55{
56
57 param_matcher m("param");
58 parameters param;
59 BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
60 BOOST_REQUIRE_EQUAL(param.path("param"), "/hello");
61 BOOST_REQUIRE_EQUAL(param["param"], "hello");
62 return make_ready_future<>();
63}
64
65SEASTAR_TEST_CASE(test_match_rule)
66{
67
68 parameters param;
69 handl* h = new handl();
70 match_rule mr(h);
71 mr.add_str("/hello").add_param("param");
72 httpd::handler_base* res = mr.get("/hello/val1", param);
73 BOOST_REQUIRE_EQUAL(res, h);
74 BOOST_REQUIRE_EQUAL(param["param"], "val1");
75 res = mr.get("/hell/val1", param);
76 httpd::handler_base* nl = nullptr;
77 BOOST_REQUIRE_EQUAL(res, nl);
78 return make_ready_future<>();
79}
80
f67539c2
TL
81SEASTAR_TEST_CASE(test_match_rule_order)
82{
83 parameters param;
84 routes route;
85
86 handl* h1 = new handl();
87 route.add(operation_type::GET, url("/hello"), h1);
88
89 handl* h2 = new handl();
90 route.add(operation_type::GET, url("/hello"), h2);
91
92 auto rh = route.get_handler(GET, "/hello", param);
93 BOOST_REQUIRE_EQUAL(rh, h1);
94
95 return make_ready_future<>();
96}
97
98SEASTAR_TEST_CASE(test_put_drop_rule)
99{
100 routes rts;
101 auto h = std::make_unique<handl>();
102 parameters params;
103
104 {
105 auto reg = handler_registration(rts, *h, "/hello", operation_type::GET);
106 auto res = rts.get_handler(operation_type::GET, "/hello", params);
107 BOOST_REQUIRE_EQUAL(res, h.get());
108 }
109
110 auto res = rts.get_handler(operation_type::GET, "/hello", params);
111 httpd::handler_base* nl = nullptr;
112 BOOST_REQUIRE_EQUAL(res, nl);
113 return make_ready_future<>();
114}
115
116// Putting a duplicated exact rule would result
117// in a memory leak due to the fact that rules are implemented
118// as raw pointers. In order to prevent such leaks,
119// an exception is thrown if somebody tries to put
120// a duplicated rule without removing the old one first.
121// The interface demands that the callee allocates the handle,
122// so it should also expect the callee to free it before
123// overwriting.
124SEASTAR_TEST_CASE(test_duplicated_exact_rule)
125{
126 parameters param;
127 routes route;
128
129 handl* h1 = new handl;
130 route.put(operation_type::GET, "/hello", h1);
131
132 handl* h2 = new handl;
133 BOOST_REQUIRE_THROW(route.put(operation_type::GET, "/hello", h2), std::runtime_error);
134
135 delete route.drop(operation_type::GET, "/hello");
136 route.put(operation_type::GET, "/hello", h2);
137
138 return make_ready_future<>();
139}
140
141SEASTAR_TEST_CASE(test_add_del_cookie)
142{
143 routes rts;
144 handl* h = new handl();
145 match_rule mr(h);
146 mr.add_str("/hello");
147 parameters params;
148
149 {
150 auto reg = rule_registration(rts, mr, operation_type::GET);
151 auto res = rts.get_handler(operation_type::GET, "/hello", params);
152 BOOST_REQUIRE_EQUAL(res, h);
153 }
154
155 auto res = rts.get_handler(operation_type::GET, "/hello", params);
156 httpd::handler_base* nl = nullptr;
157 BOOST_REQUIRE_EQUAL(res, nl);
158 return make_ready_future<>();
159}
160
11fdf7f2
TL
161SEASTAR_TEST_CASE(test_formatter)
162{
163 BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true");
164 BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false");
165 BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1");
166 const char* txt = "efg";
167 BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt), "\"efg\"");
168 sstring str = "abc";
169 BOOST_REQUIRE_EQUAL(json::formatter::to_json(str), "\"abc\"");
170 float f = 1;
171 BOOST_REQUIRE_EQUAL(json::formatter::to_json(f), "1");
172 f = 1.0/0.0;
173 BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
174 f = -1.0/0.0;
175 BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
176 f = 0.0/0.0;
177 BOOST_CHECK_THROW(json::formatter::to_json(f), std::invalid_argument);
178 double d = -1;
179 BOOST_REQUIRE_EQUAL(json::formatter::to_json(d), "-1");
180 d = 1.0/0.0;
181 BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
182 d = -1.0/0.0;
183 BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
184 d = 0.0/0.0;
185 BOOST_CHECK_THROW(json::formatter::to_json(d), std::invalid_argument);
186 return make_ready_future<>();
187}
188
189SEASTAR_TEST_CASE(test_decode_url) {
190 request req;
191 req._url = "/a?q=%23%24%23";
192 sstring url = http_server::connection::set_query_param(req);
193 BOOST_REQUIRE_EQUAL(url, "/a");
194 BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#");
195 req._url = "/a?a=%23%24%23&b=%22%26%22";
196 http_server::connection::set_query_param(req);
197 BOOST_REQUIRE_EQUAL(req.get_query_param("a"), "#$#");
198 BOOST_REQUIRE_EQUAL(req.get_query_param("b"), "\"&\"");
199 return make_ready_future<>();
200}
201
202SEASTAR_TEST_CASE(test_routes) {
203 handl* h1 = new handl();
204 handl* h2 = new handl();
205 routes route;
206 route.add(operation_type::GET, url("/api").remainder("path"), h1);
207 route.add(operation_type::GET, url("/"), h2);
208 std::unique_ptr<request> req = std::make_unique<request>();
209 std::unique_ptr<reply> rep = std::make_unique<reply>();
210
211 auto f1 =
212 route.handle("/api", std::move(req), std::move(rep)).then(
213 [] (std::unique_ptr<reply> rep) {
214 BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
215 });
216 req.reset(new request);
217 rep.reset(new reply);
218
219 auto f2 =
220 route.handle("/", std::move(req), std::move(rep)).then(
221 [] (std::unique_ptr<reply> rep) {
222 BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok);
223 });
224 req.reset(new request);
225 rep.reset(new reply);
226 auto f3 =
227 route.handle("/api/abc", std::move(req), std::move(rep)).then(
228 [] (std::unique_ptr<reply> rep) {
229 });
230 req.reset(new request);
231 rep.reset(new reply);
232 auto f4 =
233 route.handle("/ap", std::move(req), std::move(rep)).then(
234 [] (std::unique_ptr<reply> rep) {
235 BOOST_REQUIRE_EQUAL((int )rep->_status,
236 (int )reply::status_type::not_found);
237 });
238 return when_all(std::move(f1), std::move(f2), std::move(f3), std::move(f4))
239 .then([] (std::tuple<future<>, future<>, future<>, future<>> fs) {
240 std::get<0>(fs).get();
241 std::get<1>(fs).get();
242 std::get<2>(fs).get();
243 std::get<3>(fs).get();
244 });
245}
246
247SEASTAR_TEST_CASE(test_json_path) {
248 shared_ptr<bool> res1 = make_shared<bool>(false);
249 shared_ptr<bool> res2 = make_shared<bool>(false);
250 shared_ptr<bool> res3 = make_shared<bool>(false);
251 shared_ptr<routes> route = make_shared<routes>();
252 path_description path1("/my/path",GET,"path1",
253 {{"param1", path_description::url_component_type::PARAM}
254 ,{"/text", path_description::url_component_type::FIXED_STRING}},{});
255 path_description path2("/my/path",GET,"path2",
256 {{"param1", path_description::url_component_type::PARAM}
257 ,{"param2", path_description::url_component_type::PARAM}},{});
258 path_description path3("/my/path",GET,"path3",
259 {{"param1", path_description::url_component_type::PARAM}
260 ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH}},{});
261
262 path1.set(*route, [res1] (const_req req) {
263 (*res1) = true;
264 BOOST_REQUIRE_EQUAL(req.param["param1"], "value1");
265 return "";
266 });
267
268 path2.set(*route, [res2] (const_req req) {
269 (*res2) = true;
270 BOOST_REQUIRE_EQUAL(req.param["param1"], "value2");
271 BOOST_REQUIRE_EQUAL(req.param["param2"], "text1");
272 return "";
273 });
274
275 path3.set(*route, [res3] (const_req req) {
276 (*res3) = true;
277 BOOST_REQUIRE_EQUAL(req.param["param1"], "value3");
278 BOOST_REQUIRE_EQUAL(req.param["param2"], "text2/text3");
279 return "";
280 });
281
282 auto f1 = route->handle("/my/path/value1/text", std::make_unique<request>(), std::make_unique<reply>()).then([res1, route] (auto f) {
283 BOOST_REQUIRE_EQUAL(*res1, true);
284 });
285
286 auto f2 = route->handle("/my/path/value2/text1", std::make_unique<request>(), std::make_unique<reply>()).then([res2, route] (auto f) {
287 BOOST_REQUIRE_EQUAL(*res2, true);
288 });
289
290 auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique<request>(), std::make_unique<reply>()).then([res3, route] (auto f) {
291 BOOST_REQUIRE_EQUAL(*res3, true);
292 });
293
294 return when_all(std::move(f1), std::move(f2), std::move(f3))
295 .then([] (std::tuple<future<>, future<>, future<>> fs) {
296 std::get<0>(fs).get();
297 std::get<1>(fs).get();
298 std::get<2>(fs).get();
299 });
300}
301
302/*!
303 * \brief a helper data sink that stores everything it gets in a stringstream
304 */
305class memory_data_sink_impl : public data_sink_impl {
306 std::stringstream& _ss;
307public:
308 memory_data_sink_impl(std::stringstream& ss) : _ss(ss) {
309 }
310 virtual future<> put(net::packet data) override {
311 abort();
312 return make_ready_future<>();
313 }
314 virtual future<> put(temporary_buffer<char> buf) override {
315 _ss.write(buf.get(), buf.size());
316 return make_ready_future<>();
317 }
318 virtual future<> flush() override {
319 return make_ready_future<>();
320 }
321
322 virtual future<> close() override {
323 return make_ready_future<>();
324 }
325};
326
327class memory_data_sink : public data_sink {
328public:
329 memory_data_sink(std::stringstream& ss)
330 : data_sink(std::make_unique<memory_data_sink_impl>(ss)) {}
331};
332
333future<> test_transformer_stream(std::stringstream& ss, content_replace& cr, std::vector<sstring>&& buffer_parts) {
334 std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
335 ss.str("");
336 req->_headers["Host"] = "localhost";
337 return do_with(output_stream<char>(cr.transform(std::move(req), "json", output_stream<char>(memory_data_sink(ss), 32000, true))),
9f95a23c 338 std::vector<sstring>(std::move(buffer_parts)), [] (output_stream<char>& os, std::vector<sstring>& parts) {
11fdf7f2
TL
339 return do_for_each(parts, [&os](auto& p) {
340 return os.write(p);
9f95a23c 341 }).then([&os] {
11fdf7f2
TL
342 return os.close();
343 });
344 });
345}
346
347SEASTAR_TEST_CASE(test_transformer) {
348 return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream& ss, content_replace& cr) {
349 return do_with(output_stream<char>(cr.transform(std::make_unique<seastar::httpd::request>(), "html", output_stream<char>(memory_data_sink(ss), 32000, true))),
9f95a23c 350 [] (output_stream<char>& os) {
11fdf7f2
TL
351 return os.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os] {
352 return os.close();
353 });
354 }).then([&ss, &cr] () {
355 BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Protocol}}-xyz-{{Host}}");
356 return test_transformer_stream(ss, cr, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
357 BOOST_REQUIRE_EQUAL(ss.str(), "hello-http-xyz-localhost{{Pr");
358 return test_transformer_stream(ss, cr, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
359 BOOST_REQUIRE_EQUAL(ss.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr");
9f95a23c 360 return test_transformer_stream(ss, cr, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss] {
11fdf7f2
TL
361 BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Prothttpocol}}localhost");
362 });
363 });
364 });
365 });
366 });
367}
368
369struct http_consumer {
370 std::map<sstring, std::string> _headers;
371 std::string _body;
372 uint32_t _remain = 0;
373 std::string _current;
374 char last = '\0';
375 uint32_t _size = 0;
376 bool _concat = true;
377
378 enum class status_type {
379 READING_HEADERS,
380 CHUNK_SIZE,
381 CHUNK_BODY,
382 CHUNK_END,
383 READING_BODY_BY_SIZE,
384 DONE
385 };
386 status_type status = status_type::READING_HEADERS;
387
388 bool read(const temporary_buffer<char>& b) {
389 for (auto c : b) {
390 if (last =='\r' && c == '\n') {
391 if (_current == "") {
392 if (status == status_type::READING_HEADERS || (status == status_type::CHUNK_BODY && _remain == 0)) {
393 if (status == status_type::READING_HEADERS && _headers.find("Content-Length") != _headers.end()) {
394 _remain = stoi(_headers["Content-Length"], nullptr, 16);
395 if (_remain == 0) {
396 status = status_type::DONE;
397 break;
398 }
399 status = status_type::READING_BODY_BY_SIZE;
400 } else {
401 status = status_type::CHUNK_SIZE;
402 }
403 } else if (status == status_type::CHUNK_END) {
404 status = status_type::DONE;
405 break;
406 }
407 } else {
408 switch (status) {
409 case status_type::READING_HEADERS: add_header(_current);
410 break;
411 case status_type::CHUNK_SIZE: set_chunk(_current);
412 break;
413 default:
414 break;
415 }
416 _current = "";
417 }
418 last = '\0';
419 } else {
420 if (last != '\0') {
421 if (status == status_type::CHUNK_BODY || status == status_type::READING_BODY_BY_SIZE) {
422 if (_concat) {
423 _body = _body + last;
424 }
425 _size++;
426 _remain--;
427 if (_remain <= 1 && status == status_type::READING_BODY_BY_SIZE) {
428 if (_concat) {
429 _body = _body + c;
430 }
431 _size++;
432 status = status_type::DONE;
433 break;
434 }
435 } else {
436 _current = _current + last;
437 }
438
439 }
440 last = c;
441 }
442 }
443 return status == status_type::DONE;
444 }
445
446 void set_chunk(const std::string& s) {
447 _remain = stoi(s, nullptr, 16);
448 if (_remain == 0) {
449 status = status_type::CHUNK_END;
450 } else {
451 status = status_type::CHUNK_BODY;
452 }
453 }
454
455 void add_header(const std::string& s) {
456 std::vector<std::string> strs;
457 boost::split(strs, s, boost::is_any_of(":"));
458 if (strs.size() > 1) {
459 _headers[strs[0]] = strs[1];
460 }
461 }
462};
463
464class test_client_server {
465public:
466 static future<> write_request(output_stream<char>& output) {
467 return output.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output]{
468 return output.flush();
469 });
470 }
471
472 static future<> run_test(std::function<future<>(output_stream<char> &&)>&& write_func, std::function<bool(size_t, http_consumer&)> reader) {
473 return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
474 [reader, &write_func] (loopback_connection_factory& lcf, auto& server) {
475 return do_with(loopback_socket_impl(lcf), [&server, &lcf, reader, &write_func](loopback_socket_impl& lsi) {
476 httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
477
478 auto client = seastar::async([&lsi, reader] {
f67539c2 479 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
9f95a23c
TL
480 input_stream<char> input(c_socket.input());
481 output_stream<char> output(c_socket.output());
11fdf7f2
TL
482 bool more = true;
483 size_t count = 0;
484 while (more) {
485 http_consumer htp;
486 htp._concat = false;
487
488 write_request(output).get();
9f95a23c
TL
489 repeat([&input, &htp] {
490 return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
11fdf7f2
TL
491 return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
492 make_ready_future<stop_iteration>(stop_iteration::no);
493 });
494 }).get();
495 std::cout << htp._body << std::endl;
496 more = reader(count, htp);
497 count++;
498 }
499 if (input.eof()) {
500 input.close().get();
501 }
502 });
503
f67539c2 504 auto server_setup = seastar::async([&server, &write_func] {
11fdf7f2
TL
505 class test_handler : public handler_base {
506 size_t count = 0;
507 http_server& _server;
508 std::function<future<>(output_stream<char> &&)> _write_func;
509 promise<> _all_message_sent;
510 public:
511 test_handler(http_server& server, std::function<future<>(output_stream<char> &&)>&& write_func) : _server(server), _write_func(write_func) {
512 }
513 future<std::unique_ptr<reply>> handle(const sstring& path,
514 std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
515 rep->write_body("json", std::move(_write_func));
516 count++;
517 _all_message_sent.set_value();
518 return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
519 }
520 future<> wait_for_message() {
521 return _all_message_sent.get_future();
522 }
523 };
524 auto handler = new test_handler(*server, std::move(write_func));
525 server->_routes.put(GET, "/test", handler);
526 when_all(server->do_accepts(0), handler->wait_for_message()).get();
527 });
f67539c2 528 return when_all(std::move(client), std::move(server_setup));
11fdf7f2
TL
529 }).discard_result().then_wrapped([&server] (auto f) {
530 f.ignore_ready_future();
531 return server->stop();
532 });
533 });
534 }
535 static future<> run(std::vector<std::tuple<bool, size_t>> tests) {
536 return do_with(loopback_connection_factory(), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
537 [tests] (loopback_connection_factory& lcf, auto& server) {
538 return do_with(loopback_socket_impl(lcf), [&server, &lcf, tests](loopback_socket_impl& lsi) {
539 httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
540
541 auto client = seastar::async([&lsi, tests] {
f67539c2 542 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
9f95a23c
TL
543 input_stream<char> input(c_socket.input());
544 output_stream<char> output(c_socket.output());
11fdf7f2
TL
545 bool more = true;
546 size_t count = 0;
547 while (more) {
548 http_consumer htp;
549 write_request(output).get();
9f95a23c
TL
550 repeat([&input, &htp] {
551 return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
11fdf7f2
TL
552 return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
553 make_ready_future<stop_iteration>(stop_iteration::no);
554 });
555 }).get();
556 if (std::get<bool>(tests[count])) {
557 BOOST_REQUIRE_EQUAL(htp._body.length(), std::get<size_t>(tests[count]));
558 } else {
559 BOOST_REQUIRE_EQUAL(input.eof(), true);
560 more = false;
561 }
562 count++;
563 if (count == tests.size()) {
564 more = false;
565 }
566 }
567 if (input.eof()) {
568 input.close().get();
569 }
570 });
571
f67539c2 572 auto server_setup = seastar::async([&server, tests] {
11fdf7f2
TL
573 class test_handler : public handler_base {
574 size_t count = 0;
575 http_server& _server;
576 std::vector<std::tuple<bool, size_t>> _tests;
577 promise<> _all_message_sent;
578 public:
579 test_handler(http_server& server, const std::vector<std::tuple<bool, size_t>>& tests) : _server(server), _tests(tests) {
580 }
581 future<std::unique_ptr<reply>> handle(const sstring& path,
582 std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
583 rep->write_body("txt", make_writer(std::get<size_t>(_tests[count]), std::get<bool>(_tests[count])));
584 count++;
585 if (count == _tests.size()) {
586 _all_message_sent.set_value();
587 }
588 return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
589 }
590 future<> wait_for_message() {
591 return _all_message_sent.get_future();
592 }
593 };
594 auto handler = new test_handler(*server, tests);
595 server->_routes.put(GET, "/test", handler);
596 when_all(server->do_accepts(0), handler->wait_for_message()).get();
597 });
f67539c2 598 return when_all(std::move(client), std::move(server_setup));
11fdf7f2
TL
599 }).discard_result().then_wrapped([&server] (auto f) {
600 f.ignore_ready_future();
601 return server->stop();
602 });
603 });
604 }
605
606 static noncopyable_function<future<>(output_stream<char>&& o_stream)> make_writer(size_t len, bool success) {
607 return [len, success] (output_stream<char>&& o_stream) mutable {
608 return do_with(output_stream<char>(std::move(o_stream)), uint32_t(len/10), [success](output_stream<char>& str, uint32_t& remain) {
609 if (remain == 0) {
610 if (success) {
611 return str.close();
612 } else {
613 throw std::runtime_error("Throwing exception before writing");
614 }
615 }
9f95a23c 616 return repeat([&str, &remain] () mutable {
11fdf7f2
TL
617 return str.write("1234567890").then([&remain]() mutable {
618 remain--;
619 return (remain == 0)? make_ready_future<stop_iteration>(stop_iteration::yes) : make_ready_future<stop_iteration>(stop_iteration::no);
620 });
621 }).then([&str, success] {
622 if (!success) {
623 return str.flush();
624 }
625 return make_ready_future<>();
626 }).then([&str, success] {
627 if (success) {
628 return str.close();
629 } else {
630 throw std::runtime_error("Throwing exception after writing");
631 }
632 });
633 });
634 };
635 }
636};
637
638SEASTAR_TEST_CASE(test_message_with_error_non_empty_body) {
639 std::vector<std::tuple<bool, size_t>> tests = {
640 std::make_tuple(true, 100),
641 std::make_tuple(false, 10000)};
642 return test_client_server::run(tests);
643}
644
645SEASTAR_TEST_CASE(test_simple_chunked) {
646 std::vector<std::tuple<bool, size_t>> tests = {
647 std::make_tuple(true, 100000),
648 std::make_tuple(true, 100)};
649 return test_client_server::run(tests);
650}
651
652SEASTAR_TEST_CASE(test_http_client_server_full) {
653 std::vector<std::tuple<bool, size_t>> tests = {
654 std::make_tuple(true, 100),
655 std::make_tuple(true, 10000),
656 std::make_tuple(true, 100),
657 std::make_tuple(true, 0),
658 std::make_tuple(true, 5000),
659 std::make_tuple(true, 10000),
660 std::make_tuple(true, 9000),
661 std::make_tuple(true, 10000)};
662 return test_client_server::run(tests);
663}
664
665/*
666 * return string in the given size
667 * The string size takes the quotes into consideration.
668 */
669std::string get_value(int size) {
670 std::stringstream res;
671 for (auto i = 0; i < size - 2; i++) {
672 res << "a";
673 }
674 return res.str();
675}
676
677/*
678 * A helper object that map to a big json string
679 * in the format of:
680 * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...}
681 *
682 * The object can have an arbitrary size in multiplication of 10000 bytes
683 * */
684struct extra_big_object : public json::json_base {
685 json::json_element<sstring>* value;
686 extra_big_object(size_t size) {
687 value = new json::json_element<sstring>;
688 // size = brackets + (name + ": " + get_value) * n + ", " * (n-1)
689 // size = 2 + (name + 6 + get_value) * n - 2
690 value->_name = "valu";
691 *value = get_value(9990);
692 for (size_t i = 0; i < size/10000; i++) {
693 _elements.emplace_back(value);
694 }
695 }
696
697 virtual ~extra_big_object() {
698 delete value;
699 }
700
701 extra_big_object(const extra_big_object& o) {
702 value = new json::json_element<sstring>;
703 value->_name = o.value->_name;
704 *value = (*o.value)();
705 for (size_t i = 0; i < o._elements.size(); i++) {
706 _elements.emplace_back(value);
707 }
708 }
11fdf7f2
TL
709};
710
711SEASTAR_TEST_CASE(json_stream) {
712 std::vector<extra_big_object> vec;
713 size_t num_objects = 1000;
714 size_t total_size = num_objects * 1000001 + 1;
715 for (size_t i = 0; i < num_objects; i++) {
9f95a23c 716 vec.emplace_back(1000000);
11fdf7f2
TL
717 }
718 return test_client_server::run_test(json::stream_object(vec), [total_size](size_t s, http_consumer& h) {
719 BOOST_REQUIRE_EQUAL(h._size, total_size);
720 return false;
721 });
722}
9f95a23c 723
f67539c2
TL
724class json_test_handler : public handler_base {
725 std::function<future<>(output_stream<char> &&)> _write_func;
726public:
727 json_test_handler(std::function<future<>(output_stream<char> &&)>&& write_func) : _write_func(write_func) {
728 }
729 future<std::unique_ptr<reply>> handle(const sstring& path,
730 std::unique_ptr<request> req, std::unique_ptr<reply> rep) override {
731 rep->write_body("json", _write_func);
732 return make_ready_future<std::unique_ptr<reply>>(std::move(rep));
733 }
734};
735
9f95a23c
TL
736SEASTAR_TEST_CASE(content_length_limit) {
737 return seastar::async([] {
738 loopback_connection_factory lcf;
739 http_server server("test");
740 server.set_content_length_limit(11);
741 loopback_socket_impl lsi(lcf);
742 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
743
744 future<> client = seastar::async([&lsi] {
f67539c2 745 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
9f95a23c
TL
746 input_stream<char> input(c_socket.input());
747 output_stream<char> output(c_socket.output());
748
749 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
750 output.flush().get();
751 auto resp = input.read().get0();
752 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
753
754 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 11\r\n\r\nxxxxxxxxxxx")).get();
755 output.flush().get();
756 resp = input.read().get0();
757 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
758
759 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\n\r\nxxxxxxxxxxxxxxxx")).get();
760 output.flush().get();
761 resp = input.read().get0();
762 BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
763 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
764
765 input.close().get();
766 output.close().get();
767 });
768
f67539c2
TL
769 auto handler = new json_test_handler(json::stream_object("hello"));
770 server._routes.put(GET, "/test", handler);
771 server.do_accepts(0).get();
772
773 client.get();
774 server.stop().get();
775 });
776}
777
778SEASTAR_TEST_CASE(test_100_continue) {
779 return seastar::async([] {
780 loopback_connection_factory lcf;
781 http_server server("test");
782 server.set_content_length_limit(11);
783 loopback_socket_impl lsi(lcf);
784 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
785 future<> client = seastar::async([&lsi] {
786 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
787 input_stream<char> input(c_socket.input());
788 output_stream<char> output(c_socket.output());
789
790 for (auto version : {sstring("1.0"), sstring("1.1")}) {
791 for (auto content : {sstring(""), sstring("xxxxxxxxxxx")}) {
792 for (auto expect : {sstring(""), sstring("Expect: 100-continue\r\n"), sstring("Expect: 100-cOnTInUE\r\n")}) {
793 auto content_len = content.empty() ? sstring("") : (sstring("Content-Length: ") + to_sstring(content.length()) + sstring("\r\n"));
794 sstring req = sstring("GET /test HTTP/") + version + sstring("\r\nHost: test\r\nConnection: Keep-Alive\r\n") + content_len + expect + sstring("\r\n");
795 output.write(req).get();
796 output.flush().get();
797 bool already_ok = false;
798 if (version == "1.1" && expect.length()) {
799 auto resp = input.read().get0();
800 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
801 already_ok = content.empty() && std::string(resp.get(), resp.size()).find("200 OK") != std::string::npos;
802 }
803 if (!already_ok) {
804 //If the body is empty, the final response might have already been read
805 output.write(content).get();
806 output.flush().get();
807 auto resp = input.read().get0();
808 BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
809 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
810 }
811 }
9f95a23c 812 }
f67539c2
TL
813 }
814 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
815 output.flush().get();
816 auto resp = input.read().get0();
817 BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
818 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
819
820 input.close().get();
821 output.close().get();
9f95a23c
TL
822 });
823
f67539c2
TL
824 auto handler = new json_test_handler(json::stream_object("hello"));
825 server._routes.put(GET, "/test", handler);
826 server.do_accepts(0).get();
827
9f95a23c 828 client.get();
9f95a23c
TL
829 server.stop().get();
830 });
831}
f67539c2
TL
832
833
834SEASTAR_TEST_CASE(test_unparsable_request) {
835 // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response
836 return seastar::async([] {
837 loopback_connection_factory lcf;
838 http_server server("test");
839 loopback_socket_impl lsi(lcf);
840 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
841 future<> client = seastar::async([&lsi] {
842 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
843 input_stream<char> input(c_socket.input());
844 output_stream<char> output(c_socket.output());
845
846 output.write(sstring("GET /test HTTP/1.1\r\nhello\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
847 output.flush().get();
848 auto resp = input.read().get0();
849 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("400 Bad Request"), std::string::npos);
850 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("Can't parse the request"), std::string::npos);
851
852 input.close().get();
853 output.close().get();
854 });
855
856 auto handler = new json_test_handler(json::stream_object("hello"));
857 server._routes.put(GET, "/test", handler);
858 server.do_accepts(0).get();
859
860 client.get();
861 server.stop().get();
862 });
863}
864
865SEASTAR_TEST_CASE(case_insensitive_header) {
866 std::unique_ptr<seastar::httpd::request> req = std::make_unique<seastar::httpd::request>();
867 req->_headers["conTEnt-LengtH"] = "17";
868 BOOST_REQUIRE_EQUAL(req->get_header("content-length"), "17");
869 BOOST_REQUIRE_EQUAL(req->get_header("Content-Length"), "17");
870 BOOST_REQUIRE_EQUAL(req->get_header("cOnTeNT-lEnGTh"), "17");
871 return make_ready_future<>();
872}
873
874SEASTAR_THREAD_TEST_CASE(multiple_connections) {
875 loopback_connection_factory lcf;
876 http_server server("test");
877 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
878 socket_address addr{ipv4_addr()};
879
880 std::vector<connected_socket> socks;
881 // Make sure one shard has two connections pending.
882 for (unsigned i = 0; i <= smp::count; ++i) {
883 socks.push_back(loopback_socket_impl(lcf).connect(addr, addr).get0());
884 }
885
886 server.do_accepts(0).get();
887 server.stop().get();
888 lcf.destroy_all_shards().get();
889}