From 5f9532759e9cc364832d080329995b51a24f4d7b Mon Sep 17 00:00:00 2001 From: Chunting Gu Date: Wed, 10 Apr 2019 13:48:33 +0800 Subject: [PATCH] Refine files upload. --- examples/http_client.cc | 28 ------------- webcc/CMakeLists.txt | 2 + webcc/globals.cc | 45 +-------------------- webcc/globals.h | 18 --------- webcc/http_client_pool.cc | 10 +++-- webcc/http_client_session.cc | 50 +++++++++++++++-------- webcc/http_client_session.h | 10 +++-- webcc/http_file.cc | 64 ++++++++++++++++++++++++++++++ webcc/http_file.h | 75 +++++++++++++++++++++++++++++++++++ webcc/http_request_builder.cc | 29 +++++--------- webcc/http_request_builder.h | 16 ++++---- 11 files changed, 207 insertions(+), 140 deletions(-) create mode 100644 webcc/http_file.cc create mode 100644 webcc/http_file.h diff --git a/examples/http_client.cc b/examples/http_client.cc index 1ca2079..aed97e2 100644 --- a/examples/http_client.cc +++ b/examples/http_client.cc @@ -103,34 +103,6 @@ void ExampleImage(const std::string& path) { ofs << r->content(); } -// Post/upload files. -void ExamplePostFiles() { - webcc::HttpClientSession session; - - auto r = session.Request(webcc::HttpRequestBuilder{} - .Post() - .Url("http://httpbin.org/post") - .FileData("file1", "report.xls", "", "application/vnd.ms-excel") - .FileData("file2", "report.xml", "", "text/xml")()); - - std::cout << r->content() << std::endl; -} - -// Post/upload files by file path. -void ExamplePostFiles(const std::string& url, - const std::string& name, - const std::string& file_name, - const std::string& file_path, - const std::string& content_type) { - webcc::HttpClientSession session; - - auto r = session.Request(webcc::HttpRequestBuilder{}.Post(). - Url(url). - File(name, file_name, file_path, content_type)()); - - std::cout << r->content() << std::endl; -} - int main() { WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); diff --git a/webcc/CMakeLists.txt b/webcc/CMakeLists.txt index 54114d6..9b1d001 100644 --- a/webcc/CMakeLists.txt +++ b/webcc/CMakeLists.txt @@ -21,6 +21,7 @@ set(HEADERS http_client_session.h http_connection.h http_connection_pool.h + http_file.h http_message.h http_parser.h http_request.h @@ -50,6 +51,7 @@ set(SOURCES http_client_session.cc http_connection.cc http_connection_pool.cc + http_file.cc http_message.cc http_parser.cc http_request.cc diff --git a/webcc/globals.cc b/webcc/globals.cc index 13bc9a6..4299bc4 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -1,33 +1,13 @@ #include "webcc/globals.h" -#include #include -#include "boost/filesystem/path.hpp" - #include "webcc/version.h" namespace webcc { // ----------------------------------------------------------------------------- -// Read entire file into string. -static bool ReadFile(const std::string& path, std::string* output) { - std::ifstream ifs{path, std::ios::binary | std::ios::ate}; - if (!ifs) { - return false; - } - - auto size = ifs.tellg(); - output->resize((std::size_t)size, '\0'); - ifs.seekg(0); - ifs.read(&(*output)[0], size); // TODO: Error handling - - return true; -} - -// ----------------------------------------------------------------------------- - namespace http { const std::string& UserAgent() { @@ -35,29 +15,6 @@ const std::string& UserAgent() { return s_user_agent; } -File::File(const std::string& file_path) { - if (!ReadFile(file_path, &data)) { - throw Exception(kFileIOError, "Cannot read the file."); - } - - namespace bfs = boost::filesystem; - - // Determine file name from file path. - //if (file_name.empty()) { - file_name = bfs::path(file_path).filename().string(); - //} else { - // file_name = file_name; - //} - - // Determine content type from file extension. - //if (mime_type.empty()) { - std::string extension = bfs::path(file_path).extension().string(); - mime_type = http::media_types::FromExtension(extension, false); - //} else { - //mime_type = mime_type; - //} -} - namespace media_types { // TODO: Add more. @@ -97,6 +54,8 @@ std::string FromExtension(const std::string& extension, } // namespace http +// ----------------------------------------------------------------------------- + const char* DescribeError(Error error) { switch (error) { case kSchemaError: diff --git a/webcc/globals.h b/webcc/globals.h index d190fb4..864d8fc 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -154,24 +154,6 @@ enum class ContentEncoding { // Return default user agent for HTTP headers. const std::string& UserAgent(); -// File for HTTP transfer (upload/download). -class File { -public: - File() = default; - - File(const std::string& file_path); - - // Binary file data. - // TODO: don't use std::string? - std::string data; - - // E.g., example.jpg - std::string file_name; - - // E.g., image/jpeg - std::string mime_type; -}; - } // namespace http // ----------------------------------------------------------------------------- diff --git a/webcc/http_client_pool.cc b/webcc/http_client_pool.cc index 5fa6ade..07df165 100644 --- a/webcc/http_client_pool.cc +++ b/webcc/http_client_pool.cc @@ -5,11 +5,13 @@ namespace webcc { HttpClientPool::~HttpClientPool() { - LOG_INFO("Close socket for all (%u) connections in the pool.", - clients_.size()); + if (!clients_.empty()) { + LOG_INFO("Close socket for all (%u) connections in the pool.", + clients_.size()); - for (auto& pair : clients_) { - pair.second->Close(); + for (auto& pair : clients_) { + pair.second->Close(); + } } } diff --git a/webcc/http_client_session.cc b/webcc/http_client_session.cc index 1a34ff6..5b7a1af 100644 --- a/webcc/http_client_session.cc +++ b/webcc/http_client_session.cc @@ -40,8 +40,8 @@ static void SetHeaders(const std::vector& headers, HttpResponsePtr HttpClientSession::Get( const std::string& url, const std::vector& parameters, const std::vector& headers) { - HttpRequestBuilder builder{http::methods::kGet}; - builder.Url(url); + HttpRequestBuilder builder; + builder.Get().Url(url); assert(parameters.size() % 2 == 0); for (std::size_t i = 1; i < parameters.size(); i += 2) { @@ -56,8 +56,8 @@ HttpResponsePtr HttpClientSession::Get( HttpResponsePtr HttpClientSession::Post( const std::string& url, std::string&& data, bool json, const std::vector& headers) { - HttpRequestBuilder builder{http::methods::kPost}; - builder.Url(url); + HttpRequestBuilder builder; + builder.Post().Url(url); SetHeaders(headers, &builder); @@ -67,16 +67,32 @@ HttpResponsePtr HttpClientSession::Post( return Request(builder()); } -HttpResponsePtr HttpClientSession::PostFile(const std::string& url, - const std::string& name, - http::File&& file, - const std::vector& headers) { - HttpRequestBuilder builder{http::methods::kPost}; - builder.Url(url); +HttpResponsePtr HttpClientSession::PostFile( + const std::string& url, const std::string& name, const Path& path, + const std::vector& headers) { + HttpRequestBuilder builder; + builder.Post().Url(url); SetHeaders(headers, &builder); - builder.File(name, std::move(file)); + builder.File(name, path); + + return Request(builder()); +} + +HttpResponsePtr HttpClientSession::PostFiles( + const std::string& url, const std::map& paths, + const std::vector& headers) { + assert(!paths.empty()); + + HttpRequestBuilder builder; + builder.Post().Url(url); + + SetHeaders(headers, &builder); + + for (auto& pair : paths) { + builder.File(pair.first, pair.second); + } return Request(builder()); } @@ -84,8 +100,8 @@ HttpResponsePtr HttpClientSession::PostFile(const std::string& url, HttpResponsePtr HttpClientSession::Put( const std::string& url, std::string&& data, bool json, const std::vector& headers) { - HttpRequestBuilder builder{http::methods::kPut}; - builder.Url(url); + HttpRequestBuilder builder; + builder.Put().Url(url); SetHeaders(headers, &builder); @@ -97,8 +113,8 @@ HttpResponsePtr HttpClientSession::Put( HttpResponsePtr HttpClientSession::Delete( const std::string& url, const std::vector& headers) { - HttpRequestBuilder builder{http::methods::kDelete}; - builder.Url(url); + HttpRequestBuilder builder; + builder.Delete().Url(url); SetHeaders(headers, &builder); @@ -108,8 +124,8 @@ HttpResponsePtr HttpClientSession::Delete( HttpResponsePtr HttpClientSession::Patch( const std::string& url, std::string&& data, bool json, const std::vector& headers) { - HttpRequestBuilder builder{http::methods::kPatch}; - builder.Url(url); + HttpRequestBuilder builder; + builder.Patch().Url(url); SetHeaders(headers, &builder); diff --git a/webcc/http_client_session.h b/webcc/http_client_session.h index 6cb2f5f..06c7ba7 100644 --- a/webcc/http_client_session.h +++ b/webcc/http_client_session.h @@ -60,11 +60,15 @@ public: const std::vector& headers = {}); // Post a file. - HttpResponsePtr PostFile(const std::string& url, - const std::string& name, - http::File&& file, + HttpResponsePtr PostFile(const std::string& url, const std::string& name, + const Path& path, const std::vector& headers = {}); + // Post multiple files. + HttpResponsePtr PostFiles(const std::string& url, + const std::map& paths, + const std::vector& headers = {}); + // Shortcut for PUT request. HttpResponsePtr Put(const std::string& url, std::string&& data, bool json, const std::vector& headers = {}); diff --git a/webcc/http_file.cc b/webcc/http_file.cc new file mode 100644 index 0000000..50466aa --- /dev/null +++ b/webcc/http_file.cc @@ -0,0 +1,64 @@ +#include "webcc/http_file.h" + +#include "boost/filesystem/fstream.hpp" + +namespace bfs = boost::filesystem; + +namespace webcc { + +// ----------------------------------------------------------------------------- + +// Read entire file into string. +static bool ReadFile(const Path& path, std::string* output) { + bfs::ifstream ifs{path, std::ios::binary | std::ios::ate}; + if (!ifs) { + return false; + } + + auto size = ifs.tellg(); + output->resize(static_cast(size), '\0'); + ifs.seekg(0); + ifs.read(&(*output)[0], size); // TODO: Error handling + + return true; +} + +// ----------------------------------------------------------------------------- + +HttpFile::HttpFile(const Path& path, const std::string& mime_type) { + if (!ReadFile(path, &data_)) { + throw Exception(kFileIOError, "Cannot read the file."); + } + + // Determine file name from file path. + // TODO: Encoding + file_name_ = path.filename().string(); + + // Determine content type from file extension. + if (mime_type.empty()) { + std::string extension = path.extension().string(); + mime_type_ = http::media_types::FromExtension(extension, false); + } else { + mime_type_ = mime_type; + } +} + +HttpFile::HttpFile(std::string&& data, const std::string& file_name, + const std::string& mime_type) { + data_ = std::move(data); + + file_name_ = file_name; + + mime_type_ = mime_type; + + // Determine content type from file extension. + if (mime_type_.empty()) { + std::size_t pos = file_name_.find_last_of('.'); + if (pos != std::string::npos) { + std::string extension = file_name_.substr(pos + 1); + mime_type_ = http::media_types::FromExtension(extension, false); + } + } +} + +} // namespace webcc diff --git a/webcc/http_file.h b/webcc/http_file.h new file mode 100644 index 0000000..f94861a --- /dev/null +++ b/webcc/http_file.h @@ -0,0 +1,75 @@ +#ifndef WEBCC_HTTP_FILE_H_ +#define WEBCC_HTTP_FILE_H_ + +#include + +#include "boost/filesystem/path.hpp" + +#include "webcc/globals.h" + +namespace webcc { + +using Path = boost::filesystem::path; + +// File for HTTP transfer (upload/download). +class HttpFile { +public: + HttpFile() = default; + + explicit HttpFile(const Path& path, const std::string& mime_type = ""); + + HttpFile(std::string&& data, const std::string& file_name, + const std::string& mime_type = ""); + +#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN + + HttpFile(HttpFile&&) = default; + HttpFile& operator=(HttpFile&&) = default; + +#else + + HttpFile(HttpFile&& rhs) + : data_(std::move(rhs.data_)), + file_name_(std::move(rhs.file_name_)), + mime_type_(std::move(rhs.mime_type_)) { + } + + HttpFile& operator=(HttpFile&& rhs) { + if (&rhs != this) { + data_ = std::move(rhs.data_); + file_name_ = std::move(rhs.file_name_); + mime_type_ = std::move(rhs.mime_type_); + } + return *this; + } + +#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN + + const std::string& data() const { + return data_; + } + + const std::string& file_name() const { + return file_name_; + } + + const std::string& mime_type() const { + return mime_type_; + } + +private: + // Binary file data. + // TODO: don't use std::string? + std::string data_; + + // E.g., example.jpg + // TODO: Unicode + std::string file_name_; + + // E.g., image/jpeg + std::string mime_type_; +}; + +} // namespace webcc + +#endif // WEBCC_HTTP_FILE_H_ diff --git a/webcc/http_request_builder.cc b/webcc/http_request_builder.cc index a46e8d1..f43c410 100644 --- a/webcc/http_request_builder.cc +++ b/webcc/http_request_builder.cc @@ -51,33 +51,22 @@ HttpRequestPtr HttpRequestBuilder::Build() { } HttpRequestBuilder& HttpRequestBuilder::File(const std::string& name, - const std::string& file_path, - const std::string& file_name, + const Path& path, const std::string& mime_type) { assert(!name.empty()); - // TODO - files_[name] = http::File(file_path/*, file_name, mime_type*/); + files_[name] = HttpFile(path, mime_type); return *this; } -HttpRequestBuilder& HttpRequestBuilder::File(const std::string& name, - http::File&& file) { - files_[name] = std::move(file); - return *this; -} - HttpRequestBuilder& HttpRequestBuilder::FileData(const std::string& name, std::string&& file_data, const std::string& file_name, const std::string& mime_type) { - http::File file; - file.data = std::move(file_data); - file.file_name = file_name; - file.mime_type = mime_type; + assert(!name.empty()); - files_[name] = std::move(file); + files_[name] = HttpFile(std::move(file_data), file_name, mime_type); return *this; } @@ -121,21 +110,21 @@ void HttpRequestBuilder::CreateFormData(std::string* data, if (!pair.first.empty()) { data->append("; name=\"" + pair.first + "\""); } - if (!pair.second.file_name.empty()) { - data->append("; filename=\"" + pair.second.file_name + "\""); + if (!pair.second.file_name().empty()) { + data->append("; filename=\"" + pair.second.file_name() + "\""); } data->append(kCRLF); // Content-Type header - if (!pair.second.mime_type.empty()) { - data->append("Content-Type: " + pair.second.mime_type); + if (!pair.second.mime_type().empty()) { + data->append("Content-Type: " + pair.second.mime_type()); data->append(kCRLF); } data->append(kCRLF); // Payload - data->append(pair.second.data); + data->append(pair.second.data()); data->append(kCRLF); } diff --git a/webcc/http_request_builder.h b/webcc/http_request_builder.h index b78bd57..deabc9c 100644 --- a/webcc/http_request_builder.h +++ b/webcc/http_request_builder.h @@ -5,6 +5,7 @@ #include #include +#include "webcc/http_file.h" #include "webcc/http_request.h" namespace webcc { @@ -66,15 +67,16 @@ public: } // Upload a file with its path. - // TODO: UNICODE file path. - HttpRequestBuilder& File(const std::string& name, - const std::string& file_path, - const std::string& file_name = "", + HttpRequestBuilder& File(const std::string& name, const Path& path, const std::string& mime_type = ""); - HttpRequestBuilder& File(const std::string& name, http::File&& file); + HttpRequestBuilder& File(const std::string& name, HttpFile&& file) { + files_[name] = std::move(file); + return *this; + } // Upload a file with its data. + // TODO: Unicode |file_name|. HttpRequestBuilder& FileData(const std::string& name, std::string&& file_data, const std::string& file_name = "", @@ -122,8 +124,8 @@ private: // Is the data to send a JSON string? bool json_ = false; - // Files to upload for a POST (or PUT?) request. - std::map files_; + // Files to upload for a POST request. + std::map files_; // Compress the request content. // NOTE: Most servers don't support compressed requests.