Refine the support for uploading files.

master
Chunting Gu 6 years ago
parent d1ddfa4b3d
commit 6f7acfc442

@ -0,0 +1,2 @@
**upload**: files for testing upload.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@ -0,0 +1,17 @@
Remember
BY CHRISTINA ROSSETTI
Remember me when I am gone away,
Gone far away into the silent land;
When you can no more hold me by the hand,
Nor I half turn to go yet turning stay.
Remember me when no more day by day
You tell me of our future that you plann'd:
Only remember me; you understand
It will be late to counsel then or pray.
Yet if you should forget me for a while
And afterwards remember, do not grieve:
For if the darkness and corruption leave
A vestige of the thoughts that once I had,
Better by far you should forget and smile
Than that you should remember and be sad.

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@ -4,8 +4,6 @@
#include "webcc/http_client_session.h"
#include "webcc/logger.h"
using namespace webcc;
#if (defined(WIN32) || defined(_WIN64))
// You need to set environment variable SSL_CERT_FILE properly to enable
// SSL verification.
@ -65,7 +63,7 @@ void ExampleHttps() {
// ExampleKeepAlive("https://api.github.com/events");
//
void ExampleKeepAlive(const std::string& url) {
HttpClientSession session;
webcc::HttpClientSession session;
session.set_ssl_verify(kSslVerify);
// Keep-Alive
@ -79,7 +77,7 @@ void ExampleKeepAlive(const std::string& url) {
}
void ExampleCompression() {
HttpClientSession session;
webcc::HttpClientSession session;
auto r = session.Get("http://httpbin.org/gzip");
@ -93,7 +91,7 @@ void ExampleCompression() {
// Get an image from HttpBin.org and save to the given file path.
// E.g., ExampleImage("E:\\example.jpg")
void ExampleImage(const std::string& path) {
HttpClientSession session;
webcc::HttpClientSession session;
auto r = session.Get("http://httpbin.org/image/jpeg");
@ -107,9 +105,9 @@ void ExampleImage(const std::string& path) {
// Post/upload files.
void ExamplePostFiles() {
HttpClientSession session;
webcc::HttpClientSession session;
auto r = session.Request(HttpRequestBuilder{}
auto r = session.Request(webcc::HttpRequestBuilder{}
.Post()
.Url("http://httpbin.org/post")
.FileData("file1", "report.xls", "<xls report data>", "application/vnd.ms-excel")
@ -119,29 +117,28 @@ void ExamplePostFiles() {
}
// Post/upload files by file path.
void ExamplePostFiles(const std::string& name,
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) {
HttpClientSession session;
webcc::HttpClientSession session;
auto r =
session.Request(HttpRequestBuilder{}
.Post()
.Url("http://httpbin.org/post")
.File(name, file_name, file_path, content_type)());
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("", LOG_CONSOLE);
WEBCC_LOG_INIT("", webcc::LOG_CONSOLE);
try {
ExampleBasic();
} catch (const Exception& e) {
} catch (const webcc::Exception& e) {
std::cout << "Exception: " << e.what() << std::endl;
}

@ -1,9 +1,33 @@
#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(size, '\0');
ifs.seekg(0);
ifs.read(&(*output)[0], size); // TODO: Error handling
return true;
}
// -----------------------------------------------------------------------------
namespace http {
const std::string& UserAgent() {
@ -11,6 +35,66 @@ 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.
static void InitMap(std::map<std::string, std::string>& map) {
map["gif"] = "image/gif";
map["htm"] = "text/html";
map["html"] = "text/html";
map["jpg"] = "image/jpeg";
map["jpeg"] = "image/jpeg";
map["png"] = "image/png";
map["txt"] = "text/plain";
map[""] = "";
}
// TODO: Ignore case on Windows.
std::string FromExtension(const std::string& extension,
bool default_to_plain_text) {
static std::map<std::string, std::string> s_map;
if (s_map.empty()) {
InitMap(s_map);
}
auto it = s_map.find(extension);
if (it != s_map.end()) {
return it->second;
}
if (default_to_plain_text) {
return "text/plain";
} else {
return "";
}
}
} // namespace media_types
} // namespace http
const char* DescribeError(Error error) {

@ -24,8 +24,9 @@ const std::size_t kMaxDumpSize = 2048;
// Default buffer size for socket reading.
const std::size_t kBufferSize = 1024;
// Default ports.
// Default port for HTTP.
const char* const kPort80 = "80";
// Default port for HTTPS.
const char* const kPort443 = "443";
// Why 1400? See the following page:
@ -36,7 +37,6 @@ const std::size_t kGzipThreshold = 1400;
// -----------------------------------------------------------------------------
// HTTP headers.
namespace http {
namespace methods {
@ -44,15 +44,15 @@ namespace methods {
// HTTP methods (verbs) in string.
// Don't use enum to avoid converting back and forth.
const char* const kGet = "GET";
const char* const kHead = "HEAD";
const char* const kPost = "POST";
const char* const kPut = "PUT";
const char* const kDelete = "DELETE";
const char* const kConnect = "CONNECT";
const char* const kOptions = "OPTIONS";
const char* const kTrace = "TRACE";
const char* const kPatch = "PATCH";
const char* const kGet = "GET";
const char* const kHead = "HEAD";
const char* const kPost = "POST";
const char* const kPut = "PUT";
const char* const kDelete = "DELETE";
const char* const kConnect = "CONNECT";
const char* const kOptions = "OPTIONS";
const char* const kTrace = "TRACE";
const char* const kPatch = "PATCH";
} // namespace methods
@ -95,16 +95,17 @@ const char* const kServer = "Server";
namespace media_types {
// NOTE:
// According to www.w3.org when placing SOAP messages in HTTP bodies, the HTTP
// Content-type header must be chosen as "application/soap+xml" [RFC 3902].
// But in practice, many web servers cannot understand it.
// See: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#L26854
// See the following link for the full list of media types:
// https://www.iana.org/assignments/media-types/media-types.xhtml
const char* const kApplicationJson = "application/json";
const char* const kApplicationSoapXml = "application/soap+xml";
const char* const kTextXml = "text/xml";
// Get media type from file extension.
std::string FromExtension(const std::string& extension,
bool default_to_plain_text = true);
} // namespace media_types
namespace charsets {
@ -122,6 +123,24 @@ 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
// -----------------------------------------------------------------------------

@ -165,6 +165,7 @@ void HttpClient::DoReadResponse(Error* error) {
// Stop the deadline timer once the read has started (or failed).
CancelTimer();
// TODO: Is it necessary to check `length == 0`?
if (ec || length == 0) {
Close();
*error = kSocketReadError;

@ -67,6 +67,20 @@ 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);
SetHeaders(headers, &builder);
builder.File(name, std::move(file));
return Request(builder());
}
HttpResponsePtr HttpClientSession::Put(
const std::string& url, std::string&& data, bool json,
const std::vector<std::string>& headers) {

@ -59,6 +59,12 @@ public:
HttpResponsePtr Post(const std::string& url, std::string&& data, bool json,
const std::vector<std::string>& headers = {});
// Post a file.
HttpResponsePtr PostFile(const std::string& url,
const std::string& name,
http::File&& file,
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 = {});

@ -166,7 +166,9 @@ void HttpMessage::Dump(std::ostream& os, std::size_t indent,
os << indent_str << std::endl;
// NOTE: The content will be truncated if it's too large to display.
// NOTE:
// - The content will be truncated if it's too large to display.
// - Binary content will not be dumped (TODO).
if (!content_.empty()) {
if (indent == 0) {

@ -1,7 +1,5 @@
#include "webcc/http_request_builder.h"
#include <fstream>
#include "webcc/base64.h"
#include "webcc/logger.h"
#include "webcc/utility.h"
@ -9,25 +7,6 @@
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(size, '\0');
ifs.seekg(0);
ifs.read(&(*output)[0], size); // TODO: Error handling
return true;
}
// -----------------------------------------------------------------------------
HttpRequestPtr HttpRequestBuilder::Build() {
assert(parameters_.size() % 2 == 0);
assert(headers_.size() % 2 == 0);
@ -72,15 +51,33 @@ HttpRequestPtr HttpRequestBuilder::Build() {
}
HttpRequestBuilder& HttpRequestBuilder::File(const std::string& name,
const std::string& file_name,
const std::string& file_path,
const std::string& content_type) {
std::string file_data;
if (!ReadFile(file_path, &file_data)) {
throw Exception(kFileIOError, "Cannot read the file.");
}
const std::string& file_name,
const std::string& mime_type) {
assert(!name.empty());
// TODO
files_[name] = http::File(file_path/*, file_name, 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;
files_.push_back({name, file_name, std::move(file_data), content_type});
files_[name] = std::move(file);
return *this;
}
@ -116,29 +113,29 @@ void HttpRequestBuilder::SetContent(HttpRequestPtr request,
void HttpRequestBuilder::CreateFormData(std::string* data,
const std::string& boundary) {
for (UploadFile& file : files_) {
for (auto& pair : files_) {
data->append("--" + boundary + kCRLF);
// Content-Disposition header
data->append("Content-Disposition: form-data");
if (!file.name.empty()) {
data->append("; name=\"" + file.name + "\"");
if (!pair.first.empty()) {
data->append("; name=\"" + pair.first + "\"");
}
if (!file.file_name.empty()) {
data->append("; filename=\"" + file.file_name + "\"");
if (!pair.second.file_name.empty()) {
data->append("; filename=\"" + pair.second.file_name + "\"");
}
data->append(kCRLF);
// Content-Type header
if (!file.content_type.empty()) {
data->append("Content-Type: " + file.content_type);
if (!pair.second.mime_type.empty()) {
data->append("Content-Type: " + pair.second.mime_type);
data->append(kCRLF);
}
data->append(kCRLF);
// Payload
data->append(file.file_data);
data->append(pair.second.data);
data->append(kCRLF);
}

@ -1,6 +1,7 @@
#ifndef WEBCC_HTTP_REQUEST_BUILDER_H_
#define WEBCC_HTTP_REQUEST_BUILDER_H_
#include <map>
#include <string>
#include <vector>
@ -67,18 +68,17 @@ public:
// Upload a file with its path.
// TODO: UNICODE file path.
HttpRequestBuilder& File(const std::string& name,
const std::string& file_name,
const std::string& file_path,
const std::string& content_type = "");
const std::string& file_name = "",
const std::string& mime_type = "");
HttpRequestBuilder& File(const std::string& name, http::File&& file);
// Upload a file with its data.
HttpRequestBuilder& FileData(const std::string& name,
const std::string& file_name,
std::string&& file_data,
const std::string& content_type = "") {
files_.push_back({name, file_name, file_data, content_type});
return *this;
}
const std::string& file_name = "",
const std::string& mime_type = "");
HttpRequestBuilder& Gzip(bool gzip = true) {
gzip_ = gzip;
@ -122,19 +122,8 @@ private:
// Is the data to send a JSON string?
bool json_ = false;
// A file to upload.
// Examples:
// { "images", "example.jpg", "BinaryData", "image/jpeg" }
// { "file", "report.csv", "BinaryData", "" }
struct UploadFile {
std::string name;
std::string file_name;
std::string file_data; // Binary file data
std::string content_type;
};
// Files to upload for a POST (or PUT?) request.
std::vector<UploadFile> files_;
std::map<std::string, http::File> files_;
// Compress the request content.
// NOTE: Most servers don't support compressed requests.

@ -51,6 +51,12 @@ bool SoapClient::Request(const std::string& operation,
http_request->SetContent(std::move(http_content), true);
// NOTE:
// According to www.w3.org when placing SOAP messages in HTTP bodies, the HTTP
// Content-type header must be chosen as "application/soap+xml" [RFC 3902].
// But in practice, many web servers cannot understand it.
// See: https://www.w3.org/TR/2007/REC-soap12-part0-20070427/#L26854
if (soap_version_ == kSoapV11) {
http_request->SetContentType(http::media_types::kTextXml,
http::charsets::kUtf8);

Loading…
Cancel
Save