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