Allow to set buffer size for client; don't auto adjust reading buffer size; refine the global definitions.

master
Chunting Gu 7 years ago
parent ae9d4efcf1
commit 3e60c76da1

@ -5,6 +5,11 @@
#include "webcc/rest_ssl_client.h"
#include "webcc/logger.h"
const bool kSslVerify = false;
#define PRINT_CONTENT 0
static Json::Value StringToJson(const std::string& str) {
Json::Value json;
@ -18,10 +23,11 @@ static Json::Value StringToJson(const std::string& str) {
return json;
}
void Test() {
webcc::RestSslClient client("api.github.com");
void ListPublicEvents() {
webcc::RestSslClient client("api.github.com", "", kSslVerify);
if (client.Get("/events")) {
#if PRINT_CONTENT
Json::Value json = StringToJson(client.response()->content());
// Pretty print the JSON.
@ -33,6 +39,33 @@ void Test() {
writer->write(json, &std::cout);
std::cout << std::endl;
#endif // PRINT_CONTENT
} else {
std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) {
std::cout << " (timed out)";
}
std::cout << std::endl;
}
}
void ListUserFollowers() {
webcc::RestSslClient client("api.github.com", "", kSslVerify);
if (client.Get("/users/sprinfall/followers")) {
#if PRINT_CONTENT
Json::Value json = StringToJson(client.response()->content());
// Pretty print the JSON.
Json::StreamWriterBuilder builder;
builder["indentation"] = " ";
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(json, &std::cout);
std::cout << std::endl;
#endif // PRINT_CONTENT
} else {
std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) {
@ -45,7 +78,9 @@ void Test() {
int main() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
Test();
//ListPublicEvents();
ListUserFollowers();
return 0;
}

@ -37,10 +37,7 @@
#include "webcc/logger.h"
void Test() {
webcc::HttpRequest request;
request.set_method(webcc::kHttpGet);
request.set_url("/get");
request.set_host("httpbin.org"/*, "80"*/);
webcc::HttpRequest request(webcc::kHttpGet, "/get", "httpbin.org"/*, "80"*/);
request.Make();
webcc::HttpClient client;

@ -11,11 +11,9 @@
// The default port number should be 8000.
void Test(boost::asio::io_context& io_context) {
webcc::HttpRequestPtr request(new webcc::HttpRequest());
webcc::HttpRequestPtr request = std::make_shared<webcc::HttpRequest>(
webcc::kHttpGet, "/index.html", "localhost", "8000");
request->set_method(webcc::kHttpGet);
request->set_url("/index.html");
request->set_host("localhost", "8000");
request->Make();
webcc::HttpAsyncClientPtr client(new webcc::HttpAsyncClient(io_context));

@ -9,10 +9,8 @@
// The default port number should be 8000.
void Test() {
webcc::HttpRequest request;
request.set_method(webcc::kHttpGet);
request.set_url("/index.html");
request.set_host("localhost", "8000");
webcc::HttpRequest request(webcc::kHttpGet, "/index.html", "localhost",
"8000");
request.Make();
webcc::HttpClient client;

@ -21,20 +21,19 @@ int main(int argc, char* argv[]) {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
webcc::HttpRequest request;
request.set_method(webcc::kHttpGet);
request.set_url(url);
request.set_host(host); // Leave port to default value.
// Leave port to default value.
webcc::HttpRequest request(webcc::kHttpGet, url, host);
request.Make();
// Verify the certificate of the peer or not.
// See HttpSslClient::Request() for more details.
bool ssl_verify = false;
webcc::HttpSslClient client(ssl_verify);
webcc::HttpSslClient client(2000, ssl_verify);
if (client.Request(request)) {
std::cout << client.response()->content() << std::endl;
//std::cout << client.response()->content() << std::endl;
} else {
std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) {

@ -16,7 +16,7 @@ class BookClientBase {
const std::string& host, const std::string& port,
int timeout_seconds)
: rest_client_(io_context, host, port) {
rest_client_.set_timeout_seconds(timeout_seconds);
rest_client_.SetTimeout(timeout_seconds);
}
virtual ~BookClientBase() = default;
@ -58,10 +58,10 @@ class BookListClient : public BookClientBase {
: BookClientBase(io_context, host, port, timeout_seconds) {
}
void ListBooks(webcc::HttpResponseHandler handler) {
void ListBooks(webcc::HttpResponseCallback callback) {
std::cout << "ListBooks" << std::endl;
rest_client_.Get("/books", handler);
rest_client_.Get("/books", callback);
}
void CreateBook(const std::string& title, double price,
@ -96,7 +96,7 @@ class BookDetailClient : public BookClientBase {
: BookClientBase(io_context, host, port, timeout_seconds) {
}
void GetBook(const std::string& id, webcc::HttpResponseHandler handler) {
void GetBook(const std::string& id, webcc::HttpResponseCallback callback) {
std::cout << "GetBook: " << id << std::endl;
auto rsp_callback = [](webcc::HttpResponsePtr response) {
@ -105,13 +105,13 @@ class BookDetailClient : public BookClientBase {
//id_callback(rsp_json["id"].asString());
};
rest_client_.Get("/books/" + id, handler);
rest_client_.Get("/books/" + id, callback);
}
void UpdateBook(const std::string& id,
const std::string& title,
double price,
webcc::HttpResponseHandler handler) {
webcc::HttpResponseCallback callback) {
std::cout << "UpdateBook: " << id << " " << title << " " << price
<< std::endl;
@ -120,13 +120,13 @@ class BookDetailClient : public BookClientBase {
json["title"] = title;
json["price"] = price;
rest_client_.Put("/books/" + id, JsonToString(json), handler);
rest_client_.Put("/books/" + id, JsonToString(json), callback);
}
void DeleteBook(const std::string& id, webcc::HttpResponseHandler handler) {
void DeleteBook(const std::string& id, webcc::HttpResponseCallback callback) {
std::cout << "DeleteBook: " << id << std::endl;
rest_client_.Delete("/books/" + id, handler);
rest_client_.Delete("/books/" + id, callback);
}
};

@ -40,7 +40,7 @@ class BookClientBase {
}
// Check HTTP response status.
bool CheckStatus(webcc::HttpStatus::Enum expected_status) {
bool CheckStatus(webcc::http::Status expected_status) {
int status = rest_client_.response_status();
if (status != expected_status) {
LOG_ERRO("HTTP status error (actual: %d, expected: %d).",
@ -69,7 +69,7 @@ class BookListClient : public BookClientBase {
return false;
}
if (!CheckStatus(webcc::HttpStatus::kOK)) {
if (!CheckStatus(webcc::http::Status::kOK)) {
// Response HTTP status error.
return false;
}
@ -97,7 +97,7 @@ class BookListClient : public BookClientBase {
return false;
}
if (!CheckStatus(webcc::HttpStatus::kCreated)) {
if (!CheckStatus(webcc::http::Status::kCreated)) {
return false;
}
@ -123,7 +123,7 @@ class BookDetailClient : public BookClientBase {
return false;
}
if (!CheckStatus(webcc::HttpStatus::kOK)) {
if (!CheckStatus(webcc::http::Status::kOK)) {
return false;
}
@ -141,7 +141,7 @@ class BookDetailClient : public BookClientBase {
return false;
}
if (!CheckStatus(webcc::HttpStatus::kOK)) {
if (!CheckStatus(webcc::http::Status::kOK)) {
return false;
}
@ -154,7 +154,7 @@ class BookDetailClient : public BookClientBase {
return false;
}
if (!CheckStatus(webcc::HttpStatus::kOK)) {
if (!CheckStatus(webcc::http::Status::kOK)) {
return false;
}

@ -35,7 +35,7 @@ void BookListService::Get(const webcc::UrlQuery& /*query*/,
}
response->content = JsonToString(json);
response->status = webcc::HttpStatus::kOK;
response->status = webcc::http::Status::kOK;
}
void BookListService::Post(const std::string& request_content,
@ -50,10 +50,10 @@ void BookListService::Post(const std::string& request_content,
json["id"] = id;
response->content = JsonToString(json);
response->status = webcc::HttpStatus::kCreated;
response->status = webcc::http::Status::kCreated;
} else {
// Invalid JSON
response->status = webcc::HttpStatus::kBadRequest;
response->status = webcc::http::Status::kBadRequest;
}
}
@ -67,7 +67,7 @@ void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches,
if (url_sub_matches.size() != 1) {
// Using kNotFound means the resource specified by the URL cannot be found.
// kBadRequest could be another choice.
response->status = webcc::HttpStatus::kNotFound;
response->status = webcc::http::Status::kNotFound;
return;
}
@ -75,12 +75,12 @@ void BookDetailService::Get(const webcc::UrlSubMatches& url_sub_matches,
const Book& book = g_book_store.GetBook(book_id);
if (book.IsNull()) {
response->status = webcc::HttpStatus::kNotFound;
response->status = webcc::http::Status::kNotFound;
return;
}
response->content = BookToJsonString(book);
response->status = webcc::HttpStatus::kOK;
response->status = webcc::http::Status::kOK;
}
void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches,
@ -89,7 +89,7 @@ void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches,
Sleep(sleep_seconds_);
if (url_sub_matches.size() != 1) {
response->status = webcc::HttpStatus::kNotFound;
response->status = webcc::http::Status::kNotFound;
return;
}
@ -97,14 +97,14 @@ void BookDetailService::Put(const webcc::UrlSubMatches& url_sub_matches,
Book book;
if (!JsonStringToBook(request_content, &book)) {
response->status = webcc::HttpStatus::kBadRequest;
response->status = webcc::http::Status::kBadRequest;
return;
}
book.id = book_id;
g_book_store.UpdateBook(book);
response->status = webcc::HttpStatus::kOK;
response->status = webcc::http::Status::kOK;
}
void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches,
@ -112,16 +112,16 @@ void BookDetailService::Delete(const webcc::UrlSubMatches& url_sub_matches,
Sleep(sleep_seconds_);
if (url_sub_matches.size() != 1) {
response->status = webcc::HttpStatus::kNotFound;
response->status = webcc::http::Status::kNotFound;
return;
}
const std::string& book_id = url_sub_matches[0];
if (!g_book_store.DeleteBook(book_id)) {
response->status = webcc::HttpStatus::kNotFound;
response->status = webcc::http::Status::kNotFound;
return;
}
response->status = webcc::HttpStatus::kOK;
response->status = webcc::http::Status::kOK;
}

@ -104,7 +104,7 @@ bool BookClient::Call1(const std::string& operation,
bool BookClient::Call(const std::string& operation,
std::vector<webcc::SoapParameter>&& parameters,
std::string* result_str) {
if (!soap_client_.Request(operation, std::move(parameters), kResult,
if (!soap_client_.Request(operation, std::move(parameters), kResult, 0,
result_str)) {
PrintError();
return false;

@ -55,7 +55,7 @@ class CalcClient {
};
std::string result_str;
if (!soap_client_.Request(operation, std::move(parameters), kResultName,
if (!soap_client_.Request(operation, std::move(parameters), kResultName, 0,
&result_str)) {
PrintError();
return false;

@ -56,7 +56,7 @@ class CalcClient {
};
std::string result_str;
if (!soap_client_.Request(operation, std::move(parameters), kResultName,
if (!soap_client_.Request(operation, std::move(parameters), kResultName, 0,
&result_str)) {
PrintError();
return false;

@ -5,7 +5,7 @@ class TestRestService : public webcc::RestService {
public:
void Handle(const webcc::RestRequest& request,
webcc::RestResponse* response) final {
response->status = webcc::HttpStatus::kOK;
response->status = webcc::http::Status::kOK;
}
};

@ -65,11 +65,13 @@ set(SOURCES
if(WEBCC_ENABLE_SSL)
set(HEADERS ${HEADERS}
http_ssl_client.h
# rest_ssl_async_client.h
rest_ssl_client.h
)
set(SOURCES ${SOURCES}
http_ssl_client.cc
# rest_ssl_async_client.cc
rest_ssl_client.cc
)
endif()

@ -4,19 +4,6 @@ namespace webcc {
// -----------------------------------------------------------------------------
// NOTE: Field names are case-insensitive.
// See https://stackoverflow.com/a/5259004 for more details.
const std::string kHost = "Host";
const std::string kContentType = "Content-Type";
const std::string kContentLength = "Content-Length";
const std::string kTransferEncoding = "Transfer-Encoding";
const std::string kUserAgent = "User-Agent";
const std::string kAppJsonUtf8 = "application/json; charset=utf-8";
const std::string kHttpPort = "80";
const std::string kHttpSslPort = "443";
const std::string kHttpHead = "HEAD";
const std::string kHttpGet = "GET";
const std::string kHttpPost = "POST";

@ -46,20 +46,69 @@ const int kMaxReadSeconds = 30;
const std::size_t kMaxDumpSize = 2048;
// HTTP headers.
extern const std::string kHost;
extern const std::string kContentType;
extern const std::string kContentLength;
extern const std::string kTransferEncoding;
extern const std::string kUserAgent;
namespace http {
extern const std::string kAppJsonUtf8;
// HTTP response status.
// This is not a full list.
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
// NTOE: Don't use enum class because we want to convert to/from int easily.
enum Status {
kOK = 200,
kCreated = 201,
kAccepted = 202,
kNoContent = 204,
kNotModified = 304,
kBadRequest = 400,
kNotFound = 404,
kInternalServerError = 500,
kNotImplemented = 501,
kServiceUnavailable = 503,
};
namespace headers {
// NOTE: Field names are case-insensitive.
// See https://stackoverflow.com/a/5259004 for more details.
const char* const kHost = "Host";
const char* const kContentType = "Content-Type";
const char* const kContentLength = "Content-Length";
const char* const kConnection = "Connection";
const char* const kTransferEncoding = "Transfer-Encoding";
const char* const kAccept = "Accept";
const char* const kAcceptEncoding = "Accept-Encoding";
const char* const kUserAgent = "User-Agent";
} // namespace headers
namespace media_types {
// NOTE:
// According to www.w3.org when placing SOAP messages in HTTP bodies, the HTTP
// Content-type header must be chosen as "application/soap+xml" [RFC 3902].
// But in practice, many web servers cannot understand it.
// See: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#L26854
const char* const kApplicationJson = "application/json";
const char* const kApplicationSoapXml = "application/soap+xml";
const char* const kTextXml = "text/xml";
} // namespace media_types
namespace charsets {
const char* const kUtf8 = "utf-8";
} // namespace charsets
} // namespace http
// Default ports.
extern const std::string kHttpPort;
extern const std::string kHttpSslPort;
const char* const kHttpPort = "80";
const char* const kHttpSslPort = "443";
// HTTP methods (verbs) in string ("HEAD", "GET", etc.).
// NOTE: Don't use enum to avoid converting back and forth.
// TODO: Add enum.
extern const std::string kHttpHead;
extern const std::string kHttpGet;
extern const std::string kHttpPost;
@ -67,25 +116,6 @@ extern const std::string kHttpPatch;
extern const std::string kHttpPut;
extern const std::string kHttpDelete;
// HTTP response status.
// This is not a full list.
// Full list: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
// NTOE: Don't use enum class because we want to convert to/from int easily.
struct HttpStatus {
enum Enum {
kOK = 200,
kCreated = 201,
kAccepted = 202,
kNoContent = 204,
kNotModified = 304,
kBadRequest = 400,
kNotFound = 404,
kInternalServerError = 500,
kNotImplemented = 501,
kServiceUnavailable = 503,
};
};
// Client side error codes.
enum Error {
kNoError = 0, // i.e., OK

@ -9,20 +9,27 @@
namespace webcc {
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context)
HttpAsyncClient::HttpAsyncClient(boost::asio::io_context& io_context,
std::size_t buffer_size)
: socket_(io_context),
resolver_(io_context),
buffer_(kBufferSize),
buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
deadline_(io_context),
timeout_seconds_(kMaxReadSeconds),
stopped_(false),
timed_out_(false) {
}
void HttpAsyncClient::SetTimeout(int seconds) {
if (seconds > 0) {
timeout_seconds_ = seconds;
}
}
void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
HttpResponseHandler response_handler) {
HttpResponseCallback response_callback) {
assert(request);
assert(response_handler);
assert(response_callback);
response_.reset(new HttpResponse());
response_parser_.reset(new HttpResponseParser(response_.get()));
@ -33,7 +40,7 @@ void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
request_ = request;
response_handler_ = response_handler;
response_callback_ = response_callback;
resolver_.async_resolve(tcp::v4(), request->host(), request->port(kHttpPort),
std::bind(&HttpAsyncClient::OnResolve,
@ -43,31 +50,20 @@ void HttpAsyncClient::Request(std::shared_ptr<HttpRequest> request,
}
void HttpAsyncClient::Stop() {
if (stopped_) {
return;
}
stopped_ = true;
LOG_INFO("Close socket...");
LOG_INFO("The user asks to cancel the request.");
boost::system::error_code ec;
socket_.close(ec);
if (ec) {
LOG_ERRO("Socket close error (%s).", ec.message().c_str());
}
LOG_INFO("Cancel deadline timer...");
deadline_.cancel();
DoStop();
}
void HttpAsyncClient::OnResolve(boost::system::error_code ec,
tcp::resolver::results_type endpoints) {
if (ec) {
LOG_ERRO("Can't resolve host (%s): %s, %s", ec.message().c_str(),
LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(),
request_->host().c_str(), request_->port().c_str());
response_handler_(response_, kHostResolveError, timed_out_);
response_callback_(response_, kHostResolveError, timed_out_);
} else {
LOG_VERB("Connect to server...");
// ConnectHandler: void(boost::system::error_code, tcp::endpoint)
boost::asio::async_connect(socket_, endpoints,
std::bind(&HttpAsyncClient::OnConnect,
@ -81,19 +77,19 @@ void HttpAsyncClient::OnConnect(boost::system::error_code ec,
tcp::endpoint endpoint) {
if (ec) {
LOG_ERRO("Socket connect error (%s).", ec.message().c_str());
Stop();
response_handler_(response_, kEndpointConnectError, timed_out_);
DoStop();
response_callback_(response_, kEndpointConnectError, timed_out_);
return;
}
LOG_VERB("Socket connected.");
// The deadline actor may have had a chance to run and close our socket, even
// though the connect operation notionally succeeded.
// Even though the connect operation notionally succeeded, the user could
// have stopped the operation by calling Stop(). And if we started the
// deadline timer, it could also be stopped due to timeout.
if (stopped_) {
// |timed_out_| should be true in this case.
LOG_ERRO("Socket connect timed out.");
response_handler_(response_, kEndpointConnectError, timed_out_);
// TODO: Use some other error.
response_callback_(response_, kEndpointConnectError, timed_out_);
return;
}
@ -102,9 +98,10 @@ void HttpAsyncClient::OnConnect(boost::system::error_code ec,
}
void HttpAsyncClient::DoWrite() {
if (stopped_) {
return;
}
// NOTE:
// It doesn't make much sense to set a timeout for socket write.
// I find that it's almost impossible to simulate a situation in the server
// side to test this timeout.
boost::asio::async_write(socket_,
request_->ToBuffers(),
@ -115,13 +112,19 @@ void HttpAsyncClient::DoWrite() {
void HttpAsyncClient::OnWrite(boost::system::error_code ec) {
if (stopped_) {
// TODO: Use some other error.
response_callback_(response_, kSocketWriteError, timed_out_);
return;
}
if (ec) {
Stop();
response_handler_(response_, kSocketWriteError, timed_out_);
LOG_ERRO("Socket write error (%s).", ec.message().c_str());
DoStop();
response_callback_(response_, kSocketWriteError, timed_out_);
} else {
LOG_INFO("Request sent.");
LOG_VERB("Read response (timeout: %ds)...", timeout_seconds_);
deadline_.expires_from_now(boost::posix_time::seconds(timeout_seconds_));
DoWaitDeadline();
@ -142,36 +145,30 @@ void HttpAsyncClient::OnRead(boost::system::error_code ec,
LOG_VERB("Socket async read handler.");
if (ec || length == 0) {
Stop();
DoStop();
LOG_ERRO("Socket read error (%s).", ec.message().c_str());
response_handler_(response_, kSocketReadError, timed_out_);
response_callback_(response_, kSocketReadError, timed_out_);
return;
}
LOG_INFO("Read data, length: %d.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
LOG_INFO("Read data, length: %u.", length);
// Parse the response piece just read.
// If the content has been fully received, |finished()| will be true.
if (!response_parser_->Parse(buffer_.data(), length)) {
Stop();
DoStop();
LOG_ERRO("Failed to parse HTTP response.");
response_handler_(response_, kHttpError, timed_out_);
response_callback_(response_, kHttpError, timed_out_);
return;
}
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) {
DoStop();
LOG_INFO("Finished to read and parse HTTP response.");
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
Stop();
response_handler_(response_, kNoError, timed_out_);
response_callback_(response_, kNoError, timed_out_);
return;
}
@ -186,17 +183,50 @@ void HttpAsyncClient::DoWaitDeadline() {
}
void HttpAsyncClient::OnDeadline(boost::system::error_code ec) {
LOG_VERB("Deadline handler.");
if (stopped_) {
return;
}
if (ec == boost::asio::error::operation_aborted) {
LOG_VERB("Deadline timer canceled.");
LOG_VERB("OnDeadline.");
// NOTE: Can't check this:
// if (ec == boost::asio::error::operation_aborted) {
// LOG_VERB("Deadline timer canceled.");
// return;
// }
if (deadline_.expires_at() <=
boost::asio::deadline_timer::traits_type::now()) {
// The deadline has passed.
// The socket is closed so that any outstanding asynchronous operations
// are canceled.
LOG_WARN("HTTP client timed out.");
timed_out_ = true;
Stop();
return;
}
LOG_WARN("HTTP client timed out.");
timed_out_ = true;
// Put the actor back to sleep.
DoWaitDeadline();
}
Stop();
void HttpAsyncClient::DoStop() {
if (stopped_) {
return;
}
stopped_ = true;
LOG_INFO("Close socket...");
boost::system::error_code ec;
socket_.close(ec);
if (ec) {
LOG_ERRO("Socket close error (%s).", ec.message().c_str());
}
LOG_INFO("Cancel deadline timer...");
deadline_.cancel();
}
} // namespace webcc

@ -16,8 +16,8 @@
namespace webcc {
// Response handler/callback.
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseHandler;
// Response callback.
typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseCallback;
// HTTP client session in asynchronous mode.
// A request will return without waiting for the response, the callback handler
@ -25,21 +25,22 @@ typedef std::function<void(HttpResponsePtr, Error, bool)> HttpResponseHandler;
// Don't use the same HttpAsyncClient object in multiple threads.
class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
public:
explicit HttpAsyncClient(boost::asio::io_context& io_context);
// The |buffer_size| is the bytes of the buffer for reading response.
// 0 means default value (e.g., 1024) will be used.
explicit HttpAsyncClient(boost::asio::io_context& io_context,
std::size_t buffer_size = 0);
WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClient);
void set_timeout_seconds(int timeout_seconds) {
timeout_seconds_ = timeout_seconds;
}
// Set the timeout seconds for reading response.
// The |seconds| is only effective when greater than 0.
void SetTimeout(int seconds);
// Asynchronously connect to the server, send the request, read the response,
// and call the |response_handler| when all these finish.
void Request(HttpRequestPtr request, HttpResponseHandler response_handler);
// and call the |response_callback| when all these finish.
void Request(HttpRequestPtr request, HttpResponseCallback response_callback);
// Terminate all the actors to shut down the connection. It may be called by
// the user of the client class, or by the class itself in response to
// graceful termination or an unrecoverable error.
// Called by the user to cancel the request.
void Stop();
private:
@ -59,6 +60,9 @@ class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
void DoWaitDeadline();
void OnDeadline(boost::system::error_code ec);
// Terminate all the actors to shut down the connection.
void DoStop();
tcp::resolver resolver_;
tcp::socket socket_;
@ -67,7 +71,7 @@ class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
HttpResponsePtr response_;
std::unique_ptr<HttpResponseParser> response_parser_;
HttpResponseHandler response_handler_;
HttpResponseCallback response_callback_;
// Timer for the timeout control.
boost::asio::deadline_timer deadline_;
@ -76,7 +80,11 @@ class HttpAsyncClient : public std::enable_shared_from_this<HttpAsyncClient> {
// Only for receiving response from server.
int timeout_seconds_;
// Request stopped due to timeout or socket error.
bool stopped_;
// If the error was caused by timeout or not.
// Will be passed to the response handler/callback.
bool timed_out_;
};

@ -14,9 +14,9 @@ using boost::asio::ip::tcp;
namespace webcc {
HttpClient::HttpClient()
HttpClient::HttpClient(std::size_t buffer_size)
: socket_(io_context_),
buffer_(kBufferSize),
buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
deadline_(io_context_),
timeout_seconds_(kMaxReadSeconds),
stopped_(false),
@ -30,7 +30,7 @@ void HttpClient::SetTimeout(int seconds) {
}
}
bool HttpClient::Request(const HttpRequest& request) {
bool HttpClient::Request(const HttpRequest& request, std::size_t buffer_size) {
io_context_.restart();
response_.reset(new HttpResponse());
@ -40,6 +40,8 @@ bool HttpClient::Request(const HttpRequest& request) {
timed_out_ = false;
error_ = kNoError;
BufferResizer buffer_resizer(&buffer_, buffer_size);
if ((error_ = Connect(request)) != kNoError) {
return false;
}
@ -147,8 +149,6 @@ void HttpClient::DoReadResponse(Error* error) {
LOG_INFO("Read data, length: %u.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read.
if (!response_parser_->Parse(buffer_.data(), length)) {
Stop();
@ -157,17 +157,14 @@ void HttpClient::DoReadResponse(Error* error) {
return;
}
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) {
// Stop trying to read once all content has been received,
// because some servers will block extra call to read_some().
// Stop trying to read once all content has been received, because
// some servers will block extra call to read_some().
Stop();
LOG_INFO("Finished to read and parse HTTP response.");
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
return;
}
@ -195,10 +192,10 @@ void HttpClient::OnDeadline(boost::system::error_code ec) {
LOG_VERB("OnDeadline.");
// NOTE: Can't check this:
// if (ec == boost::asio::error::operation_aborted) {
// LOG_VERB("Deadline timer canceled.");
// return;
// }
// if (ec == boost::asio::error::operation_aborted) {
// LOG_VERB("Deadline timer canceled.");
// return;
// }
if (deadline_.expires_at() <=
boost::asio::deadline_timer::traits_type::now()) {

@ -21,7 +21,10 @@ namespace webcc {
// Don't use the same HttpClient object in multiple threads.
class HttpClient {
public:
HttpClient();
// The |buffer_size| is the bytes of the buffer for reading response.
// 0 means default value (e.g., 1024) will be used.
explicit HttpClient(std::size_t buffer_size = 0);
~HttpClient() = default;
WEBCC_DELETE_COPY_ASSIGN(HttpClient);
@ -31,7 +34,9 @@ class HttpClient {
void SetTimeout(int seconds);
// Connect to server, send request, wait until response is received.
bool Request(const HttpRequest& request);
// Set |buffer_size| to non-zero to use a different buffer size for this
// specific request.
bool Request(const HttpRequest& request, std::size_t buffer_size = 0);
HttpResponsePtr response() const { return response_; }
@ -54,7 +59,6 @@ class HttpClient {
void Stop();
boost::asio::io_context io_context_;
boost::asio::ip::tcp::socket socket_;
std::vector<char> buffer_;
@ -62,12 +66,14 @@ class HttpClient {
HttpResponsePtr response_;
std::unique_ptr<HttpResponseParser> response_parser_;
// Timer for the timeout control.
boost::asio::deadline_timer deadline_;
// Maximum seconds to wait before the client cancels the operation.
// Only for reading response from server.
int timeout_seconds_;
// Request stopped due to timeout or socket error.
bool stopped_;
// If the error was caused by timeout or not.

@ -37,6 +37,27 @@ void HttpMessage::SetHeader(std::string&& name, std::string&& value) {
headers_.push_back({ std::move(name), std::move(value) });
}
// NOTE:
// According to HTTP 1.1 RFC7231, the following examples are all equivalent,
// but the first is preferred for consistency:
// text/html;charset=utf-8
// text/html;charset=UTF-8
// Text/HTML;Charset="utf-8"
// text/html; charset="utf-8"
// See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
void HttpMessage::SetContentType(const std::string& media_type,
const std::string& charset) {
SetHeader(http::headers::kContentType,
media_type + ";charset=" + charset);
}
void HttpMessage::SetContent(std::string&& content, bool set_length) {
content_ = std::move(content);
if (set_length) {
SetContentLength(content_.size());
}
}
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
assert(!start_line_.empty());

@ -39,16 +39,10 @@ class HttpMessage {
void SetHeader(std::string&& name, std::string&& value);
// E.g., "application/json; charset=utf-8"
void SetContentType(const std::string& content_type) {
SetHeader(kContentType, content_type);
}
void SetContentType(const std::string& media_type,
const std::string& charset);
void SetContent(std::string&& content, bool set_length) {
content_ = std::move(content);
if (set_length) {
SetContentLength(content_.size());
}
}
void SetContent(std::string&& content, bool set_length);
// Make the message (e.g., update start line).
// Must be called before ToBuffers()!
@ -70,7 +64,7 @@ class HttpMessage {
protected:
void SetContentLength(std::size_t content_length) {
content_length_ = content_length;
SetHeader(kContentLength, std::to_string(content_length));
SetHeader(http::headers::kContentLength, std::to_string(content_length));
}
// Start line with trailing CRLF.

@ -132,7 +132,7 @@ bool HttpParser::ParseHeaderLine(const std::string& line) {
do {
if (!chunked_ && !content_length_parsed_) {
if (boost::iequals(name, kContentLength)) {
if (boost::iequals(name, http::headers::kContentLength)) {
content_length_parsed_ = true;
if (!StringToSizeT(value, 10, &content_length_)) {
@ -156,7 +156,7 @@ bool HttpParser::ParseHeaderLine(const std::string& line) {
// TODO: Replace `!chunked_` with <TransferEncodingParsed>.
if (!chunked_ && !content_length_parsed_) {
if (boost::iequals(name, kTransferEncoding)) {
if (boost::iequals(name, http::headers::kTransferEncoding)) {
if (value == "chunked") {
// The content is chunked.
chunked_ = true;

@ -20,7 +20,6 @@ class HttpParser {
bool finished() const { return finished_; }
bool content_length_parsed() const { return content_length_parsed_; }
std::size_t content_length() const { return content_length_; }
bool Parse(const char* data, std::size_t length);

@ -2,6 +2,13 @@
namespace webcc {
HttpRequest::HttpRequest(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port)
: method_(method), url_(url), host_(host), port_(port) {
}
void HttpRequest::Make() {
start_line_ = method_;
start_line_ += " ";
@ -10,13 +17,19 @@ void HttpRequest::Make() {
start_line_ += CRLF;
if (port_.empty()) {
SetHeader(kHost, host_);
SetHeader(http::headers::kHost, host_);
} else {
SetHeader(kHost, host_ + ":" + port_);
SetHeader(http::headers::kHost, host_ + ":" + port_);
}
// TODO: Support Keep-Alive connection.
//SetHeader(http::headers::kConnection, "close");
// TODO: Support gzip, deflate
SetHeader(http::headers::kAcceptEncoding, "identity");
// NOTE: C++11 requires a space between literal and string macro.
SetHeader(kUserAgent, "Webcc/" WEBCC_VERSION);
SetHeader(http::headers::kUserAgent, "Webcc/" WEBCC_VERSION);
}
} // namespace webcc

@ -8,16 +8,26 @@
namespace webcc {
class HttpRequestParser;
class HttpRequest : public HttpMessage {
public:
HttpRequest() = default;
// The |host| is a descriptive name (e.g., www.google.com) or a numeric IP
// address (127.0.0.1).
// The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP
// or 443 for HTTPS) will be used to connect to server if it's empty.
HttpRequest(const std::string& method,
const std::string& url,
const std::string& host,
const std::string& port = "");
~HttpRequest() override = default;
const std::string& method() const { return method_; }
void set_method(const std::string& method) { method_ = method; }
const std::string& url() const { return url_; }
void set_url(const std::string& url) { url_ = url; }
const std::string& host() const { return host_; }
const std::string& port() const { return port_; }
@ -26,20 +36,15 @@ class HttpRequest : public HttpMessage {
return port_.empty() ? default_port : port_;
}
// Set host name and port number.
// The |host| is a descriptive name (e.g., www.google.com) or a numeric IP
// address (127.0.0.1).
// The |port| is a numeric number (e.g., 9000). The default value (80 for HTTP
// or 443 for HTTPS) will be used to connect to server if it's empty.
void set_host(const std::string& host, const std::string& port = "") {
host_ = host;
port_ = port;
}
// Compose start line, set Host header, etc.
void Make() override;
private:
friend class HttpRequestParser;
void set_method(const std::string& method) { method_ = method; }
void set_url(const std::string& url) { url_ = url; }
// HTTP method.
std::string method_;

@ -20,34 +20,34 @@ const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable\r\n";
const std::string& ToString(int status) {
switch (status) {
case HttpStatus::kOK:
case http::Status::kOK:
return OK;
case HttpStatus::kCreated:
case http::Status::kCreated:
return CREATED;
case HttpStatus::kAccepted:
case http::Status::kAccepted:
return ACCEPTED;
case HttpStatus::kNoContent:
case http::Status::kNoContent:
return NO_CONTENT;
case HttpStatus::kNotModified:
case http::Status::kNotModified:
return NOT_MODIFIED;
case HttpStatus::kBadRequest:
case http::Status::kBadRequest:
return BAD_REQUEST;
case HttpStatus::kNotFound:
case http::Status::kNotFound:
return NOT_FOUND;
case HttpStatus::kInternalServerError:
case http::Status::kInternalServerError:
return INTERNAL_SERVER_ERROR;
case HttpStatus::kNotImplemented:
case http::Status::kNotImplemented:
return NOT_IMPLEMENTED;
case HttpStatus::kServiceUnavailable:
case http::Status::kServiceUnavailable:
return SERVICE_UNAVAILABLE;
default:
@ -66,8 +66,8 @@ void HttpResponse::Make() {
SetHeader("Date", GetHttpDateTimestamp());
}
HttpResponse HttpResponse::Fault(HttpStatus::Enum status) {
assert(status != HttpStatus::kOK);
HttpResponse HttpResponse::Fault(http::Status status) {
assert(status != http::Status::kOK);
HttpResponse response;
response.set_status(status);

@ -10,7 +10,7 @@ namespace webcc {
class HttpResponse : public HttpMessage {
public:
HttpResponse() : status_(HttpStatus::kOK) {}
HttpResponse() : status_(http::Status::kOK) {}
~HttpResponse() override = default;
@ -23,7 +23,7 @@ class HttpResponse : public HttpMessage {
// Get a fault response when HTTP status is not OK.
// TODO: Avoid copy.
static HttpResponse Fault(HttpStatus::Enum status);
static HttpResponse Fault(http::Status status);
private:
int status_;

@ -33,12 +33,13 @@ void HttpSession::Close() {
}
void HttpSession::SetResponseContent(std::string&& content,
const std::string& type) {
const std::string& media_type,
const std::string& charset) {
response_.SetContent(std::move(content), true);
response_.SetContentType(type);
response_.SetContentType(media_type, charset);
}
void HttpSession::SendResponse(HttpStatus::Enum status) {
void HttpSession::SendResponse(http::Status status) {
response_.set_status(status);
response_.Make();
DoWrite();
@ -63,7 +64,7 @@ void HttpSession::OnRead(boost::system::error_code ec, std::size_t length) {
if (!request_parser_.Parse(buffer_.data(), length)) {
// Bad request.
LOG_ERRO("Failed to parse HTTP request.");
response_ = HttpResponse::Fault(HttpStatus::kBadRequest);
response_ = HttpResponse::Fault(http::Status::kBadRequest);
DoWrite();
return;
}

@ -33,10 +33,12 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
// Close the socket.
void Close();
void SetResponseContent(std::string&& content, const std::string& type);
void SetResponseContent(std::string&& content,
const std::string& media_type,
const std::string& charset);
// Send response to client with the given status.
void SendResponse(HttpStatus::Enum status);
void SendResponse(http::Status status);
private:
void DoRead();

@ -15,10 +15,10 @@ namespace ssl = boost::asio::ssl;
namespace webcc {
HttpSslClient::HttpSslClient(bool ssl_verify)
HttpSslClient::HttpSslClient(std::size_t buffer_size, bool ssl_verify)
: ssl_context_(ssl::context::sslv23),
ssl_socket_(io_context_, ssl_context_),
buffer_(kBufferSize),
buffer_(buffer_size == 0 ? kBufferSize : buffer_size),
deadline_(io_context_),
ssl_verify_(ssl_verify),
timeout_seconds_(kMaxReadSeconds),
@ -35,7 +35,8 @@ void HttpSslClient::SetTimeout(int seconds) {
}
}
bool HttpSslClient::Request(const HttpRequest& request) {
bool HttpSslClient::Request(const HttpRequest& request,
std::size_t buffer_size) {
io_context_.restart();
response_.reset(new HttpResponse());
@ -45,6 +46,8 @@ bool HttpSslClient::Request(const HttpRequest& request) {
timed_out_ = false;
error_ = kNoError;
BufferResizer buffer_resizer(&buffer_, buffer_size);
if ((error_ = Connect(request)) != kNoError) {
return false;
}
@ -178,8 +181,6 @@ void HttpSslClient::DoReadResponse(Error* error) {
LOG_INFO("Read data, length: %u.", length);
bool content_length_parsed = response_parser_->content_length_parsed();
// Parse the response piece just read.
if (!response_parser_->Parse(buffer_.data(), length)) {
Stop();
@ -188,12 +189,6 @@ void HttpSslClient::DoReadResponse(Error* error) {
return;
}
if (!content_length_parsed &&
response_parser_->content_length_parsed()) {
// Content length just has been parsed.
AdjustBufferSize(response_parser_->content_length(), &buffer_);
}
if (response_parser_->finished()) {
// Stop trying to read once all content has been received,
// because some servers will block extra call to read_some().

@ -23,11 +23,12 @@ namespace webcc {
// Don't use the same HttpClient object in multiple threads.
class HttpSslClient {
public:
// NOTE:
// SSL verification (ssl_verify=true) needs CA certificates to be found
// The |buffer_size| is the bytes of the buffer for reading response.
// 0 means default value (e.g., 1024) will be used.
// SSL verification (|ssl_verify|) needs CA certificates to be found
// in the default verify paths of OpenSSL. On Windows, it means you need to
// set environment variable SSL_CERT_FILE properly.
HttpSslClient(bool ssl_verify = true);
explicit HttpSslClient(std::size_t buffer_size = 0, bool ssl_verify = true);
~HttpSslClient() = default;
@ -38,7 +39,9 @@ class HttpSslClient {
void SetTimeout(int seconds);
// Connect to server, send request, wait until response is received.
bool Request(const HttpRequest& request);
// Set |buffer_size| to non-zero to use a different buffer size for this
// specific request.
bool Request(const HttpRequest& request, std::size_t buffer_size = 0);
HttpResponsePtr response() const { return response_; }

@ -4,34 +4,37 @@ namespace webcc {
RestAsyncClient::RestAsyncClient(boost::asio::io_context& io_context,
const std::string& host,
const std::string& port)
: io_context_(io_context), host_(host), port_(port), timeout_seconds_(0) {
const std::string& port,
std::size_t buffer_size)
: io_context_(io_context),
host_(host), port_(port),
timeout_seconds_(0),
buffer_size_(buffer_size) {
}
void RestAsyncClient::Request(const std::string& method,
const std::string& url,
std::string&& content,
HttpResponseHandler response_handler) {
HttpRequestPtr request(new webcc::HttpRequest());
request->set_method(method);
request->set_url(url);
request->set_host(host_, port_);
HttpResponseCallback callback) {
HttpRequestPtr http_request(new HttpRequest(method, url, host_, port_));
if (!content.empty()) {
request->SetContent(std::move(content), true);
request->SetContentType(kAppJsonUtf8);
http_request->SetContent(std::move(content), true);
http_request->SetContentType(http::media_types::kApplicationJson,
http::charsets::kUtf8);
}
request->Make();
http_request->Make();
HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_));
HttpAsyncClientPtr http_async_client{
new HttpAsyncClient(io_context_, buffer_size_)
};
if (timeout_seconds_ > 0) {
http_client->set_timeout_seconds(timeout_seconds_);
http_async_client->SetTimeout(timeout_seconds_);
}
http_client->Request(request, response_handler);
http_async_client->Request(http_request, callback);
}
} // namespace webcc

@ -10,41 +10,40 @@ namespace webcc {
class RestAsyncClient {
public:
RestAsyncClient(boost::asio::io_context& io_context, // NOLINT
const std::string& host, const std::string& port);
RestAsyncClient(boost::asio::io_context& io_context,
const std::string& host, const std::string& port,
std::size_t buffer_size = 0);
void set_timeout_seconds(int timeout_seconds) {
timeout_seconds_ = timeout_seconds;
void SetTimeout(int seconds) {
timeout_seconds_ = seconds;
}
void Get(const std::string& url,
HttpResponseHandler response_handler) {
Request(kHttpGet, url, "", response_handler);
void Get(const std::string& url, HttpResponseCallback callback) {
Request(kHttpGet, url, "", callback);
}
void Post(const std::string& url, std::string&& content,
HttpResponseHandler response_handler) {
Request(kHttpPost, url, std::move(content), response_handler);
HttpResponseCallback callback) {
Request(kHttpPost, url, std::move(content), callback);
}
void Put(const std::string& url, std::string&& content,
HttpResponseHandler response_handler) {
Request(kHttpPut, url, std::move(content), response_handler);
HttpResponseCallback callback) {
Request(kHttpPut, url, std::move(content), callback);
}
void Patch(const std::string& url, std::string&& content,
HttpResponseHandler response_handler) {
Request(kHttpPatch, url, std::move(content), response_handler);
HttpResponseCallback callback) {
Request(kHttpPatch, url, std::move(content), callback);
}
void Delete(const std::string& url,
HttpResponseHandler response_handler) {
Request(kHttpDelete, url, "", response_handler);
void Delete(const std::string& url, HttpResponseCallback callback) {
Request(kHttpDelete, url, "", callback);
}
private:
void Request(const std::string& method, const std::string& url,
std::string&& content, HttpResponseHandler response_handler);
std::string&& content, HttpResponseCallback callback);
boost::asio::io_context& io_context_;
@ -53,6 +52,8 @@ class RestAsyncClient {
// Timeout in seconds; only effective when > 0.
int timeout_seconds_;
std::size_t buffer_size_;
};
} // namespace webcc

@ -1,34 +1,30 @@
#include "webcc/rest_client.h"
#include "webcc/utility.h"
namespace webcc {
RestClient::RestClient(const std::string& host, const std::string& port)
: host_(host), port_(port) {
if (port_.empty()) {
std::size_t i = host_.find_last_of(':');
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
RestClient::RestClient(const std::string& host, const std::string& port,
std::size_t buffer_size)
: host_(host), port_(port), http_client_(buffer_size) {
AdjustHostPort(host_, port_);
}
bool RestClient::Request(const std::string& method, const std::string& url,
std::string&& content) {
HttpRequest http_request;
std::string&& content, std::size_t buffer_size) {
HttpRequest http_request(method, url, host_, port_);
http_request.set_method(method);
http_request.set_url(url);
http_request.set_host(host_, port_);
http_request.SetHeader(http::headers::kAccept,
http::media_types::kApplicationJson);
if (!content.empty()) {
http_request.SetContent(std::move(content), true);
http_request.SetContentType(kAppJsonUtf8);
http_request.SetContentType(http::media_types::kApplicationJson,
http::charsets::kUtf8);
}
http_request.Make();
if (!http_client_.Request(http_request)) {
if (!http_client_.Request(http_request, buffer_size)) {
return false;
}

@ -17,7 +17,8 @@ class RestClient {
// If |port| is empty, |host| will be checked to see if it contains port or
// not (separated by ':').
explicit RestClient(const std::string& host,
const std::string& port = "");
const std::string& port = "",
std::size_t buffer_size = 0);
~RestClient() = default;
@ -33,24 +34,27 @@ class RestClient {
// timed_out() for more information if it's failed. Check response_status()
// instead for the HTTP status code.
inline bool Get(const std::string& url) {
return Request(kHttpGet, url, "");
inline bool Get(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpGet, url, "", buffer_size);
}
inline bool Post(const std::string& url, std::string&& content) {
return Request(kHttpPost, url, std::move(content));
inline bool Post(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), buffer_size);
}
inline bool Put(const std::string& url, std::string&& content) {
return Request(kHttpPut, url, std::move(content));
inline bool Put(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) {
return Request(kHttpPut, url, std::move(content), buffer_size);
}
inline bool Patch(const std::string& url, std::string&& content) {
return Request(kHttpPatch, url, std::move(content));
inline bool Patch(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) {
return Request(kHttpPatch, url, std::move(content), buffer_size);
}
inline bool Delete(const std::string& url) {
return Request(kHttpDelete, url, "");
inline bool Delete(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpDelete, url, "", buffer_size);
}
HttpResponsePtr response() const {
@ -76,8 +80,8 @@ class RestClient {
}
private:
bool Request(const std::string& method, const std::string& url,
std::string&& content);
bool Request(const std::string& method, const std::string& url,
std::string&& content, std::size_t buffer_size);
std::string host_;
std::string port_;

@ -19,7 +19,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
Url url(http_request.url(), /*decode*/true);
if (!url.IsPathValid()) {
session->SendResponse(HttpStatus::kBadRequest);
session->SendResponse(http::Status::kBadRequest);
return;
}
@ -33,7 +33,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
if (!service) {
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
session->SendResponse(HttpStatus::kNotFound);
session->SendResponse(http::Status::kNotFound);
return;
}
@ -42,7 +42,8 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
if (!rest_response.content.empty()) {
session->SetResponseContent(std::move(rest_response.content),
kAppJsonUtf8);
http::media_types::kApplicationJson,
http::charsets::kUtf8);
}
// Send response back to client.

@ -38,7 +38,7 @@ struct RestRequest {
};
struct RestResponse {
HttpStatus::Enum status;
http::Status status;
std::string content;
};

@ -1,35 +1,28 @@
#include "webcc/rest_ssl_client.h"
#include "webcc/utility.h"
namespace webcc {
RestSslClient::RestSslClient(const std::string& host, const std::string& port,
bool ssl_verify)
: host_(host), port_(port), http_client_(ssl_verify) {
if (port_.empty()) {
std::size_t i = host_.find_last_of(':');
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
std::size_t buffer_size, bool ssl_verify)
: host_(host), port_(port),
http_client_(buffer_size, ssl_verify) {
AdjustHostPort(host_, port_);
}
bool RestSslClient::Request(const std::string& method, const std::string& url,
std::string&& content) {
HttpRequest http_request;
http_request.set_method(method);
http_request.set_url(url);
http_request.set_host(host_, port_);
std::string&& content, std::size_t buffer_size) {
HttpRequest http_request(method, url, host_, port_);
if (!content.empty()) {
http_request.SetContent(std::move(content), true);
http_request.SetContentType(kAppJsonUtf8);
http_request.SetContentType(http::media_types::kApplicationJson,
http::charsets::kUtf8);
}
http_request.Make();
if (!http_client_.Request(http_request)) {
if (!http_client_.Request(http_request, buffer_size)) {
return false;
}

@ -18,6 +18,7 @@ class RestSslClient {
// not (separated by ':').
explicit RestSslClient(const std::string& host,
const std::string& port = "",
std::size_t buffer_size = 0,
bool ssl_verify = true);
~RestSslClient() = default;
@ -34,24 +35,27 @@ class RestSslClient {
// timed_out() for more information if it's failed. Check response_status()
// instead for the HTTP status code.
inline bool Get(const std::string& url) {
return Request(kHttpGet, url, "");
inline bool Get(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpGet, url, "", buffer_size);
}
inline bool Post(const std::string& url, std::string&& content) {
return Request(kHttpPost, url, std::move(content));
inline bool Post(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), buffer_size);
}
inline bool Put(const std::string& url, std::string&& content) {
return Request(kHttpPut, url, std::move(content));
inline bool Put(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) {
return Request(kHttpPut, url, std::move(content), buffer_size);
}
inline bool Patch(const std::string& url, std::string&& content) {
return Request(kHttpPatch, url, std::move(content));
inline bool Patch(const std::string& url, std::string&& content,
std::size_t buffer_size = 0) {
return Request(kHttpPatch, url, std::move(content), buffer_size);
}
inline bool Delete(const std::string& url) {
return Request(kHttpDelete, url, "");
inline bool Delete(const std::string& url, std::size_t buffer_size = 0) {
return Request(kHttpDelete, url, "", buffer_size);
}
HttpResponsePtr response() const {
@ -77,9 +81,8 @@ class RestSslClient {
}
private:
bool Request(const std::string& method,
const std::string& url,
std::string&& content);
bool Request(const std::string& method, const std::string& url,
std::string&& content, std::size_t buffer_size);
std::string host_;
std::string port_;

@ -3,33 +3,29 @@
#include <cassert>
#include <utility> // for move()
#include "webcc/http_async_client.h"
#include "webcc/soap_globals.h"
#include "webcc/soap_request.h"
#include "webcc/soap_response.h"
#include "webcc/utility.h"
namespace webcc {
SoapAsyncClient::SoapAsyncClient(boost::asio::io_context& io_context,
const std::string& host,
const std::string& port,
SoapVersion soap_version)
SoapVersion soap_version,
std::size_t buffer_size)
: io_context_(io_context),
host_(host), port_(port),
soap_version_(soap_version),
buffer_size_(buffer_size),
format_raw_(true), timeout_seconds_(0) {
if (port_.empty()) {
std::size_t i = host_.find_last_of(':');
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
AdjustHostPort(host_, port_);
}
void SoapAsyncClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
SoapResponseHandler soap_response_handler) {
SoapResponseCallback soap_response_callback) {
assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty());
assert(!result_name_.empty());
@ -54,50 +50,53 @@ void SoapAsyncClient::Request(const std::string& operation,
std::string http_content;
soap_request.ToXml(format_raw_, indent_str_, &http_content);
HttpRequestPtr http_request;
HttpRequestPtr http_request(new HttpRequest(kHttpPost, url_, host_, port_));
http_request->set_method(kHttpPost);
http_request->set_url(url_);
http_request->SetContent(std::move(http_content), true);
if (soap_version_ == kSoapV11) {
http_request->SetContentType(kTextXmlUtf8);
http_request->SetContentType(http::media_types::kTextXml,
http::charsets::kUtf8);
} else {
http_request->SetContentType(kAppSoapXmlUtf8);
http_request->SetContentType(http::media_types::kApplicationSoapXml,
http::charsets::kUtf8);
}
http_request->set_host(host_, port_);
http_request->SetHeader(kSoapAction, operation);
http_request->Make();
HttpAsyncClientPtr http_client(new HttpAsyncClient(io_context_));
HttpAsyncClientPtr http_async_client{
new HttpAsyncClient(io_context_, buffer_size_)
};
if (timeout_seconds_ > 0) {
http_client->set_timeout_seconds(timeout_seconds_);
http_async_client->SetTimeout(timeout_seconds_);
}
auto http_response_handler = std::bind(&SoapAsyncClient::ResponseHandler,
this, soap_response_handler,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3);
auto http_response_callback = std::bind(&SoapAsyncClient::OnHttpResponse,
this, soap_response_callback,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3);
http_client->Request(http_request, http_response_handler);
http_async_client->Request(http_request, http_response_callback);
}
void SoapAsyncClient::ResponseHandler(SoapResponseHandler soap_response_handler,
HttpResponsePtr http_response,
Error error, bool timed_out) {
void SoapAsyncClient::OnHttpResponse(SoapResponseCallback soap_response_callback,
HttpResponsePtr http_response,
Error error, bool timed_out) {
if (error != kNoError) {
soap_response_handler("", error, timed_out);
soap_response_callback("", error, timed_out);
} else {
SoapResponse soap_response;
// TODO
//soap_response.set_result_name(result_name_);
if (!soap_response.FromXml(http_response->content())) {
soap_response_handler("", kXmlError, false);
soap_response_callback("", kXmlError, false);
} else {
//soap_response_handler(soap_response.result_moved(), kNoError, false);
// TODO
//soap_response_callback(soap_response.result_moved(), kNoError, false);
}
}
}

@ -11,8 +11,8 @@
namespace webcc {
// Response handler/callback.
typedef std::function<void(std::string, Error, bool)> SoapResponseHandler;
// Response callback.
typedef std::function<void(std::string, Error, bool)> SoapResponseCallback;
class SoapAsyncClient {
public:
@ -20,7 +20,8 @@ class SoapAsyncClient {
// not (separated by ':').
SoapAsyncClient(boost::asio::io_context& io_context, // NOLINT
const std::string& host, const std::string& port = "",
SoapVersion soap_version = kSoapV12);
SoapVersion soap_version = kSoapV12,
std::size_t buffer_size = 0);
~SoapAsyncClient() = default;
@ -48,12 +49,12 @@ class SoapAsyncClient {
void Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
SoapResponseHandler soap_response_handler);
SoapResponseCallback soap_response_callback);
private:
void ResponseHandler(SoapResponseHandler soap_response_handler,
HttpResponsePtr http_response,
Error error, bool timed_out);
void OnHttpResponse(SoapResponseCallback soap_response_callback,
HttpResponsePtr http_response,
Error error, bool timed_out);
boost::asio::io_context& io_context_;
@ -62,6 +63,8 @@ class SoapAsyncClient {
SoapVersion soap_version_;
std::size_t buffer_size_;
// Request URL.
std::string url_;

@ -6,27 +6,22 @@
#include "boost/algorithm/string.hpp"
#include "webcc/soap_request.h"
#include "webcc/soap_response.h"
#include "webcc/utility.h"
namespace webcc {
SoapClient::SoapClient(const std::string& host, const std::string& port,
SoapVersion soap_version)
: host_(host), port_(port),
soap_version_(soap_version),
SoapVersion soap_version, std::size_t buffer_size)
: host_(host), port_(port), soap_version_(soap_version),
http_client_(buffer_size),
format_raw_(true), error_(kNoError) {
if (port_.empty()) {
std::size_t i = host_.find_last_of(':');
if (i != std::string::npos) {
port_ = host_.substr(i + 1);
host_ = host_.substr(0, i);
}
}
AdjustHostPort(host_, port_);
}
bool SoapClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
SoapResponse::Parser parser) {
SoapResponse::Parser parser,
std::size_t buffer_size) {
assert(service_ns_.IsValid());
assert(!url_.empty() && !host_.empty());
@ -53,23 +48,23 @@ bool SoapClient::Request(const std::string& operation,
std::string http_content;
soap_request.ToXml(format_raw_, indent_str_, &http_content);
HttpRequest http_request;
HttpRequest http_request(kHttpPost, url_, host_, port_);
http_request.set_method(kHttpPost);
http_request.set_url(url_);
http_request.SetContent(std::move(http_content), true);
if (soap_version_ == kSoapV11) {
http_request.SetContentType(kTextXmlUtf8);
http_request.SetContentType(http::media_types::kTextXml,
http::charsets::kUtf8);
} else {
http_request.SetContentType(kAppSoapXmlUtf8);
http_request.SetContentType(http::media_types::kApplicationSoapXml,
http::charsets::kUtf8);
}
http_request.set_host(host_, port_);
http_request.SetHeader(kSoapAction, operation);
http_request.Make();
if (!http_client_.Request(http_request)) {
if (!http_client_.Request(http_request, buffer_size)) {
error_ = http_client_.error();
return false;
}
@ -95,6 +90,7 @@ bool SoapClient::Request(const std::string& operation,
bool SoapClient::Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
const std::string& result_name,
std::size_t buffer_size,
std::string* result) {
auto parser = [result, &result_name](pugi::xml_node xnode) {
if (boost::iequals(soap_xml::GetNameNoPrefix(xnode), result_name)) {
@ -103,7 +99,7 @@ bool SoapClient::Request(const std::string& operation,
return false; // Stop next call.
};
return Request(operation, std::move(parameters), parser);
return Request(operation, std::move(parameters), parser, buffer_size);
}
} // namespace webcc

@ -16,7 +16,8 @@ class SoapClient {
// If |port| is empty, |host| will be checked to see if it contains port or
// not (separated by ':').
explicit SoapClient(const std::string& host, const std::string& port = "",
SoapVersion soap_version = kSoapV12);
SoapVersion soap_version = kSoapV12,
std::size_t buffer_size = 0);
~SoapClient() = default;
@ -42,7 +43,8 @@ class SoapClient {
bool Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
SoapResponse::Parser parser);
SoapResponse::Parser parser,
std::size_t buffer_size = 0);
// Shortcut for responses with single result node.
// The name of the single result node is specified by |result_name|.
@ -50,6 +52,7 @@ class SoapClient {
bool Request(const std::string& operation,
std::vector<SoapParameter>&& parameters,
const std::string& result_name,
std::size_t buffer_size, // Pass 0 for using default size.
std::string* result);
// HTTP status code (200, 500, etc.) in the response.
@ -72,6 +75,8 @@ class SoapClient {
SoapVersion soap_version_;
HttpClient http_client_;
// Request URL.
std::string url_;
@ -85,8 +90,6 @@ class SoapClient {
// Applicable when |format_raw_| is false.
std::string indent_str_;
HttpClient http_client_;
Error error_;
std::shared_ptr<SoapFault> fault_;

@ -18,14 +18,14 @@ bool SoapRequestHandler::Bind(SoapServicePtr service, const std::string& url) {
void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
SoapServicePtr service = GetServiceByUrl(session->request().url());
if (!service) {
session->SendResponse(HttpStatus::kBadRequest);
session->SendResponse(http::Status::kBadRequest);
return;
}
// Parse the SOAP request XML.
SoapRequest soap_request;
if (!soap_request.FromXml(session->request().content())) {
session->SendResponse(HttpStatus::kBadRequest);
session->SendResponse(http::Status::kBadRequest);
return;
}
@ -40,7 +40,7 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
}
if (!service->Handle(soap_request, &soap_response)) {
session->SendResponse(HttpStatus::kBadRequest);
session->SendResponse(http::Status::kBadRequest);
return;
}
@ -48,12 +48,16 @@ void SoapRequestHandler::HandleSession(HttpSessionPtr session) {
soap_response.ToXml(format_raw_, indent_str_, &content);
if (soap_version_ == kSoapV11) {
session->SetResponseContent(std::move(content), kTextXmlUtf8);
session->SetResponseContent(std::move(content),
http::media_types::kTextXml,
http::charsets::kUtf8);
} else {
session->SetResponseContent(std::move(content), kAppSoapXmlUtf8);
session->SetResponseContent(std::move(content),
http::media_types::kApplicationSoapXml,
http::charsets::kUtf8);
}
session->SendResponse(HttpStatus::kOK);
session->SendResponse(http::Status::kOK);
}
SoapServicePtr SoapRequestHandler::GetServiceByUrl(const std::string& url) {

@ -20,7 +20,7 @@ class SoapService {
SoapResponse* soap_response) = 0;
protected:
HttpStatus::Enum http_status_ = HttpStatus::kOK;
http::Status http_status_ = http::Status::kOK;
};
typedef std::shared_ptr<SoapService> SoapServicePtr;

@ -12,21 +12,13 @@ using tcp = boost::asio::ip::tcp;
namespace webcc {
void AdjustBufferSize(std::size_t content_length, std::vector<char>* buffer) {
const std::size_t kMaxTimes = 10;
// According to test, a client never read more than 200000 bytes a time.
// So it doesn't make sense to set any larger size, e.g., 1MB.
const std::size_t kMaxBufferSize = 200000;
LOG_INFO("Adjust buffer size according to content length.");
std::size_t min_buffer_size = content_length / kMaxTimes;
if (min_buffer_size > buffer->size()) {
buffer->resize(std::min(min_buffer_size, kMaxBufferSize));
LOG_INFO("Resize read buffer to %u.", buffer->size());
} else {
LOG_INFO("Keep the current buffer size: %u.", buffer->size());
void AdjustHostPort(std::string& host, std::string& port) {
if (port.empty()) {
std::size_t i = host.find_last_of(':');
if (i != std::string::npos) {
port = host.substr(i + 1);
host = host.substr(0, i);
}
}
}

@ -9,9 +9,8 @@
namespace webcc {
// Adjust buffer size according to content length.
// This is to avoid reading too many times.
void AdjustBufferSize(std::size_t content_length, std::vector<char>* buffer);
// If |port| is empty, try to extract it from |host| (separated by ':').
void AdjustHostPort(std::string& host, std::string& port);
void PrintEndpoint(std::ostream& ostream,
const boost::asio::ip::tcp::endpoint& endpoint);
@ -27,6 +26,25 @@ std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint);
// See: https://tools.ietf.org/html/rfc7231#section-7.1.1.2
std::string GetHttpDateTimestamp();
// Resize a buffer in ctor and restore its original size in dtor.
struct BufferResizer {
BufferResizer(std::vector<char>* buffer, std::size_t new_size)
: buffer_(buffer), old_size_(buffer->size()) {
if (new_size != 0 && new_size != old_size_) {
buffer_->resize(new_size);
}
}
~BufferResizer() {
if (buffer_->size() != old_size_) {
buffer_->resize(old_size_);
}
}
std::vector<char>* buffer_;
std::size_t old_size_;
};
} // namespace webcc
#endif // WEBCC_UTILITY_H_

Loading…
Cancel
Save