diff --git a/jml b/jml index 507e5578..dc01d758 160000 --- a/jml +++ b/jml @@ -1 +1 @@ -Subproject commit 507e55784271a6c88d0a924c5d0f581dbb1824e7 +Subproject commit dc01d75892b54caa8ea8d9c48ebb00d736f5fb90 diff --git a/jml-build b/jml-build index e46878a2..e188b79d 160000 --- a/jml-build +++ b/jml-build @@ -1 +1 @@ -Subproject commit e46878a2eb3a4e8022e786ed702a4d7cbeae066e +Subproject commit e188b79da502f70ce2170b028953bdd9a42439c4 diff --git a/service/http_client.cc b/service/http_client.cc index 92882b98..0b55f6cc 100644 --- a/service/http_client.cc +++ b/service/http_client.cc @@ -110,6 +110,78 @@ HttpClient(const string & baseUrl, int numParallel, int queueSize, enablePipelining(false); } +HttpClientResponse +HttpClient::getSync( + const std::string& resource, + const RestParams& queryParams, + const RestParams& headers, + int timeout) +{ + HttpSyncFuture future; + if (!get(resource, + std::make_shared(future), + queryParams, headers, timeout)) { + throw ML::Exception("Failed to enqueue request"); + } + + return future.get(); +} + +HttpClientResponse +HttpClient::postSync( + const std::string& resource, + const HttpRequest::Content& content, + const RestParams& queryParams, + const RestParams& headers, + int timeout) +{ + HttpSyncFuture future; + if (!post(resource, + std::make_shared(future), + content, + queryParams, headers, timeout)) { + throw ML::Exception("Failed to enqueue request"); + } + + return future.get(); +} + +HttpClientResponse +HttpClient::putSync( + const std::string& resource, + const HttpRequest::Content& content, + const RestParams& queryParams, + const RestParams& headers, + int timeout) +{ + HttpSyncFuture future; + if (!put(resource, + std::make_shared(future), + content, + queryParams, headers, timeout)) { + throw ML::Exception("Failed to enqueue request"); + } + + return future.get(); +} + +HttpClientResponse +HttpClient::delSync( + const std::string& resource, + const RestParams& queryParams, + const RestParams& headers, + int timeout) +{ + HttpSyncFuture future; + + if (!del(resource, + std::make_shared(future), + queryParams, headers, timeout)) { + throw ML::Exception("Failed to enqueue request"); + } + + return future.get(); +} /****************************************************************************/ /* HTTP CLIENT CALLBACKS */ diff --git a/service/http_client.h b/service/http_client.h index 365b3c94..361f921d 100644 --- a/service/http_client.h +++ b/service/http_client.h @@ -24,7 +24,9 @@ #include "soa/jsoncpp/value.h" #include "soa/service/async_event_source.h" #include "soa/service/http_header.h" - +#include "soa/service/http_endpoint.h" +#include "jml/arch/futex.h" +#include "jml/utils/string_functions.h" namespace Datacratic { @@ -32,6 +34,51 @@ namespace Datacratic { struct HttpClientCallbacks; +/** The response of a request. Has a return code and a body. */ +struct HttpClientResponse { + HttpClientResponse() + : code_(0), errorCode_(0) + { + } + + /** Return code of the REST call. */ + int code() const { + return code_; + } + + /** Body of the REST call. */ + std::string body() const + { + return body_; + } + + Json::Value jsonBody() const + { + return Json::parse(body_); + } + + /** Get the given response header of the REST call. */ + std::string getHeader(const std::string & name) const + { + auto it = header_.headers.find(name); + if (it == header_.headers.end()) + it = header_.headers.find(ML::lowercase(name)); + if (it == header_.headers.end()) + throw ML::Exception("required header " + name + " not found"); + return it->second; + } + + long code_; + std::string body_; + HttpHeader header_; + + /// Error code for request, normally a CURL code, 0 is OK + int errorCode_; + + /// Error string for an error request, empty is OK + std::string errorMessage_; +}; + /****************************************************************************/ /* HTTP REQUEST */ @@ -101,6 +148,7 @@ struct HttpRequest { }; + /****************************************************************************/ /* HTTP CLIENT IMPL */ /****************************************************************************/ @@ -160,6 +208,46 @@ enum struct HttpClientError { std::ostream & operator << (std::ostream & stream, HttpClientError error); +struct HttpSyncFuture { + + HttpSyncFuture() + : done(0) + { + } + + operator + std::function + () + { + return [=](const HttpRequest&, HttpClientError error, int statusCode, + std::string&& headers, std::string&& body) { + if (error != HttpClientError::None) { + response.errorCode_ = static_cast(error); + } + else { + response.code_ = statusCode; + response.body_ = std::move(body); + + response.header_.parse(headers); + + } + + done = 1; + ML::futex_wake(done); + }; + } + + HttpClientResponse get() const { + while (!done) + ML::futex_wait(done, 0); + + return response; + } + +private: + int done; + HttpClientResponse response; +}; /****************************************************************************/ /* HTTP CLIENT */ @@ -241,6 +329,13 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous GET request. + */ + HttpClientResponse getSync(const std::string & resource, + const RestParams & queryParams = RestParams(), + const RestParams & headers = RestParams(), + int timeout = -1); + /** Performs a POST request, using similar parameters as get with the * addition of "content" which defines the contents body and type. * @@ -257,6 +352,14 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous POST request + */ + HttpClientResponse postSync(const std::string & resource, + const HttpRequest::Content & content = HttpRequest::Content(), + const RestParams & queryParams = RestParams(), + const RestParams & headers = RestParams(), + int timeout = -1); + /** Performs a PUT request in a similar fashion to "post" above. * * Returns "true" when the request could successfully be enqueued. @@ -272,6 +375,14 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous PUT request. + */ + HttpClientResponse putSync(const std::string & resource, + const HttpRequest::Content & content = HttpRequest::Content(), + const RestParams & queryParams = RestParams(), + const RestParams & headers = RestParams(), + int timeout = -1); + /** Performs a DELETE request. Note that this method cannot be named * "delete", which is a reserved keyword in C++. * @@ -288,6 +399,13 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous DELETE request. + */ + HttpClientResponse delSync(const std::string & resource, + const RestParams & queryParams = RestParams(), + const RestParams & headers = RestParams(), + int timeout = -1); + /** Enqueue (or perform) the specified request */ bool enqueueRequest(const std::string & verb, const std::string & resource, diff --git a/service/http_rest_proxy.h b/service/http_rest_proxy.h index f9feaa54..e5a696b4 100644 --- a/service/http_rest_proxy.h +++ b/service/http_rest_proxy.h @@ -11,6 +11,7 @@ #include "jml/utils/string_functions.h" #include "soa/types/value_description.h" #include "soa/service/http_endpoint.h" +#include "soa/service/http_client.h" namespace curlpp { @@ -36,6 +37,8 @@ struct HttpRestProxy { { } + typedef HttpClientResponse Response; + void init(const std::string & serviceUri) { this->serviceUri = serviceUri; @@ -43,50 +46,6 @@ struct HttpRestProxy { ~HttpRestProxy(); - /** The response of a request. Has a return code and a body. */ - struct Response { - Response() - : code_(0), errorCode_(0) - { - } - - /** Return code of the REST call. */ - int code() const { - return code_; - } - - /** Body of the REST call. */ - std::string body() const - { - return body_; - } - - Json::Value jsonBody() const - { - return Json::parse(body_); - } - - /** Get the given response header of the REST call. */ - std::string getHeader(const std::string & name) const - { - auto it = header_.headers.find(name); - if (it == header_.headers.end()) - it = header_.headers.find(ML::lowercase(name)); - if (it == header_.headers.end()) - throw ML::Exception("required header " + name + " not found"); - return it->second; - } - - long code_; - std::string body_; - HttpHeader header_; - - /// Error code for request, normally a CURL code, 0 is OK - int errorCode_; - - /// Error string for an error request, empty is OK - std::string errorMessage_; - }; /** Add a cookie to the connection that comes in from the response. */ void setCookieFromResponse(const Response& r) @@ -100,6 +59,7 @@ struct HttpRestProxy { cookies.push_back("Set-Cookie: " + value); } + /** Structure used to hold content for a POST request. */ struct Content { Content() diff --git a/service/testing/http_client_test.cc b/service/testing/http_client_test.cc index 5817c52f..efbaf267 100644 --- a/service/testing/http_client_test.cc +++ b/service/testing/http_client_test.cc @@ -609,3 +609,46 @@ BOOST_AUTO_TEST_CASE( test_http_client_expect_100_continue ) service.shutdown(); } #endif + +BOOST_AUTO_TEST_CASE( test_synchronous_requests ) +{ + ML::Watchdog watchdog(10); + cerr << "client_expect_100_continue" << endl; + + auto proxies = make_shared(); + + HttpGetService service(proxies); + service.addResponse("GET", "/hello", 200, "world"); + service.addResponse("POST", "/foo", 200, "bar"); + service.addResponse("PUT", "/ying", 200, "yang"); + service.start(); + + string baseUrl("http://127.0.0.1:" + + to_string(service.port())); + + auto client = make_shared(baseUrl, 4); + + MessageLoop loop; + loop.addSource("HttpClient", client); + loop.start(); + + { + auto resp = client->getSync("/hello"); + BOOST_CHECK_EQUAL(resp.code_, 200); + } + + { + auto resp = client->getSync("/not-found"); + BOOST_CHECK_EQUAL(resp.code_, 404); + } + + { + auto resp = client->postSync("/foo"); + BOOST_CHECK_EQUAL(resp.code_, 200); + } + + { + auto resp = client->putSync("/ying"); + BOOST_CHECK_EQUAL(resp.code_, 200); + } +}