Refine multipart form.

master
Chunting Gu 6 years ago
parent 3742143657
commit de4574a437

@ -55,7 +55,7 @@ int main(int argc, char* argv[]) {
auto r = session.Request(webcc::HttpRequestBuilder{}.Post(). auto r = session.Request(webcc::HttpRequestBuilder{}.Post().
Url(url). Url(url).
File("file", upload_dir / "remember.txt"). File("file", upload_dir / "remember.txt").
Form("text", "text default") Form("json", "{}", "application/json")
()); ());
//std::cout << r->content() << std::endl; //std::cout << r->content() << std::endl;

@ -11,12 +11,12 @@ class FileUploadService : public webcc::RestService {
public: 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() == "POST") {
std::cout << "files: " << request.http->form_parts().size() << std::endl; std::cout << "files: " << request.http->form_parts().size() << std::endl;
for (auto& file : request.http->form_parts()) { for (auto& part : request.http->form_parts()) {
std::cout << "name: " << file.name() << std::endl; std::cout << "name: " << part.name() << std::endl;
std::cout << "data: " << std::endl << file.data() << std::endl; std::cout << "data: " << std::endl << part.data() << std::endl;
} }
response->content = "OK"; response->content = "OK";

@ -15,6 +15,8 @@ namespace webcc {
namespace misc_strings { namespace misc_strings {
// Literal strings can't be used because they have an extra '\0'.
const char HEADER_SEPARATOR[] = { ':', ' ' }; const char HEADER_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' }; const char CRLF[] = { '\r', '\n' };
@ -210,8 +212,8 @@ bool ContentDisposition::Init(const std::string& str) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
FormPart::FormPart(const std::string& name, const Path& path, FormPart::FormPart(const std::string& name, const Path& path,
const std::string& mime_type) const std::string& media_type)
: name_(name), mime_type_(mime_type) { : name_(name), media_type_(media_type) {
if (!ReadFile(path, &data_)) { if (!ReadFile(path, &data_)) {
throw Exception(kFileIOError, "Cannot read the file."); throw Exception(kFileIOError, "Cannot read the file.");
} }
@ -220,50 +222,61 @@ FormPart::FormPart(const std::string& name, const Path& path,
// TODO: encoding // TODO: encoding
file_name_ = path.filename().string(std::codecvt_utf8<wchar_t>()); file_name_ = path.filename().string(std::codecvt_utf8<wchar_t>());
// Determine content type from file extension. // Determine media type from file extension.
if (mime_type_.empty()) { if (media_type_.empty()) {
std::string extension = path.extension().string(); std::string extension = path.extension().string();
mime_type_ = http::media_types::FromExtension(extension, false); media_type_ = http::media_types::FromExtension(extension, false);
} }
} }
FormPart::FormPart(const std::string& name, std::string&& data, FormPart::FormPart(const std::string& name, std::string&& data,
const std::string& mime_type) const std::string& media_type)
: name_(name), data_(std::move(data)), mime_type_(mime_type) { : name_(name), data_(std::move(data)), media_type_(media_type) {
} }
void FormPart::Prepare(std::vector<boost::asio::const_buffer>& payload) { void FormPart::Prepare(Payload* payload) {
// The payload buffers don't own the memory.
// It depends on some existing variables/objects to keep the memory.
// That's why we need |headers_|.
if (headers_.empty()) { if (headers_.empty()) {
std::string value = "form-data"; SetHeaders();
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; using boost::asio::buffer;
for (const HttpHeader& h : headers_.data()) { for (const HttpHeader& h : headers_.data()) {
payload.push_back(buffer(h.first)); payload->push_back(buffer(h.first));
payload.push_back(buffer(misc_strings::HEADER_SEPARATOR)); payload->push_back(buffer(misc_strings::HEADER_SEPARATOR));
payload.push_back(buffer(h.second)); payload->push_back(buffer(h.second));
payload.push_back(buffer(misc_strings::CRLF)); payload->push_back(buffer(misc_strings::CRLF));
} }
payload.push_back(buffer(misc_strings::CRLF)); payload->push_back(buffer(misc_strings::CRLF));
if (!data_.empty()) { if (!data_.empty()) {
payload.push_back(buffer(data_)); payload->push_back(buffer(data_));
}
payload->push_back(buffer(misc_strings::CRLF));
}
void FormPart::SetHeaders() {
// Header: Content-Disposition
std::string content_disposition = "form-data";
if (!name_.empty()) {
content_disposition.append("; name=\"" + name_ + "\"");
} }
if (!file_name_.empty()) {
content_disposition.append("; filename=\"" + file_name_ + "\"");
}
headers_.Set(http::headers::kContentDisposition, content_disposition);
payload.push_back(buffer(misc_strings::CRLF)); // Header: Content-Type
if (!media_type_.empty()) {
headers_.Set(http::headers::kContentType, media_type_);
}
} }
} // namespace webcc } // namespace webcc

@ -151,16 +151,23 @@ private:
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Form data part. // A part of the multipart form data.
class FormPart { class FormPart {
public: public:
FormPart() = default; FormPart() = default;
// Construct a file part.
// The file name will be extracted from path.
// The media type, if not provided, will be inferred from file extension.
FormPart(const std::string& name, const Path& path, FormPart(const std::string& name, const Path& path,
const std::string& mime_type = ""); const std::string& media_type = "");
// Construct a non-file part.
// The data will be moved, no file name is needed.
// The media type is optional. If the data is a JSON string, you can specify
// media type as "application/json".
FormPart(const std::string& name, std::string&& data, FormPart(const std::string& name, std::string&& data,
const std::string& mime_type = ""); const std::string& media_type = "");
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN #if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
@ -172,7 +179,7 @@ public:
FormPart(FormPart&& rhs) FormPart(FormPart&& rhs)
: name_(std::move(rhs.name_)), : name_(std::move(rhs.name_)),
file_name_(std::move(rhs.file_name_)), file_name_(std::move(rhs.file_name_)),
mime_type_(std::move(rhs.mime_type_)), media_type_(std::move(rhs.media_type_)),
data_(std::move(rhs.data_)) { data_(std::move(rhs.data_)) {
} }
@ -180,7 +187,7 @@ public:
if (&rhs != this) { if (&rhs != this) {
name_ = std::move(rhs.name_); name_ = std::move(rhs.name_);
file_name_ = std::move(rhs.file_name_); file_name_ = std::move(rhs.file_name_);
mime_type_ = std::move(rhs.mime_type_); media_type_ = std::move(rhs.media_type_);
data_ = std::move(rhs.data_); data_ = std::move(rhs.data_);
} }
return *this; return *this;
@ -188,53 +195,74 @@ public:
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN #endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
// API: SERVER
const std::string& name() const { const std::string& name() const {
return name_; return name_;
} }
// API: SERVER/PARSER
void set_name(const std::string& name) { void set_name(const std::string& name) {
name_ = name; name_ = name;
} }
// API: SERVER
const std::string& file_name() const { const std::string& file_name() const {
return file_name_; return file_name_;
} }
// API: SERVER/PARSER
void set_file_name(const std::string& file_name) { void set_file_name(const std::string& file_name) {
file_name_ = file_name; file_name_ = file_name;
} }
const std::string& mime_type() const { // API: SERVER
return mime_type_; const std::string& media_type() const {
return media_type_;
} }
// API: SERVER
const std::string& data() const { const std::string& data() const {
return data_; return data_;
} }
// API: SERVER/PARSER
void AppendData(const std::string& data) { void AppendData(const std::string& data) {
data_.append(data); data_.append(data);
} }
void AppendData(const char* data, std::size_t size) { // API: SERVER/PARSER
data_.append(data, size); void AppendData(const char* data, std::size_t count) {
data_.append(data, count);
} }
void Prepare(Payload& payload); // API: CLIENT
void Prepare(Payload* payload);
private: private:
// Generate headers from properties.
void SetHeaders();
private:
// The <input> name within the original HTML form.
// E.g., given HTML form:
// <input name="file1" type="file">
// the name will be "file1".
std::string name_; std::string name_;
// E.g., example.jpg // The original local file name.
// TODO: Unicode // E.g., "baby.jpg".
std::string file_name_; std::string file_name_;
// E.g., image/jpeg // The content-type if the media type is known (e.g., inferred from the file
std::string mime_type_; // extension or operating system typing information) or as
// application/octet-stream.
// E.g., "image/jpeg".
std::string media_type_;
// Headers generated from the above properties.
// Only Used to prepare payload.
HttpHeaders headers_; HttpHeaders headers_;
// Binary file data.
std::string data_; std::string data_;
}; };

@ -13,6 +13,8 @@ namespace webcc {
namespace misc_strings { namespace misc_strings {
// Literal strings can't be used because they have an extra '\0'.
const char HEADER_SEPARATOR[] = { ':', ' ' }; const char HEADER_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' }; const char CRLF[] = { '\r', '\n' };

@ -9,6 +9,8 @@ namespace webcc {
namespace misc_strings { namespace misc_strings {
// Literal strings can't be used because they have an extra '\0'.
const char CRLF[] = { '\r', '\n' }; const char CRLF[] = { '\r', '\n' };
const char DOUBLE_DASHES[] = { '-', '-' }; const char DOUBLE_DASHES[] = { '-', '-' };
@ -27,10 +29,12 @@ void HttpRequest::Prepare() {
if (form_parts_.empty()) { if (form_parts_.empty()) {
HttpMessage::Prepare(); HttpMessage::Prepare();
} else { return;
}
// Multipart form data. // Multipart form data.
// Another choice to generate the boundary is what Apache does. // Another choice to generate the boundary is like what Apache does.
// See: https://stackoverflow.com/a/5686863 // See: https://stackoverflow.com/a/5686863
if (boundary_.empty()) { if (boundary_.empty()) {
boundary_ = RandomUuid(); boundary_ = RandomUuid();
@ -48,7 +52,7 @@ void HttpRequest::Prepare() {
data_payload.push_back(buffer(boundary_)); data_payload.push_back(buffer(boundary_));
data_payload.push_back(buffer(misc_strings::CRLF)); data_payload.push_back(buffer(misc_strings::CRLF));
part.Prepare(data_payload); part.Prepare(&data_payload);
} }
// Boundary end // Boundary end
@ -69,7 +73,6 @@ void HttpRequest::Prepare() {
// Append payload of content data. // Append payload of content data.
payload_.insert(payload_.end(), data_payload.begin(), data_payload.end()); payload_.insert(payload_.end(), data_payload.begin(), data_payload.end());
}
} }
void HttpRequest::CreateStartLine() { void HttpRequest::CreateStartLine() {

@ -1,7 +1,6 @@
#ifndef WEBCC_HTTP_REQUEST_BUILDER_H_ #ifndef WEBCC_HTTP_REQUEST_BUILDER_H_
#define WEBCC_HTTP_REQUEST_BUILDER_H_ #define WEBCC_HTTP_REQUEST_BUILDER_H_
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>

Loading…
Cancel
Save