]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/tests/unit/httpd_test.cc
update ceph source to reef 18.1.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>
20effc67 23#include <seastar/http/response_parser.hh>
11fdf7f2 24#include <sstream>
20effc67 25#include <seastar/core/shared_future.hh>
1e59de90
TL
26#include <seastar/http/client.hh>
27#include <seastar/http/url.hh>
20effc67 28#include <seastar/util/later.hh>
1e59de90 29#include <seastar/util/short_streams.hh>
11fdf7f2
TL
30
31using namespace seastar;
32using namespace httpd;
33
34class handl : public httpd::handler_base {
35public:
1e59de90
TL
36 virtual future<std::unique_ptr<http::reply> > handle(const sstring& path,
37 std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) {
11fdf7f2 38 rep->done("html");
1e59de90 39 return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
11fdf7f2
TL
40 }
41};
42
43SEASTAR_TEST_CASE(test_reply)
44{
1e59de90 45 http::reply r;
11fdf7f2
TL
46 r.set_content_type("txt");
47 BOOST_REQUIRE_EQUAL(r._headers["Content-Type"], sstring("text/plain"));
48 return make_ready_future<>();
49}
50
51SEASTAR_TEST_CASE(test_str_matcher)
52{
53
54 str_matcher m("/hello");
55 parameters param;
56 BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
57 return make_ready_future<>();
58}
59
60SEASTAR_TEST_CASE(test_param_matcher)
61{
62
63 param_matcher m("param");
64 parameters param;
65 BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u);
66 BOOST_REQUIRE_EQUAL(param.path("param"), "/hello");
67 BOOST_REQUIRE_EQUAL(param["param"], "hello");
68 return make_ready_future<>();
69}
70
71SEASTAR_TEST_CASE(test_match_rule)
72{
73
74 parameters param;
75 handl* h = new handl();
76 match_rule mr(h);
77 mr.add_str("/hello").add_param("param");
78 httpd::handler_base* res = mr.get("/hello/val1", param);
79 BOOST_REQUIRE_EQUAL(res, h);
80 BOOST_REQUIRE_EQUAL(param["param"], "val1");
81 res = mr.get("/hell/val1", param);
82 httpd::handler_base* nl = nullptr;
83 BOOST_REQUIRE_EQUAL(res, nl);
84 return make_ready_future<>();
85}
86
f67539c2
TL
87SEASTAR_TEST_CASE(test_match_rule_order)
88{
89 parameters param;
90 routes route;
91
92 handl* h1 = new handl();
93 route.add(operation_type::GET, url("/hello"), h1);
94
95 handl* h2 = new handl();
96 route.add(operation_type::GET, url("/hello"), h2);
97
98 auto rh = route.get_handler(GET, "/hello", param);
99 BOOST_REQUIRE_EQUAL(rh, h1);
100
101 return make_ready_future<>();
102}
103
104SEASTAR_TEST_CASE(test_put_drop_rule)
105{
106 routes rts;
107 auto h = std::make_unique<handl>();
108 parameters params;
109
110 {
111 auto reg = handler_registration(rts, *h, "/hello", operation_type::GET);
112 auto res = rts.get_handler(operation_type::GET, "/hello", params);
113 BOOST_REQUIRE_EQUAL(res, h.get());
114 }
115
116 auto res = rts.get_handler(operation_type::GET, "/hello", params);
117 httpd::handler_base* nl = nullptr;
118 BOOST_REQUIRE_EQUAL(res, nl);
119 return make_ready_future<>();
120}
121
122// Putting a duplicated exact rule would result
123// in a memory leak due to the fact that rules are implemented
124// as raw pointers. In order to prevent such leaks,
125// an exception is thrown if somebody tries to put
126// a duplicated rule without removing the old one first.
127// The interface demands that the callee allocates the handle,
128// so it should also expect the callee to free it before
129// overwriting.
130SEASTAR_TEST_CASE(test_duplicated_exact_rule)
131{
132 parameters param;
133 routes route;
134
135 handl* h1 = new handl;
136 route.put(operation_type::GET, "/hello", h1);
137
138 handl* h2 = new handl;
139 BOOST_REQUIRE_THROW(route.put(operation_type::GET, "/hello", h2), std::runtime_error);
140
141 delete route.drop(operation_type::GET, "/hello");
142 route.put(operation_type::GET, "/hello", h2);
143
144 return make_ready_future<>();
145}
146
147SEASTAR_TEST_CASE(test_add_del_cookie)
148{
149 routes rts;
150 handl* h = new handl();
151 match_rule mr(h);
152 mr.add_str("/hello");
153 parameters params;
154
155 {
156 auto reg = rule_registration(rts, mr, operation_type::GET);
157 auto res = rts.get_handler(operation_type::GET, "/hello", params);
158 BOOST_REQUIRE_EQUAL(res, h);
159 }
160
161 auto res = rts.get_handler(operation_type::GET, "/hello", params);
162 httpd::handler_base* nl = nullptr;
163 BOOST_REQUIRE_EQUAL(res, nl);
164 return make_ready_future<>();
165}
166
11fdf7f2
TL
167SEASTAR_TEST_CASE(test_formatter)
168{
169 BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true");
170 BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false");
171 BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1");
172 const char* txt = "efg";
173 BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt), "\"efg\"");
174 sstring str = "abc";
175 BOOST_REQUIRE_EQUAL(json::formatter::to_json(str), "\"abc\"");
176 float f = 1;
177 BOOST_REQUIRE_EQUAL(json::formatter::to_json(f), "1");
178 f = 1.0/0.0;
179 BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
180 f = -1.0/0.0;
181 BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range);
182 f = 0.0/0.0;
183 BOOST_CHECK_THROW(json::formatter::to_json(f), std::invalid_argument);
184 double d = -1;
185 BOOST_REQUIRE_EQUAL(json::formatter::to_json(d), "-1");
186 d = 1.0/0.0;
187 BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
188 d = -1.0/0.0;
189 BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range);
190 d = 0.0/0.0;
191 BOOST_CHECK_THROW(json::formatter::to_json(d), std::invalid_argument);
192 return make_ready_future<>();
193}
194
195SEASTAR_TEST_CASE(test_decode_url) {
1e59de90 196 http::request req;
11fdf7f2 197 req._url = "/a?q=%23%24%23";
1e59de90 198 sstring url = req.parse_query_param();
11fdf7f2
TL
199 BOOST_REQUIRE_EQUAL(url, "/a");
200 BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#");
201 req._url = "/a?a=%23%24%23&b=%22%26%22";
1e59de90 202 req.parse_query_param();
11fdf7f2
TL
203 BOOST_REQUIRE_EQUAL(req.get_query_param("a"), "#$#");
204 BOOST_REQUIRE_EQUAL(req.get_query_param("b"), "\"&\"");
205 return make_ready_future<>();
206}
207
208SEASTAR_TEST_CASE(test_routes) {
209 handl* h1 = new handl();
210 handl* h2 = new handl();
211 routes route;
212 route.add(operation_type::GET, url("/api").remainder("path"), h1);
213 route.add(operation_type::GET, url("/"), h2);
1e59de90
TL
214 std::unique_ptr<http::request> req = std::make_unique<http::request>();
215 std::unique_ptr<http::reply> rep = std::make_unique<http::reply>();
11fdf7f2
TL
216
217 auto f1 =
218 route.handle("/api", std::move(req), std::move(rep)).then(
1e59de90
TL
219 [] (std::unique_ptr<http::reply> rep) {
220 BOOST_REQUIRE_EQUAL((int )rep->_status, (int )http::reply::status_type::ok);
11fdf7f2 221 });
1e59de90
TL
222 req.reset(new http::request);
223 rep.reset(new http::reply);
11fdf7f2
TL
224
225 auto f2 =
226 route.handle("/", std::move(req), std::move(rep)).then(
1e59de90
TL
227 [] (std::unique_ptr<http::reply> rep) {
228 BOOST_REQUIRE_EQUAL((int )rep->_status, (int )http::reply::status_type::ok);
11fdf7f2 229 });
1e59de90
TL
230 req.reset(new http::request);
231 rep.reset(new http::reply);
11fdf7f2
TL
232 auto f3 =
233 route.handle("/api/abc", std::move(req), std::move(rep)).then(
1e59de90 234 [] (std::unique_ptr<http::reply> rep) {
11fdf7f2 235 });
1e59de90
TL
236 req.reset(new http::request);
237 rep.reset(new http::reply);
11fdf7f2
TL
238 auto f4 =
239 route.handle("/ap", std::move(req), std::move(rep)).then(
1e59de90 240 [] (std::unique_ptr<http::reply> rep) {
11fdf7f2 241 BOOST_REQUIRE_EQUAL((int )rep->_status,
1e59de90 242 (int )http::reply::status_type::not_found);
11fdf7f2
TL
243 });
244 return when_all(std::move(f1), std::move(f2), std::move(f3), std::move(f4))
245 .then([] (std::tuple<future<>, future<>, future<>, future<>> fs) {
246 std::get<0>(fs).get();
247 std::get<1>(fs).get();
248 std::get<2>(fs).get();
249 std::get<3>(fs).get();
250 });
251}
252
253SEASTAR_TEST_CASE(test_json_path) {
254 shared_ptr<bool> res1 = make_shared<bool>(false);
255 shared_ptr<bool> res2 = make_shared<bool>(false);
256 shared_ptr<bool> res3 = make_shared<bool>(false);
257 shared_ptr<routes> route = make_shared<routes>();
258 path_description path1("/my/path",GET,"path1",
259 {{"param1", path_description::url_component_type::PARAM}
260 ,{"/text", path_description::url_component_type::FIXED_STRING}},{});
261 path_description path2("/my/path",GET,"path2",
262 {{"param1", path_description::url_component_type::PARAM}
263 ,{"param2", path_description::url_component_type::PARAM}},{});
264 path_description path3("/my/path",GET,"path3",
265 {{"param1", path_description::url_component_type::PARAM}
266 ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH}},{});
267
268 path1.set(*route, [res1] (const_req req) {
269 (*res1) = true;
270 BOOST_REQUIRE_EQUAL(req.param["param1"], "value1");
271 return "";
272 });
273
274 path2.set(*route, [res2] (const_req req) {
275 (*res2) = true;
276 BOOST_REQUIRE_EQUAL(req.param["param1"], "value2");
277 BOOST_REQUIRE_EQUAL(req.param["param2"], "text1");
278 return "";
279 });
280
281 path3.set(*route, [res3] (const_req req) {
282 (*res3) = true;
283 BOOST_REQUIRE_EQUAL(req.param["param1"], "value3");
284 BOOST_REQUIRE_EQUAL(req.param["param2"], "text2/text3");
285 return "";
286 });
287
1e59de90 288 auto f1 = route->handle("/my/path/value1/text", std::make_unique<http::request>(), std::make_unique<http::reply>()).then([res1, route] (auto f) {
11fdf7f2
TL
289 BOOST_REQUIRE_EQUAL(*res1, true);
290 });
291
1e59de90 292 auto f2 = route->handle("/my/path/value2/text1", std::make_unique<http::request>(), std::make_unique<http::reply>()).then([res2, route] (auto f) {
11fdf7f2
TL
293 BOOST_REQUIRE_EQUAL(*res2, true);
294 });
295
1e59de90 296 auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique<http::request>(), std::make_unique<http::reply>()).then([res3, route] (auto f) {
11fdf7f2
TL
297 BOOST_REQUIRE_EQUAL(*res3, true);
298 });
299
300 return when_all(std::move(f1), std::move(f2), std::move(f3))
301 .then([] (std::tuple<future<>, future<>, future<>> fs) {
302 std::get<0>(fs).get();
303 std::get<1>(fs).get();
304 std::get<2>(fs).get();
305 });
306}
307
308/*!
309 * \brief a helper data sink that stores everything it gets in a stringstream
310 */
311class memory_data_sink_impl : public data_sink_impl {
312 std::stringstream& _ss;
313public:
314 memory_data_sink_impl(std::stringstream& ss) : _ss(ss) {
315 }
316 virtual future<> put(net::packet data) override {
317 abort();
318 return make_ready_future<>();
319 }
320 virtual future<> put(temporary_buffer<char> buf) override {
321 _ss.write(buf.get(), buf.size());
322 return make_ready_future<>();
323 }
324 virtual future<> flush() override {
325 return make_ready_future<>();
326 }
327
328 virtual future<> close() override {
329 return make_ready_future<>();
330 }
331};
332
333class memory_data_sink : public data_sink {
334public:
335 memory_data_sink(std::stringstream& ss)
336 : data_sink(std::make_unique<memory_data_sink_impl>(ss)) {}
337};
338
339future<> test_transformer_stream(std::stringstream& ss, content_replace& cr, std::vector<sstring>&& buffer_parts) {
1e59de90 340 std::unique_ptr<seastar::http::request> req = std::make_unique<seastar::http::request>();
11fdf7f2
TL
341 ss.str("");
342 req->_headers["Host"] = "localhost";
20effc67
TL
343 output_stream_options opts;
344 opts.trim_to_size = true;
345 return do_with(output_stream<char>(cr.transform(std::move(req), "json", output_stream<char>(memory_data_sink(ss), 32000, opts))),
9f95a23c 346 std::vector<sstring>(std::move(buffer_parts)), [] (output_stream<char>& os, std::vector<sstring>& parts) {
11fdf7f2
TL
347 return do_for_each(parts, [&os](auto& p) {
348 return os.write(p);
9f95a23c 349 }).then([&os] {
11fdf7f2
TL
350 return os.close();
351 });
352 });
353}
354
355SEASTAR_TEST_CASE(test_transformer) {
356 return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream& ss, content_replace& cr) {
20effc67
TL
357 output_stream_options opts;
358 opts.trim_to_size = true;
1e59de90 359 return do_with(output_stream<char>(cr.transform(std::make_unique<seastar::http::request>(), "html", output_stream<char>(memory_data_sink(ss), 32000, opts))),
9f95a23c 360 [] (output_stream<char>& os) {
11fdf7f2
TL
361 return os.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os] {
362 return os.close();
363 });
364 }).then([&ss, &cr] () {
365 BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Protocol}}-xyz-{{Host}}");
366 return test_transformer_stream(ss, cr, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
367 BOOST_REQUIRE_EQUAL(ss.str(), "hello-http-xyz-localhost{{Pr");
368 return test_transformer_stream(ss, cr, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] {
369 BOOST_REQUIRE_EQUAL(ss.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr");
9f95a23c 370 return test_transformer_stream(ss, cr, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss] {
11fdf7f2
TL
371 BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Prothttpocol}}localhost");
372 });
373 });
374 });
375 });
376 });
377}
378
379struct http_consumer {
380 std::map<sstring, std::string> _headers;
381 std::string _body;
382 uint32_t _remain = 0;
383 std::string _current;
384 char last = '\0';
385 uint32_t _size = 0;
386 bool _concat = true;
387
388 enum class status_type {
389 READING_HEADERS,
390 CHUNK_SIZE,
391 CHUNK_BODY,
392 CHUNK_END,
393 READING_BODY_BY_SIZE,
394 DONE
395 };
396 status_type status = status_type::READING_HEADERS;
397
398 bool read(const temporary_buffer<char>& b) {
399 for (auto c : b) {
400 if (last =='\r' && c == '\n') {
401 if (_current == "") {
402 if (status == status_type::READING_HEADERS || (status == status_type::CHUNK_BODY && _remain == 0)) {
403 if (status == status_type::READING_HEADERS && _headers.find("Content-Length") != _headers.end()) {
404 _remain = stoi(_headers["Content-Length"], nullptr, 16);
405 if (_remain == 0) {
406 status = status_type::DONE;
407 break;
408 }
409 status = status_type::READING_BODY_BY_SIZE;
410 } else {
411 status = status_type::CHUNK_SIZE;
412 }
413 } else if (status == status_type::CHUNK_END) {
414 status = status_type::DONE;
415 break;
416 }
417 } else {
418 switch (status) {
419 case status_type::READING_HEADERS: add_header(_current);
420 break;
421 case status_type::CHUNK_SIZE: set_chunk(_current);
422 break;
423 default:
424 break;
425 }
426 _current = "";
427 }
428 last = '\0';
429 } else {
430 if (last != '\0') {
431 if (status == status_type::CHUNK_BODY || status == status_type::READING_BODY_BY_SIZE) {
432 if (_concat) {
433 _body = _body + last;
434 }
435 _size++;
436 _remain--;
437 if (_remain <= 1 && status == status_type::READING_BODY_BY_SIZE) {
438 if (_concat) {
439 _body = _body + c;
440 }
441 _size++;
442 status = status_type::DONE;
443 break;
444 }
445 } else {
446 _current = _current + last;
447 }
448
449 }
450 last = c;
451 }
452 }
453 return status == status_type::DONE;
454 }
455
456 void set_chunk(const std::string& s) {
457 _remain = stoi(s, nullptr, 16);
458 if (_remain == 0) {
459 status = status_type::CHUNK_END;
460 } else {
461 status = status_type::CHUNK_BODY;
462 }
463 }
464
465 void add_header(const std::string& s) {
466 std::vector<std::string> strs;
467 boost::split(strs, s, boost::is_any_of(":"));
468 if (strs.size() > 1) {
469 _headers[strs[0]] = strs[1];
470 }
471 }
472};
473
474class test_client_server {
475public:
476 static future<> write_request(output_stream<char>& output) {
477 return output.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output]{
478 return output.flush();
479 });
480 }
481
482 static future<> run_test(std::function<future<>(output_stream<char> &&)>&& write_func, std::function<bool(size_t, http_consumer&)> reader) {
1e59de90 483 return do_with(loopback_connection_factory(1), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
11fdf7f2
TL
484 [reader, &write_func] (loopback_connection_factory& lcf, auto& server) {
485 return do_with(loopback_socket_impl(lcf), [&server, &lcf, reader, &write_func](loopback_socket_impl& lsi) {
486 httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
487
488 auto client = seastar::async([&lsi, reader] {
f67539c2 489 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
9f95a23c
TL
490 input_stream<char> input(c_socket.input());
491 output_stream<char> output(c_socket.output());
11fdf7f2
TL
492 bool more = true;
493 size_t count = 0;
494 while (more) {
495 http_consumer htp;
496 htp._concat = false;
497
498 write_request(output).get();
9f95a23c
TL
499 repeat([&input, &htp] {
500 return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
11fdf7f2
TL
501 return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
502 make_ready_future<stop_iteration>(stop_iteration::no);
503 });
504 }).get();
505 std::cout << htp._body << std::endl;
506 more = reader(count, htp);
507 count++;
508 }
509 if (input.eof()) {
510 input.close().get();
511 }
512 });
513
f67539c2 514 auto server_setup = seastar::async([&server, &write_func] {
11fdf7f2
TL
515 class test_handler : public handler_base {
516 size_t count = 0;
517 http_server& _server;
518 std::function<future<>(output_stream<char> &&)> _write_func;
519 promise<> _all_message_sent;
520 public:
521 test_handler(http_server& server, std::function<future<>(output_stream<char> &&)>&& write_func) : _server(server), _write_func(write_func) {
522 }
1e59de90
TL
523 future<std::unique_ptr<http::reply>> handle(const sstring& path,
524 std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override {
11fdf7f2
TL
525 rep->write_body("json", std::move(_write_func));
526 count++;
527 _all_message_sent.set_value();
1e59de90 528 return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
11fdf7f2
TL
529 }
530 future<> wait_for_message() {
531 return _all_message_sent.get_future();
532 }
533 };
534 auto handler = new test_handler(*server, std::move(write_func));
535 server->_routes.put(GET, "/test", handler);
536 when_all(server->do_accepts(0), handler->wait_for_message()).get();
537 });
f67539c2 538 return when_all(std::move(client), std::move(server_setup));
11fdf7f2
TL
539 }).discard_result().then_wrapped([&server] (auto f) {
540 f.ignore_ready_future();
541 return server->stop();
542 });
543 });
544 }
545 static future<> run(std::vector<std::tuple<bool, size_t>> tests) {
1e59de90 546 return do_with(loopback_connection_factory(1), foreign_ptr<shared_ptr<http_server>>(make_shared<http_server>("test")),
11fdf7f2
TL
547 [tests] (loopback_connection_factory& lcf, auto& server) {
548 return do_with(loopback_socket_impl(lcf), [&server, &lcf, tests](loopback_socket_impl& lsi) {
549 httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket());
550
551 auto client = seastar::async([&lsi, tests] {
f67539c2 552 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
9f95a23c
TL
553 input_stream<char> input(c_socket.input());
554 output_stream<char> output(c_socket.output());
11fdf7f2
TL
555 bool more = true;
556 size_t count = 0;
557 while (more) {
558 http_consumer htp;
559 write_request(output).get();
9f95a23c
TL
560 repeat([&input, &htp] {
561 return input.read().then([&htp](const temporary_buffer<char>& b) mutable {
11fdf7f2
TL
562 return (b.size() == 0 || htp.read(b)) ? make_ready_future<stop_iteration>(stop_iteration::yes) :
563 make_ready_future<stop_iteration>(stop_iteration::no);
564 });
565 }).get();
566 if (std::get<bool>(tests[count])) {
567 BOOST_REQUIRE_EQUAL(htp._body.length(), std::get<size_t>(tests[count]));
568 } else {
569 BOOST_REQUIRE_EQUAL(input.eof(), true);
570 more = false;
571 }
572 count++;
573 if (count == tests.size()) {
574 more = false;
575 }
576 }
577 if (input.eof()) {
578 input.close().get();
579 }
580 });
581
f67539c2 582 auto server_setup = seastar::async([&server, tests] {
11fdf7f2
TL
583 class test_handler : public handler_base {
584 size_t count = 0;
585 http_server& _server;
586 std::vector<std::tuple<bool, size_t>> _tests;
587 promise<> _all_message_sent;
588 public:
589 test_handler(http_server& server, const std::vector<std::tuple<bool, size_t>>& tests) : _server(server), _tests(tests) {
590 }
1e59de90
TL
591 future<std::unique_ptr<http::reply>> handle(const sstring& path,
592 std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override {
11fdf7f2
TL
593 rep->write_body("txt", make_writer(std::get<size_t>(_tests[count]), std::get<bool>(_tests[count])));
594 count++;
595 if (count == _tests.size()) {
596 _all_message_sent.set_value();
597 }
1e59de90 598 return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
11fdf7f2
TL
599 }
600 future<> wait_for_message() {
601 return _all_message_sent.get_future();
602 }
603 };
604 auto handler = new test_handler(*server, tests);
605 server->_routes.put(GET, "/test", handler);
606 when_all(server->do_accepts(0), handler->wait_for_message()).get();
607 });
f67539c2 608 return when_all(std::move(client), std::move(server_setup));
11fdf7f2
TL
609 }).discard_result().then_wrapped([&server] (auto f) {
610 f.ignore_ready_future();
611 return server->stop();
612 });
613 });
614 }
615
616 static noncopyable_function<future<>(output_stream<char>&& o_stream)> make_writer(size_t len, bool success) {
617 return [len, success] (output_stream<char>&& o_stream) mutable {
618 return do_with(output_stream<char>(std::move(o_stream)), uint32_t(len/10), [success](output_stream<char>& str, uint32_t& remain) {
619 if (remain == 0) {
620 if (success) {
621 return str.close();
622 } else {
623 throw std::runtime_error("Throwing exception before writing");
624 }
625 }
9f95a23c 626 return repeat([&str, &remain] () mutable {
11fdf7f2
TL
627 return str.write("1234567890").then([&remain]() mutable {
628 remain--;
629 return (remain == 0)? make_ready_future<stop_iteration>(stop_iteration::yes) : make_ready_future<stop_iteration>(stop_iteration::no);
630 });
631 }).then([&str, success] {
632 if (!success) {
633 return str.flush();
634 }
635 return make_ready_future<>();
636 }).then([&str, success] {
637 if (success) {
638 return str.close();
639 } else {
640 throw std::runtime_error("Throwing exception after writing");
641 }
642 });
643 });
644 };
645 }
646};
647
648SEASTAR_TEST_CASE(test_message_with_error_non_empty_body) {
649 std::vector<std::tuple<bool, size_t>> tests = {
650 std::make_tuple(true, 100),
651 std::make_tuple(false, 10000)};
652 return test_client_server::run(tests);
653}
654
655SEASTAR_TEST_CASE(test_simple_chunked) {
656 std::vector<std::tuple<bool, size_t>> tests = {
657 std::make_tuple(true, 100000),
658 std::make_tuple(true, 100)};
659 return test_client_server::run(tests);
660}
661
662SEASTAR_TEST_CASE(test_http_client_server_full) {
663 std::vector<std::tuple<bool, size_t>> tests = {
664 std::make_tuple(true, 100),
665 std::make_tuple(true, 10000),
666 std::make_tuple(true, 100),
667 std::make_tuple(true, 0),
668 std::make_tuple(true, 5000),
669 std::make_tuple(true, 10000),
670 std::make_tuple(true, 9000),
671 std::make_tuple(true, 10000)};
672 return test_client_server::run(tests);
673}
674
675/*
676 * return string in the given size
677 * The string size takes the quotes into consideration.
678 */
679std::string get_value(int size) {
680 std::stringstream res;
681 for (auto i = 0; i < size - 2; i++) {
682 res << "a";
683 }
684 return res.str();
685}
686
687/*
688 * A helper object that map to a big json string
689 * in the format of:
690 * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...}
691 *
692 * The object can have an arbitrary size in multiplication of 10000 bytes
693 * */
694struct extra_big_object : public json::json_base {
695 json::json_element<sstring>* value;
696 extra_big_object(size_t size) {
697 value = new json::json_element<sstring>;
698 // size = brackets + (name + ": " + get_value) * n + ", " * (n-1)
699 // size = 2 + (name + 6 + get_value) * n - 2
700 value->_name = "valu";
701 *value = get_value(9990);
702 for (size_t i = 0; i < size/10000; i++) {
703 _elements.emplace_back(value);
704 }
705 }
706
707 virtual ~extra_big_object() {
708 delete value;
709 }
710
711 extra_big_object(const extra_big_object& o) {
712 value = new json::json_element<sstring>;
713 value->_name = o.value->_name;
714 *value = (*o.value)();
715 for (size_t i = 0; i < o._elements.size(); i++) {
716 _elements.emplace_back(value);
717 }
718 }
11fdf7f2
TL
719};
720
721SEASTAR_TEST_CASE(json_stream) {
722 std::vector<extra_big_object> vec;
723 size_t num_objects = 1000;
724 size_t total_size = num_objects * 1000001 + 1;
725 for (size_t i = 0; i < num_objects; i++) {
9f95a23c 726 vec.emplace_back(1000000);
11fdf7f2
TL
727 }
728 return test_client_server::run_test(json::stream_object(vec), [total_size](size_t s, http_consumer& h) {
729 BOOST_REQUIRE_EQUAL(h._size, total_size);
730 return false;
731 });
732}
9f95a23c 733
f67539c2
TL
734class json_test_handler : public handler_base {
735 std::function<future<>(output_stream<char> &&)> _write_func;
736public:
737 json_test_handler(std::function<future<>(output_stream<char> &&)>&& write_func) : _write_func(write_func) {
738 }
1e59de90
TL
739 future<std::unique_ptr<http::reply>> handle(const sstring& path,
740 std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override {
f67539c2 741 rep->write_body("json", _write_func);
1e59de90 742 return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
f67539c2
TL
743 }
744};
745
9f95a23c
TL
746SEASTAR_TEST_CASE(content_length_limit) {
747 return seastar::async([] {
1e59de90 748 loopback_connection_factory lcf(1);
9f95a23c
TL
749 http_server server("test");
750 server.set_content_length_limit(11);
751 loopback_socket_impl lsi(lcf);
752 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
753
754 future<> client = seastar::async([&lsi] {
f67539c2 755 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
9f95a23c
TL
756 input_stream<char> input(c_socket.input());
757 output_stream<char> output(c_socket.output());
758
759 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
760 output.flush().get();
761 auto resp = input.read().get0();
762 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
763
764 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 11\r\n\r\nxxxxxxxxxxx")).get();
765 output.flush().get();
766 resp = input.read().get0();
767 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
768
769 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\n\r\nxxxxxxxxxxxxxxxx")).get();
770 output.flush().get();
771 resp = input.read().get0();
772 BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
773 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
774
775 input.close().get();
776 output.close().get();
777 });
778
f67539c2
TL
779 auto handler = new json_test_handler(json::stream_object("hello"));
780 server._routes.put(GET, "/test", handler);
781 server.do_accepts(0).get();
782
783 client.get();
784 server.stop().get();
785 });
786}
787
788SEASTAR_TEST_CASE(test_100_continue) {
789 return seastar::async([] {
1e59de90 790 loopback_connection_factory lcf(1);
f67539c2
TL
791 http_server server("test");
792 server.set_content_length_limit(11);
793 loopback_socket_impl lsi(lcf);
794 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
795 future<> client = seastar::async([&lsi] {
796 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
797 input_stream<char> input(c_socket.input());
798 output_stream<char> output(c_socket.output());
799
800 for (auto version : {sstring("1.0"), sstring("1.1")}) {
801 for (auto content : {sstring(""), sstring("xxxxxxxxxxx")}) {
802 for (auto expect : {sstring(""), sstring("Expect: 100-continue\r\n"), sstring("Expect: 100-cOnTInUE\r\n")}) {
803 auto content_len = content.empty() ? sstring("") : (sstring("Content-Length: ") + to_sstring(content.length()) + sstring("\r\n"));
804 sstring req = sstring("GET /test HTTP/") + version + sstring("\r\nHost: test\r\nConnection: Keep-Alive\r\n") + content_len + expect + sstring("\r\n");
805 output.write(req).get();
806 output.flush().get();
807 bool already_ok = false;
808 if (version == "1.1" && expect.length()) {
809 auto resp = input.read().get0();
810 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
811 already_ok = content.empty() && std::string(resp.get(), resp.size()).find("200 OK") != std::string::npos;
812 }
813 if (!already_ok) {
814 //If the body is empty, the final response might have already been read
815 output.write(content).get();
816 output.flush().get();
817 auto resp = input.read().get0();
818 BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
819 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("200 OK"), std::string::npos);
820 }
821 }
9f95a23c 822 }
f67539c2
TL
823 }
824 output.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
825 output.flush().get();
826 auto resp = input.read().get0();
827 BOOST_REQUIRE_EQUAL(std::string(resp.get(), resp.size()).find("100 Continue"), std::string::npos);
828 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("413 Payload Too Large"), std::string::npos);
829
830 input.close().get();
831 output.close().get();
9f95a23c
TL
832 });
833
f67539c2
TL
834 auto handler = new json_test_handler(json::stream_object("hello"));
835 server._routes.put(GET, "/test", handler);
836 server.do_accepts(0).get();
837
9f95a23c 838 client.get();
9f95a23c
TL
839 server.stop().get();
840 });
841}
f67539c2
TL
842
843
844SEASTAR_TEST_CASE(test_unparsable_request) {
845 // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response
846 return seastar::async([] {
1e59de90 847 loopback_connection_factory lcf(1);
f67539c2
TL
848 http_server server("test");
849 loopback_socket_impl lsi(lcf);
850 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
851 future<> client = seastar::async([&lsi] {
852 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
853 input_stream<char> input(c_socket.input());
854 output_stream<char> output(c_socket.output());
855
856 output.write(sstring("GET /test HTTP/1.1\r\nhello\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
857 output.flush().get();
858 auto resp = input.read().get0();
859 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("400 Bad Request"), std::string::npos);
860 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find("Can't parse the request"), std::string::npos);
861
862 input.close().get();
863 output.close().get();
864 });
865
866 auto handler = new json_test_handler(json::stream_object("hello"));
867 server._routes.put(GET, "/test", handler);
868 server.do_accepts(0).get();
869
870 client.get();
871 server.stop().get();
872 });
873}
874
1e59de90
TL
875struct echo_handler : public handler_base {
876 bool chunked_reply;
877 echo_handler(bool chunked_reply_) : handler_base(), chunked_reply(chunked_reply_) {}
878
879 future<std::unique_ptr<http::reply>> do_handle(std::unique_ptr<http::request>& req, std::unique_ptr<http::reply>& rep, sstring& content) {
880 for (auto it : req->chunk_extensions) {
881 content += it.first;
882 if (it.second != "") {
883 content += to_sstring("=") + it.second;
884 }
885 }
886 for (auto it : req->trailing_headers) {
887 content += it.first;
888 if (it.second != "") {
889 content += to_sstring(": ") + it.second;
890 }
891 }
892 if (!chunked_reply) {
893 rep->write_body("txt", content);
894 } else {
895 rep->write_body("txt", [ c = content ] (output_stream<char>&& out) {
896 return do_with(std::move(out), [ c = std::move(c) ] (output_stream<char>& out) {
897 return out.write(std::move(c)).then([&out] {
898 return out.flush().then([&out] {
899 return out.close();
900 });
901 });
902 });
903 });
904 }
905 return make_ready_future<std::unique_ptr<http::reply>>(std::move(rep));
906 }
907};
908
20effc67
TL
909/*
910 * A request handler that responds with the same body that was used in the request using the requests content_stream
911 * */
1e59de90
TL
912struct echo_stream_handler : public echo_handler {
913 echo_stream_handler(bool chunked_reply = false) : echo_handler(chunked_reply) {}
914 future<std::unique_ptr<http::reply>> handle(const sstring& path,
915 std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override {
916 return do_with(std::move(req), std::move(rep), sstring(), [this] (std::unique_ptr<http::request>& req, std::unique_ptr<http::reply>& rep, sstring& rep_content) {
20effc67
TL
917 return do_until([&req] { return req->content_stream->eof(); }, [&req, &rep_content] {
918 return req->content_stream->read().then([&rep_content] (temporary_buffer<char> tmp) {
919 rep_content += to_sstring(std::move(tmp));
920 });
1e59de90
TL
921 }).then([&req, &rep, &rep_content, this] {
922 return this->do_handle(req, rep, rep_content);
20effc67
TL
923 });
924 });
925 }
926};
927
928/*
929 * Same handler as above, but without using streams
930 * */
1e59de90
TL
931struct echo_string_handler : public echo_handler {
932 echo_string_handler(bool chunked_reply = false) : echo_handler(chunked_reply) {}
933 future<std::unique_ptr<http::reply>> handle(const sstring& path,
934 std::unique_ptr<http::request> req, std::unique_ptr<http::reply> rep) override {
935 return this->do_handle(req, rep, req->content);
20effc67
TL
936 }
937};
938
939/*
940 * Checks if the server responds to the request equivalent to the concatenation of all req_parts with a reply containing
941 * the resp_parts strings, assuming that the content streaming is set to stream and the /test route is handled by handl
942 * */
943future<> check_http_reply (std::vector<sstring>&& req_parts, std::vector<std::string>&& resp_parts, bool stream, handler_base* handl) {
944 return seastar::async([req_parts = std::move(req_parts), resp_parts = std::move(resp_parts), stream, handl] {
1e59de90 945 loopback_connection_factory lcf(1);
20effc67
TL
946 http_server server("test");
947 server.set_content_streaming(stream);
948 loopback_socket_impl lsi(lcf);
949 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
950 future<> client = seastar::async([req_parts = std::move(req_parts), resp_parts = std::move(resp_parts), &lsi] {
951 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
952 input_stream<char> input(c_socket.input());
953 output_stream<char> output(c_socket.output());
954
955 for (auto& str : req_parts) {
956 output.write(std::move(str)).get();
957 output.flush().get();
958 }
959 auto resp = input.read().get0();
960 for (auto& str : resp_parts) {
961 BOOST_REQUIRE_NE(std::string(resp.get(), resp.size()).find(std::move(str)), std::string::npos);
962 }
963
964 input.close().get();
965 output.close().get();
966 });
967
968 server._routes.put(GET, "/test", handl);
969 server.do_accepts(0).get();
970
971 client.get();
972 server.stop().get();
973 });
974};
975
1e59de90
TL
976static future<> test_basic_content(bool streamed, bool chunked_reply) {
977 return seastar::async([streamed, chunked_reply] {
978 loopback_connection_factory lcf(1);
20effc67 979 http_server server("test");
1e59de90
TL
980 if (streamed) {
981 server.set_content_streaming(true);
982 }
20effc67
TL
983 loopback_socket_impl lsi(lcf);
984 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
1e59de90 985 future<> client = seastar::async([&lsi, chunked_reply] {
20effc67 986 connected_socket c_socket = lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
1e59de90
TL
987 http::experimental::connection conn(std::move(c_socket));
988
989 {
990 fmt::print("Simple request test\n");
991 auto req = http::request::make("GET", "test", "/test");
992 auto resp = conn.make_request(std::move(req)).get0();
993 BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok);
994 if (!chunked_reply) {
995 BOOST_REQUIRE_EQUAL(resp.content_length, 0);
996 } else {
997 // If response is chunked it will contain the single termination
998 // zero-sized chunk that still needs to be read out
999 auto in = conn.in(resp);
1000 sstring body = util::read_entire_stream_contiguous(in).get0();
1001 BOOST_REQUIRE_EQUAL(body, "");
1002 }
1003 }
20effc67 1004
1e59de90
TL
1005 {
1006 fmt::print("Request with body test\n");
1007 auto req = http::request::make("GET", "test", "/test");
1008 req.write_body("txt", sstring("12345 78901\t34521345"));
1009 auto resp = conn.make_request(std::move(req)).get0();
1010 BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok);
1011 if (!chunked_reply) {
1012 BOOST_REQUIRE_EQUAL(resp.content_length, 20);
1013 }
1014 auto in = conn.in(resp);
1015 sstring body = util::read_entire_stream_contiguous(in).get0();
1016 BOOST_REQUIRE_EQUAL(body, sstring("12345 78901\t34521345"));
1017 }
20effc67 1018
1e59de90
TL
1019 {
1020 fmt::print("Request with content-length body\n");
1021 auto req = http::request::make("GET", "test", "/test");
1022 req.write_body("txt", 12, [] (output_stream<char>&& out) {
1023 return seastar::async([out = std::move(out)] () mutable {
1024 out.write(sstring("1234567890")).get();
1025 out.write(sstring("AB")).get();
1026 out.flush().get();
1027 out.close().get();
1028 });
1029 });
1030 auto resp = conn.make_request(std::move(req)).get0();
1031 BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok);
1032 if (!chunked_reply) {
1033 BOOST_REQUIRE_EQUAL(resp.content_length, 12);
1034 }
1035 auto in = conn.in(resp);
1036 sstring body = util::read_entire_stream_contiguous(in).get0();
1037 BOOST_REQUIRE_EQUAL(body, sstring("1234567890AB"));
1038 }
20effc67 1039
1e59de90
TL
1040 {
1041 const size_t size = 128*1024;
1042 fmt::print("Request with {}-kbytes content-length body\n", size >> 10);
1043 temporary_buffer<char> jumbo(size);
1044 temporary_buffer<char> jumbo_copy(size);
1045 for (size_t i = 0; i < size; i++) {
1046 jumbo.get_write()[i] = 'a' + i % ('z' - 'a');
1047 jumbo_copy.get_write()[i] = jumbo[i];
1048 }
1049 auto req = http::request::make("GET", "test", "/test");
1050 req.write_body("txt", size, [jumbo = std::move(jumbo)] (output_stream<char>&& out) mutable {
1051 return seastar::async([out = std::move(out), jumbo = std::move(jumbo)] () mutable {
1052 out.write(jumbo.get(), jumbo.size()).get();
1053 out.flush().get();
1054 out.close().get();
1055 });
1056 });
1057 auto resp = conn.make_request(std::move(req)).get0();
1058 BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok);
1059 if (!chunked_reply) {
1060 BOOST_REQUIRE_EQUAL(resp.content_length, size);
1061 }
1062 auto in = conn.in(resp);
1063 sstring body = util::read_entire_stream_contiguous(in).get0();
1064 BOOST_REQUIRE_EQUAL(body, to_sstring(std::move(jumbo_copy)));
1065 }
20effc67 1066
1e59de90
TL
1067 {
1068 fmt::print("Request with chunked body\n");
1069 auto req = http::request::make("GET", "test", "/test");
1070 req.write_body("txt", [] (auto&& out) -> future<> {
1071 return seastar::async([out = std::move(out)] () mutable {
1072 out.write(sstring("req")).get();
1073 out.write(sstring("1234\r\n7890")).get();
1074 out.flush().get();
1075 out.close().get();
1076 });
1077 });
1078 auto resp = conn.make_request(std::move(req)).get0();
1079 BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok);
1080 if (!chunked_reply) {
1081 BOOST_REQUIRE_EQUAL(resp.content_length, 13);
1082 }
1083 auto in = conn.in(resp);
1084 sstring body = util::read_entire_stream_contiguous(in).get0();
1085 BOOST_REQUIRE_EQUAL(body, sstring("req1234\r\n7890"));
20effc67 1086 }
20effc67 1087
1e59de90
TL
1088 {
1089 fmt::print("Request with expect-continue\n");
1090 auto req = http::request::make("GET", "test", "/test");
1091 req.write_body("txt", sstring("foobar"));
1092 req.set_expects_continue();
1093 auto resp = conn.make_request(std::move(req)).get0();
1094 BOOST_REQUIRE_EQUAL(resp._status, http::reply::status_type::ok);
1095 if (!chunked_reply) {
1096 BOOST_REQUIRE_EQUAL(resp.content_length, 6);
1097 }
1098 auto in = conn.in(resp);
1099 sstring body = util::read_entire_stream_contiguous(in).get0();
1100 BOOST_REQUIRE_EQUAL(body, sstring("foobar"));
1101 }
20effc67 1102
1e59de90
TL
1103 {
1104 fmt::print("Request with incomplete content-length body\n");
1105 auto req = http::request::make("GET", "test", "/test");
1106 req.write_body("txt", 12, [] (output_stream<char>&& out) {
1107 return seastar::async([out = std::move(out)] () mutable {
1108 out.write(sstring("1234567890A")).get();
1109 out.flush().get();
1110 out.close().get();
1111 });
1112 });
1113 BOOST_REQUIRE_THROW(conn.make_request(std::move(req)).get0(), std::runtime_error);
1114 }
20effc67 1115
1e59de90
TL
1116 {
1117 bool callback_completed = false;
1118 fmt::print("Request with too large content-length body\n");
1119 auto req = http::request::make("GET", "test", "/test");
1120 req.write_body("txt", 12, [&callback_completed] (output_stream<char>&& out) {
1121 return seastar::async([out = std::move(out), &callback_completed] () mutable {
1122 out.write(sstring("1234567890ABC")).get();
1123 out.flush().get();
1124 out.close().get();
1125 callback_completed = true;
1126 });
1127 });
1128 BOOST_REQUIRE_NE(callback_completed, true); // should throw early
1129 BOOST_REQUIRE_THROW(conn.make_request(std::move(req)).get0(), std::runtime_error);
1130 }
1131
1132 conn.close().get();
20effc67
TL
1133 });
1134
1e59de90
TL
1135 handler_base* handler;
1136 if (streamed) {
1137 handler = new echo_stream_handler(chunked_reply);
1138 } else {
1139 handler = new echo_string_handler(chunked_reply);
1140 }
20effc67
TL
1141 server._routes.put(GET, "/test", handler);
1142 server.do_accepts(0).get();
1143
1144 client.get();
1145 server.stop().get();
1146 });
1147}
1148
1e59de90
TL
1149SEASTAR_TEST_CASE(test_string_content) {
1150 return test_basic_content(false, false);
1151}
1152
1153SEASTAR_TEST_CASE(test_string_content_chunked) {
1154 return test_basic_content(false, true);
1155}
1156
1157SEASTAR_TEST_CASE(test_stream_content) {
1158 return test_basic_content(true, false);
1159}
1160
1161SEASTAR_TEST_CASE(test_stream_content_chunked) {
1162 return test_basic_content(true, true);
1163}
1164
20effc67
TL
1165SEASTAR_TEST_CASE(test_not_implemented_encoding) {
1166 return check_http_reply({
1167 "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: gzip, chunked\r\n\r\n",
1168 "a\r\n1234567890\r\n",
1169 "a\r\n1234521345\r\n",
1170 "0\r\n\r\n"
1171 }, {"501 Not Implemented", "Encodings other than \"chunked\" are not implemented (received encoding: \"gzip, chunked\")"}, false, new echo_string_handler());
1172}
1173
20effc67
TL
1174SEASTAR_TEST_CASE(test_full_chunk_format) {
1175 return check_http_reply({
1176 "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
1177 "a;abc-def;hello=world;aaaa\r\n1234567890\r\n",
1178 "a;a0-!#$%&'*+.^_`|~=\"quoted string obstext\x80\x81\xff quoted_pair: \\a\"\r\n1234521345\r\n",
1179 "0\r\na:b\r\n~|`_^.+*'&%$#!-0a: ~!@#$%^&*()_+\x80\x81\xff\r\n obs fold \r\n\r\n"
1180 }, {"12345678901234521345", "abc-def", "hello=world", "aaaa", "a0-!#$%&'*+.^_`|~=quoted string obstext\x80\x81\xff quoted_pair: a",
1181 "a: b", "~|`_^.+*'&%$#!-0a: ~!@#$%^&*()_+\x80\x81\xff obs fold"
1182 }, false, new echo_string_handler());
1183}
1184
1185SEASTAR_TEST_CASE(test_chunk_extension_parser_fail) {
1186 return check_http_reply({
1187 "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
1188 "7; \r\nnoparse\r\n",
1189 "0\r\n\r\n"
1190 }, {"400 Bad Request", "Can't parse chunk size and extensions"}, false, new echo_string_handler());
1191}
1192
1193SEASTAR_TEST_CASE(test_trailer_part_parser_fail) {
1194 return check_http_reply({
1195 "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
1196 "8\r\nparsable\r\n",
1197 "0\r\ngood:header\r\nbad=header\r\n\r\n"
1198 }, {"400 Bad Request", "Can't parse chunked request trailer"}, false, new echo_string_handler());
1199}
1200
1201SEASTAR_TEST_CASE(test_too_long_chunk) {
1202 return check_http_reply({
1203 "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
1204 "a\r\n1234567890\r\n",
1205 "a\r\n1234521345X\r\n",
1206 "0\r\n\r\n"
1207 }, {"400 Bad Request", "The actual chunk length exceeds the specified length"}, true, new echo_stream_handler());
1208}
1209
1210SEASTAR_TEST_CASE(test_bad_chunk_length) {
1211 return check_http_reply({
1212 "GET /test HTTP/1.1\r\nHost: test\r\nTransfer-Encoding: chunked\r\n\r\n",
1213 "a\r\n1234567890\r\n",
1214 "aX\r\n1234521345\r\n",
1215 "0\r\n\r\n"
1216 }, {"400 Bad Request", "Can't parse chunk size and extensions"}, true, new echo_stream_handler());
1217}
1218
f67539c2 1219SEASTAR_TEST_CASE(case_insensitive_header) {
1e59de90 1220 std::unique_ptr<seastar::http::request> req = std::make_unique<seastar::http::request>();
f67539c2
TL
1221 req->_headers["conTEnt-LengtH"] = "17";
1222 BOOST_REQUIRE_EQUAL(req->get_header("content-length"), "17");
1223 BOOST_REQUIRE_EQUAL(req->get_header("Content-Length"), "17");
1224 BOOST_REQUIRE_EQUAL(req->get_header("cOnTeNT-lEnGTh"), "17");
1225 return make_ready_future<>();
1226}
1227
1228SEASTAR_THREAD_TEST_CASE(multiple_connections) {
1e59de90 1229 loopback_connection_factory lcf(1);
f67539c2
TL
1230 http_server server("test");
1231 httpd::http_server_tester::listeners(server).emplace_back(lcf.get_server_socket());
1232 socket_address addr{ipv4_addr()};
1233
1234 std::vector<connected_socket> socks;
1235 // Make sure one shard has two connections pending.
1236 for (unsigned i = 0; i <= smp::count; ++i) {
1237 socks.push_back(loopback_socket_impl(lcf).connect(addr, addr).get0());
1238 }
1239
1240 server.do_accepts(0).get();
1241 server.stop().get();
1242 lcf.destroy_all_shards().get();
1243}
20effc67
TL
1244
1245SEASTAR_TEST_CASE(http_parse_response_status) {
1246 http_response_parser parser;
1247 parser.init();
1248 char r101[] = "HTTP/1.1 101 Switching Protocols\r\n\r\n";
1249 char r200[] = "HTTP/1.1 200 OK\r\nHost: localhost\r\nhello\r\n";
1250
1251 parser.parse(r101, r101 + sizeof(r101), r101 + sizeof(r101));
1252 auto response = parser.get_parsed_response();
1253 BOOST_REQUIRE_EQUAL(response->_status_code, 101);
1254
1255 parser.init();
1256 parser.parse(r200, r200 + sizeof(r200), r200 + sizeof(r200));
1257 response = parser.get_parsed_response();
1258 BOOST_REQUIRE_EQUAL(response->_status_code, 200);
1259 return make_ready_future<>();
1260}
1261
1262SEASTAR_TEST_CASE(test_shared_future) {
1263 shared_promise<json::json_return_type> p;
1264 auto fut = p.get_shared_future();
1265
1e59de90 1266 (void)yield().then([p = std::move(p)] () mutable {
20effc67
TL
1267 p.set_value(json::json_void());
1268 });
1269
1270 return std::move(fut).discard_result();
1271}
1e59de90
TL
1272
1273SEASTAR_TEST_CASE(test_url_encode_decode) {
1274 sstring encoded, decoded;
1275 bool ok;
1276
1277 sstring all_valid = "~abcdefghijklmnopqrstuvwhyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789.";
1278 encoded = http::internal::url_encode(all_valid);
1279 ok = http::internal::url_decode(encoded, decoded);
1280 BOOST_REQUIRE_EQUAL(ok, true);
1281 BOOST_REQUIRE_EQUAL(decoded, all_valid);
1282 BOOST_REQUIRE_EQUAL(all_valid, encoded);
1283
1284 sstring some_invalid = "a?/!@#$%^&*()[]=.\\ \tZ";
1285 encoded = http::internal::url_encode(some_invalid);
1286 ok = http::internal::url_decode(encoded, decoded);
1287 BOOST_REQUIRE_EQUAL(ok, true);
1288 BOOST_REQUIRE_EQUAL(decoded, some_invalid);
1289 for (size_t i = 0; i < encoded.length(); i++) {
1290 if (encoded[i] != '%' && encoded[i] != '+') {
1291 auto f = std::find(std::begin(all_valid), std::end(all_valid), encoded[i]);
1292 BOOST_REQUIRE_NE(f, std::end(all_valid));
1293 }
1294 }
1295
1296 return make_ready_future<>();
1297}
1298
1299SEASTAR_TEST_CASE(test_url_param_encode_decode) {
1300 http::request to_send;
1301 to_send._url = "/foo/bar";
1302 to_send.query_parameters["a"] = "a+a*a";
1303 to_send.query_parameters["b"] = "b/b\%b";
1304
1305 http::request to_recv;
1306 to_recv._url = to_send.format_url();
1307 sstring url = to_recv.parse_query_param();
1308
1309 BOOST_REQUIRE_EQUAL(url, to_send._url);
1310 BOOST_REQUIRE_EQUAL(to_recv.query_parameters.size(), to_send.query_parameters.size());
1311 for (const auto& p : to_send.query_parameters) {
1312 auto it = to_recv.query_parameters.find(p.first);
1313 BOOST_REQUIRE(it != to_recv.query_parameters.end());
1314 BOOST_REQUIRE_EQUAL(it->second, p.second);
1315 }
1316
1317 return make_ready_future<>();
1318}