Breaking changes on the client, push only for keeping the history.

master
Chunting Gu 6 years ago
parent d42556ce5a
commit b38b7e6d38

@ -1,13 +1,27 @@
#include <iostream> #include <iostream>
#include <map>
#include "json/json.h" #include "json/json.h"
#include "webcc/logger.h" #include "webcc/logger.h"
#include "webcc/rest_ssl_client.h" #include "webcc/rest_ssl_client.h"
const bool kSslVerify = false; // -----------------------------------------------------------------------------
#define PRINT_CONTENT 0 // Change to 1 to print response JSON.
#define PRINT_RESPONSE 0
#if (defined(WIN32) || defined(_WIN64))
// You need to set environment variable SSL_CERT_FILE properly to enable
// SSL verification.
bool kSslVerify = false;
#else
bool kSslVerify = true;
#endif
const std::string kGithubHost = "api.github.com";
// -----------------------------------------------------------------------------
static Json::Value StringToJson(const std::string& str) { static Json::Value StringToJson(const std::string& str) {
Json::Value json; Json::Value json;
@ -22,64 +36,76 @@ static Json::Value StringToJson(const std::string& str) {
return json; return json;
} }
void ListPublicEvents() { // Print the JSON string in pretty format.
webcc::RestSslClient client("api.github.com", "", kSslVerify); static void PrettyPrintJsonString(const std::string& str) {
Json::Value json = StringToJson(str);
if (client.Get("/events")) { Json::StreamWriterBuilder builder;
#if PRINT_CONTENT builder["indentation"] = " ";
Json::Value json = StringToJson(client.response()->content());
// Pretty print the JSON. std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(json, &std::cout);
Json::StreamWriterBuilder builder; std::cout << std::endl;
builder["indentation"] = " "; }
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter()); // -----------------------------------------------------------------------------
writer->write(json, &std::cout);
std::cout << std::endl; #if PRINT_RESPONSE
#endif // PRINT_CONTENT #define PRINT_JSON_STRING(str) PrettyPrintJsonString(str)
} else { #else
std::cout << webcc::DescribeError(client.error()); #define PRINT_JSON_STRING(str)
if (client.timed_out()) { #endif // PRINT_RESPONSE
std::cout << " (timed out)";
} static void PrintError(const webcc::RestSslClient& client) {
std::cout << std::endl; 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; // List public events.
builder["indentation"] = " "; static void ListEvents(webcc::RestSslClient& client) {
if (client.Get("/events")) {
PRINT_JSON_STRING(client.response_content());
} else {
PrintError(client);
}
}
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter()); // List the followers of the given user.
writer->write(json, &std::cout); static void ListUserFollowers(webcc::RestSslClient& client,
const std::string& user) {
if (client.Get("/users/" + user + "/followers")) {
PRINT_JSON_STRING(client.response_content());
} else {
PrintError(client);
}
}
std::cout << std::endl; // List the followers of the current authorized user.
#endif // PRINT_CONTENT // Header syntax: Authorization: <type> <credentials>
static void ListAuthorizedUserFollowers(webcc::RestSslClient& client,
const std::string& auth) {
if (client.Get("/user/followers", { { "Authorization", auth } })) {
PRINT_JSON_STRING(client.response_content());
} else { } else {
std::cout << webcc::DescribeError(client.error()); PrintError(client);
if (client.timed_out()) {
std::cout << " (timed out)";
}
std::cout << std::endl;
} }
} }
// -----------------------------------------------------------------------------
int main() { int main() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
//ListPublicEvents(); webcc::RestSslClient client(kGithubHost, "", kSslVerify, {}, 1500);
ListUserFollowers(); //ListAuthorizedUserFollowers(client, "Basic c3ByaW5mYWxsQGdtYWlsLmNvbTpYaWFvTHVhbjFA");
ListAuthorizedUserFollowers(client, "Token 1d42e2cce49929f2d24b1b6e96260003e5b3e1b0");
return 0; return 0;
} }

@ -23,25 +23,38 @@ class BookClientBase {
public: public:
BookClientBase(const std::string& host, const std::string& port, BookClientBase(const std::string& host, const std::string& port,
int timeout_seconds) int timeout_seconds)
: rest_client_(host, port) { : host_(host), port_(port) {
rest_client_.SetTimeout(timeout_seconds); http_client_.SetTimeout(timeout_seconds);
} }
virtual ~BookClientBase() = default; virtual ~BookClientBase() = default;
protected: protected:
// Helper function to make a request.
webcc::HttpRequestPtr MakeRequest(const std::string& method,
const std::string& url,
std::string&& content = "") {
auto request = webcc::HttpRequest::New(method, url, host_, port_);
request->AcceptAppJson();
if (!content.empty()) {
request->SetContentInAppJsonUtf8(JsonToString(content), true);
}
request->Prepare();
return request;
}
// Log the socket communication error. // Log the socket communication error.
void LogError() { void LogError() {
if (rest_client_.timed_out()) { if (http_client_.timed_out()) {
LOG_ERRO("%s (timed out)", webcc::DescribeError(rest_client_.error())); LOG_ERRO("%s (timed out)", webcc::DescribeError(http_client_.error()));
} else { } else {
LOG_ERRO(webcc::DescribeError(rest_client_.error())); LOG_ERRO(webcc::DescribeError(http_client_.error()));
} }
} }
// Check HTTP response status. // Check HTTP response status.
bool CheckStatus(webcc::http::Status expected_status) { bool CheckStatus(webcc::http::Status expected_status) {
int status = rest_client_.response_status(); int status = http_client_.response_status();
if (status != expected_status) { if (status != expected_status) {
LOG_ERRO("HTTP status error (actual: %d, expected: %d).", LOG_ERRO("HTTP status error (actual: %d, expected: %d).",
status, expected_status); status, expected_status);
@ -50,7 +63,9 @@ class BookClientBase {
return true; return true;
} }
webcc::RestClient rest_client_; std::string host_;
std::string port_;
webcc::HttpClient http_client_;
}; };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -63,7 +78,9 @@ class BookListClient : public BookClientBase {
} }
bool ListBooks(std::list<Book>* books) { bool ListBooks(std::list<Book>* books) {
if (!rest_client_.Get("/books")) { auto request = MakeRequest(webcc::kHttpGet, "/books");
if (!http_client_.Request(*request)) {
// Socket communication error. // Socket communication error.
LogError(); LogError();
return false; return false;
@ -74,7 +91,7 @@ class BookListClient : public BookClientBase {
return false; return false;
} }
Json::Value rsp_json = StringToJson(rest_client_.response_content()); Json::Value rsp_json = StringToJson(http_client_.response_content());
if (!rsp_json.isArray()) { if (!rsp_json.isArray()) {
return false; // Should be a JSON array of books. return false; // Should be a JSON array of books.
@ -92,7 +109,10 @@ class BookListClient : public BookClientBase {
req_json["title"] = title; req_json["title"] = title;
req_json["price"] = price; req_json["price"] = price;
if (!rest_client_.Post("/books", JsonToString(req_json))) { auto request = MakeRequest(webcc::kHttpPost, "/books",
JsonToString(req_json));
if (!http_client_.Request(*request)) {
LogError(); LogError();
return false; return false;
} }
@ -101,7 +121,7 @@ class BookListClient : public BookClientBase {
return false; return false;
} }
Json::Value rsp_json = StringToJson(rest_client_.response_content()); Json::Value rsp_json = StringToJson(http_client_.response_content());
*id = rsp_json["id"].asString(); *id = rsp_json["id"].asString();
return !id->empty(); return !id->empty();
@ -118,7 +138,9 @@ class BookDetailClient : public BookClientBase {
} }
bool GetBook(const std::string& id, Book* book) { bool GetBook(const std::string& id, Book* book) {
if (!rest_client_.Get("/books/" + id)) { auto request = MakeRequest(webcc::kHttpGet, "/books/" + id);
if (!http_client_.Request(*request)) {
LogError(); LogError();
return false; return false;
} }
@ -127,7 +149,7 @@ class BookDetailClient : public BookClientBase {
return false; return false;
} }
return JsonStringToBook(rest_client_.response_content(), book); return JsonStringToBook(http_client_.response_content(), book);
} }
bool UpdateBook(const std::string& id, const std::string& title, bool UpdateBook(const std::string& id, const std::string& title,
@ -136,7 +158,10 @@ class BookDetailClient : public BookClientBase {
json["title"] = title; json["title"] = title;
json["price"] = price; json["price"] = price;
if (!rest_client_.Put("/books/" + id, JsonToString(json))) { auto request = MakeRequest(webcc::kHttpPut, "/books/" + id,
JsonToString(json));
if (!http_client_.Request(*request)) {
LogError(); LogError();
return false; return false;
} }
@ -149,7 +174,9 @@ class BookDetailClient : public BookClientBase {
} }
bool DeleteBook(const std::string& id) { bool DeleteBook(const std::string& id) {
if (!rest_client_.Delete("/books/" + id)) { auto request = MakeRequest(webcc::kHttpDelete, "/books/" + id);
if (!http_client_.Request(*request)) {
LogError(); LogError();
return false; return false;
} }
@ -238,26 +265,26 @@ int main(int argc, char* argv[]) {
PrintBook(book); PrintBook(book);
} }
PrintSeparator(); //PrintSeparator();
detail_client.UpdateBook(id, "1Q84", 32.1); //detail_client.UpdateBook(id, "1Q84", 32.1);
PrintSeparator(); //PrintSeparator();
if (detail_client.GetBook(id, &book)) { //if (detail_client.GetBook(id, &book)) {
PrintBook(book); // PrintBook(book);
} //}
PrintSeparator(); //PrintSeparator();
detail_client.DeleteBook(id); //detail_client.DeleteBook(id);
PrintSeparator(); //PrintSeparator();
books.clear(); //books.clear();
if (list_client.ListBooks(&books)) { //if (list_client.ListBooks(&books)) {
PrintBookList(books); // PrintBookList(books);
} //}
return 0; return 0;
} }

@ -31,6 +31,8 @@ class HttpAsyncClientBase
explicit HttpAsyncClientBase(boost::asio::io_context& io_context, explicit HttpAsyncClientBase(boost::asio::io_context& io_context,
std::size_t buffer_size = 0); std::size_t buffer_size = 0);
virtual ~HttpAsyncClientBase() = default;
WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClientBase); WEBCC_DELETE_COPY_ASSIGN(HttpAsyncClientBase);
// Set the timeout seconds for reading response. // Set the timeout seconds for reading response.

@ -27,7 +27,7 @@ class HttpClientBase {
// 0 means default value (e.g., 1024) will be used. // 0 means default value (e.g., 1024) will be used.
explicit HttpClientBase(std::size_t buffer_size = 0); explicit HttpClientBase(std::size_t buffer_size = 0);
~HttpClientBase() = default; virtual ~HttpClientBase() = default;
WEBCC_DELETE_COPY_ASSIGN(HttpClientBase); WEBCC_DELETE_COPY_ASSIGN(HttpClientBase);

@ -58,6 +58,12 @@ void HttpMessage::SetContent(std::string&& content, bool set_length) {
} }
} }
void HttpMessage::SetContentInAppJsonUtf8(std::string&& content,
bool set_length) {
SetContent(std::move(content), set_length);
SetContentType(http::media_types::kApplicationJson, http::charsets::kUtf8);
}
// ATTENTION: The buffers don't hold the memory! // ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const { std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
assert(!start_line_.empty()); assert(!start_line_.empty());

@ -34,16 +34,21 @@ class HttpMessage {
const std::string& content() const { return content_; } const std::string& content() const { return content_; }
// TODO: Rename to AddHeader.
void SetHeader(const std::string& name, const std::string& value); void SetHeader(const std::string& name, const std::string& value);
// TODO: Remove
void SetHeader(std::string&& name, std::string&& value); void SetHeader(std::string&& name, std::string&& value);
// E.g., "application/json; charset=utf-8" // E.g., "application/json; charset=utf-8"
void SetContentType(const std::string& media_type, void SetContentType(const std::string& media_type,
const std::string& charset); const std::string& charset);
// TODO: Remove parameter |set_length|.
void SetContent(std::string&& content, bool set_length); void SetContent(std::string&& content, bool set_length);
void SetContentInAppJsonUtf8(std::string&& content, bool set_length);
// Make the message (e.g., update start line). // Make the message (e.g., update start line).
// Must be called before ToBuffers()! // Must be called before ToBuffers()!
virtual void Prepare() = 0; virtual void Prepare() = 0;

@ -39,6 +39,16 @@ class HttpRequest : public HttpMessage {
return port_.empty() ? default_port : port_; return port_.empty() ? default_port : port_;
} }
// Shortcut to set `Accept` header.
void Accept(const std::string& media_type) {
SetHeader(http::headers::kAccept, media_type);
}
// Shortcut to set `Accept` header.
void AcceptAppJson() {
SetHeader(http::headers::kAccept, http::media_types::kApplicationJson);
}
// Prepare payload. // Prepare payload.
// Compose start line, set Host header, etc. // Compose start line, set Host header, etc.
void Prepare() override; void Prepare() override;

@ -10,7 +10,7 @@ namespace ssl = boost::asio::ssl;
namespace webcc { namespace webcc {
HttpSslClient::HttpSslClient(std::size_t buffer_size, bool ssl_verify) HttpSslClient::HttpSslClient(bool ssl_verify, std::size_t buffer_size)
: HttpClientBase(buffer_size), : HttpClientBase(buffer_size),
ssl_context_(ssl::context::sslv23), ssl_context_(ssl::context::sslv23),
ssl_socket_(io_context_, ssl_context_), ssl_socket_(io_context_, ssl_context_),

@ -13,7 +13,7 @@ class HttpSslClient : public HttpClientBase {
// SSL verification (|ssl_verify|) needs CA certificates to be found // SSL verification (|ssl_verify|) needs CA certificates to be found
// in the default verify paths of OpenSSL. On Windows, it means you need to // in the default verify paths of OpenSSL. On Windows, it means you need to
// set environment variable SSL_CERT_FILE properly. // set environment variable SSL_CERT_FILE properly.
explicit HttpSslClient(std::size_t buffer_size = 0, bool ssl_verify = true); explicit HttpSslClient(bool ssl_verify = true, std::size_t buffer_size = 0);
~HttpSslClient() = default; ~HttpSslClient() = default;

@ -37,6 +37,7 @@ void RestRequestHandler::HandleSession(HttpSessionPtr session) {
return; return;
} }
// TODO: Let the service to provide the media-type and charset.
RestResponse rest_response; RestResponse rest_response;
service->Handle(rest_request, &rest_response); service->Handle(rest_request, &rest_response);

@ -1,28 +1,50 @@
#include "webcc/rest_ssl_client.h" #include "webcc/rest_ssl_client.h"
#include "boost/algorithm/string.hpp"
#include "webcc/utility.h" #include "webcc/utility.h"
namespace webcc { namespace webcc {
RestClientBase::RestClientBase(HttpClientBase* http_client_base,
const SSMap& headers)
: http_client_base_(http_client_base),
content_media_type_(http::media_types::kApplicationJson),
content_charset_(http::charsets::kUtf8),
headers_(headers) {
}
RestSslClient::RestSslClient(const std::string& host, const std::string& port, RestSslClient::RestSslClient(const std::string& host, const std::string& port,
std::size_t buffer_size, bool ssl_verify) bool ssl_verify, const SSMap& headers,
: host_(host), port_(port), std::size_t buffer_size)
http_client_(buffer_size, ssl_verify) { : RestClientBase(&http_ssl_client_, headers),
host_(host), port_(port),
http_ssl_client_(ssl_verify, buffer_size) {
AdjustHostPort(host_, port_); AdjustHostPort(host_, port_);
} }
bool RestSslClient::Request(const std::string& method, const std::string& url, bool RestSslClient::Request(const std::string& method, const std::string& url,
std::string&& content, std::size_t buffer_size) { std::string&& content, const SSMap& headers,
std::size_t buffer_size) {
HttpRequest http_request(method, url, host_, port_); HttpRequest http_request(method, url, host_, port_);
if (!content.empty()) { if (!content.empty()) {
http_request.SetContent(std::move(content), true); http_request.SetContent(std::move(content), true);
http_request.SetContentType(http::media_types::kApplicationJson, http_request.SetContentType(content_media_type_, content_charset_);
http::charsets::kUtf8); }
for (auto& h : headers_) {
http_request.SetHeader(h.first, h.second);
}
for (auto& h : headers) {
http_request.SetHeader(h.first, h.second);
} }
http_request.Prepare(); http_request.Prepare();
if (!http_client_.Request(http_request, buffer_size)) { if (!http_ssl_client_.Request(http_request, buffer_size)) {
return false; return false;
} }

@ -2,6 +2,7 @@
#define WEBCC_REST_SSL_CLIENT_H_ #define WEBCC_REST_SSL_CLIENT_H_
#include <cassert> #include <cassert>
#include <map>
#include <string> #include <string>
#include <utility> // for move() #include <utility> // for move()
@ -12,54 +13,30 @@
namespace webcc { namespace webcc {
class RestSslClient { typedef std::map<std::string, std::string> SSMap;
public:
// If |port| is empty, |host| will be checked to see if it contains port or
// 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;
WEBCC_DELETE_COPY_ASSIGN(RestSslClient);
void SetTimeout(int seconds) {
http_client_.SetTimeout(seconds);
}
// NOTE:
// The return value of the following methods (Get, Post, etc.) only indicates
// if the socket communication is successful or not. Check error() and
// 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, std::size_t buffer_size = 0) { class RestClientBase {
return Request(kHttpGet, url, "", buffer_size); public:
} RestClientBase(HttpClientBase* http_client_base,
const SSMap& headers = {});
inline bool Post(const std::string& url, std::string&& content, virtual ~RestClientBase() = default;
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, WEBCC_DELETE_COPY_ASSIGN(RestClientBase);
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, void SetTimeout(int seconds) {
std::size_t buffer_size = 0) { http_client_base_->SetTimeout(seconds);
return Request(kHttpPatch, url, std::move(content), buffer_size);
} }
inline bool Delete(const std::string& url, std::size_t buffer_size = 0) { // Overwrite the default content type (json & utf-8).
return Request(kHttpDelete, url, "", buffer_size); void SetContentType(const std::string& media_type,
const std::string& charset) {
content_media_type_ = media_type;
content_charset_ = charset;
} }
HttpResponsePtr response() const { HttpResponsePtr response() const {
return http_client_.response(); return http_client_base_->response();
} }
int response_status() const { int response_status() const {
@ -73,21 +50,79 @@ class RestSslClient {
} }
bool timed_out() const { bool timed_out() const {
return http_client_.timed_out(); return http_client_base_->timed_out();
} }
Error error() const { Error error() const {
return http_client_.error(); return http_client_base_->error();
} }
protected:
// Default: "application/json".
std::string content_media_type_;
// Default: "utf-8".
std::string content_charset_;
// Default headers for all session requests.
std::map<std::string, std::string> headers_;
private: private:
HttpClientBase* http_client_base_;
};
class RestSslClient : public RestClientBase {
public:
// If |port| is empty, |host| will be checked to see if it contains port or
// not (separated by ':').
explicit RestSslClient(const std::string& host,
const std::string& port = "",
bool ssl_verify = true,
const SSMap& headers = {},
std::size_t buffer_size = 0);
~RestSslClient() = default;
// NOTE:
// The return value of the following methods (Get, Post, etc.) only indicates
// if the socket communication is successful or not. Check error() and
// 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, const SSMap& headers = {},
std::size_t buffer_size = 0) {
return Request(kHttpGet, url, "", headers, buffer_size);
}
inline bool Post(const std::string& url, std::string&& content,
const SSMap& headers = {}, std::size_t buffer_size = 0) {
return Request(kHttpPost, url, std::move(content), headers, buffer_size);
}
inline bool Put(const std::string& url, std::string&& content,
const SSMap& headers = {}, std::size_t buffer_size = 0) {
return Request(kHttpPut, url, std::move(content), headers, buffer_size);
}
inline bool Patch(const std::string& url, std::string&& content,
const SSMap& headers = {}, std::size_t buffer_size = 0) {
return Request(kHttpPatch, url, std::move(content), headers, buffer_size);
}
inline bool Delete(const std::string& url, const SSMap& headers = {},
std::size_t buffer_size = 0) {
return Request(kHttpDelete, url, "", headers, buffer_size);
}
bool Request(const std::string& method, const std::string& url, bool Request(const std::string& method, const std::string& url,
std::string&& content, std::size_t buffer_size); std::string&& content, const SSMap& headers = {},
std::size_t buffer_size = 0);
private:
std::string host_; std::string host_;
std::string port_; std::string port_;
HttpSslClient http_client_; HttpSslClient http_ssl_client_;
}; };
} // namespace webcc } // namespace webcc

Loading…
Cancel
Save