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',