#include "webcc/client_session.h" #include "webcc/base64.h" #include "webcc/logger.h" #include "webcc/url.h" #include "webcc/utility.h" namespace webcc { ClientSession::ClientSession(int timeout, bool ssl_verify, std::size_t buffer_size) : timeout_(timeout), ssl_verify_(ssl_verify), buffer_size_(buffer_size) { InitHeaders(); } void ClientSession::Auth(const std::string& type, const std::string& credentials) { headers_.Set(headers::kAuthorization, type + " " + credentials); } void ClientSession::AuthBasic(const std::string& login, const std::string& password) { auto credentials = Base64Encode(login + ":" + password); return Auth("Basic", credentials); } void ClientSession::AuthToken(const std::string& token) { return Auth("Token", token); } ResponsePtr ClientSession::Request(RequestPtr request, bool stream) { assert(request); for (auto& h : headers_.data()) { if (!request->HasHeader(h.first)) { request->SetHeader(h.first, h.second); } } if (!request->body()->IsEmpty() && !media_type_.empty() && !request->HasHeader(headers::kContentType)) { request->SetContentType(media_type_, charset_); } request->Prepare(); return Send(request, stream); } static void SetHeaders(const Strings& headers, RequestBuilder* builder) { assert(headers.size() % 2 == 0); for (std::size_t i = 1; i < headers.size(); i += 2) { builder->Header(headers[i - 1], headers[i]); } } ResponsePtr ClientSession::Get(const std::string& url, const Strings& parameters, const Strings& headers) { RequestBuilder builder; builder.Get(url); assert(parameters.size() % 2 == 0); for (std::size_t i = 1; i < parameters.size(); i += 2) { builder.Query(parameters[i - 1], parameters[i]); } SetHeaders(headers, &builder); return Request(builder()); } ResponsePtr ClientSession::Head(const std::string& url, const Strings& parameters, const Strings& headers) { RequestBuilder builder; builder.Head(url); assert(parameters.size() % 2 == 0); for (std::size_t i = 1; i < parameters.size(); i += 2) { builder.Query(parameters[i - 1], parameters[i]); } SetHeaders(headers, &builder); return Request(builder()); } ResponsePtr ClientSession::Post(const std::string& url, std::string&& data, bool json, const Strings& headers) { RequestBuilder builder; builder.Post(url); SetHeaders(headers, &builder); builder.Body(std::move(data)); if (json) { builder.Json(); } return Request(builder()); } ResponsePtr ClientSession::Put(const std::string& url, std::string&& data, bool json, const Strings& headers) { RequestBuilder builder; builder.Put(url); SetHeaders(headers, &builder); builder.Body(std::move(data)); if (json) { builder.Json(); } return Request(builder()); } ResponsePtr ClientSession::Delete(const std::string& url, const Strings& headers) { RequestBuilder builder; builder.Delete(url); SetHeaders(headers, &builder); return Request(builder()); } ResponsePtr ClientSession::Patch(const std::string& url, std::string&& data, bool json, const Strings& headers) { RequestBuilder builder; builder.Patch(url); SetHeaders(headers, &builder); builder.Body(std::move(data)); if (json) { builder.Json(); } return Request(builder()); } void ClientSession::InitHeaders() { using namespace headers; headers_.Set(kUserAgent, utility::UserAgent()); // Content-Encoding Tokens: // (https://en.wikipedia.org/wiki/HTTP_compression) // // * compress ¨C UNIX "compress" program method (historic; deprecated in most // applications and replaced by gzip or deflate); // * deflate ¨C compression based on the deflate algorithm, a combination of // the LZ77 algorithm and Huffman coding, wrapped inside the // zlib data format; // * gzip ¨C GNU zip format. Uses the deflate algorithm for compression, // but the data format and the checksum algorithm differ from // the "deflate" content-encoding. This method is the most // broadly supported as of March 2011. // * identity ¨C No transformation is used. This is the default value for // content coding. // // A note about "deflate": // "gzip" is the gzip format, and "deflate" is the zlib format. They should // probably have called the second one "zlib" instead to avoid confusion with // the raw deflate compressed data format. // Simply put, "deflate" is not recommended for HTTP 1.1 encoding. // (https://www.zlib.net/zlib_faq.html#faq39) #if WEBCC_ENABLE_GZIP headers_.Set(kAcceptEncoding, "gzip, deflate"); #else headers_.Set(kAcceptEncoding, "identity"); #endif // WEBCC_ENABLE_GZIP headers_.Set(kAccept, "*/*"); headers_.Set(kConnection, "Keep-Alive"); } ResponsePtr ClientSession::Send(RequestPtr request, bool stream) { const ClientPool::Key key{ request->url() }; // Reuse a pooled connection. bool reuse = false; ClientPtr client = pool_.Get(key); if (!client) { client.reset(new Client{}); reuse = false; } else { LOG_VERB("Reuse an existing connection."); reuse = true; } client->set_ssl_verify(ssl_verify_); client->set_buffer_size(buffer_size_); client->set_timeout(timeout_); Error error = client->Request(request, !reuse, stream); if (error) { if (reuse && error.code() == Error::kSocketWriteError) { LOG_WARN("Cannot send request with the reused connection. " "The server must have closed it, reconnect and try again."); error = client->Request(request, true, stream); } } if (error) { // Remove the failed connection from pool. if (reuse) { pool_.Remove(key); } throw error; } // Update connection pool. if (reuse) { if (client->closed()) { pool_.Remove(key); } } else { if (!client->closed()) { pool_.Add(key, client); } } return client->response(); } } // namespace webcc