Skip to content

Instantly share code, notes, and snippets.

@QiMata
Last active February 15, 2016 12:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save QiMata/b268639e8c1913bf646f to your computer and use it in GitHub Desktop.
Save QiMata/b268639e8c1913bf646f to your computer and use it in GitHub Desktop.
An http client (like .NET's) using folly and proxygen
#include "curl_client.hpp"
#include <folly/FileUtil.h>
#include <folly/String.h>
#include <proxygen/lib/http/HTTPCommonHeaders.h>
#include <proxygen/lib/http/HTTPMessage.h>
#include <proxygen/lib/http/session/HTTPUpstreamSession.h>
#include <proxygen/lib/ssl/SSLContextConfig.h>
using namespace folly;
using namespace proxygen;
using namespace std;
curl_client::curl_client(folly::EventBase* evb, proxygen::HTTPMethod httpMethod,
const proxygen::URL& url, const proxygen::HTTPHeaders& headers)
: evb_(evb), httpMethod_(httpMethod), url_(url)
{
headers.forEach([this] (const string& header, const string& val) {
request_.getHeaders().add(header, val);
});
}
curl_client::curl_client(folly::EventBase *evb, proxygen::HTTPMethod httpMethod, const proxygen::URL &url,
const proxygen::HTTPHeaders &headers, const std::string &request_body)
: evb_(evb), httpMethod_(httpMethod), url_(url), request_body_(folly::make_unique<folly::IOBuf>())
{
headers.forEach([this] (const string& header, const string& val) {
request_.getHeaders().add(header, val);
});
}
curl_client::~curl_client()
{
}
void curl_client::initializeSsl(const string& nextProtos)
{
sslContext_ = std::make_shared<folly::SSLContext>();
sslContext_->setOptions(SSL_OP_NO_COMPRESSION);
SSLContextConfig config;
sslContext_->ciphers(config.sslCiphers);
sslContext_->loadTrustedCertificates("/etc/ssl/certs/ca-certificates.crt"); //may not work for not ubuntu
list<string> nextProtoList;
folly::splitTo<string>(',', nextProtos, std::inserter(nextProtoList,
nextProtoList.begin()));
sslContext_->setAdvertisedNextProtocols(nextProtoList);
}
void curl_client::sslHandshakeFollowup(proxygen::HTTPUpstreamSession* session) noexcept
{
AsyncSSLSocket* sslSocket = dynamic_cast<AsyncSSLSocket*>(session->getTransport());
const unsigned char* nextProto = nullptr;
unsigned nextProtoLength = 0;
sslSocket->getSelectedNextProtocol(&nextProto, &nextProtoLength);
if (nextProto) {
string((const char*)nextProto, nextProtoLength);
}
// Note: This ssl session can be used by defining a member and setting
// something like sslSession_ = sslSocket->getSSLSession() and then
// passing it to the connector::connectSSL() method
}
void curl_client::connectSuccess(proxygen::HTTPUpstreamSession* session)
{
if (url_.isSecure())
{
sslHandshakeFollowup(session);
}
txn_ = session->newTransaction(this);
request_.setMethod(httpMethod_);
request_.setHTTPVersion(1, 1);
request_.setURL(url_.makeRelativeURL());
request_.setSecure(url_.isSecure());
if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_USER_AGENT))
{
request_.getHeaders().add(HTTP_HEADER_USER_AGENT, "fusethru");
}
if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_HOST))
{
request_.getHeaders().add(HTTP_HEADER_HOST, url_.getHostAndPort());
}
if (!request_.getHeaders().getNumberOfValues(HTTP_HEADER_ACCEPT))
{
request_.getHeaders().add("Accept", "*/*");
}
request_.dumpMessage(4);
txn_->sendHeaders(request_);
if (request_body_)
{
txn_->sendBody(std::move(request_body_));
request_body_ = std::unique_ptr<folly::IOBuf>();
}
txn_->sendEOM();
session->closeWhenIdle();
}
void curl_client::connectError(const folly::AsyncSocketException& ex)
{
socket_ex_ = ex;
}
void curl_client::setTransaction(HTTPTransaction*) noexcept {}
void curl_client::detachTransaction() noexcept {}
void curl_client::onHeadersComplete(unique_ptr<HTTPMessage> msg) noexcept
{
message_ = std::move(msg);
}
void curl_client::onBody(std::unique_ptr<folly::IOBuf> chain) noexcept
{
response_body_ = std::move(chain);
}
void curl_client::onTrailers(std::unique_ptr<HTTPHeaders>) noexcept
{
}
void curl_client::onEOM() noexcept
{
}
void curl_client::onUpgrade(UpgradeProtocol) noexcept
{
}
void curl_client::onError(const HTTPException& error) noexcept
{
}
void curl_client::onEgressPaused() noexcept
{
}
void curl_client::onEgressResumed() noexcept
{
}
const string& curl_client::getServerName() const {
const string& res = request_.getHeaders().getSingleOrEmpty(HTTP_HEADER_HOST);
if (res.empty())
{
return url_.getHost();
}
return res;
}
#ifndef __UTIL__HTTP__CLIENT__CURL__CURL_CLIENT__HPP__
#define __UTIL__HTTP__CLIENT__CURL__CURL_CLIENT__HPP__
#include <boost/optional.hpp>
#include <folly/io/async/EventBase.h>
#include <folly/io/async/SSLContext.h>
#include <proxygen/lib/http/HTTPConnector.h>
#include <proxygen/lib/http/session/HTTPTransaction.h>
#include <proxygen/lib/utils/URL.h>
class curl_client : public proxygen::HTTPConnector::Callback,
public proxygen::HTTPTransactionHandler
{
public:
curl_client(folly::EventBase* evb, proxygen::HTTPMethod httpMethod,
const proxygen::URL& url, const proxygen::HTTPHeaders& headers);
curl_client(folly::EventBase* evb, proxygen::HTTPMethod httpMethod,
const proxygen::URL& url, const proxygen::HTTPHeaders& headers,
const std::string& request_body);
~curl_client() override;
// initial SSL related structures
void initializeSsl(const std::string& nextProtos);
void sslHandshakeFollowup(proxygen::HTTPUpstreamSession* session) noexcept;
// HTTPConnector methods
void connectSuccess(proxygen::HTTPUpstreamSession* session) override;
void connectError(const folly::AsyncSocketException& ex) override;
// HTTPTransactionHandler methods
void setTransaction(proxygen::HTTPTransaction* txn) noexcept override;
void detachTransaction() noexcept override;
void onHeadersComplete(std::unique_ptr<proxygen::HTTPMessage> msg) noexcept override;
void onBody(std::unique_ptr<folly::IOBuf> chain) noexcept override;
void onTrailers(std::unique_ptr<proxygen::HTTPHeaders> trailers) noexcept override;
void onEOM() noexcept override;
void onUpgrade(proxygen::UpgradeProtocol protocol) noexcept override;
void onError(const proxygen::HTTPException& error) noexcept override;
void onEgressPaused() noexcept override;
void onEgressResumed() noexcept override;
// Getters
folly::SSLContextPtr getSSLContext() { return sslContext_; }
const std::string& getServerName() const;
std::unique_ptr<proxygen::HTTPMessage> get_message() { return std::move(message_); }
std::unique_ptr<folly::IOBuf> get_response_body() { return std::move(response_body_); }
boost::optional<folly::AsyncSocketException> get_exception() {
return socket_ex_;
}
protected:
private:
proxygen::HTTPTransaction* txn_{nullptr};
folly::EventBase* evb_{nullptr};
proxygen::HTTPMethod httpMethod_;
proxygen::URL url_;
proxygen::HTTPMessage request_;
folly::SSLContextPtr sslContext_;
boost::optional<folly::AsyncSocketException> socket_ex_;
std::unique_ptr<folly::IOBuf> request_body_;
std::unique_ptr<proxygen::HTTPMessage> message_;
std::unique_ptr<folly::IOBuf> response_body_;
};
#endif //__UTIL__HTTP__CLIENT__CURL__CURL_CLIENT__HPP__
#include "http_client.hpp"
#include "curl/curl_client.hpp"
#include "http_message.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <folly/io/async/EventBase.h>
#include <folly/io/async/SSLContext.h>
#include <folly/SocketAddress.h>
#include <proxygen/lib/http/HTTPConnector.h>
#include <folly/FileUtil.h>
#include <folly/String.h>
#include <proxygen/lib/http/HTTPCommonHeaders.h>
#include <proxygen/lib/http/HTTPMessage.h>
#include <proxygen/lib/http/session/HTTPUpstreamSession.h>
#include <proxygen/lib/ssl/SSLContextConfig.h>
using namespace proxygen;
using namespace folly;
http_client::http_client(const std::string& domain)
: domain_(domain)
{
}
http_message http_client::get_async(const std::string& path)
{
folly::EventBase evb;
return make_request(HTTPMethod::GET,evb,path);
}
std::string http_client::create_url(const std::string &path)
{
if (boost::starts_with(path,"/"))
{
return domain_ + path.substr(1);
}
else
{
return domain_ + path;
}
}
http_message http_client::put_async(const std::string &path, const std::string &body) {
folly::EventBase evb;
return make_request(HTTPMethod::PUT,evb,path,body);
}
http_message http_client::make_request(proxygen::HTTPMethod method, folly::EventBase& evb,
const std::string &path)
{
proxygen::URL url(create_url(path));
curl_client curlClient(&evb, method, url, headers);
return getMessage(evb, url, curlClient);
}
http_message http_client::getMessage(EventBase &evb, const URL &url, curl_client &curlClient) const {
SocketAddress addr(url.getHost(), url.getPort(), true);
HHWheelTimer::UniquePtr timer{
new HHWheelTimer(
&evb,
std::chrono::milliseconds(HHWheelTimer::DEFAULT_TICK_INTERVAL),
TimeoutManager::InternalEnum::NORMAL,
std::chrono::milliseconds(5000))};
HTTPConnector connector(&curlClient, timer.get());
static const AsyncSocket::OptionMap opts{{{SOL_SOCKET, SO_REUSEADDR}, 1}};
if (url.isSecure()) {
curlClient.initializeSsl("h2,h2-14,spdy/3.1,spdy/3,http/1.1");
connector.connectSSL(
&evb, addr, curlClient.getSSLContext(), nullptr,
std::chrono::milliseconds(1000), opts,
AsyncSocket::anyAddress(), curlClient.getServerName());
} else {
connector.connect(&evb, addr,
std::chrono::milliseconds(1000), opts);
}
evb.loop();
http_message message;
message.set_body(move(curlClient.get_response_body()));
message.message = move(curlClient.get_message());
return message;
}
http_message http_client::make_request(proxygen::HTTPMethod method,
folly::EventBase& evb,
const std::string &path, const std::string &body) {
proxygen::URL url(create_url(path));
curl_client curlClient(&evb, method, url, headers,body);
return getMessage(evb, url, curlClient);
}
#ifndef __UTIL__HTTP__CLIENT__HTTP_CLIENT__HPP__
#define __UTIL__HTTP__CLIENT__HTTP_CLIENT__HPP__
#include <proxygen/lib/http/HTTPMessage.h>
#include <proxygen/lib/utils/URL.h>
#include <util/http/client/curl/curl_client.hpp>
#include "http_message.hpp"
class http_client
{
public:
http_client(const std::string& domain);
fusethru::util::http::client::http_message get_async(const std::string& path);
fusethru::util::http::client::http_message post_async(const std::string& path,const std::string& body);
fusethru::util::http::client::http_message put_async(const std::string& path,const std::string& body);
public:
proxygen::HTTPHeaders headers;
protected:
private:
std::string create_url(const std::string& path);
fusethru::util::http::client::http_message make_request(proxygen::HTTPMethod method,folly::EventBase& evb,const std::string& path);
fusethru::util::http::client::http_message make_request(proxygen::HTTPMethod method,folly::EventBase& evb,const std::string& path,const std::string& body);
fusethru::util::http::client::http_message getMessage(folly::EventBase &evb, const proxygen::URL &url,
fusethru::util::http::client::curl::curl_client &curlClient) const;
private:
std::string domain_;
};
#endif //__UTIL__HTTP__CLIENT__HTTP_CLIENT__HPP__
#ifndef __UTIL__HTTP__CLIENT__HTTP_MESSAGE__HPP__
#define __UTIL__HTTP__CLIENT__HTTP_MESSAGE__HPP__
#include <string>
#include <memory>
#include <proxygen/lib/http/HTTPConnector.h>
#include <folly/FBString.h>
class http_message
{
public:
std::unique_ptr<proxygen::HTTPMessage> message;
void set_body(std::unique_ptr<folly::IOBuf> body)
{
body_.swap(body);
}
std::unique_ptr<folly::IOBuf> extract_body()
{
return std::move(body_);
}
folly::fbstring get_folly_str_body()
{
return body_->moveToFbString();
}
std::string get_std_str_body()
{
return body_->moveToFbString().toStdString();
}
private:
std::unique_ptr<folly::IOBuf> body_;
};
#endif //__UTIL__HTTP__CLIENT__HTTP_MESSAGE__HPP__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment