Remove HttpFile to FormPart; refine payload prepare.

master
Chunting Gu 6 years ago
parent 80d0c73617
commit a96109c3b7

@ -14,9 +14,13 @@ bool kSslVerify = true;
#endif #endif
void Help(const char* argv0) { void Help(const char* argv0) {
std::cout << "Usage: " << argv0 << " <upload_dir>" << std::endl; std::cout << "Usage: " << argv0 << " <upload_dir> [url]" << std::endl;
std::cout << "Default Url: http://httpbin.org/post" << std::endl;
std::cout << " E.g.," << std::endl; std::cout << " E.g.," << std::endl;
std::cout << " " << argv0 << "E:/github/webcc/data/upload" << std::endl; std::cout << " " << argv0 << "E:/github/webcc/data/upload" << std::endl;
std::cout << " " << argv0
<< "E:/github/webcc/data/upload http://httpbin.org/post"
<< std::endl;
} }
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@ -29,6 +33,13 @@ int main(int argc, char* argv[]) {
const webcc::Path upload_dir(argv[1]); const webcc::Path upload_dir(argv[1]);
std::string url;
if (argc == 3) {
url = argv[2];
} else {
url = "http://httpbin.org/post";
}
namespace bfs = boost::filesystem; namespace bfs = boost::filesystem;
if (!bfs::is_directory(upload_dir) || !bfs::exists(upload_dir)) { if (!bfs::is_directory(upload_dir) || !bfs::exists(upload_dir)) {
@ -38,9 +49,6 @@ int main(int argc, char* argv[]) {
webcc::HttpClientSession session; webcc::HttpClientSession session;
//std::string url = "http://httpbin.org/post";
std::string url = "http://localhost:8080/upload";
try { try {
auto r = session.PostFile(url, "file", auto r = session.PostFile(url, "file",
upload_dir / "remember.txt"); upload_dir / "remember.txt");

@ -12,11 +12,11 @@ public:
void Handle(const webcc::RestRequest& request, void Handle(const webcc::RestRequest& request,
webcc::RestResponse* response) final { webcc::RestResponse* response) final {
if (request.http->method() == webcc::http::methods::kPost) { if (request.http->method() == webcc::http::methods::kPost) {
std::cout << "files: " << request.http->files().size() << std::endl; std::cout << "files: " << request.http->form_parts().size() << std::endl;
for (auto& pair : request.http->files()) { for (auto& file : request.http->form_parts()) {
std::cout << "name: " << pair.first << std::endl; std::cout << "name: " << file.name() << std::endl;
std::cout << "data: " << std::endl << pair.second.data() << std::endl; std::cout << "data: " << std::endl << file.data() << std::endl;
} }
response->content = "OK"; response->content = "OK";

@ -18,8 +18,8 @@ void ExampleBasic() {
auto r = session.Request(webcc::HttpRequestBuilder{} auto r = session.Request(webcc::HttpRequestBuilder{}
.Get() .Get()
.Url("http://httpbin.org/get") .Url("http://httpbin.org/get")
.Parameter("key1", "value1") .Query("key1", "value1")
.Parameter("key2", "value2") .Query("key2", "value2")
.Header("Accept", "application/json")()); .Header("Accept", "application/json")());
std::cout << r->content() << std::endl; std::cout << r->content() << std::endl;
@ -44,7 +44,7 @@ void ExampleHttps() {
auto r = session.Request(webcc::HttpRequestBuilder{} auto r = session.Request(webcc::HttpRequestBuilder{}
.Get() .Get()
.Url("https://httpbin.org/get") .Url("https://httpbin.org/get")
.Parameter("key1", "value1") .Query("key1", "value1")
.Header("Accept", "application/json")()); .Header("Accept", "application/json")());
std::cout << r->content() << std::endl; std::cout << r->content() << std::endl;

@ -22,7 +22,6 @@ 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
@ -53,7 +52,6 @@ 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,13 +1,25 @@
#include "webcc/common.h" #include "webcc/common.h"
#include "boost/algorithm/string.hpp" #include "boost/algorithm/string.hpp"
#include "boost/filesystem/fstream.hpp"
#include "webcc/logger.h" #include "webcc/logger.h"
namespace bfs = boost::filesystem;
namespace webcc { namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
namespace misc_strings {
const char HEADER_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
// -----------------------------------------------------------------------------
bool Split2(const std::string& str, char token, std::string* part1, bool Split2(const std::string& str, char token, std::string* part1,
std::string* part2) { std::string* part2) {
std::size_t pos = str.find(token); std::size_t pos = str.find(token);
@ -24,6 +36,20 @@ bool Split2(const std::string& str, char token, std::string* part1,
return true; return true;
} }
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;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void HttpHeaders::Set(const std::string& key, const std::string& value) { void HttpHeaders::Set(const std::string& key, const std::string& value) {
@ -169,4 +195,76 @@ bool ContentDisposition::Init(const std::string& str) {
return true; return true;
} }
// -----------------------------------------------------------------------------
FormPart::FormPart(const std::string& name, const Path& path,
const std::string& mime_type)
: name_(name), mime_type_(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);
}
}
FormPart::FormPart(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);
}
}
}
void FormPart::Prepare(std::vector<boost::asio::const_buffer>& payload) {
if (headers_.empty()) {
std::string value = "form-data";
if (!name_.empty()) {
value.append("; name=\"" + name_ + "\"");
}
if (!file_name_.empty()) {
value.append("; filename=\"" + file_name_ + "\"");
}
headers_.Set(http::headers::kContentDisposition, value);
if (!mime_type_.empty()) {
headers_.Set(http::headers::kContentType, mime_type_);
}
}
using boost::asio::buffer;
for (const HttpHeader& h : headers_.data()) {
payload.push_back(buffer(h.first));
payload.push_back(buffer(misc_strings::HEADER_SEPARATOR));
payload.push_back(buffer(h.second));
payload.push_back(buffer(misc_strings::CRLF));
}
payload.push_back(buffer(misc_strings::CRLF));
if (!data_.empty()) {
payload.push_back(buffer(data_));
}
payload.push_back(buffer(misc_strings::CRLF));
}
} // namespace webcc } // namespace webcc

@ -6,14 +6,28 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "boost/filesystem/path.hpp"
#include "webcc/globals.h"
namespace webcc { namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
using Path = boost::filesystem::path;
using Payload = std::vector<boost::asio::const_buffer>;
// -----------------------------------------------------------------------------
// Split a string to two parts by the given token. // Split a string to two parts by the given token.
bool Split2(const std::string& str, char token, std::string* part1, bool Split2(const std::string& str, char token, std::string* part1,
std::string* part2); std::string* part2);
// Read entire file into string.
bool ReadFile(const Path& path, std::string* output);
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
typedef std::pair<std::string, std::string> HttpHeader; typedef std::pair<std::string, std::string> HttpHeader;
@ -24,6 +38,10 @@ public:
return headers_.size(); return headers_.size();
} }
bool empty() const {
return headers_.empty();
}
const std::vector<HttpHeader>& data() const { const std::vector<HttpHeader>& data() const {
return headers_; return headers_;
} }
@ -131,6 +149,95 @@ private:
bool valid_ = false; bool valid_ = false;
}; };
// -----------------------------------------------------------------------------
// Form data part.
class FormPart {
public:
FormPart() = default;
explicit FormPart(const std::string& name, const Path& path,
const std::string& mime_type = "");
FormPart(std::string&& data, const std::string& file_name,
const std::string& mime_type = "");
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
FormPart(FormPart&&) = default;
FormPart& operator=(FormPart&&) = default;
#else
FormPart(FormPart&& rhs)
: name_(std::move(rhs.name_)),
file_name_(std::move(rhs.file_name_)),
mime_type_(std::move(rhs.mime_type_)),
data_(std::move(rhs.data_)) {
}
FormPart& operator=(FormPart&& rhs) {
if (&rhs != this) {
name_ = std::move(rhs.name_);
file_name_ = std::move(rhs.file_name_);
mime_type_ = std::move(rhs.mime_type_);
data_ = std::move(rhs.data_);
}
return *this;
}
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
const std::string& name() const {
return name_;
}
void set_name(const std::string& name) {
name_ = name;
}
const std::string& file_name() const {
return file_name_;
}
void set_file_name(const std::string& file_name) {
file_name_ = file_name;
}
const std::string& mime_type() const {
return mime_type_;
}
const std::string& data() const {
return data_;
}
void AppendData(const std::string& data) {
data_.append(data);
}
void AppendData(const char* data, std::size_t size) {
data_.append(data, size);
}
void Prepare(Payload& payload);
private:
std::string name_;
// E.g., example.jpg
// TODO: Unicode
std::string file_name_;
// E.g., image/jpeg
std::string mime_type_;
HttpHeaders headers_;
// Binary file data.
std::string data_;
};
} // namespace webcc } // namespace webcc
#endif // WEBCC_COMMON_H_ #endif // WEBCC_COMMON_H_

@ -45,7 +45,7 @@ HttpResponsePtr HttpClientSession::Get(
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) {
builder.Parameter(parameters[i - 1], parameters[i]); builder.Query(parameters[i - 1], parameters[i]);
} }
SetHeaders(headers, &builder); SetHeaders(headers, &builder);

@ -95,7 +95,7 @@ void HttpConnection::OnRead(boost::system::error_code ec, std::size_t length) {
void HttpConnection::DoWrite() { void HttpConnection::DoWrite() {
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
boost::asio::async_write(socket_, response_->ToBuffers(), boost::asio::async_write(socket_, response_->payload(),
std::bind(&HttpConnection::OnWrite, shared_from_this(), std::bind(&HttpConnection::OnWrite, shared_from_this(),
std::placeholders::_1, std::placeholders::_1,
std::placeholders::_2)); std::placeholders::_2));

@ -1,64 +0,0 @@
#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

@ -1,87 +0,0 @@
#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_;
}
void AppendData(const std::string& data) {
data_.append(data);
}
void AppendData(const char* data, std::size_t size) {
data_.append(data, size);
}
const std::string& file_name() const {
return file_name_;
}
void set_file_name(const std::string& file_name) {
file_name_ = 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_

@ -13,7 +13,7 @@ namespace webcc {
namespace misc_strings { namespace misc_strings {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' }; const char HEADER_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' }; const char CRLF[] = { '\r', '\n' };
} // misc_strings } // misc_strings
@ -79,28 +79,28 @@ void HttpMessage::SetContent(std::string&& content, bool set_length) {
} }
} }
// ATTENTION: The buffers don't hold the memory! void HttpMessage::Prepare() {
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
assert(!start_line_.empty()); assert(!start_line_.empty());
std::vector<boost::asio::const_buffer> buffers; using boost::asio::buffer;
buffers.push_back(boost::asio::buffer(start_line_)); payload_.clear();
payload_.push_back(buffer(start_line_));
payload_.push_back(buffer(misc_strings::CRLF));
for (const HttpHeader& h : headers_.data()) { for (const HttpHeader& h : headers_.data()) {
buffers.push_back(boost::asio::buffer(h.first)); payload_.push_back(buffer(h.first));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR)); payload_.push_back(buffer(misc_strings::HEADER_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.second)); payload_.push_back(buffer(h.second));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); payload_.push_back(buffer(misc_strings::CRLF));
} }
buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); payload_.push_back(buffer(misc_strings::CRLF));
if (content_length_ > 0) { if (!content_.empty()) {
buffers.push_back(boost::asio::buffer(content_)); payload_.push_back(buffer(content_));
} }
return buffers;
} }
void HttpMessage::Dump(std::ostream& os, std::size_t indent, void HttpMessage::Dump(std::ostream& os, std::size_t indent,
@ -111,7 +111,7 @@ void HttpMessage::Dump(std::ostream& os, std::size_t indent,
} }
indent_str.append(prefix); indent_str.append(prefix);
os << indent_str << start_line_; os << indent_str << start_line_ << std::endl;
for (const HttpHeader& h : headers_.data()) { for (const HttpHeader& h : headers_.data()) {
os << indent_str << h.first << ": " << h.second << std::endl; os << indent_str << h.first << ": " << h.second << std::endl;

@ -6,10 +6,7 @@
#include <utility> // for move() #include <utility> // for move()
#include <vector> #include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/common.h" #include "webcc/common.h"
#include "webcc/http_file.h"
#include "webcc/globals.h" #include "webcc/globals.h"
namespace webcc { namespace webcc {
@ -84,14 +81,12 @@ public:
void SetContent(std::string&& content, bool set_length); void SetContent(std::string&& content, bool set_length);
// Make the message (e.g., update start line). // Prepare payload.
// Must be called before ToBuffers()! virtual void Prepare();
virtual bool Prepare() = 0;
// Convert the message into a vector of buffers. The buffers do not own the const Payload& payload() const {
// underlying memory blocks, therefore the message object must remain valid return payload_;
// and not be changed until the write operation has completed. }
std::vector<boost::asio::const_buffer> ToBuffers() const;
// Dump to output stream. // Dump to output stream.
void Dump(std::ostream& os, std::size_t indent = 0, void Dump(std::ostream& os, std::size_t indent = 0,
@ -119,6 +114,9 @@ protected:
std::size_t content_length_; std::size_t content_length_;
HttpHeaders headers_; HttpHeaders headers_;
// NOTE: The payload itself doesn't hold the memory!
Payload payload_;
}; };
} // namespace webcc } // namespace webcc

@ -3,14 +3,14 @@
#include <string> #include <string>
#include "webcc/common.h"
#include "webcc/globals.h" #include "webcc/globals.h"
#include "webcc/http_file.h"
namespace webcc { namespace webcc {
class HttpMessage; class HttpMessage;
// HttpParser parses HTTP request and response. // HTTP request and response parser.
class HttpParser { class HttpParser {
public: public:
explicit HttpParser(HttpMessage* message); explicit HttpParser(HttpMessage* message);
@ -80,19 +80,6 @@ protected:
bool chunked_; bool chunked_;
std::size_t chunk_size_; std::size_t chunk_size_;
bool finished_; bool finished_;
struct Part {
enum Step {
kStart,
kBoundaryParsed,
kHeadersParsed,
kEnded,
};
Step step = kStart;
std::string name;
HttpFile file;
};
Part part_;
}; };
} // namespace webcc } // namespace webcc

@ -1,13 +1,84 @@
#include "webcc/http_request.h" #include "webcc/http_request.h"
#include "webcc/logger.h" #include "webcc/logger.h"
#include "webcc/utility.h"
namespace webcc { namespace webcc {
bool HttpRequest::Prepare() { // -----------------------------------------------------------------------------
namespace misc_strings {
const char CRLF[] = { '\r', '\n' };
const char DOUBLE_DASHES[] = { '-', '-' };
} // misc_strings
// -----------------------------------------------------------------------------
void HttpRequest::Prepare() {
CreateStartLine();
if (url_.port().empty()) {
SetHeader(http::headers::kHost, url_.host());
} else {
SetHeader(http::headers::kHost, url_.host() + ":" + url_.port());
}
if (form_parts_.empty()) {
HttpMessage::Prepare();
} else {
// Multipart form data.
// Another choice to generate the boundary is what Apache does.
// See: https://stackoverflow.com/a/5686863
if (boundary_.empty()) {
boundary_ = RandomUuid();
}
SetContentType("multipart/form-data; boundary=" + boundary_);
Payload data_payload;
using boost::asio::buffer;
for (auto& part : form_parts_) {
// Boundary
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
data_payload.push_back(buffer(boundary_));
data_payload.push_back(buffer(misc_strings::CRLF));
part.Prepare(data_payload);
}
// Boundary end
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
data_payload.push_back(buffer(boundary_));
data_payload.push_back(buffer(misc_strings::DOUBLE_DASHES));
data_payload.push_back(buffer(misc_strings::CRLF));
// Update Content-Length header.
std::size_t content_length = 0;
for (auto& buffer : data_payload) {
content_length += buffer.size();
}
SetContentLength(content_length);
// Prepare start line and headers.
HttpMessage::Prepare();
// Append payload of content data.
payload_.insert(payload_.end(), data_payload.begin(), data_payload.end());
}
}
void HttpRequest::CreateStartLine() {
if (!start_line_.empty()) {
return;
}
if (url_.host().empty()) { if (url_.host().empty()) {
LOG_ERRO("Invalid request: host is missing."); throw Exception(kSchemaError, "Invalid request: host is missing.");
return false;
} }
std::string target = "/" + url_.path(); std::string target = "/" + url_.path();
@ -20,15 +91,6 @@ bool HttpRequest::Prepare() {
start_line_ += " "; start_line_ += " ";
start_line_ += target; start_line_ += target;
start_line_ += " HTTP/1.1"; start_line_ += " HTTP/1.1";
start_line_ += kCRLF;
if (url_.port().empty()) {
SetHeader(http::headers::kHost, url_.host());
} else {
SetHeader(http::headers::kHost, url_.host() + ":" + url_.port());
}
return true;
} }
} // namespace webcc } // namespace webcc

@ -1,7 +1,6 @@
#ifndef WEBCC_HTTP_REQUEST_H_ #ifndef WEBCC_HTTP_REQUEST_H_
#define WEBCC_HTTP_REQUEST_H_ #define WEBCC_HTTP_REQUEST_H_
#include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -32,9 +31,8 @@ public:
url_.Init(url); url_.Init(url);
} }
// Add URL query parameter. void AddQuery(const std::string& key, const std::string& value) {
void AddParameter(const std::string& key, const std::string& value) { url_.AddQuery(key, value);
url_.AddParameter(key, value);
} }
const std::string& method() const { const std::string& method() const {
@ -57,18 +55,23 @@ public:
return port().empty() ? default_port : port(); return port().empty() ? default_port : port();
} }
const std::map<std::string, HttpFile>& files() const { const std::vector<FormPart>& form_parts() const {
return files_; return form_parts_;
} }
// Add a file to upload. void set_form_parts_(std::vector<FormPart>&& form_parts) {
void AddFile(const std::string& name, HttpFile&& file) { form_parts_ = std::move(form_parts);
files_[name] = std::move(file); }
void AddFormPart(FormPart&& form_part) {
form_parts_.push_back(std::move(form_part));
} }
// Prepare payload. // Prepare payload.
// Compose start line, set Host header, etc. void Prepare() final;
bool Prepare() final;
private:
void CreateStartLine();
private: private:
std::string method_; std::string method_;
@ -76,7 +79,9 @@ private:
Url url_; Url url_;
// Files to upload for a POST request. // Files to upload for a POST request.
std::map<std::string, HttpFile> files_; std::vector<FormPart> form_parts_;
std::string boundary_;
}; };
} // namespace webcc } // namespace webcc

@ -14,7 +14,7 @@ HttpRequestPtr HttpRequestBuilder::Build() {
auto request = std::make_shared<HttpRequest>(method_, url_); auto request = std::make_shared<HttpRequest>(method_, url_);
for (std::size_t i = 1; i < parameters_.size(); i += 2) { for (std::size_t i = 1; i < parameters_.size(); i += 2) {
request->AddParameter(parameters_[i - 1], parameters_[i]); request->AddQuery(parameters_[i - 1], parameters_[i]);
} }
for (std::size_t i = 1; i < headers_.size(); i += 2) { for (std::size_t i = 1; i < headers_.size(); i += 2) {
@ -34,17 +34,19 @@ HttpRequestPtr HttpRequestBuilder::Build() {
request->SetContentType(http::media_types::kApplicationJson, ""); request->SetContentType(http::media_types::kApplicationJson, "");
} }
} else if (!files_.empty()) { } else if (!files_.empty()) {
// Another choice to generate the boundary is what Apache does, see: request->set_form_parts_(std::move(files_));
// https://stackoverflow.com/a/5686863
const std::string boundary = RandomUuid();
request->SetContentType("multipart/form-data; boundary=" + boundary); //// Another choice to generate the boundary is what Apache does.
//// See: https://stackoverflow.com/a/5686863
//const std::string boundary = RandomUuid();
std::string data; //request->SetContentType("multipart/form-data; boundary=" + boundary);
CreateFormData(&data, boundary);
// Ingore gzip since most servers don't support it. //std::string data;
request->SetContent(std::move(data), true); //CreateFormData(&data, boundary);
//// Ingore gzip since most servers don't support it.
//request->SetContent(std::move(data), true);
} }
return request; return request;
@ -55,7 +57,7 @@ HttpRequestBuilder& HttpRequestBuilder::File(const std::string& name,
const std::string& mime_type) { const std::string& mime_type) {
assert(!name.empty()); assert(!name.empty());
files_[name] = HttpFile(path, mime_type); files_.push_back(FormPart{ name, path, mime_type });
return *this; return *this;
} }
@ -66,7 +68,8 @@ HttpRequestBuilder& HttpRequestBuilder::FileData(const std::string& name,
const std::string& mime_type) { const std::string& mime_type) {
assert(!name.empty()); assert(!name.empty());
files_[name] = HttpFile(std::move(file_data), file_name, mime_type); // TODO
//files_[name] = HttpFile(std::move(file_data), file_name, mime_type);
return *this; return *this;
} }
@ -100,37 +103,37 @@ void HttpRequestBuilder::SetContent(HttpRequestPtr request,
request->SetContent(std::move(data), true); request->SetContent(std::move(data), true);
} }
void HttpRequestBuilder::CreateFormData(std::string* data, //void HttpRequestBuilder::CreateFormData(std::string* data,
const std::string& boundary) { // const std::string& boundary) {
for (auto& pair : files_) { // for (auto& pair : files_) {
data->append("--" + boundary + kCRLF); // data->append("--" + boundary + kCRLF);
//
// Content-Disposition header // // Content-Disposition header
data->append("Content-Disposition: form-data"); // data->append("Content-Disposition: form-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);
} // }
//
data->append("--" + boundary + "--"); // data->append("--" + boundary + "--");
data->append(kCRLF); // data->append(kCRLF);
} //}
} // namespace webcc } // namespace webcc

@ -5,7 +5,6 @@
#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 {
@ -16,6 +15,11 @@ public:
: method_(method) { : method_(method) {
} }
~HttpRequestBuilder() = default;
HttpRequestBuilder(const HttpRequestBuilder&) = delete;
HttpRequestBuilder& operator=(const HttpRequestBuilder&) = delete;
// Build the request. // Build the request.
HttpRequestPtr Build(); HttpRequestPtr Build();
@ -44,7 +48,7 @@ public:
return *this; return *this;
} }
HttpRequestBuilder& Parameter(const std::string& key, HttpRequestBuilder& Query(const std::string& key,
const std::string& value) { const std::string& value) {
parameters_.push_back(key); parameters_.push_back(key);
parameters_.push_back(value); parameters_.push_back(value);
@ -70,8 +74,8 @@ public:
HttpRequestBuilder& File(const std::string& name, const Path& path, HttpRequestBuilder& File(const std::string& name, const Path& path,
const std::string& mime_type = ""); const std::string& mime_type = "");
HttpRequestBuilder& File(const std::string& name, HttpFile&& file) { HttpRequestBuilder& File(FormPart&& file) {
files_[name] = std::move(file); files_.push_back(std::move(file));
return *this; return *this;
} }
@ -108,7 +112,7 @@ public:
private: private:
void SetContent(HttpRequestPtr request, std::string&& data); void SetContent(HttpRequestPtr request, std::string&& data);
void CreateFormData(std::string* data, const std::string& boundary); //void CreateFormData(std::string* data, const std::string& boundary);
private: private:
std::string method_; std::string method_;
@ -125,7 +129,7 @@ private:
bool json_ = false; bool json_ = false;
// Files to upload for a POST request. // Files to upload for a POST request.
std::map<std::string, HttpFile> files_; std::vector<FormPart> 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.

@ -61,7 +61,7 @@ bool HttpRequestParser::ParseMultipartContent() {
break; break;
} }
if (part_.step == Part::Step::kStart) { if (step_ == Step::kStart) {
std::string line; std::string line;
if (!GetNextLine(0, &line, true)) { if (!GetNextLine(0, &line, true)) {
break; // Not enough data break; // Not enough data
@ -72,15 +72,15 @@ bool HttpRequestParser::ParseMultipartContent() {
} }
LOG_INFO("Boundary line: %s", line.c_str()); LOG_INFO("Boundary line: %s", line.c_str());
// Go to next step. // Go to next step.
part_.step = Part::Step::kBoundaryParsed; step_ = Step::kBoundaryParsed;
continue; continue;
} }
if (part_.step == Part::Step::kBoundaryParsed) { if (step_ == Step::kBoundaryParsed) {
bool need_more_data = false; bool need_more_data = false;
if (ParsePartHeaders(&need_more_data)) { if (ParsePartHeaders(&need_more_data)) {
// Go to next step. // Go to next step.
part_.step = Part::Step::kHeadersParsed; step_ = Step::kHeadersParsed;
LOG_INFO("Part headers just ended."); LOG_INFO("Part headers just ended.");
continue; continue;
} else { } else {
@ -93,13 +93,13 @@ bool HttpRequestParser::ParseMultipartContent() {
} }
} }
if (part_.step == Part::Step::kHeadersParsed) { if (step_ == Step::kHeadersParsed) {
std::size_t off = 0; std::size_t off = 0;
std::size_t count = 0; std::size_t count = 0;
bool ended = false; bool ended = false;
if (!GetNextBoundaryLine(&off, &count, &ended)) { if (!GetNextBoundaryLine(&off, &count, &ended)) {
// All pending data belongs to this part. // All pending data belongs to this part.
part_.file.AppendData(pending_data_); part_.AppendData(pending_data_);
pending_data_.clear(); pending_data_.clear();
break; break;
} }
@ -109,24 +109,24 @@ bool HttpRequestParser::ParseMultipartContent() {
// This part has ended. // This part has ended.
if (off > 2) { if (off > 2) {
// -2 for exluding the CRLF after the data. // -2 for exluding the CRLF after the data.
part_.file.AppendData(pending_data_.data(), off - 2); part_.AppendData(pending_data_.data(), off - 2);
} }
request_->AddFile(part_.name, std::move(part_.file)); request_->AddFormPart(std::move(part_));
if (ended) { if (ended) {
// Go to the end step. // Go to the end step.
part_.step = Part::Step::kEnded; step_ = Step::kEnded;
break; break;
} else { } else {
// Go to next step. // Go to next step.
part_.step = Part::Step::kBoundaryParsed; step_ = Step::kBoundaryParsed;
continue; continue;
} }
} }
} }
if (part_.step == Part::Step::kEnded) { if (step_ == Step::kEnded) {
LOG_INFO("Multipart data has ended."); LOG_INFO("Multipart data has ended.");
Finish(); Finish();
} }
@ -169,10 +169,10 @@ bool HttpRequestParser::ParsePartHeaders(bool* need_more_data) {
header.second.c_str()); header.second.c_str());
return false; return false;
} }
part_.name = content_disposition.name(); part_.set_name(content_disposition.name());
part_.file.set_file_name(content_disposition.file_name()); part_.set_file_name(content_disposition.file_name());
LOG_INFO("Content-Disposition (name=%s; filename=%s)", LOG_INFO("Content-Disposition (name=%s; filename=%s)",
part_.name.c_str(), part_.file.file_name().c_str()); part_.name().c_str(), part_.file_name().c_str());
} }
// TODO: Parse other headers. // TODO: Parse other headers.

@ -34,6 +34,16 @@ private:
private: private:
HttpRequest* request_; HttpRequest* request_;
enum Step {
kStart,
kBoundaryParsed,
kHeadersParsed,
kEnded,
};
Step step_ = kStart;
FormPart part_;
}; };
} // namespace webcc } // namespace webcc

@ -6,17 +6,17 @@ namespace webcc {
namespace status_strings { namespace status_strings {
const std::string OK = "HTTP/1.1 200 OK\r\n"; const std::string OK = "HTTP/1.1 200 OK";
const std::string CREATED = "HTTP/1.1 201 Created\r\n"; const std::string CREATED = "HTTP/1.1 201 Created";
const std::string ACCEPTED = "HTTP/1.1 202 Accepted\r\n"; const std::string ACCEPTED = "HTTP/1.1 202 Accepted";
const std::string NO_CONTENT = "HTTP/1.1 204 No Content\r\n"; const std::string NO_CONTENT = "HTTP/1.1 204 No Content";
const std::string NOT_MODIFIED = "HTTP/1.1 304 Not Modified\r\n"; const std::string NOT_MODIFIED = "HTTP/1.1 304 Not Modified";
const std::string BAD_REQUEST = "HTTP/1.1 400 Bad Request\r\n"; const std::string BAD_REQUEST = "HTTP/1.1 400 Bad Request";
const std::string NOT_FOUND = "HTTP/1.1 404 Not Found\r\n"; const std::string NOT_FOUND = "HTTP/1.1 404 Not Found";
const std::string INTERNAL_SERVER_ERROR = const std::string INTERNAL_SERVER_ERROR =
"HTTP/1.1 500 Internal Server Error\r\n"; "HTTP/1.1 500 Internal Server Error";
const std::string NOT_IMPLEMENTED = "HTTP/1.1 501 Not Implemented\r\n"; const std::string NOT_IMPLEMENTED = "HTTP/1.1 501 Not Implemented";
const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable\r\n"; const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable";
const std::string& ToString(int status) { const std::string& ToString(int status) {
switch (status) { switch (status) {
@ -57,13 +57,15 @@ const std::string& ToString(int status) {
} // namespace status_strings } // namespace status_strings
bool HttpResponse::Prepare() { void HttpResponse::Prepare() {
if (start_line_.empty()) {
start_line_ = status_strings::ToString(status_); start_line_ = status_strings::ToString(status_);
}
SetHeader(http::headers::kServer, http::UserAgent()); SetHeader(http::headers::kServer, http::UserAgent());
SetHeader(http::headers::kDate, GetHttpDateTimestamp()); SetHeader(http::headers::kDate, GetHttpDateTimestamp());
return true; HttpMessage::Prepare();
} }
} // namespace webcc } // namespace webcc

@ -19,12 +19,16 @@ public:
~HttpResponse() override = default; ~HttpResponse() override = default;
int status() const { return status_; } int status() const {
return status_;
}
void set_status(int status) { status_ = status; } void set_status(int status) {
status_ = status;
}
// Set start line according to status code. // Set start line according to status code.
bool Prepare() final; void Prepare() final;
private: private:
int status_; int status_;

@ -24,7 +24,7 @@ void HttpSocket::Connect(const std::string& /*host*/,
void HttpSocket::Write(const HttpRequest& request, void HttpSocket::Write(const HttpRequest& request,
boost::system::error_code* ec) { boost::system::error_code* ec) {
boost::asio::write(socket_, request.ToBuffers(), *ec); boost::asio::write(socket_, request.payload(), *ec);
} }
void HttpSocket::AsyncReadSome(ReadHandler&& handler, std::vector<char>* buffer) { void HttpSocket::AsyncReadSome(ReadHandler&& handler, std::vector<char>* buffer) {
@ -60,7 +60,7 @@ void HttpSslSocket::Connect(const std::string& host,
void HttpSslSocket::Write(const HttpRequest& request, void HttpSslSocket::Write(const HttpRequest& request,
boost::system::error_code* ec) { boost::system::error_code* ec) {
boost::asio::write(ssl_socket_, request.ToBuffers(), *ec); boost::asio::write(ssl_socket_, request.payload(), *ec);
} }
void HttpSslSocket::AsyncReadSome(ReadHandler&& handler, std::vector<char>* buffer) { void HttpSslSocket::AsyncReadSome(ReadHandler&& handler, std::vector<char>* buffer) {

@ -268,7 +268,7 @@ void Url::Init(const std::string& str, bool decode, bool clear) {
} }
} }
void Url::AddParameter(const std::string& key, const std::string& value) { void Url::AddQuery(const std::string& key, const std::string& value) {
if (!query_.empty()) { if (!query_.empty()) {
query_ += "&"; query_ += "&";
} }

@ -111,8 +111,8 @@ public:
return query_; return query_;
} }
// Add a parameter to the query string. // Add a query parameter.
void AddParameter(const std::string& key, const std::string& value); void AddQuery(const std::string& key, const std::string& value);
private: private:
void Parse(const std::string& str); void Parse(const std::string& str);

Loading…
Cancel
Save