Fix payload iteration issue of FormBody; Log form body; ignore body for response of HEAD.
This commit is contained in:
@@ -33,10 +33,7 @@ TEST(ClientTest, Head_RequestFunc) {
|
||||
|
||||
try {
|
||||
auto r = session.Request(webcc::RequestBuilder{}.
|
||||
Head("http://httpbin.org/get").
|
||||
Query("key1", "value1").
|
||||
Query("key2", "value2").
|
||||
Header("Accept", "application/json")
|
||||
Head("http://httpbin.org/get")
|
||||
());
|
||||
|
||||
EXPECT_EQ(webcc::Status::kOK, r->status());
|
||||
@@ -65,6 +62,31 @@ TEST(ClientTest, Head_Shortcut) {
|
||||
}
|
||||
}
|
||||
|
||||
// Force Accept-Encoding to be "identity" so that HttpBin.org will include
|
||||
// a Content-Length header in the response.
|
||||
// This tests that the response with Content-Length while no body could be
|
||||
// correctly parsed.
|
||||
TEST(ClientTest, Head_AcceptEncodingIdentity) {
|
||||
webcc::ClientSession session;
|
||||
|
||||
try {
|
||||
auto r = session.Request(webcc::RequestBuilder{}.
|
||||
Head("http://httpbin.org/get").
|
||||
Header("Accept-Encoding", "identity")
|
||||
());
|
||||
|
||||
EXPECT_EQ(webcc::Status::kOK, r->status());
|
||||
EXPECT_EQ("OK", r->reason());
|
||||
|
||||
EXPECT_TRUE(r->HasHeader(webcc::headers::kContentLength));
|
||||
|
||||
EXPECT_EQ("", r->data());
|
||||
|
||||
} catch (const webcc::Error& error) {
|
||||
std::cerr << error << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
static void AssertGet(webcc::ResponsePtr r) {
|
||||
|
||||
@@ -8,8 +8,12 @@ int main() {
|
||||
|
||||
webcc::ClientSession session;
|
||||
|
||||
webcc::ResponsePtr r;
|
||||
|
||||
try {
|
||||
auto r = session.Request(webcc::RequestBuilder{}.
|
||||
r = session.Head("http://httpbin.org/get");
|
||||
|
||||
r = session.Request(webcc::RequestBuilder{}.
|
||||
Get("http://httpbin.org/get").
|
||||
Query("key1", "value1").
|
||||
Query("key2", "value2").
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# A demo HTTP file upload server modified from:
|
||||
# http://flask.pocoo.org/docs/1.0/patterns/fileuploads/#uploading-files
|
||||
# Run:
|
||||
# (Windows)
|
||||
# $ set FLASK_APP=file_upload_server.py
|
||||
# $ flask
|
||||
|
||||
import os
|
||||
from flask import Flask, flash, request, redirect, url_for
|
||||
from flask import send_from_directory
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
|
||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
||||
app = Flask(__name__)
|
||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
||||
|
||||
|
||||
@app.route('/upload', methods=['POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
for name, data in request.form.items():
|
||||
print(f'form: {name}, {data}')
|
||||
|
||||
for name, file in request.files.items():
|
||||
if file:
|
||||
secured_filename = secure_filename(file.filename)
|
||||
print(f"file: {name}, {file.filename} ({secured_filename})")
|
||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'],
|
||||
secured_filename))
|
||||
|
||||
return "OK"
|
||||
|
||||
|
||||
@app.route('/uploads/<filename>')
|
||||
def uploaded_file(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
set(UT_SRCS
|
||||
base64_unittest.cc
|
||||
body_unittest.cc
|
||||
request_parser_unittest.cc
|
||||
url_unittest.cc
|
||||
utility_unittest.cc
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "webcc/body.h"
|
||||
|
||||
TEST(FormBodyTest, Payload) {
|
||||
std::vector<webcc::FormPartPtr> parts{
|
||||
std::make_shared<webcc::FormPart>("json", "{}", "application/json")
|
||||
};
|
||||
|
||||
webcc::FormBody form_body{ parts, "123456" };
|
||||
|
||||
form_body.InitPayload();
|
||||
|
||||
auto payload = form_body.NextPayload();
|
||||
EXPECT_EQ(false, payload.empty());
|
||||
|
||||
payload = form_body.NextPayload();
|
||||
EXPECT_EQ(true, payload.empty());
|
||||
}
|
||||
+35
-43
@@ -1,6 +1,7 @@
|
||||
#include "webcc/body.h"
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "boost/core/ignore_unused.hpp"
|
||||
|
||||
#include "webcc/logger.h"
|
||||
#include "webcc/utility.h"
|
||||
@@ -13,17 +14,6 @@ namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace misc_strings {
|
||||
|
||||
// Literal strings can't be used because they have an extra '\0'.
|
||||
|
||||
const char CRLF[] = { '\r', '\n' };
|
||||
const char DOUBLE_DASHES[] = { '-', '-' };
|
||||
|
||||
} // misc_strings
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
bool StringBody::Compress() {
|
||||
if (data_.size() <= kGzipThreshold) {
|
||||
@@ -45,7 +35,9 @@ void StringBody::InitPayload() {
|
||||
index_ = 0;
|
||||
}
|
||||
|
||||
Payload StringBody::NextPayload() {
|
||||
Payload StringBody::NextPayload(bool free_previous) {
|
||||
boost::ignore_unused(free_previous);
|
||||
|
||||
if (index_ == 0) {
|
||||
index_ = 1;
|
||||
return Payload{ boost::asio::buffer(data_) };
|
||||
@@ -57,27 +49,8 @@ Payload StringBody::NextPayload() {
|
||||
// - The data will be truncated if it's too large to display.
|
||||
// - Binary content will not be dumped (TODO).
|
||||
void StringBody::Dump(std::ostream& os, const std::string& prefix) const {
|
||||
if (data_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split by EOL to achieve more readability.
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, data_, boost::is_any_of("\n"));
|
||||
|
||||
std::size_t size = 0;
|
||||
|
||||
for (const std::string& line : lines) {
|
||||
os << prefix;
|
||||
|
||||
if (line.size() + size > kMaxDumpSize) {
|
||||
os.write(line.c_str(), kMaxDumpSize - size);
|
||||
os << "..." << std::endl;
|
||||
break;
|
||||
} else {
|
||||
os << line << std::endl;
|
||||
size += line.size();
|
||||
}
|
||||
if (!data_.empty()) {
|
||||
utility::DumpByLine(data_, os, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,43 +75,62 @@ std::size_t FormBody::GetSize() const {
|
||||
}
|
||||
|
||||
void FormBody::Dump(std::ostream& os, const std::string& prefix) const {
|
||||
// TODO
|
||||
for (auto& part : parts_) {
|
||||
os << prefix << "--" << boundary_ << std::endl;
|
||||
part->Dump(os, prefix);
|
||||
}
|
||||
os << prefix << "--" << boundary_ << "--" << std::endl;
|
||||
}
|
||||
|
||||
void FormBody::InitPayload() {
|
||||
index_ = 0;
|
||||
}
|
||||
|
||||
// TODO: Clear previous payload memory.
|
||||
Payload FormBody::NextPayload() {
|
||||
Payload FormBody::NextPayload(bool free_previous) {
|
||||
Payload payload;
|
||||
|
||||
// Free previous payload.
|
||||
if (free_previous) {
|
||||
if (index_ > 0) {
|
||||
Free(index_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (index_ < parts_.size()) {
|
||||
auto& part = parts_[index_];
|
||||
AddBoundary(&payload);
|
||||
part->Prepare(&payload);
|
||||
parts_[index_]->Prepare(&payload);
|
||||
|
||||
if (index_ + 1 == parts_.size()) {
|
||||
AddBoundaryEnd(&payload);
|
||||
}
|
||||
}
|
||||
|
||||
++index_;
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
void FormBody::AddBoundary(Payload* payload) {
|
||||
using boost::asio::buffer;
|
||||
payload->push_back(buffer(misc_strings::DOUBLE_DASHES));
|
||||
|
||||
payload->push_back(buffer(literal_buffers::DOUBLE_DASHES));
|
||||
payload->push_back(buffer(boundary_));
|
||||
payload->push_back(buffer(misc_strings::CRLF));
|
||||
payload->push_back(buffer(literal_buffers::CRLF));
|
||||
}
|
||||
|
||||
void FormBody::AddBoundaryEnd(Payload* payload) {
|
||||
using boost::asio::buffer;
|
||||
payload->push_back(buffer(misc_strings::DOUBLE_DASHES));
|
||||
|
||||
payload->push_back(buffer(literal_buffers::DOUBLE_DASHES));
|
||||
payload->push_back(buffer(boundary_));
|
||||
payload->push_back(buffer(misc_strings::DOUBLE_DASHES));
|
||||
payload->push_back(buffer(misc_strings::CRLF));
|
||||
payload->push_back(buffer(literal_buffers::DOUBLE_DASHES));
|
||||
payload->push_back(buffer(literal_buffers::CRLF));
|
||||
}
|
||||
|
||||
void FormBody::Free(std::size_t index) {
|
||||
if (index < parts_.size()) {
|
||||
parts_[index]->Free();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
+9
-7
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility> // for move()
|
||||
#include <utility>
|
||||
|
||||
#include "webcc/common.h"
|
||||
|
||||
@@ -38,11 +38,12 @@ public:
|
||||
// InitPayload();
|
||||
// for (auto p = NextPayload(); !p.empty(); p = NextPayload()) {
|
||||
// }
|
||||
virtual void InitPayload() {}
|
||||
virtual void InitPayload() {
|
||||
}
|
||||
|
||||
// Get the next payload.
|
||||
// An empty payload returned indicates the end.
|
||||
virtual Payload NextPayload() {
|
||||
virtual Payload NextPayload(bool free_previous = false) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ public:
|
||||
|
||||
void InitPayload() override;
|
||||
|
||||
Payload NextPayload() override;
|
||||
Payload NextPayload(bool free_previous = false) override;
|
||||
|
||||
void Dump(std::ostream& os, const std::string& prefix) const override;
|
||||
|
||||
@@ -93,8 +94,7 @@ private:
|
||||
// Multi-part form body for request.
|
||||
class FormBody : public Body {
|
||||
public:
|
||||
FormBody(const std::vector<FormPartPtr>& parts,
|
||||
const std::string& boundary);
|
||||
FormBody(const std::vector<FormPartPtr>& parts, const std::string& boundary);
|
||||
|
||||
std::size_t GetSize() const override;
|
||||
|
||||
@@ -104,7 +104,7 @@ public:
|
||||
|
||||
void InitPayload() override;
|
||||
|
||||
Payload NextPayload() override;
|
||||
Payload NextPayload(bool free_previous = false) override;
|
||||
|
||||
void Dump(std::ostream& os, const std::string& prefix) const override;
|
||||
|
||||
@@ -112,6 +112,8 @@ private:
|
||||
void AddBoundary(Payload* payload);
|
||||
void AddBoundaryEnd(Payload* payload);
|
||||
|
||||
void Free(std::size_t index);
|
||||
|
||||
private:
|
||||
std::vector<FormPartPtr> parts_;
|
||||
std::string boundary_;
|
||||
|
||||
+13
-3
@@ -18,6 +18,17 @@ Client::Client()
|
||||
Error Client::Request(RequestPtr request, bool connect) {
|
||||
Restart();
|
||||
|
||||
// Response to HEAD could also have Content-Length.
|
||||
// Set this flag to skip the reading and parsing of the body.
|
||||
// The test against HttpBin.org shows that:
|
||||
// - If request.Accept-Encoding is "gzip, deflate", the response doesn't
|
||||
// have Content-Length;
|
||||
// - If request.Accept-Encoding is "identity", the response do have
|
||||
// Content-Length.
|
||||
if (request->method() == methods::kHead) {
|
||||
response_parser_.set_ignroe_body(true);
|
||||
}
|
||||
|
||||
if (connect) {
|
||||
// No existing socket connection was specified, create a new one.
|
||||
Connect(request);
|
||||
@@ -131,16 +142,15 @@ void Client::WriteReqeust(RequestPtr request) {
|
||||
|
||||
if (socket_->Write(request->GetPayload(), &ec)) {
|
||||
// Write request body.
|
||||
if (request->body()) {
|
||||
auto body = request->body();
|
||||
body->InitPayload();
|
||||
for (auto p = body->NextPayload(); !p.empty(); p = body->NextPayload()) {
|
||||
for (auto p = body->NextPayload(true); !p.empty();
|
||||
p = body->NextPayload(true)) {
|
||||
if (!socket_->Write(p, &ec)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
LOG_ERRO("Socket write error (%s).", ec.message().c_str());
|
||||
|
||||
+49
-49
@@ -3,47 +3,14 @@
|
||||
#include <codecvt>
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "boost/filesystem/fstream.hpp"
|
||||
|
||||
#include "webcc/logger.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
namespace bfs = boost::filesystem;
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace misc_strings {
|
||||
|
||||
// Literal strings can't be used because they have an extra '\0'.
|
||||
|
||||
const char HEADER_SEPARATOR[] = { ':', ' ' };
|
||||
const char CRLF[] = { '\r', '\n' };
|
||||
|
||||
} // misc_strings
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
bool ReadFile(const Path& path, std::string* output) {
|
||||
// Flag "ate": seek to the end of stream immediately after open.
|
||||
bfs::ifstream stream{ path, std::ios::binary | std::ios::ate };
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = stream.tellg();
|
||||
output->resize(static_cast<std::size_t>(size), '\0');
|
||||
stream.seekg(std::ios::beg);
|
||||
stream.read(&(*output)[0], size);
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
void Headers::Set(const std::string& key, const std::string& value) {
|
||||
auto it = Find(key);
|
||||
if (it != headers_.end()) {
|
||||
@@ -66,8 +33,7 @@ bool Headers::Has(const std::string& key) const {
|
||||
return const_cast<Headers*>(this)->Find(key) != headers_.end();
|
||||
}
|
||||
|
||||
const std::string& Headers::Get(const std::string& key,
|
||||
bool* existed) const {
|
||||
const std::string& Headers::Get(const std::string& key, bool* existed) const {
|
||||
auto it = const_cast<Headers*>(this)->Find(key);
|
||||
|
||||
if (existed != nullptr) {
|
||||
@@ -204,11 +170,7 @@ bool ContentDisposition::Init(const std::string& str) {
|
||||
|
||||
FormPart::FormPart(const std::string& name, const Path& path,
|
||||
const std::string& media_type)
|
||||
: name_(name), media_type_(media_type) {
|
||||
if (!ReadFile(path, &data_)) {
|
||||
throw Error{ Error::kFileError, "Cannot read the file." };
|
||||
}
|
||||
|
||||
: name_(name), path_(path), media_type_(media_type) {
|
||||
// Determine file name from file path.
|
||||
// TODO: encoding
|
||||
file_name_ = path.filename().string(std::codecvt_utf8<wchar_t>());
|
||||
@@ -229,6 +191,12 @@ FormPart::FormPart(const std::string& name, std::string&& data,
|
||||
void FormPart::Prepare(Payload* payload) {
|
||||
using boost::asio::buffer;
|
||||
|
||||
if (data_.empty() && !path_.empty()) {
|
||||
if (!utility::ReadFile(path_, &data_)) {
|
||||
throw Error{ Error::kFileError, "Cannot read the file" };
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// The payload buffers don't own the memory.
|
||||
// It depends on some existing variables/objects to keep the memory.
|
||||
@@ -240,18 +208,23 @@ void FormPart::Prepare(Payload* payload) {
|
||||
|
||||
for (const Header& h : headers_.data()) {
|
||||
payload->push_back(buffer(h.first));
|
||||
payload->push_back(buffer(misc_strings::HEADER_SEPARATOR));
|
||||
payload->push_back(buffer(literal_buffers::HEADER_SEPARATOR));
|
||||
payload->push_back(buffer(h.second));
|
||||
payload->push_back(buffer(misc_strings::CRLF));
|
||||
payload->push_back(buffer(literal_buffers::CRLF));
|
||||
}
|
||||
|
||||
payload->push_back(buffer(misc_strings::CRLF));
|
||||
payload->push_back(buffer(literal_buffers::CRLF));
|
||||
|
||||
if (!data_.empty()) {
|
||||
payload->push_back(buffer(data_));
|
||||
}
|
||||
|
||||
payload->push_back(buffer(misc_strings::CRLF));
|
||||
payload->push_back(buffer(literal_buffers::CRLF));
|
||||
}
|
||||
|
||||
void FormPart::Free() {
|
||||
data_.clear();
|
||||
data_.shrink_to_fit();
|
||||
}
|
||||
|
||||
std::size_t FormPart::GetSize() {
|
||||
@@ -263,19 +236,46 @@ std::size_t FormPart::GetSize() {
|
||||
|
||||
for (const Header& h : headers_.data()) {
|
||||
size += h.first.size();
|
||||
size += sizeof(misc_strings::HEADER_SEPARATOR);
|
||||
size += sizeof(literal_buffers::HEADER_SEPARATOR);
|
||||
size += h.second.size();
|
||||
size += sizeof(misc_strings::CRLF);
|
||||
size += sizeof(literal_buffers::CRLF);
|
||||
}
|
||||
size += sizeof(misc_strings::CRLF);
|
||||
size += sizeof(literal_buffers::CRLF);
|
||||
|
||||
size += data_.size();
|
||||
size += GetDataSize();
|
||||
|
||||
size += sizeof(misc_strings::CRLF);
|
||||
size += sizeof(literal_buffers::CRLF);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
std::size_t FormPart::GetDataSize() {
|
||||
if (!data_.empty()) {
|
||||
return data_.size();
|
||||
}
|
||||
|
||||
auto size = utility::TellSize(path_);
|
||||
if (size == kInvalidLength) {
|
||||
throw Error{ Error::kFileError, "Cannot read the file" };
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void FormPart::Dump(std::ostream& os, const std::string& prefix) const {
|
||||
for (auto& h : headers_.data()) {
|
||||
os << prefix << h.first << ": " << h.second << std::endl;
|
||||
}
|
||||
|
||||
os << prefix << std::endl;
|
||||
|
||||
if (!path_.empty()) {
|
||||
os << prefix << "<file: " << path_.string() << ">" << std::endl;
|
||||
} else {
|
||||
utility::DumpByLine(data_, os, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
void FormPart::SetHeaders() {
|
||||
// Header: Content-Disposition
|
||||
|
||||
|
||||
+13
-13
@@ -6,24 +6,12 @@
|
||||
#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>;
|
||||
|
||||
// Read entire file into string.
|
||||
bool ReadFile(const Path& path, std::string* output);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Header = std::pair<std::string, std::string>;
|
||||
|
||||
class Headers {
|
||||
@@ -211,10 +199,19 @@ public:
|
||||
// API: CLIENT
|
||||
void Prepare(Payload* payload);
|
||||
|
||||
// Get the payload size.
|
||||
// Free the memory of the data.
|
||||
void Free();
|
||||
|
||||
// Get the size of the whole payload.
|
||||
// Used by the request to calculate content length.
|
||||
std::size_t GetSize();
|
||||
|
||||
// Get the size of the data.
|
||||
std::size_t GetDataSize();
|
||||
|
||||
// Dump to output stream for logging purpose.
|
||||
void Dump(std::ostream& os, const std::string& prefix) const;
|
||||
|
||||
private:
|
||||
// Generate headers from properties.
|
||||
void SetHeaders();
|
||||
@@ -226,6 +223,9 @@ private:
|
||||
// the name will be "file1".
|
||||
std::string name_;
|
||||
|
||||
// The path of the file to post.
|
||||
Path path_;
|
||||
|
||||
// The original local file name.
|
||||
// E.g., "baby.jpg".
|
||||
std::string file_name_;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#include "webcc/connection.h"
|
||||
|
||||
#include <utility> // for move()
|
||||
#include <utility>
|
||||
|
||||
#include "boost/asio/write.hpp"
|
||||
|
||||
|
||||
@@ -8,6 +8,16 @@ namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace literal_buffers {
|
||||
|
||||
const char HEADER_SEPARATOR[2] = { ':', ' ' };
|
||||
const char CRLF[2] = { '\r', '\n' };
|
||||
const char DOUBLE_DASHES[2] = { '-', '-' };
|
||||
|
||||
} // namespace literal_buffers
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace media_types {
|
||||
|
||||
std::string FromExtension(const std::string& ext) {
|
||||
|
||||
+35
-5
@@ -7,6 +7,9 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "boost/asio/buffer.hpp" // for const_buffer
|
||||
#include "boost/filesystem/path.hpp"
|
||||
|
||||
#include "webcc/config.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -50,6 +53,10 @@ using Strings = std::vector<std::string>;
|
||||
// Could also be considered as arguments, so named as UrlArgs.
|
||||
using UrlArgs = std::vector<std::string>;
|
||||
|
||||
using Path = boost::filesystem::path;
|
||||
|
||||
using Payload = std::vector<boost::asio::const_buffer>;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const char* const kCRLF = "\r\n";
|
||||
@@ -74,6 +81,19 @@ const std::size_t kGzipThreshold = 1400;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace literal_buffers {
|
||||
|
||||
// Buffers for composing payload.
|
||||
// Literal strings can't be used because they have an extra '\0'.
|
||||
|
||||
extern const char HEADER_SEPARATOR[2];
|
||||
extern const char CRLF[2];
|
||||
extern const char DOUBLE_DASHES[2];
|
||||
|
||||
} // namespace literal_buffers
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace methods {
|
||||
|
||||
// HTTP methods (verbs) in string.
|
||||
@@ -179,20 +199,30 @@ public:
|
||||
: code_(code), message_(message), timeout_(false) {
|
||||
}
|
||||
|
||||
Code code() const { return code_; }
|
||||
Code code() const {
|
||||
return code_;
|
||||
}
|
||||
|
||||
const std::string& message() const { return message_; }
|
||||
const std::string& message() const {
|
||||
return message_;
|
||||
}
|
||||
|
||||
void Set(Code code, const std::string& message) {
|
||||
code_ = code;
|
||||
message_ = message;
|
||||
}
|
||||
|
||||
bool timeout() const { return timeout_; }
|
||||
bool timeout() const {
|
||||
return timeout_;
|
||||
}
|
||||
|
||||
void set_timeout(bool timeout) { timeout_ = timeout; }
|
||||
void set_timeout(bool timeout) {
|
||||
timeout_ = timeout;
|
||||
}
|
||||
|
||||
operator bool() const { return code_ != kOK; }
|
||||
operator bool() const {
|
||||
return code_ != kOK;
|
||||
}
|
||||
|
||||
private:
|
||||
Code code_;
|
||||
|
||||
+4
-17
@@ -9,19 +9,6 @@
|
||||
|
||||
namespace webcc {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
namespace misc_strings {
|
||||
|
||||
// Literal strings can't be used because they have an extra '\0'.
|
||||
|
||||
const char HEADER_SEPARATOR[] = { ':', ' ' };
|
||||
const char CRLF[] = { '\r', '\n' };
|
||||
|
||||
} // misc_strings
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Message::Message() : body_(new Body{}), content_length_(kInvalidLength) {
|
||||
}
|
||||
|
||||
@@ -111,16 +98,16 @@ Payload Message::GetPayload() const {
|
||||
Payload payload;
|
||||
|
||||
payload.push_back(buffer(start_line_));
|
||||
payload.push_back(buffer(misc_strings::CRLF));
|
||||
payload.push_back(buffer(literal_buffers::CRLF));
|
||||
|
||||
for (const Header& h : headers_.data()) {
|
||||
payload.push_back(buffer(h.first));
|
||||
payload.push_back(buffer(misc_strings::HEADER_SEPARATOR));
|
||||
payload.push_back(buffer(literal_buffers::HEADER_SEPARATOR));
|
||||
payload.push_back(buffer(h.second));
|
||||
payload.push_back(buffer(misc_strings::CRLF));
|
||||
payload.push_back(buffer(literal_buffers::CRLF));
|
||||
}
|
||||
|
||||
payload.push_back(buffer(misc_strings::CRLF));
|
||||
payload.push_back(buffer(literal_buffers::CRLF));
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility> // for move()
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "webcc/body.h"
|
||||
|
||||
+5
-4
@@ -60,11 +60,12 @@ bool Parser::Parse(const char* data, std::size_t length) {
|
||||
if (!header_ended_) {
|
||||
LOG_INFO("HTTP headers will continue in next read.");
|
||||
return true;
|
||||
} else {
|
||||
LOG_INFO("HTTP headers just ended.");
|
||||
// NOTE: The left data, if any, is still in the pending data.
|
||||
return ParseContent("", 0);
|
||||
}
|
||||
|
||||
LOG_INFO("HTTP headers just ended.");
|
||||
|
||||
// The left data, if any, is still in the pending data.
|
||||
return ParseContent("", 0);
|
||||
}
|
||||
|
||||
void Parser::Reset() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <utility> // for move()
|
||||
#include <utility>
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "boost/filesystem/fstream.hpp"
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "webcc/request.h"
|
||||
#include "webcc/response.h"
|
||||
#include "webcc/url.h"
|
||||
#include "webcc/utility.h"
|
||||
|
||||
#if WEBCC_ENABLE_GZIP
|
||||
#include "webcc/gzip.h"
|
||||
@@ -20,8 +21,7 @@ namespace bfs = boost::filesystem;
|
||||
|
||||
namespace webcc {
|
||||
|
||||
RequestHandler::RequestHandler(const Path& doc_root)
|
||||
: doc_root_(doc_root) {
|
||||
RequestHandler::RequestHandler(const Path& doc_root) : doc_root_(doc_root) {
|
||||
}
|
||||
|
||||
bool RequestHandler::Route(const std::string& url, ViewPtr view,
|
||||
@@ -184,7 +184,7 @@ bool RequestHandler::ServeStatic(ConnectionPtr connection) {
|
||||
Path p = doc_root_ / path;
|
||||
|
||||
std::string data;
|
||||
if (!ReadFile(p, &data)) {
|
||||
if (!utility::ReadFile(p, &data)) {
|
||||
connection->SendResponse(Status::kNotFound);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,4 +73,12 @@ bool ResponseParser::ParseStartLine(const std::string& line) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResponseParser::ParseContent(const char* data, std::size_t length) {
|
||||
if (ignroe_body_) {
|
||||
Finish();
|
||||
return true;
|
||||
}
|
||||
return Parser::ParseContent(data, length);
|
||||
}
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
@@ -17,12 +17,24 @@ public:
|
||||
|
||||
void Init(Response* response);
|
||||
|
||||
void set_ignroe_body(bool ignroe_body) {
|
||||
ignroe_body_ = ignroe_body;
|
||||
}
|
||||
|
||||
private:
|
||||
// Parse HTTP start line; E.g., "HTTP/1.1 200 OK".
|
||||
bool ParseStartLine(const std::string& line) override;
|
||||
|
||||
// Override to allow to ignore the body of the response for HEAD request.
|
||||
bool ParseContent(const char* data, std::size_t length) override;
|
||||
|
||||
private:
|
||||
// The result response message.
|
||||
Response* response_;
|
||||
|
||||
// The response for HEAD request could also have `Content-Length` header,
|
||||
// set this flag to ignore it.
|
||||
bool ignroe_body_ = false;
|
||||
};
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "boost/algorithm/string.hpp"
|
||||
#include "boost/filesystem/fstream.hpp"
|
||||
#include "boost/uuid/random_generator.hpp"
|
||||
#include "boost/uuid/uuid_io.hpp"
|
||||
|
||||
#include "webcc/version.h"
|
||||
|
||||
namespace bfs = boost::filesystem;
|
||||
|
||||
namespace webcc {
|
||||
namespace utility {
|
||||
|
||||
@@ -48,5 +51,52 @@ bool SplitKV(const std::string& str, char delimiter,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t TellSize(const Path& path) {
|
||||
// Flag "ate": seek to the end of stream immediately after open.
|
||||
bfs::ifstream stream{ path, std::ios::binary | std::ios::ate };
|
||||
if (stream.fail()) {
|
||||
return kInvalidLength;
|
||||
}
|
||||
return static_cast<std::size_t>(stream.tellg());
|
||||
}
|
||||
|
||||
bool ReadFile(const Path& path, std::string* output) {
|
||||
// Flag "ate": seek to the end of stream immediately after open.
|
||||
bfs::ifstream stream{ path, std::ios::binary | std::ios::ate };
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = stream.tellg();
|
||||
output->resize(static_cast<std::size_t>(size), '\0');
|
||||
stream.seekg(std::ios::beg);
|
||||
stream.read(&(*output)[0], size);
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DumpByLine(const std::string& data, std::ostream& os,
|
||||
const std::string& prefix) {
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, data, boost::is_any_of("\n"));
|
||||
|
||||
std::size_t size = 0;
|
||||
|
||||
for (const std::string& line : lines) {
|
||||
os << prefix;
|
||||
|
||||
if (line.size() + size > kMaxDumpSize) {
|
||||
os.write(line.c_str(), kMaxDumpSize - size);
|
||||
os << "..." << std::endl;
|
||||
break;
|
||||
} else {
|
||||
os << line << std::endl;
|
||||
size += line.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace utility
|
||||
} // namespace webcc
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "webcc/globals.h"
|
||||
|
||||
namespace webcc {
|
||||
namespace utility {
|
||||
|
||||
@@ -22,6 +24,18 @@ std::string GetTimestamp();
|
||||
bool SplitKV(const std::string& str, char delimiter,
|
||||
std::string* key, std::string* value);
|
||||
|
||||
// Tell the size in bytes of the given file.
|
||||
// Return kInvalidLength (-1) on failure.
|
||||
std::size_t TellSize(const Path& path);
|
||||
|
||||
// Read entire file into string.
|
||||
bool ReadFile(const Path& path, std::string* output);
|
||||
|
||||
// Dump the string data line by line to achieve more readability.
|
||||
// Also limit the maximum size of the data to be dumped.
|
||||
void DumpByLine(const std::string& data, std::ostream& os,
|
||||
const std::string& prefix);
|
||||
|
||||
} // namespace utility
|
||||
} // namespace webcc
|
||||
|
||||
|
||||
Reference in New Issue
Block a user