| // 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, "http_client", "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, "http_client", "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 |