Arquivos
hhvm/hphp/runtime/base/libevent_http_client.cpp
T
Kyle Delong a8e3321fbd HPHP/XHP: 'mixed' type in attribute declarations
We'd like to start using ##mixed## instead of ##var## for attribute types to be consistent with Hack. As a followup to this (once released), we would codemod all ##var## to ##mixed##.
2013-07-18 17:28:37 -07:00

334 linhas
10 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
| Copyright (c) 1998-2010 Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/base/libevent_http_client.h"
#include "hphp/runtime/server/server_stats.h"
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/util/compression.h"
#include "hphp/util/logger.h"
#include "hphp/util/timer.h"
// libevent is not exposing this data structure, but we need it.
struct evkeyvalq_ {
struct evkeyval *tqh_first;
};
///////////////////////////////////////////////////////////////////////////////
// static handlers delegating work to instance ones
static void on_request_completed(struct evhttp_request *req, void *obj) {
assert(obj);
((HPHP::LibEventHttpClient*)obj)->onRequestCompleted(req);
}
static void on_connection_closed(struct evhttp_connection *conn, void *obj) {
assert(obj);
((HPHP::LibEventHttpClient*)obj)->onConnectionClosed();
}
static void timer_callback(int fd, short events, void *context) {
event_base_loopbreak((struct event_base *)context);
}
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
// connection pooling
static std::string get_hash(const std::string &address, int port) {
string hash = address;
if (port != 80) {
hash += ':';
hash += lexical_cast<string>(port);
}
return hash;
}
ReadWriteMutex LibEventHttpClient::ConnectionPoolMutex;
std::map<std::string, int> LibEventHttpClient::ConnectionPoolConfig;
std::map<std::string, LibEventHttpClientPtrVec>
LibEventHttpClient::ConnectionPool;
void LibEventHttpClient::SetCache(const std::string &address, int port,
int maxConnection) {
string hash = get_hash(address, port);
WriteLock lock(ConnectionPoolMutex);
if (maxConnection > 0) {
ConnectionPoolConfig[hash] = maxConnection;
} else {
ConnectionPoolConfig.erase(hash);
}
}
LibEventHttpClientPtr LibEventHttpClient::Get(const std::string &address,
int port) {
string hash = get_hash(address, port);
int maxConnection = 0;
{
ReadLock lock(ConnectionPoolMutex);
std::map<string, int>::const_iterator iter =
ConnectionPoolConfig.find(hash);
if (iter == ConnectionPoolConfig.end()) {
// not configured to cache
ServerStats::Log("evhttp.skip", 1);
ServerStats::Log("evhttp.skip." + hash, 1);
return LibEventHttpClientPtr(new LibEventHttpClient(address, port));
}
maxConnection = iter->second;
}
WriteLock lock(ConnectionPoolMutex);
LibEventHttpClientPtrVec &pool = ConnectionPool[hash];
for (unsigned int i = 0; i < pool.size(); i++) {
LibEventHttpClientPtr client = pool[i];
if (!client->m_busy) {
client->m_busy = true;
ServerStats::Log("evhttp.hit", 1);
ServerStats::Log("evhttp.hit." + hash, 1);
return client;
}
}
LibEventHttpClientPtr ret(new LibEventHttpClient(address, port));
if ((int)pool.size() < maxConnection) {
if (pool.empty()) {
pool.reserve(maxConnection);
}
pool.push_back(ret);
}
ServerStats::Log("evhttp.miss", 1);
ServerStats::Log("evhttp.miss." + hash, 1);
return ret;
}
///////////////////////////////////////////////////////////////////////////////
// constructor and destructor
LibEventHttpClient::LibEventHttpClient(const std::string &address, int port)
: m_busy(true), m_address(address), m_port(port), m_requests(0),
m_conn(nullptr), m_thread(nullptr), m_code(0), m_response(nullptr), m_len(0) {
m_eventBase = event_base_new();
}
LibEventHttpClient::~LibEventHttpClient() {
clear();
// reset all per-connection data structures
if (m_conn) {
evhttp_connection_free(m_conn);
}
if (m_eventBase) {
event_base_free(m_eventBase);
}
}
void LibEventHttpClient::clear() {
// reset all per-request data structures
if (m_thread) {
m_thread->waitForEnd();
delete m_thread;
m_thread = nullptr;
}
m_url.clear();
m_code = 0;
m_codeLine.clear();
m_len = 0;
if (m_response) {
free(m_response);
m_response = nullptr;
}
m_responseHeaders.clear();
}
void LibEventHttpClient::release() {
clear();
m_busy = false;
}
///////////////////////////////////////////////////////////////////////////////
bool LibEventHttpClient::send(const std::string &url,
const std::vector<std::string> &headers,
int timeoutSeconds, bool async,
const void *data /* = NULL */,
int size /* = 0 */) {
clear();
m_url = url;
evhttp_request* request = evhttp_request_new(on_request_completed, this);
// request headers
bool keepalive = true;
bool addHost = true;
for (unsigned int i = 0; i < headers.size(); i++) {
const std::string &header = headers[i];
size_t pos = header.find(':');
if (pos != string::npos && header[pos + 1] == ' ') {
string name = header.substr(0, pos);
if (strcasecmp(name.c_str(), "Connection") == 0) {
keepalive = false;
} else if (strcasecmp(name.c_str(), "Host") == 0) {
addHost = false;
}
int ret = evhttp_add_header(request->output_headers,
name.c_str(), header.c_str() + pos + 2);
if (ret >= 0) {
continue;
}
}
Logger::Error("invalid request header: [%s]", header.c_str());
}
if (keepalive) {
evhttp_add_header(request->output_headers, "Connection", "keep-alive");
}
if (addHost) {
// REVIEW: libevent never sends a Host header (nor does it properly send
// HTTP 400 for HTTP/1.1 requests without such a header), in blatent
// violation of RFC2616; this should perhaps be fixed in the library
// proper. For now, add it if it wasn't set by the caller.
if (m_port == 80) {
evhttp_add_header(request->output_headers, "Host", m_address.c_str());
} else {
std::ostringstream ss;
ss << m_address << ":" << m_port;
evhttp_add_header(request->output_headers, "Host", ss.str().c_str());
}
}
// post data
if (data && size) {
evbuffer_add(request->output_buffer, data, size);
}
// url
evhttp_cmd_type cmd = data ? EVHTTP_REQ_POST : EVHTTP_REQ_GET;
// if we have a cached connection, we need to pump the event loop to clear
// any "connection closed" events that may be sitting there patiently.
if (m_conn) {
event_base_loop(m_eventBase, EVLOOP_NONBLOCK);
}
// even if we had an m_conn immediately above, it may have been cleared out
// by onConnectionClosed().
if (m_conn == nullptr) {
m_conn = evhttp_connection_new(m_address.c_str(), m_port);
evhttp_connection_set_closecb(m_conn, on_connection_closed, this);
evhttp_connection_set_base(m_conn, m_eventBase);
}
int ret = evhttp_make_request(m_conn, request, cmd, url.c_str());
if (ret != 0) {
Logger::Error("evhttp_make_request failed");
return false;
}
if (timeoutSeconds > 0) {
struct timeval timeout;
timeout.tv_sec = timeoutSeconds;
timeout.tv_usec = 0;
event_set(&m_eventTimeout, -1, 0, timer_callback, m_eventBase);
event_base_set(m_eventBase, &m_eventTimeout);
event_add(&m_eventTimeout, &timeout);
}
if (async) {
m_thread = new AsyncFunc<LibEventHttpClient>
(this, &LibEventHttpClient::sendImpl);
m_thread->start();
} else {
IOStatusHelper io("libevent_http", m_address.c_str(), m_port);
sendImpl();
}
return true;
}
void LibEventHttpClient::sendImpl() {
SlowTimer timer(RuntimeOption::HttpSlowQueryThreshold, "evhttp",
m_url.c_str());
event_base_dispatch(m_eventBase);
event_del(&m_eventTimeout);
}
void LibEventHttpClient::onRequestCompleted(evhttp_request* request) {
if (!request) {
// eek -- this is just a clean-up notification because the connection's
// been closed, but we already dealt with it in onConnectionClosed
return;
}
// response code line
m_code = request->response_code;
if (request->response_code_line) {
m_codeLine = request->response_code_line;
}
bool gzip = false;
// response headers
for (evkeyval *p = ((evkeyvalq_*)request->input_headers)->tqh_first; p;
p = p->next.tqe_next) {
if (p->key && p->value) {
if (strcasecmp(p->key, "Content-Encoding") == 0 &&
strncmp(p->value, "gzip", 4) == 0 &&
(!p->value[4] || isspace(p->value[4]))) {
// in the (illegal) case of multiple Content-Encoding headers, any one
// with the value 'gzip' means we treat it as gzip.
gzip = true;
}
m_responseHeaders.push_back(string(p->key) + ": " + p->value);
}
}
// response body
m_len = EVBUFFER_LENGTH(request->input_buffer);
if (gzip) {
m_response =
gzdecode((const char*)EVBUFFER_DATA(request->input_buffer), m_len);
} else {
m_response = (char*)malloc(m_len + 1);
strncpy(m_response, (char*)EVBUFFER_DATA(request->input_buffer), m_len);
m_response[m_len] = '\0';
}
++m_requests;
event_base_loopbreak(m_eventBase);
}
void LibEventHttpClient::onConnectionClosed() {
m_conn = nullptr;
m_requests = 0;
}
char *LibEventHttpClient::recv(int &len) {
if (m_thread) {
m_thread->waitForEnd();
delete m_thread;
m_thread = nullptr;
}
char *ret = m_response;
len = m_len;
m_response = nullptr;
m_len = 0;
return ret;
}
///////////////////////////////////////////////////////////////////////////////
}