examples/ubuntu: add HttpClient based on libevent
Based on the http-client sample in libevent source tree.
Still require hostname validation and ssl.
Bug: 24204632
Change-Id: Ifb34a3b8157aefc58e3f48ab241126b81187b934
Reviewed-on: https://weave-review.googlesource.com/1231
Reviewed-by: Johan Euphrosine <proppy@google.com>
diff --git a/libweave/examples/ubuntu/event_http_client.cc b/libweave/examples/ubuntu/event_http_client.cc
new file mode 100644
index 0000000..bdf265c
--- /dev/null
+++ b/libweave/examples/ubuntu/event_http_client.cc
@@ -0,0 +1,173 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "examples/ubuntu/event_http_client.h"
+#include "examples/ubuntu/event_task_runner.h"
+
+#include <weave/enum_to_string.h>
+
+#include <string>
+
+#include <base/bind.h>
+
+#include <event2/bufferevent.h>
+#include <event2/buffer.h>
+#include <event2/http.h>
+
+// EventHttpClient based on libevent2 http-client sample
+// TODO(proppy): https
+// TODO(proppy): hostname validation
+namespace weave {
+
+namespace {
+const weave::EnumToStringMap<evhttp_cmd_type>::Map kMapMethod[] = {
+ {EVHTTP_REQ_GET, "GET"}, {EVHTTP_REQ_POST, "POST"},
+ {EVHTTP_REQ_HEAD, "HEAD"}, {EVHTTP_REQ_PUT, "PUT"},
+ {EVHTTP_REQ_PATCH, "PATCH"}, {EVHTTP_REQ_DELETE, "DELETE"},
+ {EVHTTP_REQ_OPTIONS, "OPTIONS"}};
+} // namespace
+
+template <>
+EnumToStringMap<evhttp_cmd_type>::EnumToStringMap()
+ : EnumToStringMap(kMapMethod) {}
+
+using namespace provider;
+
+namespace examples {
+
+namespace {
+
+class EventDeleter {
+ public:
+ void operator()(evhttp_uri* http_uri) { evhttp_uri_free(http_uri); }
+ void operator()(evhttp_connection* conn) { evhttp_connection_free(conn); }
+ void operator()(evhttp_request* req) { evhttp_request_free(req); }
+};
+
+class EventHttpResponse : public weave::provider::HttpClient::Response {
+ public:
+ int GetStatusCode() const { return status; }
+ std::string GetContentType() const { return content_type; }
+ const std::string& GetData() const { return data; }
+
+ int status;
+ std::string content_type;
+ std::string data;
+};
+
+struct EventRequestState {
+ TaskRunner* task_runner_;
+ int request_id_;
+ std::unique_ptr<evhttp_uri, EventDeleter> http_uri_;
+ std::unique_ptr<evhttp_connection, EventDeleter> evcon_;
+ HttpClient::SuccessCallback success_callback_;
+ HttpClient::ErrorCallback error_callback_;
+};
+
+void RequestDoneCallback(evhttp_request* req, void* ctx) {
+ std::unique_ptr<EventRequestState> state{
+ static_cast<EventRequestState*>(ctx)};
+ if (!req) {
+ ErrorPtr error;
+ auto err = EVUTIL_SOCKET_ERROR();
+ Error::AddToPrintf(&error, FROM_HERE, "http_client", "request_failed",
+ "request failed: %s",
+ evutil_socket_error_to_string(err));
+ state->task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(state->error_callback_, state->request_id_, error.get()),
+ {});
+ return;
+ }
+ std::unique_ptr<EventHttpResponse> response{new EventHttpResponse()};
+ response->status = evhttp_request_get_response_code(req);
+ auto buffer = evhttp_request_get_input_buffer(req);
+ auto length = evbuffer_get_length(buffer);
+ response->data.resize(length);
+ auto n = evbuffer_remove(buffer, &response->data[0], length);
+ CHECK_EQ(n, int(length));
+ state->task_runner_->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(state->success_callback_, state->request_id_, *response), {});
+}
+
+} // namespace
+
+EventHttpClient::EventHttpClient(EventTaskRunner* task_runner)
+ : task_runner_{task_runner} {}
+
+std::unique_ptr<provider::HttpClient::Response>
+EventHttpClient::SendRequestAndBlock(const std::string& method,
+ const std::string& url,
+ const Headers& headers,
+ const std::string& data,
+ ErrorPtr* error) {
+ return nullptr;
+}
+
+int EventHttpClient::SendRequest(const std::string& method,
+ const std::string& url,
+ const Headers& headers,
+ const std::string& data,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ ++request_id_;
+
+ evhttp_cmd_type method_id;
+ CHECK(weave::StringToEnum(method, &method_id));
+ std::unique_ptr<evhttp_uri, EventDeleter> http_uri{
+ evhttp_uri_parse(url.c_str())};
+ CHECK(http_uri);
+ auto host = evhttp_uri_get_host(http_uri.get());
+ CHECK(host);
+ auto port = evhttp_uri_get_port(http_uri.get());
+ if (port == -1)
+ port = 80;
+ std::string path{evhttp_uri_get_path(http_uri.get())};
+ if (path.length() == 0) {
+ path = "/";
+ }
+ std::string uri{path};
+ auto query = evhttp_uri_get_query(http_uri.get());
+ if (query) {
+ uri = path + "?" + query;
+ }
+ auto bev = bufferevent_socket_new(task_runner_->GetEventBase(), -1,
+ BEV_OPT_CLOSE_ON_FREE);
+ CHECK(bev);
+ std::unique_ptr<evhttp_connection, EventDeleter> conn{
+ evhttp_connection_base_bufferevent_new(task_runner_->GetEventBase(), NULL,
+ bev, host, port)};
+ CHECK(conn);
+ std::unique_ptr<evhttp_request, EventDeleter> req{evhttp_request_new(
+ &RequestDoneCallback,
+ new EventRequestState{task_runner_, request_id_, std::move(http_uri),
+ std::move(conn), success_callback,
+ error_callback})};
+ CHECK(req);
+ auto output_headers = evhttp_request_get_output_headers(req.get());
+ evhttp_add_header(output_headers, "Host", host);
+ for (auto& kv : headers)
+ evhttp_add_header(output_headers, kv.first.c_str(), kv.second.c_str());
+ if (!data.empty()) {
+ auto output_buffer = evhttp_request_get_output_buffer(req.get());
+ evbuffer_add(output_buffer, data.c_str(), data.length());
+ evhttp_add_header(output_headers, "Content-Length",
+ std::to_string(data.length()).c_str());
+ }
+ auto res =
+ evhttp_make_request(conn.get(), req.release(), method_id, uri.c_str());
+ if (res < 0) {
+ ErrorPtr error;
+ Error::AddToPrintf(&error, FROM_HERE, "http_client", "request_failed",
+ "request failed: %s %s", method.c_str(), url.c_str());
+ task_runner_->PostDelayedTask(
+ FROM_HERE, base::Bind(error_callback, request_id_, error.get()), {});
+ return request_id_;
+ }
+ return request_id_;
+}
+
+} // namespace examples
+} // namespace weave
diff --git a/libweave/examples/ubuntu/event_http_client.h b/libweave/examples/ubuntu/event_http_client.h
new file mode 100644
index 0000000..65e764e
--- /dev/null
+++ b/libweave/examples/ubuntu/event_http_client.h
@@ -0,0 +1,45 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBWEAVE_EXAMPLES_UBUNTU_EVENT_HTTP_CLIENT_H_
+#define LIBWEAVE_EXAMPLES_UBUNTU_EVENT_HTTP_CLIENT_H_
+
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <weave/provider/http_client.h>
+
+namespace weave {
+namespace examples {
+
+class EventTaskRunner;
+
+// Basic implementation of weave::HttpClient using libevent.
+class EventHttpClient : public provider::HttpClient {
+ public:
+ explicit EventHttpClient(EventTaskRunner* task_runner);
+
+ std::unique_ptr<Response> SendRequestAndBlock(const std::string& method,
+ const std::string& url,
+ const Headers& headers,
+ const std::string& data,
+ ErrorPtr* error) override;
+ int SendRequest(const std::string& method,
+ const std::string& url,
+ const Headers& headers,
+ const std::string& data,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) override;
+
+ private:
+ EventTaskRunner* task_runner_{nullptr};
+ int request_id_ = 0;
+
+ base::WeakPtrFactory<EventHttpClient> weak_ptr_factory_{this};
+};
+
+} // namespace examples
+} // namespace weave
+
+#endif // LIBWEAVE_EXAMPLES_UBUNTU_EVENT_HTTP_CLIENT_H_
diff --git a/libweave/examples/ubuntu/weave.gyp b/libweave/examples/ubuntu/weave.gyp
index a51db93..09c9189 100644
--- a/libweave/examples/ubuntu/weave.gyp
+++ b/libweave/examples/ubuntu/weave.gyp
@@ -30,6 +30,7 @@
'avahi_client.cc',
'bluez_client.cc',
'curl_http_client.cc',
+ 'event_http_client.cc',
'event_http_server.cc',
'event_task_runner.cc',
'file_config_store.cc',