From 9c5db6ddcf4860f62c8191c4a95b1428773c1b01 Mon Sep 17 00:00:00 2001 From: Mathieu Stefani Date: Thu, 11 Dec 2014 15:15:14 -0500 Subject: [PATCH 1/2] Added a synchronous interface to the HttpClient --- service/http_client.cc | 72 ++++++++++++++++++++++++++ service/http_client.h | 78 ++++++++++++++++++++++++++++- service/testing/http_client_test.cc | 43 ++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/service/http_client.cc b/service/http_client.cc index 92882b98..2ea82a3f 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); } +HttpResponse +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(); +} + +HttpResponse +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(); +} + +HttpResponse +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(); +} + +HttpResponse +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..d42d7d69 100644 --- a/service/http_client.h +++ b/service/http_client.h @@ -24,7 +24,8 @@ #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" namespace Datacratic { @@ -101,6 +102,7 @@ struct HttpRequest { }; + /****************************************************************************/ /* HTTP CLIENT IMPL */ /****************************************************************************/ @@ -160,6 +162,50 @@ enum struct HttpClientError { std::ostream & operator << (std::ostream & stream, HttpClientError error); +struct HttpSyncFuture { + + HttpSyncFuture() + : done(0) + , response(0, "", "") + { + } + + operator + std::function + () + { + return [=](const HttpRequest&, HttpClientError error, int statusCode, + std::string&& headers, std::string&& body) { + if (error != HttpClientError::None) { + } + else { + response.responseCode = statusCode; + response.body = std::move(body); + + HttpHeader header; + header.parse(headers); + + std::copy( + std::begin(header.headers), std::end(header.headers), + std::back_inserter(response.extraHeaders)); + } + + done = 1; + ML::futex_wake(done); + }; + } + + HttpResponse get() const { + while (!done) + ML::futex_wait(done, 0); + + return response; + } + +private: + int done; + HttpResponse response; +}; /****************************************************************************/ /* HTTP CLIENT */ @@ -241,6 +287,13 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous GET request. + */ + HttpResponse 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 +310,14 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous POST request + */ + HttpResponse 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 +333,14 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous PUT request. + */ + HttpResponse 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 +357,13 @@ struct HttpClient : public AsyncEventSource { queryParams, headers, timeout); } + /** Performs a synchronous DELETE request. + */ + HttpResponse 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/testing/http_client_test.cc b/service/testing/http_client_test.cc index e9583f4c..1652bece 100644 --- a/service/testing/http_client_test.cc +++ b/service/testing/http_client_test.cc @@ -607,3 +607,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.responseCode, 200); + } + + { + auto resp = client->getSync("/not-found"); + BOOST_CHECK_EQUAL(resp.responseCode, 404); + } + + { + auto resp = client->postSync("/foo"); + BOOST_CHECK_EQUAL(resp.responseCode, 200); + } + + { + auto resp = client->putSync("/ying"); + BOOST_CHECK_EQUAL(resp.responseCode, 200); + } +} From 98fbc163408baa419a057075841a4a290bba97ec Mon Sep 17 00:00:00 2001 From: Mathieu Stefani Date: Wed, 17 Dec 2014 17:09:56 -0500 Subject: [PATCH 2/2] HttpRestProxy::Response is now HttpClientResponse and is shared between HttpRestProxy and HttpClient --- jml | 2 +- jml-build | 2 +- service/http_client.cc | 8 ++-- service/http_client.h | 70 +++++++++++++++++++++++------ service/http_rest_proxy.h | 48 ++------------------ service/testing/http_client_test.cc | 8 ++-- 6 files changed, 70 insertions(+), 68 deletions(-) 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 2ea82a3f..0b55f6cc 100644 --- a/service/http_client.cc +++ b/service/http_client.cc @@ -110,7 +110,7 @@ HttpClient(const string & baseUrl, int numParallel, int queueSize, enablePipelining(false); } -HttpResponse +HttpClientResponse HttpClient::getSync( const std::string& resource, const RestParams& queryParams, @@ -127,7 +127,7 @@ HttpClient::getSync( return future.get(); } -HttpResponse +HttpClientResponse HttpClient::postSync( const std::string& resource, const HttpRequest::Content& content, @@ -146,7 +146,7 @@ HttpClient::postSync( return future.get(); } -HttpResponse +HttpClientResponse HttpClient::putSync( const std::string& resource, const HttpRequest::Content& content, @@ -165,7 +165,7 @@ HttpClient::putSync( return future.get(); } -HttpResponse +HttpClientResponse HttpClient::delSync( const std::string& resource, const RestParams& queryParams, diff --git a/service/http_client.h b/service/http_client.h index d42d7d69..361f921d 100644 --- a/service/http_client.h +++ b/service/http_client.h @@ -26,6 +26,7 @@ #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 { @@ -33,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 */ @@ -166,7 +212,6 @@ struct HttpSyncFuture { HttpSyncFuture() : done(0) - , response(0, "", "") { } @@ -177,17 +222,14 @@ struct HttpSyncFuture { return [=](const HttpRequest&, HttpClientError error, int statusCode, std::string&& headers, std::string&& body) { if (error != HttpClientError::None) { + response.errorCode_ = static_cast(error); } else { - response.responseCode = statusCode; - response.body = std::move(body); + response.code_ = statusCode; + response.body_ = std::move(body); - HttpHeader header; - header.parse(headers); + response.header_.parse(headers); - std::copy( - std::begin(header.headers), std::end(header.headers), - std::back_inserter(response.extraHeaders)); } done = 1; @@ -195,7 +237,7 @@ struct HttpSyncFuture { }; } - HttpResponse get() const { + HttpClientResponse get() const { while (!done) ML::futex_wait(done, 0); @@ -204,7 +246,7 @@ struct HttpSyncFuture { private: int done; - HttpResponse response; + HttpClientResponse response; }; /****************************************************************************/ @@ -289,7 +331,7 @@ struct HttpClient : public AsyncEventSource { /** Performs a synchronous GET request. */ - HttpResponse getSync(const std::string & resource, + HttpClientResponse getSync(const std::string & resource, const RestParams & queryParams = RestParams(), const RestParams & headers = RestParams(), int timeout = -1); @@ -312,7 +354,7 @@ struct HttpClient : public AsyncEventSource { /** Performs a synchronous POST request */ - HttpResponse postSync(const std::string & resource, + HttpClientResponse postSync(const std::string & resource, const HttpRequest::Content & content = HttpRequest::Content(), const RestParams & queryParams = RestParams(), const RestParams & headers = RestParams(), @@ -335,7 +377,7 @@ struct HttpClient : public AsyncEventSource { /** Performs a synchronous PUT request. */ - HttpResponse putSync(const std::string & resource, + HttpClientResponse putSync(const std::string & resource, const HttpRequest::Content & content = HttpRequest::Content(), const RestParams & queryParams = RestParams(), const RestParams & headers = RestParams(), @@ -359,7 +401,7 @@ struct HttpClient : public AsyncEventSource { /** Performs a synchronous DELETE request. */ - HttpResponse delSync(const std::string & resource, + HttpClientResponse delSync(const std::string & resource, const RestParams & queryParams = RestParams(), const RestParams & headers = RestParams(), int timeout = -1); 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 71f44a5c..efbaf267 100644 --- a/service/testing/http_client_test.cc +++ b/service/testing/http_client_test.cc @@ -634,21 +634,21 @@ BOOST_AUTO_TEST_CASE( test_synchronous_requests ) { auto resp = client->getSync("/hello"); - BOOST_CHECK_EQUAL(resp.responseCode, 200); + BOOST_CHECK_EQUAL(resp.code_, 200); } { auto resp = client->getSync("/not-found"); - BOOST_CHECK_EQUAL(resp.responseCode, 404); + BOOST_CHECK_EQUAL(resp.code_, 404); } { auto resp = client->postSync("/foo"); - BOOST_CHECK_EQUAL(resp.responseCode, 200); + BOOST_CHECK_EQUAL(resp.code_, 200); } { auto resp = client->putSync("/ying"); - BOOST_CHECK_EQUAL(resp.responseCode, 200); + BOOST_CHECK_EQUAL(resp.code_, 200); } }