/* * Copyright 2015 Cloudius Systems */ #include #include #include #include #include #include #include #include #include #include #include "loopback_socket.hh" #include #include #include #include #include using namespace seastar; using namespace httpd; class handl : public httpd::handler_base { public: virtual future > handle(const sstring& path, std::unique_ptr req, std::unique_ptr rep) { rep->done("html"); return make_ready_future>(std::move(rep)); } }; SEASTAR_TEST_CASE(test_reply) { reply r; r.set_content_type("txt"); BOOST_REQUIRE_EQUAL(r._headers["Content-Type"], sstring("text/plain")); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_str_matcher) { str_matcher m("/hello"); parameters param; BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_param_matcher) { param_matcher m("param"); parameters param; BOOST_REQUIRE_EQUAL(m.match("/abc/hello", 4, param), 10u); BOOST_REQUIRE_EQUAL(param.path("param"), "/hello"); BOOST_REQUIRE_EQUAL(param["param"], "hello"); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_match_rule) { parameters param; handl* h = new handl(); match_rule mr(h); mr.add_str("/hello").add_param("param"); httpd::handler_base* res = mr.get("/hello/val1", param); BOOST_REQUIRE_EQUAL(res, h); BOOST_REQUIRE_EQUAL(param["param"], "val1"); res = mr.get("/hell/val1", param); httpd::handler_base* nl = nullptr; BOOST_REQUIRE_EQUAL(res, nl); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_formatter) { BOOST_REQUIRE_EQUAL(json::formatter::to_json(true), "true"); BOOST_REQUIRE_EQUAL(json::formatter::to_json(false), "false"); BOOST_REQUIRE_EQUAL(json::formatter::to_json(1), "1"); const char* txt = "efg"; BOOST_REQUIRE_EQUAL(json::formatter::to_json(txt), "\"efg\""); sstring str = "abc"; BOOST_REQUIRE_EQUAL(json::formatter::to_json(str), "\"abc\""); float f = 1; BOOST_REQUIRE_EQUAL(json::formatter::to_json(f), "1"); f = 1.0/0.0; BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range); f = -1.0/0.0; BOOST_CHECK_THROW(json::formatter::to_json(f), std::out_of_range); f = 0.0/0.0; BOOST_CHECK_THROW(json::formatter::to_json(f), std::invalid_argument); double d = -1; BOOST_REQUIRE_EQUAL(json::formatter::to_json(d), "-1"); d = 1.0/0.0; BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range); d = -1.0/0.0; BOOST_CHECK_THROW(json::formatter::to_json(d), std::out_of_range); d = 0.0/0.0; BOOST_CHECK_THROW(json::formatter::to_json(d), std::invalid_argument); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_decode_url) { request req; req._url = "/a?q=%23%24%23"; sstring url = http_server::connection::set_query_param(req); BOOST_REQUIRE_EQUAL(url, "/a"); BOOST_REQUIRE_EQUAL(req.get_query_param("q"), "#$#"); req._url = "/a?a=%23%24%23&b=%22%26%22"; http_server::connection::set_query_param(req); BOOST_REQUIRE_EQUAL(req.get_query_param("a"), "#$#"); BOOST_REQUIRE_EQUAL(req.get_query_param("b"), "\"&\""); return make_ready_future<>(); } SEASTAR_TEST_CASE(test_routes) { handl* h1 = new handl(); handl* h2 = new handl(); routes route; route.add(operation_type::GET, url("/api").remainder("path"), h1); route.add(operation_type::GET, url("/"), h2); std::unique_ptr req = std::make_unique(); std::unique_ptr rep = std::make_unique(); auto f1 = route.handle("/api", std::move(req), std::move(rep)).then( [] (std::unique_ptr rep) { BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok); }); req.reset(new request); rep.reset(new reply); auto f2 = route.handle("/", std::move(req), std::move(rep)).then( [] (std::unique_ptr rep) { BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::ok); }); req.reset(new request); rep.reset(new reply); auto f3 = route.handle("/api/abc", std::move(req), std::move(rep)).then( [] (std::unique_ptr rep) { }); req.reset(new request); rep.reset(new reply); auto f4 = route.handle("/ap", std::move(req), std::move(rep)).then( [] (std::unique_ptr rep) { BOOST_REQUIRE_EQUAL((int )rep->_status, (int )reply::status_type::not_found); }); return when_all(std::move(f1), std::move(f2), std::move(f3), std::move(f4)) .then([] (std::tuple, future<>, future<>, future<>> fs) { std::get<0>(fs).get(); std::get<1>(fs).get(); std::get<2>(fs).get(); std::get<3>(fs).get(); }); } SEASTAR_TEST_CASE(test_json_path) { shared_ptr res1 = make_shared(false); shared_ptr res2 = make_shared(false); shared_ptr res3 = make_shared(false); shared_ptr route = make_shared(); path_description path1("/my/path",GET,"path1", {{"param1", path_description::url_component_type::PARAM} ,{"/text", path_description::url_component_type::FIXED_STRING}},{}); path_description path2("/my/path",GET,"path2", {{"param1", path_description::url_component_type::PARAM} ,{"param2", path_description::url_component_type::PARAM}},{}); path_description path3("/my/path",GET,"path3", {{"param1", path_description::url_component_type::PARAM} ,{"param2", path_description::url_component_type::PARAM_UNTIL_END_OF_PATH}},{}); path1.set(*route, [res1] (const_req req) { (*res1) = true; BOOST_REQUIRE_EQUAL(req.param["param1"], "value1"); return ""; }); path2.set(*route, [res2] (const_req req) { (*res2) = true; BOOST_REQUIRE_EQUAL(req.param["param1"], "value2"); BOOST_REQUIRE_EQUAL(req.param["param2"], "text1"); return ""; }); path3.set(*route, [res3] (const_req req) { (*res3) = true; BOOST_REQUIRE_EQUAL(req.param["param1"], "value3"); BOOST_REQUIRE_EQUAL(req.param["param2"], "text2/text3"); return ""; }); auto f1 = route->handle("/my/path/value1/text", std::make_unique(), std::make_unique()).then([res1, route] (auto f) { BOOST_REQUIRE_EQUAL(*res1, true); }); auto f2 = route->handle("/my/path/value2/text1", std::make_unique(), std::make_unique()).then([res2, route] (auto f) { BOOST_REQUIRE_EQUAL(*res2, true); }); auto f3 = route->handle("/my/path/value3/text2/text3", std::make_unique(), std::make_unique()).then([res3, route] (auto f) { BOOST_REQUIRE_EQUAL(*res3, true); }); return when_all(std::move(f1), std::move(f2), std::move(f3)) .then([] (std::tuple, future<>, future<>> fs) { std::get<0>(fs).get(); std::get<1>(fs).get(); std::get<2>(fs).get(); }); } /*! * \brief a helper data sink that stores everything it gets in a stringstream */ class memory_data_sink_impl : public data_sink_impl { std::stringstream& _ss; public: memory_data_sink_impl(std::stringstream& ss) : _ss(ss) { } virtual future<> put(net::packet data) override { abort(); return make_ready_future<>(); } virtual future<> put(temporary_buffer buf) override { _ss.write(buf.get(), buf.size()); return make_ready_future<>(); } virtual future<> flush() override { return make_ready_future<>(); } virtual future<> close() override { return make_ready_future<>(); } }; class memory_data_sink : public data_sink { public: memory_data_sink(std::stringstream& ss) : data_sink(std::make_unique(ss)) {} }; future<> test_transformer_stream(std::stringstream& ss, content_replace& cr, std::vector&& buffer_parts) { std::unique_ptr req = std::make_unique(); ss.str(""); req->_headers["Host"] = "localhost"; return do_with(output_stream(cr.transform(std::move(req), "json", output_stream(memory_data_sink(ss), 32000, true))), std::vector(std::move(buffer_parts)), [&ss, &cr] (output_stream& os, std::vector& parts) { return do_for_each(parts, [&os](auto& p) { return os.write(p); }).then([&os, &ss] { return os.close(); }); }); } SEASTAR_TEST_CASE(test_transformer) { return do_with(std::stringstream(), content_replace("json"), [] (std::stringstream& ss, content_replace& cr) { return do_with(output_stream(cr.transform(std::make_unique(), "html", output_stream(memory_data_sink(ss), 32000, true))), [&ss] (output_stream& os) { return os.write(sstring("hello-{{Protocol}}-xyz-{{Host}}")).then([&os] { return os.close(); }); }).then([&ss, &cr] () { BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Protocol}}-xyz-{{Host}}"); return test_transformer_stream(ss, cr, {"hell", "o-{", "{Pro", "tocol}}-xyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] { BOOST_REQUIRE_EQUAL(ss.str(), "hello-http-xyz-localhost{{Pr"); return test_transformer_stream(ss, cr, {"hell", "o-{{", "Pro", "tocol}}{{Protocol}}-{{Protoxyz-{{Ho", "st}}{{Pr"}).then([&ss, &cr] { BOOST_REQUIRE_EQUAL(ss.str(), "hello-httphttp-{{Protoxyz-localhost{{Pr"); return test_transformer_stream(ss, cr, {"hell", "o-{{Pro", "t{{Protocol}}ocol}}", "{{Host}}"}).then([&ss, &cr] { BOOST_REQUIRE_EQUAL(ss.str(), "hello-{{Prothttpocol}}localhost"); }); }); }); }); }); } struct http_consumer { std::map _headers; std::string _body; uint32_t _remain = 0; std::string _current; char last = '\0'; uint32_t _size = 0; bool _concat = true; enum class status_type { READING_HEADERS, CHUNK_SIZE, CHUNK_BODY, CHUNK_END, READING_BODY_BY_SIZE, DONE }; status_type status = status_type::READING_HEADERS; bool read(const temporary_buffer& b) { for (auto c : b) { if (last =='\r' && c == '\n') { if (_current == "") { if (status == status_type::READING_HEADERS || (status == status_type::CHUNK_BODY && _remain == 0)) { if (status == status_type::READING_HEADERS && _headers.find("Content-Length") != _headers.end()) { _remain = stoi(_headers["Content-Length"], nullptr, 16); if (_remain == 0) { status = status_type::DONE; break; } status = status_type::READING_BODY_BY_SIZE; } else { status = status_type::CHUNK_SIZE; } } else if (status == status_type::CHUNK_END) { status = status_type::DONE; break; } } else { switch (status) { case status_type::READING_HEADERS: add_header(_current); break; case status_type::CHUNK_SIZE: set_chunk(_current); break; default: break; } _current = ""; } last = '\0'; } else { if (last != '\0') { if (status == status_type::CHUNK_BODY || status == status_type::READING_BODY_BY_SIZE) { if (_concat) { _body = _body + last; } _size++; _remain--; if (_remain <= 1 && status == status_type::READING_BODY_BY_SIZE) { if (_concat) { _body = _body + c; } _size++; status = status_type::DONE; break; } } else { _current = _current + last; } } last = c; } } return status == status_type::DONE; } void set_chunk(const std::string& s) { _remain = stoi(s, nullptr, 16); if (_remain == 0) { status = status_type::CHUNK_END; } else { status = status_type::CHUNK_BODY; } } void add_header(const std::string& s) { std::vector strs; boost::split(strs, s, boost::is_any_of(":")); if (strs.size() > 1) { _headers[strs[0]] = strs[1]; } } }; class test_client_server { public: static future<> write_request(output_stream& output) { return output.write(sstring("GET /test HTTP/1.1\r\nHost: myhost.org\r\n\r\n")).then([&output]{ return output.flush(); }); } static future<> run_test(std::function(output_stream &&)>&& write_func, std::function reader) { return do_with(loopback_connection_factory(), foreign_ptr>(make_shared("test")), [reader, &write_func] (loopback_connection_factory& lcf, auto& server) { return do_with(loopback_socket_impl(lcf), [&server, &lcf, reader, &write_func](loopback_socket_impl& lsi) { httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket()); auto client = seastar::async([&lsi, reader] { connected_socket c_socket = std::get(lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get()); input_stream input(std::move(c_socket.input())); output_stream output(std::move(c_socket.output())); bool more = true; size_t count = 0; while (more) { http_consumer htp; htp._concat = false; write_request(output).get(); repeat([&c_socket, &input, &htp] { return input.read().then([&c_socket, &input, &htp](const temporary_buffer& b) mutable { return (b.size() == 0 || htp.read(b)) ? make_ready_future(stop_iteration::yes) : make_ready_future(stop_iteration::no); }); }).get(); std::cout << htp._body << std::endl; more = reader(count, htp); count++; } if (input.eof()) { input.close().get(); } }); auto writer = seastar::async([&server, &write_func] { class test_handler : public handler_base { size_t count = 0; http_server& _server; std::function(output_stream &&)> _write_func; promise<> _all_message_sent; public: test_handler(http_server& server, std::function(output_stream &&)>&& write_func) : _server(server), _write_func(write_func) { } future> handle(const sstring& path, std::unique_ptr req, std::unique_ptr rep) override { rep->write_body("json", std::move(_write_func)); count++; _all_message_sent.set_value(); return make_ready_future>(std::move(rep)); } future<> wait_for_message() { return _all_message_sent.get_future(); } }; auto handler = new test_handler(*server, std::move(write_func)); server->_routes.put(GET, "/test", handler); when_all(server->do_accepts(0), handler->wait_for_message()).get(); }); return when_all(std::move(client), std::move(writer)); }).discard_result().then_wrapped([&server] (auto f) { f.ignore_ready_future(); return server->stop(); }); }); } static future<> run(std::vector> tests) { return do_with(loopback_connection_factory(), foreign_ptr>(make_shared("test")), [tests] (loopback_connection_factory& lcf, auto& server) { return do_with(loopback_socket_impl(lcf), [&server, &lcf, tests](loopback_socket_impl& lsi) { httpd::http_server_tester::listeners(*server).emplace_back(lcf.get_server_socket()); auto client = seastar::async([&lsi, tests] { connected_socket c_socket = std::get(lsi.connect(socket_address(ipv4_addr()), socket_address(ipv4_addr())).get()); input_stream input(std::move(c_socket.input())); output_stream output(std::move(c_socket.output())); bool more = true; size_t count = 0; while (more) { http_consumer htp; write_request(output).get(); repeat([&c_socket, &input, &htp] { return input.read().then([&c_socket, &input, &htp](const temporary_buffer& b) mutable { return (b.size() == 0 || htp.read(b)) ? make_ready_future(stop_iteration::yes) : make_ready_future(stop_iteration::no); }); }).get(); if (std::get(tests[count])) { BOOST_REQUIRE_EQUAL(htp._body.length(), std::get(tests[count])); } else { BOOST_REQUIRE_EQUAL(input.eof(), true); more = false; } count++; if (count == tests.size()) { more = false; } } if (input.eof()) { input.close().get(); } }); auto writer = seastar::async([&server, tests] { class test_handler : public handler_base { size_t count = 0; http_server& _server; std::vector> _tests; promise<> _all_message_sent; public: test_handler(http_server& server, const std::vector>& tests) : _server(server), _tests(tests) { } future> handle(const sstring& path, std::unique_ptr req, std::unique_ptr rep) override { rep->write_body("txt", make_writer(std::get(_tests[count]), std::get(_tests[count]))); count++; if (count == _tests.size()) { _all_message_sent.set_value(); } return make_ready_future>(std::move(rep)); } future<> wait_for_message() { return _all_message_sent.get_future(); } }; auto handler = new test_handler(*server, tests); server->_routes.put(GET, "/test", handler); when_all(server->do_accepts(0), handler->wait_for_message()).get(); }); return when_all(std::move(client), std::move(writer)); }).discard_result().then_wrapped([&server] (auto f) { f.ignore_ready_future(); return server->stop(); }); }); } static noncopyable_function(output_stream&& o_stream)> make_writer(size_t len, bool success) { return [len, success] (output_stream&& o_stream) mutable { return do_with(output_stream(std::move(o_stream)), uint32_t(len/10), [success](output_stream& str, uint32_t& remain) { if (remain == 0) { if (success) { return str.close(); } else { throw std::runtime_error("Throwing exception before writing"); } } return repeat([&str, &remain, success] () mutable { return str.write("1234567890").then([&remain]() mutable { remain--; return (remain == 0)? make_ready_future(stop_iteration::yes) : make_ready_future(stop_iteration::no); }); }).then([&str, success] { if (!success) { return str.flush(); } return make_ready_future<>(); }).then([&str, success] { if (success) { return str.close(); } else { throw std::runtime_error("Throwing exception after writing"); } }); }); }; } }; SEASTAR_TEST_CASE(test_message_with_error_non_empty_body) { std::vector> tests = { std::make_tuple(true, 100), std::make_tuple(false, 10000)}; return test_client_server::run(tests); } SEASTAR_TEST_CASE(test_simple_chunked) { std::vector> tests = { std::make_tuple(true, 100000), std::make_tuple(true, 100)}; return test_client_server::run(tests); } SEASTAR_TEST_CASE(test_http_client_server_full) { std::vector> tests = { std::make_tuple(true, 100), std::make_tuple(true, 10000), std::make_tuple(true, 100), std::make_tuple(true, 0), std::make_tuple(true, 5000), std::make_tuple(true, 10000), std::make_tuple(true, 9000), std::make_tuple(true, 10000)}; return test_client_server::run(tests); } /* * return string in the given size * The string size takes the quotes into consideration. */ std::string get_value(int size) { std::stringstream res; for (auto i = 0; i < size - 2; i++) { res << "a"; } return res.str(); } /* * A helper object that map to a big json string * in the format of: * {"valu": "aaa....aa", "valu": "aaa....aa", "valu": "aaa....aa"...} * * The object can have an arbitrary size in multiplication of 10000 bytes * */ struct extra_big_object : public json::json_base { json::json_element* value; extra_big_object(size_t size) { value = new json::json_element; // size = brackets + (name + ": " + get_value) * n + ", " * (n-1) // size = 2 + (name + 6 + get_value) * n - 2 value->_name = "valu"; *value = get_value(9990); for (size_t i = 0; i < size/10000; i++) { _elements.emplace_back(value); } } virtual ~extra_big_object() { delete value; } extra_big_object(const extra_big_object& o) { value = new json::json_element; value->_name = o.value->_name; *value = (*o.value)(); for (size_t i = 0; i < o._elements.size(); i++) { _elements.emplace_back(value); } } extra_big_object(extra_big_object&&) = default; }; SEASTAR_TEST_CASE(json_stream) { std::vector vec; size_t num_objects = 1000; size_t total_size = num_objects * 1000001 + 1; for (size_t i = 0; i < num_objects; i++) { vec.emplace_back(extra_big_object(1000000)); } return test_client_server::run_test(json::stream_object(vec), [total_size](size_t s, http_consumer& h) { BOOST_REQUIRE_EQUAL(h._size, total_size); return false; }); }