diff --git a/CMakeLists.txt b/CMakeLists.txt index 018735618ff7493b3923d0663898b7afbff9cfa6..55e296d486ea952f7320d5d4d01316fbc1d5a12c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,7 @@ if (OPENDHT_TESTS) ${CMAKE_THREAD_LIBS_INIT} ${CPPUNIT_LIBRARIES} ${GNUTLS_LIBRARIES} - ssl crypto + ssl crypto jsoncpp ) enable_testing() add_test(TEST opendht_unit_tests) diff --git a/include/opendht/dht_proxy_server.h b/include/opendht/dht_proxy_server.h index 8ffd9ac8a67e76d988985fabc7ab30d70dc892a2..685c56ccc75e7c06821778e0c448954f45a61ca0 100644 --- a/include/opendht/dht_proxy_server.h +++ b/include/opendht/dht_proxy_server.h @@ -83,6 +83,8 @@ public: DhtProxyServer& operator=(const DhtProxyServer& other) = delete; DhtProxyServer& operator=(DhtProxyServer&& other) = delete; + asio::io_context& io_context() const; + struct ServerStats { /** Current number of listen operations */ size_t listenCount; @@ -310,8 +312,6 @@ private: void handlePrintStats(const asio::error_code &ec); - asio::io_context& io_context() const; - using clock = std::chrono::steady_clock; using time_point = clock::time_point; diff --git a/include/opendht/http.h b/include/opendht/http.h index 2b70366cd7e7050e3b354cb59c662d5402b03c42..c087e9c1933612c88440f87b3ac375453613db3d 100644 --- a/include/opendht/http.h +++ b/include/opendht/http.h @@ -34,6 +34,7 @@ #include <memory> #include <queue> +#include <json/json.h> extern "C" { struct http_parser; @@ -200,8 +201,11 @@ public: using OnStatusCb = std::function<void(unsigned int status_code)>; using OnDataCb = std::function<void(const char* at, size_t length)>; using OnStateChangeCb = std::function<void(State state, const Response& response)>; + using OnJsonCb = std::function<void(Json::Value value, unsigned int status_code)>; // resolves implicitly + Request(asio::io_context& ctx, const std::string& url, const Json::Value& json, OnJsonCb jsoncb, + std::shared_ptr<dht::Logger> logger = {}); Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger = {}); Request(asio::io_context& ctx, const std::string& host, const std::string& service, const bool ssl = false, std::shared_ptr<dht::Logger> logger = {}); diff --git a/src/http.cpp b/src/http.cpp index b60629ff0cdeab3cbee3274bb47ac38f59a1afb8..2ffc60a2671bad04e8875b4344c518b6e9e413bc 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -401,11 +401,42 @@ Resolver::resolve(const std::string& host, const std::string& service) unsigned int Request::ids_ = 1; + +Request::Request(asio::io_context& ctx, const std::string& url, const Json::Value& json, OnJsonCb jsoncb, + std::shared_ptr<dht::Logger> logger) + : id_(Request::ids_++), ctx_(ctx), logger_(logger) +{ + cbs_ = std::make_unique<Callbacks>(); + resolver_ = std::make_shared<Resolver>(ctx, url, logger_); + + set_header_field(restinio::http_field_t::host, get_url().host + ":" + get_url().service); + set_target(resolver_->get_url().target); + set_header_field(restinio::http_field_t::content_type, "application/json"); + set_header_field(restinio::http_field_t::accept, "application/json"); + Json::StreamWriterBuilder wbuilder; + set_body(Json::writeString(wbuilder, json)); + + add_on_state_change_callback([this, jsoncb](State state, const Response& response){ + if (state != Request::State::DONE) + return; + Json::Value json; + std::string err; + Json::CharReaderBuilder rbuilder; + auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); + if (!reader->parse(response.body.data(), response.body.data() + response.body.size(), &json, &err) and logger_) + logger_->e("[http:client] [request:%i] can't parse response to json", id_, err.c_str()); + if (jsoncb) + jsoncb(json, response.status_code); + }); +} + Request::Request(asio::io_context& ctx, const std::string& url, std::shared_ptr<dht::Logger> logger) : id_(Request::ids_++), ctx_(ctx), logger_(logger) { cbs_ = std::make_unique<Callbacks>(); resolver_ = std::make_shared<Resolver>(ctx, url, logger_); + + set_header_field(restinio::http_field_t::host, get_url().host + ":" + get_url().service); set_target(resolver_->get_url().target); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 3515a15f8ad7d5583408f1f8f74af5a824dd4d66..13ea421dc2fe9641aeb49af949cb0367639fc826 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,9 +1,9 @@ if ENABLE_TESTS bin_PROGRAMS = opendht_unit_tests -AM_CPPFLAGS = -I../include +AM_CPPFLAGS = -I../include -DOPENDHT_JSONCPP nobase_include_HEADERS = infohashtester.h valuetester.h cryptotester.h dhtrunnertester.h httptester.h dhtproxytester.h opendht_unit_tests_SOURCES = tests_runner.cpp cryptotester.cpp infohashtester.cpp valuetester.cpp dhtrunnertester.cpp httptester.cpp dhtproxytester.cpp -opendht_unit_tests_LDFLAGS = -lopendht -lcppunit -L@top_builddir@/src/.libs @GnuTLS_LIBS@ +opendht_unit_tests_LDFLAGS = -lopendht -lcppunit -ljsoncpp -L@top_builddir@/src/.libs @GnuTLS_LIBS@ endif diff --git a/tests/compile_http.sh b/tests/compile_http.sh new file mode 100755 index 0000000000000000000000000000000000000000..0f2e1a08b2f8acf860f3b3c5000c6779ac6fdb94 --- /dev/null +++ b/tests/compile_http.sh @@ -0,0 +1 @@ +g++ -Wall -Wextra -Wfatal-errors -pedantic -g ../src/http.cpp tests_runner.cpp httptester.cpp -o run.out -lopendht -lpthread -lcppunit -ljsoncpp -DOPENDHT_JSONCPP -I../include/opendht -DASIO_STANDALONE diff --git a/tests/dhtproxytester.cpp b/tests/dhtproxytester.cpp index 2153870d723469e5d747475f46946bbc9cd603db..97db95787544e03b307cf71d367bb828b6316dd8 100644 --- a/tests/dhtproxytester.cpp +++ b/tests/dhtproxytester.cpp @@ -46,7 +46,7 @@ DhtProxyTester::setUp() { serverProxy = std::unique_ptr<dht::DhtProxyServer>( new dht::DhtProxyServer( - ///*http*/nullptr, + ///*http*/dht::crypto::Identity{}, /*https*/serverIdentity, nodeProxy, 8080, /*pushServer*/"127.0.0.1:8090", logger)); diff --git a/tests/httptester.cpp b/tests/httptester.cpp index 0a6a861573625432bf89006cf6999adf7a13a34b..2493683c004298a688ab6f47fe8d763eb223ae13 100644 --- a/tests/httptester.cpp +++ b/tests/httptester.cpp @@ -33,10 +33,27 @@ CPPUNIT_TEST_SUITE_REGISTRATION(HttpTester); void HttpTester::setUp() { logger = dht::log::getStdLogger(); + + nodePeer.run(0, /*identity*/{}, /*threaded*/true); + + nodeProxy = std::make_shared<dht::DhtRunner>(); + nodeProxy->run(0, /*identity*/{}, /*threaded*/true); + nodeProxy->bootstrap(nodePeer.getBound()); + + serverProxy = std::unique_ptr<dht::DhtProxyServer>( + new dht::DhtProxyServer( + /*http*/dht::crypto::Identity{}, nodeProxy, 8080, /*pushServer*/"127.0.0.1:8090", logger)); + } void HttpTester::tearDown() { + logger->d("[tester:http] stopping peer node"); + nodePeer.join(); + logger->d("[tester:http] stopping proxy server"); + serverProxy.reset(nullptr); + logger->d("[tester:http] stopping proxy node"); + nodeProxy->join(); } void @@ -210,4 +227,38 @@ HttpTester::test_parse_url_target_ipv6() { CPPUNIT_ASSERT(parsed.target == "/going/under"); } +void +HttpTester::test_send_json() { + // Arrange + std::condition_variable cv; + std::mutex cv_m; + std::unique_lock<std::mutex> lk(cv_m); + bool done = false; + + dht::Value val {"hey"}; + auto json = val.toJson(); + json["permanent"] = false; + std::cout << "[test_send_json] sending:\n" << json << std::endl; + Json::Value resp_val; + unsigned int status = 0; + std::string url = "http://127.0.0.1:8080/key"; + // Act + auto request = std::make_shared<dht::http::Request>(serverProxy->io_context(), url, json, + [this, &cv, &done, &status, &resp_val](Json::Value value, unsigned int status_code){ + if (status_code != 200 and logger) + logger->e("[tester] [status] failed with code=%i", status_code); + std::cout << "[tester] got response:\n" << value << std::endl; + resp_val = value; + status = status_code; + done = true; + cv.notify_all(); + }, logger); + request->set_method(restinio::http_method_post()); + request->send(); + // Assert + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&]{ return done; })); + CPPUNIT_ASSERT(status == 200); + CPPUNIT_ASSERT(resp_val["data"] == val.toJson()["data"]); +} + } // namespace test diff --git a/tests/httptester.h b/tests/httptester.h index 83df193f544c85bb45f716aacad4a2ee98dec183..862ebec0247408b19cc5a458eab79467f33113a3 100644 --- a/tests/httptester.h +++ b/tests/httptester.h @@ -23,13 +23,18 @@ #include <cppunit/TestFixture.h> #include <cppunit/extensions/HelperMacros.h> +#include <opendht/value.h> #include <opendht/log.h> #include <opendht/http.h> +#include <opendht/dhtrunner.h> +#include <opendht/dht_proxy_server.h> + namespace test { class HttpTester : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(HttpTester); + // parse_url CPPUNIT_TEST(test_parse_url); CPPUNIT_TEST(test_parse_https_url_no_service); CPPUNIT_TEST(test_parse_url_no_prefix_no_target); @@ -42,6 +47,8 @@ class HttpTester : public CppUnit::TestFixture { CPPUNIT_TEST(test_parse_url_ipv6); CPPUNIT_TEST(test_parse_url_no_prefix_no_target_ipv6); CPPUNIT_TEST(test_parse_url_target_ipv6); + // send + CPPUNIT_TEST(test_send_json); CPPUNIT_TEST_SUITE_END(); public: @@ -75,9 +82,17 @@ class HttpTester : public CppUnit::TestFixture { void test_parse_url_ipv6(); void test_parse_url_no_prefix_no_target_ipv6(); void test_parse_url_target_ipv6(); + /** + * Test send(json) + */ + void test_send_json(); private: std::shared_ptr<dht::Logger> logger {}; + + dht::DhtRunner nodePeer; + std::shared_ptr<dht::DhtRunner> nodeProxy; + std::unique_ptr<dht::DhtProxyServer> serverProxy; }; } // namespace test