2 * Copyright 2015 Cloudius Systems
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>
13 #include <seastar/core/do_with.hh>
14 #include <seastar/core/loop.hh>
15 #include <seastar/core/when_all.hh>
16 #include <seastar/testing/test_case.hh>
17 #include <seastar/testing/thread_test_case.hh>
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>
25 using namespace seastar
;
26 using namespace httpd
;
28 class handl
: public httpd::handler_base
{
30 virtual future
<std::unique_ptr
<reply
> > handle(const sstring
& path
,
31 std::unique_ptr
<request
> req
, std::unique_ptr
<reply
> rep
) {
33 return make_ready_future
<std::unique_ptr
<reply
>>(std::move(rep
));
37 SEASTAR_TEST_CASE(test_reply
)
40 r
.set_content_type("txt");
41 BOOST_REQUIRE_EQUAL(r
._headers
["Content-Type"], sstring("text/plain"));
42 return make_ready_future
<>();
45 SEASTAR_TEST_CASE(test_str_matcher
)
48 str_matcher
m("/hello");
50 BOOST_REQUIRE_EQUAL(m
.match("/abc/hello", 4, param
), 10u);
51 return make_ready_future
<>();
54 SEASTAR_TEST_CASE(test_param_matcher
)
57 param_matcher
m("param");
59 BOOST_REQUIRE_EQUAL(m
.match("/abc/hello", 4, param
), 10u);
60 BOOST_REQUIRE_EQUAL(param
.path("param"), "/hello");
61 BOOST_REQUIRE_EQUAL(param
["param"], "hello");
62 return make_ready_future
<>();
65 SEASTAR_TEST_CASE(test_match_rule
)
69 handl
* h
= new handl();
71 mr
.add_str("/hello").add_param("param");
72 httpd::handler_base
* res
= mr
.get("/hello/val1", param
);
73 BOOST_REQUIRE_EQUAL(res
, h
);
74 BOOST_REQUIRE_EQUAL(param
["param"], "val1");
75 res
= mr
.get("/hell/val1", param
);
76 httpd::handler_base
* nl
= nullptr;
77 BOOST_REQUIRE_EQUAL(res
, nl
);
78 return make_ready_future
<>();
81 SEASTAR_TEST_CASE(test_match_rule_order
)
86 handl
* h1
= new handl();
87 route
.add(operation_type::GET
, url("/hello"), h1
);
89 handl
* h2
= new handl();
90 route
.add(operation_type::GET
, url("/hello"), h2
);
92 auto rh
= route
.get_handler(GET
, "/hello", param
);
93 BOOST_REQUIRE_EQUAL(rh
, h1
);
95 return make_ready_future
<>();
98 SEASTAR_TEST_CASE(test_put_drop_rule
)
101 auto h
= std::make_unique
<handl
>();
105 auto reg
= handler_registration(rts
, *h
, "/hello", operation_type::GET
);
106 auto res
= rts
.get_handler(operation_type::GET
, "/hello", params
);
107 BOOST_REQUIRE_EQUAL(res
, h
.get());
110 auto res
= rts
.get_handler(operation_type::GET
, "/hello", params
);
111 httpd::handler_base
* nl
= nullptr;
112 BOOST_REQUIRE_EQUAL(res
, nl
);
113 return make_ready_future
<>();
116 // Putting a duplicated exact rule would result
117 // in a memory leak due to the fact that rules are implemented
118 // as raw pointers. In order to prevent such leaks,
119 // an exception is thrown if somebody tries to put
120 // a duplicated rule without removing the old one first.
121 // The interface demands that the callee allocates the handle,
122 // so it should also expect the callee to free it before
124 SEASTAR_TEST_CASE(test_duplicated_exact_rule
)
129 handl
* h1
= new handl
;
130 route
.put(operation_type::GET
, "/hello", h1
);
132 handl
* h2
= new handl
;
133 BOOST_REQUIRE_THROW(route
.put(operation_type::GET
, "/hello", h2
), std::runtime_error
);
135 delete route
.drop(operation_type::GET
, "/hello");
136 route
.put(operation_type::GET
, "/hello", h2
);
138 return make_ready_future
<>();
141 SEASTAR_TEST_CASE(test_add_del_cookie
)
144 handl
* h
= new handl();
146 mr
.add_str("/hello");
150 auto reg
= rule_registration(rts
, mr
, operation_type::GET
);
151 auto res
= rts
.get_handler(operation_type::GET
, "/hello", params
);
152 BOOST_REQUIRE_EQUAL(res
, h
);
155 auto res
= rts
.get_handler(operation_type::GET
, "/hello", params
);
156 httpd::handler_base
* nl
= nullptr;
157 BOOST_REQUIRE_EQUAL(res
, nl
);
158 return make_ready_future
<>();
161 SEASTAR_TEST_CASE(test_formatter
)
163 BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true");
164 BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false");
165 BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1");
166 const char* txt
= "efg";
167 BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt
), "\"efg\"");
169 BOOST_REQUIRE_EQUAL(json::formatter::to_json(str
), "\"abc\"");
171 BOOST_REQUIRE_EQUAL(json::formatter::to_json(f
), "1");
173 BOOST_CHECK_THROW(json::formatter::to_json(f
), std::out_of_range
);
175 BOOST_CHECK_THROW(json::formatter::to_json(f
), std::out_of_range
);
177 BOOST_CHECK_THROW(json::formatter::to_json(f
), std::invalid_argument
);
179 BOOST_REQUIRE_EQUAL(json::formatter::to_json(d
), "-1");
181 BOOST_CHECK_THROW(json::formatter::to_json(d
), std::out_of_range
);
183 BOOST_CHECK_THROW(json::formatter::to_json(d
), std::out_of_range
);
185 BOOST_CHECK_THROW(json::formatter::to_json(d
), std::invalid_argument
);
186 return make_ready_future
<>();
189 SEASTAR_TEST_CASE(test_decode_url
) {
191 req
._url
= "/a?q=%23%24%23";
192 sstring url
= http_server::connection::set_query_param(req
);
193 BOOST_REQUIRE_EQUAL(url
, "/a");
194 BOOST_REQUIRE_EQUAL(req
.get_query_param("q"), "#$#");
195 req
._url
= "/a?a=%23%24%23&b=%22%26%22";
196 http_server::connection::set_query_param(req
);
197 BOOST_REQUIRE_EQUAL(req
.get_query_param("a"), "#$#");
198 BOOST_REQUIRE_EQUAL(req
.get_query_param("b"), "\"&\"");
199 return make_ready_future
<>();
202 SEASTAR_TEST_CASE(test_routes
) {
203 handl
* h1
= new handl();
204 handl
* h2
= new handl();
206 route
.add(operation_type::GET
, url("/api").remainder("path"), h1
);
207 route
.add(operation_type::GET
, url("/"), h2
);
208 std::unique_ptr
<request
> req
= std::make_unique
<request
>();
209 std::unique_ptr
<reply
> rep
= std::make_unique
<reply
>();
212 route
.handle("/api", std::move(req
), std::move(rep
)).then(
213 [] (std::unique_ptr
<reply
> rep
) {
214 BOOST_REQUIRE_EQUAL((int )rep
->_status
, (int )reply::status_type::ok
);
216 req
.reset(new request
);
217 rep
.reset(new reply
);
220 route
.handle("/", std::move(req
), std::move(rep
)).then(
221 [] (std::unique_ptr
<reply
> rep
) {
222 BOOST_REQUIRE_EQUAL((int )rep
->_status
, (int )reply::status_type::ok
);
224 req
.reset(new request
);
225 rep
.reset(new reply
);
227 route
.handle("/api/abc", std::move(req
), std::move(rep
)).then(
228 [] (std::unique_ptr
<reply
> rep
) {
230 req
.reset(new request
);
231 rep
.reset(new reply
);
233 route
.handle("/ap", std::move(req
), std::move(rep
)).then(
234 [] (std::unique_ptr
<reply
> rep
) {
235 BOOST_REQUIRE_EQUAL((int )rep
->_status
,
236 (int )reply::status_type::not_found
);
238 return when_all(std::move(f1
), std::move(f2
), std::move(f3
), std::move(f4
))
239 .then([] (std::tuple
<future
<>, future
<>, future
<>, future
<>> fs
) {
240 std::get
<0>(fs
).get();
241 std::get
<1>(fs
).get();
242 std::get
<2>(fs
).get();
243 std::get
<3>(fs
).get();
247 SEASTAR_TEST_CASE(test_json_path
) {
248 shared_ptr
<bool> res1
= make_shared
<bool>(false);
249 shared_ptr
<bool> res2
= make_shared
<bool>(false);
250 shared_ptr
<bool> res3
= make_shared
<bool>(false);
251 shared_ptr
<routes
> route
= make_shared
<routes
>();
252 path_description
path1("/my/path",GET
,"path1",
253 {{"param1", path_description::url_component_type::PARAM
}
254 ,{"/text", path_description::url_component_type::FIXED_STRING
}},{});
255 path_description
path2("/my/path",GET
,"path2",
256 {{"param1", path_description::url_component_type::PARAM
}
257 ,{"param2", path_description::url_component_type::PARAM
}},{});
258 path_description
path3("/my/path",GET
,"path3",
259 {{"param1", path_description::url_component_type::PARAM
}
260 ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH
}},{});
262 path1
.set(*route
, [res1
] (const_req req
) {
264 BOOST_REQUIRE_EQUAL(req
.param
["param1"], "value1");
268 path2
.set(*route
, [res2
] (const_req req
) {
270 BOOST_REQUIRE_EQUAL(req
.param
["param1"], "value2");
271 BOOST_REQUIRE_EQUAL(req
.param
["param2"], "text1");
275 path3
.set(*route
, [res3
] (const_req req
) {
277 BOOST_REQUIRE_EQUAL(req
.param
["param1"], "value3");
278 BOOST_REQUIRE_EQUAL(req
.param
["param2"], "text2/text3");
282 auto f1
= route
->handle("/my/path/value1/text", std::make_unique
<request
>(), std::make_unique
<reply
>()).then([res1
, route
] (auto f
) {
283 BOOST_REQUIRE_EQUAL(*res1
, true);
286 auto f2
= route
->handle("/my/path/value2/text1", std::make_unique
<request
>(), std::make_unique
<reply
>()).then([res2
, route
] (auto f
) {
287 BOOST_REQUIRE_EQUAL(*res2
, true);
290 auto f3
= route
->handle("/my/path/value3/text2/text3", std::make_unique
<request
>(), std::make_unique
<reply
>()).then([res3
, route
] (auto f
) {
291 BOOST_REQUIRE_EQUAL(*res3
, true);
294 return when_all(std::move(f1
), std::move(f2
), std::move(f3
))
295 .then([] (std::tuple
<future
<>, future
<>, future
<>> fs
) {
296 std::get
<0>(fs
).get();
297 std::get
<1>(fs
).get();
298 std::get
<2>(fs
).get();
303 * \brief a helper data sink that stores everything it gets in a stringstream
305 class memory_data_sink_impl
: public data_sink_impl
{
306 std::stringstream
& _ss
;
308 memory_data_sink_impl(std::stringstream
& ss
) : _ss(ss
) {
310 virtual future
<> put(net::packet data
) override
{
312 return make_ready_future
<>();
314 virtual future
<> put(temporary_buffer
<char> buf
) override
{
315 _ss
.write(buf
.get(), buf
.size());
316 return make_ready_future
<>();
318 virtual future
<> flush() override
{
319 return make_ready_future
<>();
322 virtual future
<> close() override
{
323 return make_ready_future
<>();
327 class memory_data_sink
: public data_sink
{
329 memory_data_sink(std::stringstream
& ss
)
330 : data_sink(std::make_unique
<memory_data_sink_impl
>(ss
)) {}
333 future
<> test_transformer_stream(std::stringstream
& ss
, content_replace
& cr
, std::vector
<sstring
>&& buffer_parts
) {
334 std::unique_ptr
<seastar::httpd::request
> req
= std::make_unique
<seastar::httpd::request
>();
336 req
->_headers
["Host"] = "localhost";
337 return do_with(output_stream
<char>(cr
.transform(std::move(req
), "json", output_stream
<char>(memory_data_sink(ss
), 32000, true))),
338 std::vector
<sstring
>(std::move(buffer_parts
)), [] (output_stream
<char>& os
, std::vector
<sstring
>& parts
) {
339 return do_for_each(parts
, [&os
](auto& p
) {
347 SEASTAR_TEST_CASE(test_transformer
) {
348 return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream
& ss
, content_replace
& cr
) {
349 return do_with(output_stream
<char>(cr
.transform(std::make_unique
<seastar::httpd::request
>(), "html", output_stream
<char>(memory_data_sink(ss
), 32000, true))),
350 [] (output_stream
<char>& os
) {
351 return os
.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os
] {
354 }).then([&ss
, &cr
] () {
355 BOOST_REQUIRE_EQUAL(ss
.str(), "hello-{{Protocol}}-xyz-{{Host}}");
356 return test_transformer_stream(ss
, cr
, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss
, &cr
] {
357 BOOST_REQUIRE_EQUAL(ss
.str(), "hello-http-xyz-localhost{{Pr");
358 return test_transformer_stream(ss
, cr
, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss
, &cr
] {
359 BOOST_REQUIRE_EQUAL(ss
.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr");
360 return test_transformer_stream(ss
, cr
, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss
] {
361 BOOST_REQUIRE_EQUAL(ss
.str(), "hello-{{Prothttpocol}}localhost");
369 struct http_consumer
{
370 std::map
<sstring
, std::string
> _headers
;
372 uint32_t _remain
= 0;
373 std::string _current
;
378 enum class status_type
{
383 READING_BODY_BY_SIZE
,
386 status_type status
= status_type::READING_HEADERS
;
388 bool read(const temporary_buffer
<char>& b
) {
390 if (last
=='\r' && c
== '\n') {
391 if (_current
== "") {
392 if (status
== status_type::READING_HEADERS
|| (status
== status_type::CHUNK_BODY
&& _remain
== 0)) {
393 if (status
== status_type::READING_HEADERS
&& _headers
.find("Content-Length") != _headers
.end()) {
394 _remain
= stoi(_headers
["Content-Length"], nullptr, 16);
396 status
= status_type::DONE
;
399 status
= status_type::READING_BODY_BY_SIZE
;
401 status
= status_type::CHUNK_SIZE
;
403 } else if (status
== status_type::CHUNK_END
) {
404 status
= status_type::DONE
;
409 case status_type::READING_HEADERS
: add_header(_current
);
411 case status_type::CHUNK_SIZE
: set_chunk(_current
);
421 if (status
== status_type::CHUNK_BODY
|| status
== status_type::READING_BODY_BY_SIZE
) {
423 _body
= _body
+ last
;
427 if (_remain
<= 1 && status
== status_type::READING_BODY_BY_SIZE
) {
432 status
= status_type::DONE
;
436 _current
= _current
+ last
;
443 return status
== status_type::DONE
;
446 void set_chunk(const std::string
& s
) {
447 _remain
= stoi(s
, nullptr, 16);
449 status
= status_type::CHUNK_END
;
451 status
= status_type::CHUNK_BODY
;
455 void add_header(const std::string
& s
) {
456 std::vector
<std::string
> strs
;
457 boost::split(strs
, s
, boost::is_any_of(":"));
458 if (strs
.size() > 1) {
459 _headers
[strs
[0]] = strs
[1];
464 class test_client_server
{
466 static future
<> write_request(output_stream
<char>& output
) {
467 return output
.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output
]{
468 return output
.flush();
472 static future
<> run_test(std::function
<future
<>(output_stream
<char> &&)>&& write_func
, std::function
<bool(size_t, http_consumer
&)> reader
) {
473 return do_with(loopback_connection_factory(), foreign_ptr
<shared_ptr
<http_server
>>(make_shared
<http_server
>("test")),
474 [reader
, &write_func
] (loopback_connection_factory
& lcf
, auto& server
) {
475 return do_with(loopback_socket_impl(lcf
), [&server
, &lcf
, reader
, &write_func
](loopback_socket_impl
& lsi
) {
476 httpd::http_server_tester::listeners(*server
).emplace_back(lcf
.get_server_socket());
478 auto client
= seastar::async([&lsi
, reader
] {
479 connected_socket c_socket
= lsi
.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
480 input_stream
<char> input(c_socket
.input());
481 output_stream
<char> output(c_socket
.output());
488 write_request(output
).get();
489 repeat([&input
, &htp
] {
490 return input
.read().then([&htp
](const temporary_buffer
<char>& b
) mutable {
491 return (b
.size() == 0 || htp
.read(b
)) ? make_ready_future
<stop_iteration
>(stop_iteration::yes
) :
492 make_ready_future
<stop_iteration
>(stop_iteration::no
);
495 std::cout
<< htp
._body
<< std::endl
;
496 more
= reader(count
, htp
);
504 auto server_setup
= seastar::async([&server
, &write_func
] {
505 class test_handler
: public handler_base
{
507 http_server
& _server
;
508 std::function
<future
<>(output_stream
<char> &&)> _write_func
;
509 promise
<> _all_message_sent
;
511 test_handler(http_server
& server
, std::function
<future
<>(output_stream
<char> &&)>&& write_func
) : _server(server
), _write_func(write_func
) {
513 future
<std::unique_ptr
<reply
>> handle(const sstring
& path
,
514 std::unique_ptr
<request
> req
, std::unique_ptr
<reply
> rep
) override
{
515 rep
->write_body("json", std::move(_write_func
));
517 _all_message_sent
.set_value();
518 return make_ready_future
<std::unique_ptr
<reply
>>(std::move(rep
));
520 future
<> wait_for_message() {
521 return _all_message_sent
.get_future();
524 auto handler
= new test_handler(*server
, std::move(write_func
));
525 server
->_routes
.put(GET
, "/test", handler
);
526 when_all(server
->do_accepts(0), handler
->wait_for_message()).get();
528 return when_all(std::move(client
), std::move(server_setup
));
529 }).discard_result().then_wrapped([&server
] (auto f
) {
530 f
.ignore_ready_future();
531 return server
->stop();
535 static future
<> run(std::vector
<std::tuple
<bool, size_t>> tests
) {
536 return do_with(loopback_connection_factory(), foreign_ptr
<shared_ptr
<http_server
>>(make_shared
<http_server
>("test")),
537 [tests
] (loopback_connection_factory
& lcf
, auto& server
) {
538 return do_with(loopback_socket_impl(lcf
), [&server
, &lcf
, tests
](loopback_socket_impl
& lsi
) {
539 httpd::http_server_tester::listeners(*server
).emplace_back(lcf
.get_server_socket());
541 auto client
= seastar::async([&lsi
, tests
] {
542 connected_socket c_socket
= lsi
.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
543 input_stream
<char> input(c_socket
.input());
544 output_stream
<char> output(c_socket
.output());
549 write_request(output
).get();
550 repeat([&input
, &htp
] {
551 return input
.read().then([&htp
](const temporary_buffer
<char>& b
) mutable {
552 return (b
.size() == 0 || htp
.read(b
)) ? make_ready_future
<stop_iteration
>(stop_iteration::yes
) :
553 make_ready_future
<stop_iteration
>(stop_iteration::no
);
556 if (std::get
<bool>(tests
[count
])) {
557 BOOST_REQUIRE_EQUAL(htp
._body
.length(), std::get
<size_t>(tests
[count
]));
559 BOOST_REQUIRE_EQUAL(input
.eof(), true);
563 if (count
== tests
.size()) {
572 auto server_setup
= seastar::async([&server
, tests
] {
573 class test_handler
: public handler_base
{
575 http_server
& _server
;
576 std::vector
<std::tuple
<bool, size_t>> _tests
;
577 promise
<> _all_message_sent
;
579 test_handler(http_server
& server
, const std::vector
<std::tuple
<bool, size_t>>& tests
) : _server(server
), _tests(tests
) {
581 future
<std::unique_ptr
<reply
>> handle(const sstring
& path
,
582 std::unique_ptr
<request
> req
, std::unique_ptr
<reply
> rep
) override
{
583 rep
->write_body("txt", make_writer(std::get
<size_t>(_tests
[count
]), std::get
<bool>(_tests
[count
])));
585 if (count
== _tests
.size()) {
586 _all_message_sent
.set_value();
588 return make_ready_future
<std::unique_ptr
<reply
>>(std::move(rep
));
590 future
<> wait_for_message() {
591 return _all_message_sent
.get_future();
594 auto handler
= new test_handler(*server
, tests
);
595 server
->_routes
.put(GET
, "/test", handler
);
596 when_all(server
->do_accepts(0), handler
->wait_for_message()).get();
598 return when_all(std::move(client
), std::move(server_setup
));
599 }).discard_result().then_wrapped([&server
] (auto f
) {
600 f
.ignore_ready_future();
601 return server
->stop();
606 static noncopyable_function
<future
<>(output_stream
<char>&& o_stream
)> make_writer(size_t len
, bool success
) {
607 return [len
, success
] (output_stream
<char>&& o_stream
) mutable {
608 return do_with(output_stream
<char>(std::move(o_stream
)), uint32_t(len
/10), [success
](output_stream
<char>& str
, uint32_t& remain
) {
613 throw std::runtime_error("Throwing exception before writing");
616 return repeat([&str
, &remain
] () mutable {
617 return str
.write("1234567890").then([&remain
]() mutable {
619 return (remain
== 0)? make_ready_future
<stop_iteration
>(stop_iteration::yes
) : make_ready_future
<stop_iteration
>(stop_iteration::no
);
621 }).then([&str
, success
] {
625 return make_ready_future
<>();
626 }).then([&str
, success
] {
630 throw std::runtime_error("Throwing exception after writing");
638 SEASTAR_TEST_CASE(test_message_with_error_non_empty_body
) {
639 std::vector
<std::tuple
<bool, size_t>> tests
= {
640 std::make_tuple(true, 100),
641 std::make_tuple(false, 10000)};
642 return test_client_server::run(tests
);
645 SEASTAR_TEST_CASE(test_simple_chunked
) {
646 std::vector
<std::tuple
<bool, size_t>> tests
= {
647 std::make_tuple(true, 100000),
648 std::make_tuple(true, 100)};
649 return test_client_server::run(tests
);
652 SEASTAR_TEST_CASE(test_http_client_server_full
) {
653 std::vector
<std::tuple
<bool, size_t>> tests
= {
654 std::make_tuple(true, 100),
655 std::make_tuple(true, 10000),
656 std::make_tuple(true, 100),
657 std::make_tuple(true, 0),
658 std::make_tuple(true, 5000),
659 std::make_tuple(true, 10000),
660 std::make_tuple(true, 9000),
661 std::make_tuple(true, 10000)};
662 return test_client_server::run(tests
);
666 * return string in the given size
667 * The string size takes the quotes into consideration.
669 std::string
get_value(int size
) {
670 std::stringstream res
;
671 for (auto i
= 0; i
< size
- 2; i
++) {
678 * A helper object that map to a big json string
680 * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...}
682 * The object can have an arbitrary size in multiplication of 10000 bytes
684 struct extra_big_object
: public json::json_base
{
685 json::json_element
<sstring
>* value
;
686 extra_big_object(size_t size
) {
687 value
= new json::json_element
<sstring
>;
688 // size = brackets + (name + ": " + get_value) * n + ", " * (n-1)
689 // size = 2 + (name + 6 + get_value) * n - 2
690 value
->_name
= "valu";
691 *value
= get_value(9990);
692 for (size_t i
= 0; i
< size
/10000; i
++) {
693 _elements
.emplace_back(value
);
697 virtual ~extra_big_object() {
701 extra_big_object(const extra_big_object
& o
) {
702 value
= new json::json_element
<sstring
>;
703 value
->_name
= o
.value
->_name
;
704 *value
= (*o
.value
)();
705 for (size_t i
= 0; i
< o
._elements
.size(); i
++) {
706 _elements
.emplace_back(value
);
711 SEASTAR_TEST_CASE(json_stream
) {
712 std::vector
<extra_big_object
> vec
;
713 size_t num_objects
= 1000;
714 size_t total_size
= num_objects
* 1000001 + 1;
715 for (size_t i
= 0; i
< num_objects
; i
++) {
716 vec
.emplace_back(1000000);
718 return test_client_server::run_test(json::stream_object(vec
), [total_size
](size_t s
, http_consumer
& h
) {
719 BOOST_REQUIRE_EQUAL(h
._size
, total_size
);
724 class json_test_handler
: public handler_base
{
725 std::function
<future
<>(output_stream
<char> &&)> _write_func
;
727 json_test_handler(std::function
<future
<>(output_stream
<char> &&)>&& write_func
) : _write_func(write_func
) {
729 future
<std::unique_ptr
<reply
>> handle(const sstring
& path
,
730 std::unique_ptr
<request
> req
, std::unique_ptr
<reply
> rep
) override
{
731 rep
->write_body("json", _write_func
);
732 return make_ready_future
<std::unique_ptr
<reply
>>(std::move(rep
));
736 SEASTAR_TEST_CASE(content_length_limit
) {
737 return seastar::async([] {
738 loopback_connection_factory lcf
;
739 http_server
server("test");
740 server
.set_content_length_limit(11);
741 loopback_socket_impl
lsi(lcf
);
742 httpd::http_server_tester::listeners(server
).emplace_back(lcf
.get_server_socket());
744 future
<> client
= seastar::async([&lsi
] {
745 connected_socket c_socket
= lsi
.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
746 input_stream
<char> input(c_socket
.input());
747 output_stream
<char> output(c_socket
.output());
749 output
.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\n\r\n")).get();
750 output
.flush().get();
751 auto resp
= input
.read().get0();
752 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("200 OK"), std::string::npos
);
754 output
.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 11\r\n\r\nxxxxxxxxxxx")).get();
755 output
.flush().get();
756 resp
= input
.read().get0();
757 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("200 OK"), std::string::npos
);
759 output
.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\n\r\nxxxxxxxxxxxxxxxx")).get();
760 output
.flush().get();
761 resp
= input
.read().get0();
762 BOOST_REQUIRE_EQUAL(std::string(resp
.get(), resp
.size()).find("200 OK"), std::string::npos
);
763 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("413 Payload Too Large"), std::string::npos
);
766 output
.close().get();
769 auto handler
= new json_test_handler(json::stream_object("hello"));
770 server
._routes
.put(GET
, "/test", handler
);
771 server
.do_accepts(0).get();
778 SEASTAR_TEST_CASE(test_100_continue
) {
779 return seastar::async([] {
780 loopback_connection_factory lcf
;
781 http_server
server("test");
782 server
.set_content_length_limit(11);
783 loopback_socket_impl
lsi(lcf
);
784 httpd::http_server_tester::listeners(server
).emplace_back(lcf
.get_server_socket());
785 future
<> client
= seastar::async([&lsi
] {
786 connected_socket c_socket
= lsi
.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
787 input_stream
<char> input(c_socket
.input());
788 output_stream
<char> output(c_socket
.output());
790 for (auto version
: {sstring("1.0"), sstring("1.1")}) {
791 for (auto content
: {sstring(""), sstring("xxxxxxxxxxx")}) {
792 for (auto expect
: {sstring(""), sstring("Expect: 100-continue\r\n"), sstring("Expect: 100-cOnTInUE\r\n")}) {
793 auto content_len
= content
.empty() ? sstring("") : (sstring("Content-Length: ") + to_sstring(content
.length()) + sstring("\r\n"));
794 sstring req
= sstring("GET /test HTTP/") + version
+ sstring("\r\nHost: test\r\nConnection: Keep-Alive\r\n") + content_len
+ expect
+ sstring("\r\n");
795 output
.write(req
).get();
796 output
.flush().get();
797 bool already_ok
= false;
798 if (version
== "1.1" && expect
.length()) {
799 auto resp
= input
.read().get0();
800 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("100 Continue"), std::string::npos
);
801 already_ok
= content
.empty() && std::string(resp
.get(), resp
.size()).find("200 OK") != std::string::npos
;
804 //If the body is empty, the final response might have already been read
805 output
.write(content
).get();
806 output
.flush().get();
807 auto resp
= input
.read().get0();
808 BOOST_REQUIRE_EQUAL(std::string(resp
.get(), resp
.size()).find("100 Continue"), std::string::npos
);
809 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("200 OK"), std::string::npos
);
814 output
.write(sstring("GET /test HTTP/1.1\r\nHost: test\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
815 output
.flush().get();
816 auto resp
= input
.read().get0();
817 BOOST_REQUIRE_EQUAL(std::string(resp
.get(), resp
.size()).find("100 Continue"), std::string::npos
);
818 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("413 Payload Too Large"), std::string::npos
);
821 output
.close().get();
824 auto handler
= new json_test_handler(json::stream_object("hello"));
825 server
._routes
.put(GET
, "/test", handler
);
826 server
.do_accepts(0).get();
834 SEASTAR_TEST_CASE(test_unparsable_request
) {
835 // Test if a message that cannot be parsed as a http request is being replied with a 400 Bad Request response
836 return seastar::async([] {
837 loopback_connection_factory lcf
;
838 http_server
server("test");
839 loopback_socket_impl
lsi(lcf
);
840 httpd::http_server_tester::listeners(server
).emplace_back(lcf
.get_server_socket());
841 future
<> client
= seastar::async([&lsi
] {
842 connected_socket c_socket
= lsi
.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get0();
843 input_stream
<char> input(c_socket
.input());
844 output_stream
<char> output(c_socket
.output());
846 output
.write(sstring("GET /test HTTP/1.1\r\nhello\r\nContent-Length: 17\r\nExpect: 100-continue\r\n\r\n")).get();
847 output
.flush().get();
848 auto resp
= input
.read().get0();
849 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("400 Bad Request"), std::string::npos
);
850 BOOST_REQUIRE_NE(std::string(resp
.get(), resp
.size()).find("Can't parse the request"), std::string::npos
);
853 output
.close().get();
856 auto handler
= new json_test_handler(json::stream_object("hello"));
857 server
._routes
.put(GET
, "/test", handler
);
858 server
.do_accepts(0).get();
865 SEASTAR_TEST_CASE(case_insensitive_header
) {
866 std::unique_ptr
<seastar::httpd::request
> req
= std::make_unique
<seastar::httpd::request
>();
867 req
->_headers
["conTEnt-LengtH"] = "17";
868 BOOST_REQUIRE_EQUAL(req
->get_header("content-length"), "17");
869 BOOST_REQUIRE_EQUAL(req
->get_header("Content-Length"), "17");
870 BOOST_REQUIRE_EQUAL(req
->get_header("cOnTeNT-lEnGTh"), "17");
871 return make_ready_future
<>();
874 SEASTAR_THREAD_TEST_CASE(multiple_connections
) {
875 loopback_connection_factory lcf
;
876 http_server
server("test");
877 httpd::http_server_tester::listeners(server
).emplace_back(lcf
.get_server_socket());
878 socket_address addr
{ipv4_addr()};
880 std::vector
<connected_socket
> socks
;
881 // Make sure one shard has two connections pending.
882 for (unsigned i
= 0; i
<= smp::count
; ++i
) {
883 socks
.push_back(loopback_socket_impl(lcf
).connect(addr
, addr
).get0());
886 server
.do_accepts(0).get();
888 lcf
.destroy_all_shards().get();