Remove HttpFile to FormPart; refine payload prepare.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
+107
@@ -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_
|
|
||||||
+15
-15
@@ -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;
|
||||||
|
|||||||
+8
-10
@@ -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
|
||||||
|
|||||||
+2
-15
@@ -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
|
||||||
|
|||||||
+74
-12
@@ -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
|
||||||
|
|||||||
+17
-12
@@ -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
|
||||||
|
|||||||
+14
-12
@@ -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) {
|
||||||
|
|||||||
+1
-1
@@ -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_ += "&";
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user