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