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
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 << " " << 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[]) {
@ -29,6 +33,13 @@ int main(int argc, char* argv[]) {
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;
if (!bfs::is_directory(upload_dir) || !bfs::exists(upload_dir)) {
@ -38,9 +49,6 @@ int main(int argc, char* argv[]) {
webcc::HttpClientSession session;
//std::string url = "http://httpbin.org/post";
std::string url = "http://localhost:8080/upload";
try {
auto r = session.PostFile(url, "file",
upload_dir / "remember.txt");

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

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

@ -22,7 +22,6 @@ set(HEADERS
http_client_session.h
http_connection.h
http_connection_pool.h
http_file.h
http_message.h
http_parser.h
http_request.h
@ -53,7 +52,6 @@ set(SOURCES
http_client_session.cc
http_connection.cc
http_connection_pool.cc
http_file.cc
http_message.cc
http_parser.cc
http_request.cc

@ -1,13 +1,25 @@
#include "webcc/common.h"
#include "boost/algorithm/string.hpp"
#include "boost/filesystem/fstream.hpp"
#include "webcc/logger.h"
namespace bfs = boost::filesystem;
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,
std::string* part2) {
std::size_t pos = str.find(token);
@ -24,6 +36,20 @@ bool Split2(const std::string& str, char token, std::string* part1,
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) {
@ -169,4 +195,76 @@ bool ContentDisposition::Init(const std::string& str) {
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

@ -6,14 +6,28 @@
#include <utility>
#include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "boost/filesystem/path.hpp"
#include "webcc/globals.h"
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.
bool Split2(const std::string& str, char token, std::string* part1,
std::string* part2);
// Read entire file into string.
bool ReadFile(const Path& path, std::string* output);
// -----------------------------------------------------------------------------
typedef std::pair<std::string, std::string> HttpHeader;
@ -24,6 +38,10 @@ public:
return headers_.size();
}
bool empty() const {
return headers_.empty();
}
const std::vector<HttpHeader>& data() const {
return headers_;
}
@ -131,6 +149,95 @@ private:
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
#endif // WEBCC_COMMON_H_

@ -45,7 +45,7 @@ HttpResponsePtr HttpClientSession::Get(
assert(parameters.size() % 2 == 0);
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);

@ -95,7 +95,7 @@ void HttpConnection::OnRead(boost::system::error_code ec, std::size_t length) {
void HttpConnection::DoWrite() {
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::placeholders::_1,
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 {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' };
const char HEADER_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
@ -79,28 +79,28 @@ void HttpMessage::SetContent(std::string&& content, bool set_length) {
}
}
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
void HttpMessage::Prepare() {
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()) {
buffers.push_back(boost::asio::buffer(h.first));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.second));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
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));
}
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
payload_.push_back(buffer(misc_strings::CRLF));
if (content_length_ > 0) {
buffers.push_back(boost::asio::buffer(content_));
if (!content_.empty()) {
payload_.push_back(buffer(content_));
}
return buffers;
}
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);
os << indent_str << start_line_;
os << indent_str << start_line_ << std::endl;
for (const HttpHeader& h : headers_.data()) {
os << indent_str << h.first << ": " << h.second << std::endl;

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

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

@ -1,13 +1,84 @@
#include "webcc/http_request.h"
#include "webcc/logger.h"
#include "webcc/utility.h"
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()) {
LOG_ERRO("Invalid request: host is missing.");
return false;
throw Exception(kSchemaError, "Invalid request: host is missing.");
}
std::string target = "/" + url_.path();
@ -20,15 +91,6 @@ bool HttpRequest::Prepare() {
start_line_ += " ";
start_line_ += target;
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

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

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

@ -5,7 +5,6 @@
#include <string>
#include <vector>
#include "webcc/http_file.h"
#include "webcc/http_request.h"
namespace webcc {
@ -16,6 +15,11 @@ public:
: method_(method) {
}
~HttpRequestBuilder() = default;
HttpRequestBuilder(const HttpRequestBuilder&) = delete;
HttpRequestBuilder& operator=(const HttpRequestBuilder&) = delete;
// Build the request.
HttpRequestPtr Build();
@ -44,8 +48,8 @@ public:
return *this;
}
HttpRequestBuilder& Parameter(const std::string& key,
const std::string& value) {
HttpRequestBuilder& Query(const std::string& key,
const std::string& value) {
parameters_.push_back(key);
parameters_.push_back(value);
return *this;
@ -70,8 +74,8 @@ public:
HttpRequestBuilder& File(const std::string& name, const Path& path,
const std::string& mime_type = "");
HttpRequestBuilder& File(const std::string& name, HttpFile&& file) {
files_[name] = std::move(file);
HttpRequestBuilder& File(FormPart&& file) {
files_.push_back(std::move(file));
return *this;
}
@ -108,7 +112,7 @@ public:
private:
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:
std::string method_;
@ -125,7 +129,7 @@ private:
bool json_ = false;
// Files to upload for a POST request.
std::map<std::string, HttpFile> files_;
std::vector<FormPart> files_;
// Compress the request content.
// NOTE: Most servers don't support compressed requests.

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

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

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

@ -19,13 +19,17 @@ public:
~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.
bool Prepare() final;
void Prepare() final;
private:
int status_;
};

@ -24,7 +24,7 @@ void HttpSocket::Connect(const std::string& /*host*/,
void HttpSocket::Write(const HttpRequest& request,
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) {
@ -60,7 +60,7 @@ void HttpSslSocket::Connect(const std::string& host,
void HttpSslSocket::Write(const HttpRequest& request,
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) {

@ -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()) {
query_ += "&";
}

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

Loading…
Cancel
Save