blob: 09a74a672abf40e66baaf51b3d6a492aedd802c1 [file] [log] [blame]
/********************************************************************
* 2014 -
* open source under Apache License Version 2.0
********************************************************************/
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
#include "HttpClient.h"
#include "Logger.h"
using namespace Hdfs::Internal;
namespace Hdfs {
#define CURL_SETOPT(handle, option, optarg, fmt, ...) \
res = curl_easy_setopt(handle, option, optarg); \
if (res != CURLE_OK) { \
THROW(HdfsIOException, fmt, ##__VA_ARGS__); \
}
#define CURL_SETOPT_ERROR1(handle, option, optarg, fmt) \
CURL_SETOPT(handle, option, optarg, fmt, curl_easy_strerror(res));
#define CURL_SETOPT_ERROR2(handle, option, optarg, fmt) \
CURL_SETOPT(handle, option, optarg, fmt, curl_easy_strerror(res), \
errorString().c_str())
#define CURL_PERFORM(handle, fmt) \
res = curl_easy_perform(handle); \
if (res != CURLE_OK) { \
THROW(HdfsIOException, fmt, curl_easy_strerror(res), errorString().c_str()); \
}
#define CURL_GETOPT_ERROR2(handle, option, optarg, fmt) \
res = curl_easy_getinfo(handle, option, optarg); \
if (res != CURLE_OK) { \
THROW(HdfsIOException, fmt, curl_easy_strerror(res), errorString().c_str()); \
}
#define CURL_GET_RESPONSE(handle, code, fmt) \
CURL_GETOPT_ERROR2(handle, CURLINFO_RESPONSE_CODE, code, fmt);
HttpClient::HttpClient() : curl(NULL), list(NULL) {
}
/**
* Construct a HttpClient instance.
* @param url a url which is the address to send the request to the corresponding http server.
*/
HttpClient::HttpClient(const std::string &url) {
curl = NULL;
list = NULL;
this->url = url;
}
/**
* Destroy a HttpClient instance.
*/
HttpClient::~HttpClient()
{
destroy();
}
/**
* Receive error string from curl.
*/
std::string HttpClient::errorString() {
if (strlen(errbuf) == 0) {
return "";
}
return errbuf;
}
/**
* Curl call back function to receive the reponse messages.
* @return return the size of reponse messages.
*/
size_t HttpClient::CurlWriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
if (userp == NULL || contents == NULL) {
return 0;
}
((std::string *) userp)->append((const char *) contents, realsize);
LOG(DEBUG3, "HttpClient : Http response is : %s", ((std::string * )userp)->c_str());
return realsize;
}
/**
* Init curl handler and set curl options.
*/
void HttpClient::init() {
if (!initialized) {
initialized = true;
if (curl_global_init (CURL_GLOBAL_ALL)) {
THROW(HdfsIOException, "Cannot initialize curl client for KMS");
}
}
curl = curl_easy_init();
if (!curl) {
THROW(HdfsIOException, "Cannot initialize curl handle for KMS");
}
CURL_SETOPT_ERROR1(curl, CURLOPT_ERRORBUFFER, errbuf,
"Cannot initialize curl error buffer for KMS: %s");
errbuf[0] = 0;
CURL_SETOPT_ERROR2(curl, CURLOPT_NOPROGRESS, 1,
"Cannot initialize no progress in HttpClient: %s: %s");
CURL_SETOPT_ERROR2(curl, CURLOPT_VERBOSE, 0,
"Cannot initialize no verbose in HttpClient: %s: %s");
CURL_SETOPT_ERROR2(curl, CURLOPT_COOKIEFILE, "",
"Cannot initialize cookie behavior in HttpClient: %s: %s");
CURL_SETOPT_ERROR2(curl, CURLOPT_HTTPHEADER, list,
"Cannot initialize headers in HttpClient: %s: %s");
CURL_SETOPT_ERROR2(curl, CURLOPT_WRITEFUNCTION, HttpClient::CurlWriteMemoryCallback,
"Cannot initialize body reader in HttpClient: %s: %s");
CURL_SETOPT_ERROR2(curl, CURLOPT_WRITEDATA, (void *)&response,
"Cannot initialize body reader data in HttpClient: %s: %s");
/* Some servers don't like requests that are made without a user-agent
* field, so we provide one
*/
CURL_SETOPT_ERROR2(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0",
"Cannot initialize user agent in HttpClient: %s: %s");
list = NULL;
}
/**
* Do clean up for curl.
*/
void HttpClient::destroy() {
if (curl) {
curl_easy_cleanup(curl);
curl = NULL;
}
if (list) {
curl_slist_free_all(list);
list = NULL;
}
initialized = false;
}
/**
* Set url for http client.
*/
void HttpClient::setURL(const std::string &url) {
this->url = url;
}
/**
* Set retry times for http request which can be configured in config file.
*/
void HttpClient::setRequestRetryTimes(int request_retry_times) {
if (request_retry_times < 0) {
THROW(InvalidParameter, "HttpClient : Invalid value for request_retry_times.");
}
this->request_retry_times = request_retry_times;
}
/**
* Set request timeout which can be configured in config file.
*/
void HttpClient::setRequestTimeout(int64_t curl_timeout) {
if (curl_timeout < 0) {
THROW(InvalidParameter, "HttpClient : Invalid value for curl_timeout.");
}
this->curl_timeout = curl_timeout;
}
/**
* Set headers for http client.
*/
void HttpClient::setHeaders(const std::vector<std::string> &headers) {
if (!headers.empty()) {
this->headers = headers;
for (std::string header : headers) {
list = curl_slist_append(list, header.c_str());
if (!list) {
THROW(HdfsIOException, "Cannot add header in HttpClient.");
}
}
} else {
LOG(DEBUG3, "HttpClient : Header is empty.");
}
}
/**
* Set body for http client.
*/
void HttpClient::setBody(const std::string &body) {
this->body = body;
}
/**
* Set expected response code.
*/
void HttpClient::setExpectedResponseCode(int64_t response_code_ok) {
this->response_code_ok = response_code_ok;
}
/**
* Http common method to get response info by sending request to http server.
* @param method : define different http methods.
* @return return response info.
*/
std::string HttpClient::httpCommon(httpMethod method) {
/* Set headers and url. */
if (list != NULL) {
CURL_SETOPT_ERROR2(curl, CURLOPT_HTTPHEADER, list,
"Cannot initialize headers in HttpClient: %s: %s");
} else {
LOG(DEBUG3, "HttpClient : Http Header is NULL");
}
if (curl != NULL) {
CURL_SETOPT_ERROR2(curl, CURLOPT_URL, url.c_str(),
"Cannot initialize url in HttpClient: %s: %s");
} else {
LOG(LOG_ERROR, "HttpClient : Http URL is NULL");
}
/* Set body based on different http method. */
switch (method) {
case HTTP_GET:
{
break;
}
case HTTP_POST:
{
CURL_SETOPT_ERROR2(curl, CURLOPT_COPYPOSTFIELDS, body.c_str(),
"Cannot initialize post data in HttpClient: %s: %s");
break;
}
case HTTP_DELETE:
{
CURL_SETOPT_ERROR2(curl, CURLOPT_CUSTOMREQUEST, "DELETE",
"Cannot initialize set customer request in HttpClient: %s: %s");
break;
}
case HTTP_PUT:
{
CURL_SETOPT_ERROR2(curl, CURLOPT_CUSTOMREQUEST, "PUT",
"Cannot initialize set customer request in HttpClient: %s: %s");
CURL_SETOPT_ERROR2(curl, CURLOPT_COPYPOSTFIELDS, body.c_str(),
"Cannot initialize post data in HttpClient: %s: %s");
break;
}
default:
{
LOG(LOG_ERROR, "HttpClient : unknown method: %d", method);
}
}
/* Do several http request try according to request_retry_times
* until got the right response code.
*/
int64_t response_code = -1;
while (request_retry_times >= 0 && response_code != response_code_ok) {
request_retry_times -= 1;
response = "";
CURL_SETOPT_ERROR2(curl, CURLOPT_TIMEOUT, curl_timeout,
"Send request to http server timeout: %s: %s");
CURL_PERFORM(curl, "Could not send request in HttpClient: %s %s");
CURL_GET_RESPONSE(curl, &response_code,
"Cannot get response code in HttpClient: %s: %s");
}
LOG(DEBUG3, "HttpClient : The http method is %d. The http url is %s. The http response is %s.",
method, url.c_str(), response.c_str());
return response;
}
/**
* Http GET method.
*/
std::string HttpClient::get() {
return httpCommon(HTTP_GET);
}
/**
* Http POST method.
*/
std::string HttpClient::post() {
return httpCommon(HTTP_POST);
}
/**
* Http DELETE method.
*/
std::string HttpClient::del() {
return httpCommon(HTTP_DELETE);
}
/**
* Http PUT method.
*/
std::string HttpClient::put() {
return httpCommon(HTTP_PUT);
}
/**
* URL encodes the given string.
*/
std::string HttpClient::escape(const std::string &data) {
if (curl) {
char *output = curl_easy_escape(curl, data.c_str(), data.length());
if (output) {
std::string out(output);
return out;
} else {
THROW(HdfsIOException, "HttpClient : Curl escape failed.");
}
} else {
LOG(WARNING, "HttpClient : Curl in escape method is NULL");
}
std::string empty;
return empty;
}
}
/* Curl global init only can be done once. */
bool Hdfs::HttpClient::initialized = false;