blob: d8d7c5d3a26c1df45a49c47ecd0ef99d6414eba2 [file] [log] [blame]
/*
* Copyright 2015 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
////////////////////////////////////////////////////////////////////////////////
// Implements the HTTPClient class. See httpclient.h for details on the API
////////////////////////////////////////////////////////////////////////////////
#include "network/httpclient.h"
#include <evhttp.h>
#include <utility>
#include "glog/logging.h"
#include "basics/basics.h"
const sp_int32 HTTPClientTimeoutInSecs = 3600;
evhttp_cmd_type GetRequestType(BaseHTTPRequest::HTTPRequestType _type) {
switch (_type) {
case BaseHTTPRequest::GET:
return EVHTTP_REQ_GET;
case BaseHTTPRequest::POST:
return EVHTTP_REQ_POST;
case BaseHTTPRequest::HEAD:
return EVHTTP_REQ_HEAD;
case BaseHTTPRequest::PUT:
return EVHTTP_REQ_PUT;
case BaseHTTPRequest::DELETE:
return EVHTTP_REQ_DELETE;
default:
CHECK(false) << "Unhandled http request";
return EVHTTP_REQ_GET;
}
}
// 'C' style callbacks for the libevent
void httpdonecb(struct evhttp_request* _request, void* _arg) {
HTTPClient::Combo* combo = static_cast<HTTPClient::Combo*>(_arg);
HTTPClient* client = combo->client_;
OutgoingHTTPRequest* request = combo->request_;
delete combo;
if (client) {
auto response = new IncomingHTTPResponse(_request);
client->HandleRequestDone(request, response);
}
}
void httpconnectionclose(struct evhttp_connection* _connection, void* _arg) {
HTTPClient* client = static_cast<HTTPClient*>(_arg);
client->HandleConnectionClose(_connection);
}
HTTPClient::HTTPClient(EventLoop* eventLoop, AsyncDNS* _dns) : eventLoop_(eventLoop), dns_(_dns) {}
HTTPClient::~HTTPClient() {
for (auto iter = connections_.begin(); iter != connections_.end(); ++iter) {
evhttp_connection_set_closecb(iter->second, NULL, NULL);
evhttp_connection_free(iter->second);
}
}
sp_int32 HTTPClient::SendRequest(OutgoingHTTPRequest* _request,
VCallback<IncomingHTTPResponse*> cb) {
struct evhttp_connection* connection = NULL;
std::pair<sp_string, sp_int32> pr = make_pair(_request->host(), _request->port());
if (connections_.find(pr) == connections_.end()) {
connection = CreateConnection(_request->host(), _request->port());
if (connection == NULL) {
return SP_NOTOK;
}
connections_[pr] = connection;
rconnections_[connection] = pr;
} else {
connection = connections_[pr];
}
auto combo = new Combo(_request, this);
struct evhttp_request* httprequest = CreateUnderlyingRequest(_request, combo);
if (!httprequest) {
delete combo;
return SP_NOTOK;
}
inflight_urls_[_request] = std::move(cb);
if (evhttp_make_request(connection, httprequest, GetRequestType(_request->type()),
_request->query().c_str()) != 0) {
inflight_urls_.erase(_request);
delete combo;
return SP_NOTOK;
}
return SP_OK;
}
void HTTPClient::HandleRequestDone(OutgoingHTTPRequest* _request, IncomingHTTPResponse* _response) {
if (inflight_urls_.find(_request) == inflight_urls_.end()) {
// This is strange! We dont have any account for this url.
LOG(ERROR) << "Unknown HTTPrequest completed\n";
return;
}
VCallback<IncomingHTTPResponse*> cb = std::move(inflight_urls_[_request]);
inflight_urls_.erase(_request);
delete _request;
cb(_response);
}
void HTTPClient::HandleConnectionClose(struct evhttp_connection* _connection) {
if (rconnections_.find(_connection) == rconnections_.end()) {
LOG(ERROR) << "Unknown connection closed on us!\n";
return;
}
std::pair<sp_string, sp_int32> pr = rconnections_[_connection];
connections_.erase(pr);
rconnections_.erase(_connection);
}
struct evhttp_connection* HTTPClient::CreateConnection(const sp_string& _host, sp_int32 _port) {
unsigned short port = _port;
struct evhttp_connection* connection =
evhttp_connection_base_new(eventLoop_->dispatcher(), dns_->dns(), _host.c_str(), port);
if (!connection) return NULL;
evhttp_connection_set_closecb(connection, httpconnectionclose, this);
evhttp_connection_set_timeout(connection, HTTPClientTimeoutInSecs);
return connection;
}
struct evhttp_request* HTTPClient::CreateUnderlyingRequest(OutgoingHTTPRequest* _request,
HTTPClient::Combo* _combo) {
// First create the libevent structure
struct evhttp_request* request = evhttp_request_new(httpdonecb, _combo);
if (!request) {
return NULL;
}
// Next add all reqeusted headers
const auto& header = _request->header();
for (auto iter = header.begin(); iter != header.end(); ++iter) {
if (evhttp_add_header(request->output_headers, iter->first.c_str(), iter->second.c_str()) !=
0) {
evhttp_request_free(request);
return NULL;
}
}
// Add compulsory headers
evhttp_add_header(request->output_headers, "Accept-Encoding", "identity");
std::ostringstream hoststr;
hoststr << _request->host() << ":" << _request->port();
evhttp_add_header(request->output_headers, "Host", hoststr.str().c_str());
sp_string body;
bool first = true;
const HTTPKeyValuePairs& kv = _request->kv();
for (size_t i = 0; i < kv.size(); ++i) {
if (first) {
body += BaseHTTPRequest::http_encode(kv[i].first) + "=" +
BaseHTTPRequest::http_encode(kv[i].second);
first = false;
} else {
body += "&" + BaseHTTPRequest::http_encode(kv[i].first) + "=" +
BaseHTTPRequest::http_encode(kv[i].second);
}
}
if (_request->type() == BaseHTTPRequest::POST || _request->type() == BaseHTTPRequest::PUT) {
std::ostringstream o;
o << body.size();
evhttp_add_header(request->output_headers, "Content-Length", o.str().c_str());
CHECK_GE(evbuffer_add_printf(request->output_buffer, "%s", body.c_str()), 0);
} else {
_request->ExtendQuery(body);
}
return request;
}