Refine files upload.
This commit is contained in:
@@ -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", "<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() {
|
||||
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-43
@@ -1,33 +1,13 @@
|
||||
#include "webcc/globals.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
#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:
|
||||
|
||||
@@ -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
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ static void SetHeaders(const std::vector<std::string>& headers,
|
||||
HttpResponsePtr HttpClientSession::Get(
|
||||
const std::string& url, const std::vector<std::string>& parameters,
|
||||
const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<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());
|
||||
}
|
||||
@@ -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<std::string>& 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<std::string>& 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<std::string>& headers) {
|
||||
HttpRequestBuilder builder{http::methods::kPatch};
|
||||
builder.Url(url);
|
||||
HttpRequestBuilder builder;
|
||||
builder.Patch().Url(url);
|
||||
|
||||
SetHeaders(headers, &builder);
|
||||
|
||||
|
||||
@@ -60,11 +60,15 @@ public:
|
||||
const std::vector<std::string>& 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<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.
|
||||
HttpResponsePtr Put(const std::string& url, std::string&& data, bool json,
|
||||
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,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<std::string, http::File> files_;
|
||||
// Files to upload for a POST request.
|
||||
std::map<std::string, HttpFile> files_;
|
||||
|
||||
// Compress the request content.
|
||||
// NOTE: Most servers don't support compressed requests.
|
||||
|
||||
Reference in New Issue
Block a user