Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jml
Submodule jml updated 1 files
+1 −2 arch/arch.mk
2 changes: 1 addition & 1 deletion jml-build
72 changes: 72 additions & 0 deletions service/http_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpClientSimpleCallbacks>(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<HttpClientSimpleCallbacks>(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<HttpClientSimpleCallbacks>(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<HttpClientSimpleCallbacks>(future),
queryParams, headers, timeout)) {
throw ML::Exception("Failed to enqueue request");
}

return future.get();
}

/****************************************************************************/
/* HTTP CLIENT CALLBACKS */
Expand Down
120 changes: 119 additions & 1 deletion service/http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,61 @@
#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 {

/* Forward declarations */

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 */
Expand Down Expand Up @@ -101,6 +148,7 @@ struct HttpRequest {
};



/****************************************************************************/
/* HTTP CLIENT IMPL */
/****************************************************************************/
Expand Down Expand Up @@ -160,6 +208,46 @@ enum struct HttpClientError {

std::ostream & operator << (std::ostream & stream, HttpClientError error);

struct HttpSyncFuture {

HttpSyncFuture()
: done(0)
{
}

operator
std::function<void (const HttpRequest &, HttpClientError, int, std::string &&, std::string &&)>
()
{
return [=](const HttpRequest&, HttpClientError error, int statusCode,
std::string&& headers, std::string&& body) {
if (error != HttpClientError::None) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si une erreur est retournee, elle doit faire partie de la reponse retournee ou lancer une exception. Ici elle est simplement ignoree, ce qui est dangereux.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I didn't know what do to with that since HttpResponse does not contain any errorCode field. I didn't want to add this field in the class directly because the class is used for server-side code (HttpEndpoint).

So I guess throwing an exception will do for now ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I didn't know what do to with that since HttpResponse does not
contain any errorCode field. I didn't want to add this field in the
class directly because the class is used for server-side code
(HttpEndpoint).

So I guess throwing an exception will do for now ?

What matters here is to have a behaviour that would be compatible with
the one from HttpRestProxy. But on the other hand, I think the passing
of error codes enables better practices with regards to error handling.
I would thus improve HttpResponse with an HttpClientError member instead.

response.errorCode_ = static_cast<int>(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 */
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
Expand All @@ -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++.
*
Expand All @@ -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,
Expand Down
48 changes: 4 additions & 44 deletions service/http_rest_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -36,57 +37,15 @@ struct HttpRestProxy {
{
}

typedef HttpClientResponse Response;

void init(const std::string & serviceUri)
{
this->serviceUri = serviceUri;
}

~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)
Expand All @@ -100,6 +59,7 @@ struct HttpRestProxy {
cookies.push_back("Set-Cookie: " + value);
}


/** Structure used to hold content for a POST request. */
struct Content {
Content()
Expand Down
43 changes: 43 additions & 0 deletions service/testing/http_client_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServiceProxies>();

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<HttpClient>(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);
}
}