Refine files upload.

master
Chunting Gu 6 years ago
parent ba705e930e
commit 5f9532759e

@ -103,34 +103,6 @@ void ExampleImage(const std::string& path) {
ofs << r->content(); 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", "<xls report data>", "application/vnd.ms-excel")
.FileData("file2", "report.xml", "<xml report data>", "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() { int main() {
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE); WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);

@ -21,6 +21,7 @@ set(HEADERS
http_client_session.h http_client_session.h
http_connection.h http_connection.h
http_connection_pool.h http_connection_pool.h
http_file.h
http_message.h http_message.h
http_parser.h http_parser.h
http_request.h http_request.h
@ -50,6 +51,7 @@ set(SOURCES
http_client_session.cc http_client_session.cc
http_connection.cc http_connection.cc
http_connection_pool.cc http_connection_pool.cc
http_file.cc
http_message.cc http_message.cc
http_parser.cc http_parser.cc
http_request.cc http_request.cc

@ -1,33 +1,13 @@
#include "webcc/globals.h" #include "webcc/globals.h"
#include <fstream>
#include <map> #include <map>
#include "boost/filesystem/path.hpp"
#include "webcc/version.h" #include "webcc/version.h"
namespace webcc { 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 { namespace http {
const std::string& UserAgent() { const std::string& UserAgent() {
@ -35,29 +15,6 @@ const std::string& UserAgent() {
return s_user_agent; 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 { namespace media_types {
// TODO: Add more. // TODO: Add more.
@ -97,6 +54,8 @@ std::string FromExtension(const std::string& extension,
} // namespace http } // namespace http
// -----------------------------------------------------------------------------
const char* DescribeError(Error error) { const char* DescribeError(Error error) {
switch (error) { switch (error) {
case kSchemaError: case kSchemaError:

@ -154,24 +154,6 @@ enum class ContentEncoding {
// Return default user agent for HTTP headers. // Return default user agent for HTTP headers.
const std::string& UserAgent(); 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 } // namespace http
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

@ -5,11 +5,13 @@
namespace webcc { namespace webcc {
HttpClientPool::~HttpClientPool() { HttpClientPool::~HttpClientPool() {
LOG_INFO("Close socket for all (%u) connections in the pool.", if (!clients_.empty()) {
clients_.size()); LOG_INFO("Close socket for all (%u) connections in the pool.",
clients_.size());
for (auto& pair : clients_) { for (auto& pair : clients_) {
pair.second->Close(); pair.second->Close();
}
} }
} }

@ -40,8 +40,8 @@ static void SetHeaders(const std::vector<std::string>& headers,
HttpResponsePtr HttpClientSession::Get( HttpResponsePtr HttpClientSession::Get(
const std::string& url, const std::vector<std::string>& parameters, const std::string& url, const std::vector<std::string>& parameters,
const std::vector<std::string>& headers) { const std::vector<std::string>& headers) {
HttpRequestBuilder builder{http::methods::kGet}; HttpRequestBuilder builder;
builder.Url(url); builder.Get().Url(url);
assert(parameters.size() % 2 == 0); assert(parameters.size() % 2 == 0);
for (std::size_t i = 1; i < parameters.size(); i += 2) { for (std::size_t i = 1; i < parameters.size(); i += 2) {
@ -56,8 +56,8 @@ HttpResponsePtr HttpClientSession::Get(
HttpResponsePtr HttpClientSession::Post( HttpResponsePtr HttpClientSession::Post(
const std::string& url, std::string&& data, bool json, const std::string& url, std::string&& data, bool json,
const std::vector<std::string>& headers) { const std::vector<std::string>& headers) {
HttpRequestBuilder builder{http::methods::kPost}; HttpRequestBuilder builder;
builder.Url(url); builder.Post().Url(url);
SetHeaders(headers, &builder); SetHeaders(headers, &builder);
@ -67,16 +67,32 @@ HttpResponsePtr HttpClientSession::Post(
return Request(builder()); return Request(builder());
} }
HttpResponsePtr HttpClientSession::PostFile(const std::string& url, HttpResponsePtr HttpClientSession::PostFile(
const std::string& name, const std::string& url, const std::string& name, const Path& path,
http::File&& file, const std::vector<std::string>& headers) {
const std::vector<std::string>& headers) { HttpRequestBuilder builder;
HttpRequestBuilder builder{http::methods::kPost}; builder.Post().Url(url);
builder.Url(url);
SetHeaders(headers, &builder); 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<std::string, Path>& paths,
const std::vector<std::string>& 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()); return Request(builder());
} }
@ -84,8 +100,8 @@ HttpResponsePtr HttpClientSession::PostFile(const std::string& url,
HttpResponsePtr HttpClientSession::Put( HttpResponsePtr HttpClientSession::Put(
const std::string& url, std::string&& data, bool json, const std::string& url, std::string&& data, bool json,
const std::vector<std::string>& headers) { const std::vector<std::string>& headers) {
HttpRequestBuilder builder{http::methods::kPut}; HttpRequestBuilder builder;
builder.Url(url); builder.Put().Url(url);
SetHeaders(headers, &builder); SetHeaders(headers, &builder);
@ -97,8 +113,8 @@ HttpResponsePtr HttpClientSession::Put(
HttpResponsePtr HttpClientSession::Delete( HttpResponsePtr HttpClientSession::Delete(
const std::string& url, const std::vector<std::string>& headers) { const std::string& url, const std::vector<std::string>& headers) {
HttpRequestBuilder builder{http::methods::kDelete}; HttpRequestBuilder builder;
builder.Url(url); builder.Delete().Url(url);
SetHeaders(headers, &builder); SetHeaders(headers, &builder);
@ -108,8 +124,8 @@ HttpResponsePtr HttpClientSession::Delete(
HttpResponsePtr HttpClientSession::Patch( HttpResponsePtr HttpClientSession::Patch(
const std::string& url, std::string&& data, bool json, const std::string& url, std::string&& data, bool json,
const std::vector<std::string>& headers) { const std::vector<std::string>& headers) {
HttpRequestBuilder builder{http::methods::kPatch}; HttpRequestBuilder builder;
builder.Url(url); builder.Patch().Url(url);
SetHeaders(headers, &builder); SetHeaders(headers, &builder);

@ -60,11 +60,15 @@ public:
const std::vector<std::string>& headers = {}); const std::vector<std::string>& headers = {});
// Post a file. // Post a file.
HttpResponsePtr PostFile(const std::string& url, HttpResponsePtr PostFile(const std::string& url, const std::string& name,
const std::string& name, const Path& path,
http::File&& file,
const std::vector<std::string>& headers = {}); const std::vector<std::string>& headers = {});
// Post multiple files.
HttpResponsePtr PostFiles(const std::string& url,
const std::map<std::string, Path>& paths,
const std::vector<std::string>& headers = {});
// Shortcut for PUT request. // Shortcut for PUT request.
HttpResponsePtr Put(const std::string& url, std::string&& data, bool json, HttpResponsePtr Put(const std::string& url, std::string&& data, bool json,
const std::vector<std::string>& headers = {}); const std::vector<std::string>& headers = {});

@ -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<std::size_t>(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

@ -0,0 +1,75 @@
#ifndef WEBCC_HTTP_FILE_H_
#define WEBCC_HTTP_FILE_H_
#include <string>
#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_

@ -51,33 +51,22 @@ HttpRequestPtr HttpRequestBuilder::Build() {
} }
HttpRequestBuilder& HttpRequestBuilder::File(const std::string& name, HttpRequestBuilder& HttpRequestBuilder::File(const std::string& name,
const std::string& file_path, const Path& path,
const std::string& file_name,
const std::string& mime_type) { const std::string& mime_type) {
assert(!name.empty()); assert(!name.empty());
// TODO files_[name] = HttpFile(path, mime_type);
files_[name] = http::File(file_path/*, file_name, mime_type*/);
return *this; 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, HttpRequestBuilder& HttpRequestBuilder::FileData(const std::string& name,
std::string&& file_data, std::string&& file_data,
const std::string& file_name, const std::string& file_name,
const std::string& mime_type) { const std::string& mime_type) {
http::File file; assert(!name.empty());
file.data = std::move(file_data);
file.file_name = file_name;
file.mime_type = mime_type;
files_[name] = std::move(file); files_[name] = HttpFile(std::move(file_data), file_name, mime_type);
return *this; return *this;
} }
@ -121,21 +110,21 @@ void HttpRequestBuilder::CreateFormData(std::string* data,
if (!pair.first.empty()) { if (!pair.first.empty()) {
data->append("; name=\"" + pair.first + "\""); data->append("; name=\"" + pair.first + "\"");
} }
if (!pair.second.file_name.empty()) { if (!pair.second.file_name().empty()) {
data->append("; filename=\"" + pair.second.file_name + "\""); data->append("; filename=\"" + pair.second.file_name() + "\"");
} }
data->append(kCRLF); data->append(kCRLF);
// Content-Type header // Content-Type header
if (!pair.second.mime_type.empty()) { if (!pair.second.mime_type().empty()) {
data->append("Content-Type: " + pair.second.mime_type); data->append("Content-Type: " + pair.second.mime_type());
data->append(kCRLF); data->append(kCRLF);
} }
data->append(kCRLF); data->append(kCRLF);
// Payload // Payload
data->append(pair.second.data); data->append(pair.second.data());
data->append(kCRLF); data->append(kCRLF);
} }

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "webcc/http_file.h"
#include "webcc/http_request.h" #include "webcc/http_request.h"
namespace webcc { namespace webcc {
@ -66,15 +67,16 @@ public:
} }
// Upload a file with its path. // Upload a file with its path.
// TODO: UNICODE file path. HttpRequestBuilder& File(const std::string& name, const Path& path,
HttpRequestBuilder& File(const std::string& name,
const std::string& file_path,
const std::string& file_name = "",
const std::string& mime_type = ""); 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. // Upload a file with its data.
// TODO: Unicode |file_name|.
HttpRequestBuilder& FileData(const std::string& name, HttpRequestBuilder& FileData(const std::string& name,
std::string&& file_data, std::string&& file_data,
const std::string& file_name = "", const std::string& file_name = "",
@ -122,8 +124,8 @@ private:
// Is the data to send a JSON string? // Is the data to send a JSON string?
bool json_ = false; bool json_ = false;
// Files to upload for a POST (or PUT?) request. // Files to upload for a POST request.
std::map<std::string, http::File> files_; std::map<std::string, HttpFile> files_;
// Compress the request content. // Compress the request content.
// NOTE: Most servers don't support compressed requests. // NOTE: Most servers don't support compressed requests.

Loading…
Cancel
Save