aboutsummaryrefslogtreecommitdiff
path: root/examples/provider/event_http_client.cc
blob: 0346a678edcbb2b30e1b7b3f994cd4c73f03a903 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// Copyright 2015 The Weave 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/provider/event_http_client.h"
#include "examples/provider/event_task_runner.h"

#include <base/bind.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/http.h>
#include <weave/enum_to_string.h>

#include "examples/provider/event_deleter.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 EventHttpResponse : public weave::provider::HttpClient::Response {
 public:
  int GetStatusCode() const override { return status; }
  std::string GetContentType() const override { return content_type; }
  std::string GetData() const { return data; }

  int status;
  std::string content_type;
  std::string data;
};

struct EventRequestState {
  TaskRunner* task_runner_;
  EventPtr<evhttp_uri> http_uri_;
  EventPtr<evhttp_connection> evcon_;
  HttpClient::SendRequestCallback 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, "request_failed",
                       "request failed: %s",
                       evutil_socket_error_to_string(err));
    state->task_runner_->PostDelayedTask(
        FROM_HERE, base::Bind(state->callback_, nullptr, base::Passed(&error)),
        {});
    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->callback_, base::Passed(&response), nullptr),
      {});
}

}  // namespace

EventHttpClient::EventHttpClient(EventTaskRunner* task_runner)
    : task_runner_{task_runner} {}

void EventHttpClient::SendRequest(Method method,
                                  const std::string& url,
                                  const Headers& headers,
                                  const std::string& data,
                                  const SendRequestCallback& callback) {
  evhttp_cmd_type method_id = EVHTTP_REQ_GET;
  CHECK(weave::StringToEnum(weave::EnumToString(method), &method_id));
  EventPtr<evhttp_uri> 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);
  EventPtr<evhttp_connection> conn{evhttp_connection_base_bufferevent_new(
      task_runner_->GetEventBase(), NULL, bev, host, port)};
  CHECK(conn);
  EventPtr<evhttp_request> req{evhttp_request_new(
      &RequestDoneCallback,
      new EventRequestState{task_runner_, std::move(http_uri), std::move(conn),
                            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)
    return;
  ErrorPtr error;
  Error::AddToPrintf(&error, FROM_HERE, "request_failed",
                     "request failed: %s %s", EnumToString(method).c_str(),
                     url.c_str());
  task_runner_->PostDelayedTask(
      FROM_HERE, base::Bind(callback, nullptr, base::Passed(&error)), {});
}

}  // namespace examples
}  // namespace weave