Refine HTTP request and response.

master
Adam Gu 7 years ago
parent 88511caff6
commit cbf3efbcba

@ -2,8 +2,8 @@
#include "boost/asio/io_context.hpp"
#include "webcc/logger.h"
#include "webcc/async_http_client.h"
#include "webcc/logger.h"
// In order to test this client, create a file index.html whose content is
// simply "Hello, World!", then start a HTTP server with Python 3:
@ -11,13 +11,12 @@
// The default port number should be 8000.
void Test(boost::asio::io_context& ioc) {
std::shared_ptr<webcc::HttpRequest> request(new webcc::HttpRequest());
webcc::HttpRequestPtr request(new webcc::HttpRequest());
request->set_method(webcc::kHttpGet);
request->set_url("/index.html");
request->SetHost("localhost", "8000");
request->Build();
request->UpdateStartLine();
webcc::HttpAsyncClientPtr client(new webcc::AsyncHttpClient(ioc));

@ -1,7 +1,7 @@
#include <iostream>
#include "webcc/logger.h"
#include "webcc/http_client.h"
#include "webcc/logger.h"
// In order to test this client, create a file index.html whose content is
// simply "Hello, World!", then start a HTTP server with Python 3:
@ -10,21 +10,21 @@
void Test() {
webcc::HttpRequest request;
request.set_method(webcc::kHttpGet);
request.set_url("/index.html");
request.SetHost("localhost", "8000");
request.Build();
webcc::HttpResponse response;
request.UpdateStartLine();
webcc::HttpClient client;
if (!client.Request(request)) {
return;
if (client.Request(request)) {
std::cout << client.response()->content() << std::endl;
} else {
std::cout << webcc::DescribeError(client.error());
if (client.timed_out()) {
std::cout << " (timed out)";
}
std::cout << std::endl;
}
std::cout << response.content() << std::endl;
}
int main() {

@ -2,8 +2,8 @@
#include "json/json.h"
#include "webcc/logger.h"
#include "webcc/async_rest_client.h"
#include "webcc/logger.h"
// -----------------------------------------------------------------------------
@ -118,7 +118,11 @@ int main(int argc, char* argv[]) {
if (error == webcc::kNoError) {
std::cout << response->content() << std::endl;
} else {
std::cout << webcc::DescribeError(error) << std::endl;
std::cout << webcc::DescribeError(error);
if (timed_out) {
std::cout << " (timed out)";
}
std::cout << std::endl;
}
};

@ -24,7 +24,7 @@ void AsyncRestClient::Request(const std::string& method,
request->SetContent(content);
}
request->Build();
request->UpdateStartLine();
HttpAsyncClientPtr http_client(new AsyncHttpClient(io_context_));

@ -17,6 +17,8 @@ namespace webcc {
// -----------------------------------------------------------------------------
// Constants
const char* const CRLF = "\r\n";
// Default buffer size for socket reading.
const std::size_t kBufferSize = 1024;

@ -24,8 +24,8 @@ void HttpConnection::Start() {
}
void HttpConnection::Close() {
boost::system::error_code ec;
socket_.close(ec);
boost::system::error_code ignored_ec;
socket_.close(ignored_ec);
}
void HttpConnection::SetResponseContent(std::string&& content,
@ -36,6 +36,7 @@ void HttpConnection::SetResponseContent(std::string&& content,
void HttpConnection::SendResponse(HttpStatus::Enum status) {
response_.set_status(status);
response_.UpdateStartLine();
AsyncWrite();
}
@ -58,6 +59,7 @@ void HttpConnection::ReadHandler(boost::system::error_code ec,
if (!request_parser_.Parse(buffer_.data(), length)) {
// Bad request.
LOG_ERRO("Failed to parse HTTP request.");
response_ = HttpResponse::Fault(HttpStatus::kBadRequest);
AsyncWrite();
return;
@ -69,13 +71,16 @@ void HttpConnection::ReadHandler(boost::system::error_code ec,
return;
}
LOG_VERB("HTTP request:\n%s", request_.Dump(4, "> ").c_str());
// Enqueue this connection.
// Some worker thread will handle it later.
// And DoWrite() will be called in the worker thread.
request_handler_->Enqueue(shared_from_this());
}
void HttpConnection::AsyncWrite() {
LOG_VERB("HTTP response:\n%s", response_.Dump(4, "> ").c_str());
boost::asio::async_write(socket_,
response_.ToBuffers(),
std::bind(&HttpConnection::WriteHandler,
@ -86,8 +91,8 @@ void HttpConnection::AsyncWrite() {
// NOTE:
// This write handler will be called from main thread (the thread calling
// io_context.run), even though DoWrite() is invoked by worker threads. This is
// ensured by Asio.
// io_context.run), even though AsyncWrite() is invoked by worker threads.
// This is ensured by Asio.
void HttpConnection::WriteHandler(boost::system::error_code ec,
std::size_t length) {
if (!ec) {

@ -6,6 +6,17 @@
namespace webcc {
// -----------------------------------------------------------------------------
namespace misc_strings {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
// -----------------------------------------------------------------------------
void HttpMessage::SetHeader(const std::string& name, const std::string& value) {
for (HttpHeader& h : headers_) {
if (h.name == name) {
@ -17,6 +28,30 @@ void HttpMessage::SetHeader(const std::string& name, const std::string& value) {
headers_.push_back({ name, value });
}
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpMessage::ToBuffers() const {
assert(!start_line_.empty());
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(start_line_));
for (const HttpHeader& h : headers_) {
buffers.push_back(boost::asio::buffer(h.name));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.value));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
}
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
if (content_length_ > 0) {
buffers.push_back(boost::asio::buffer(content_));
}
return buffers;
}
void HttpMessage::Dump(std::ostream& os, std::size_t indent,
const std::string& prefix) const {
std::string indent_str;
@ -38,7 +73,7 @@ void HttpMessage::Dump(std::ostream& os, std::size_t indent,
os << content_ << std::endl;
} else {
std::vector<std::string> splitted;
boost::split(splitted, content_, boost::is_any_of("\r\n"));
boost::split(splitted, content_, boost::is_any_of(CRLF));
for (const std::string& line : splitted) {
os << indent_str << line << std::endl;
}

@ -6,6 +6,8 @@
#include <utility> // for move()
#include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/globals.h"
namespace webcc {
@ -18,6 +20,8 @@ struct HttpHeader {
// Base class for HTTP request and response messages.
class HttpMessage {
public:
HttpMessage() : content_length_(kInvalidLength) {}
virtual ~HttpMessage() = default;
const std::string& start_line() const { return start_line_; }
@ -47,6 +51,15 @@ class HttpMessage {
SetContentLength(content_.size());
}
// Set start line according to other informations.
// Must be called before ToBuffers()!
virtual void UpdateStartLine() = 0;
// 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;
// Dump to output stream.
void Dump(std::ostream& os, std::size_t indent = 0,
const std::string& prefix = "") const;
@ -61,10 +74,10 @@ class HttpMessage {
SetHeader(kContentLength, std::to_string(content_length));
}
// Start line with trailing "\r\n".
// Start line with trailing CRLF.
std::string start_line_;
std::size_t content_length_ = kInvalidLength;
std::size_t content_length_;
std::vector<HttpHeader> headers_;

@ -33,7 +33,7 @@ bool HttpParser::Parse(const char* data, std::size_t length) {
std::size_t off = 0;
while (true) {
std::size_t pos = pending_data_.find("\r\n", off);
std::size_t pos = pending_data_.find(CRLF, off);
if (pos == std::string::npos) {
break;
}
@ -48,6 +48,7 @@ bool HttpParser::Parse(const char* data, std::size_t length) {
if (!start_line_parsed_) {
start_line_parsed_ = true;
message_->set_start_line(line + CRLF);
if (!ParseStartLine(line)) {
return false;
}
@ -111,7 +112,7 @@ void HttpParser::ParseContentLength(const std::string& line) {
try {
content_length_ = static_cast<std::size_t>(std::stoul(value));
} catch (const std::exception& e) {
} catch (const std::exception&) {
LOG_ERRO("Invalid content length: %s.", value.c_str());
}

@ -2,17 +2,6 @@
namespace webcc {
// -----------------------------------------------------------------------------
namespace misc_strings {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
// -----------------------------------------------------------------------------
void HttpRequest::SetHost(const std::string& host, const std::string& port) {
host_ = host;
port_ = port;
@ -24,37 +13,12 @@ void HttpRequest::SetHost(const std::string& host, const std::string& port) {
}
}
void HttpRequest::Build() {
if (start_line_.empty()) {
start_line_ = method_;
start_line_ += " ";
start_line_ += url_;
start_line_ += " HTTP/1.1\r\n";
}
}
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpRequest::ToBuffers() const {
assert(!start_line_.empty());
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(start_line_));
for (const HttpHeader& h : headers_) {
buffers.push_back(boost::asio::buffer(h.name));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.value));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
}
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
if (content_length_ > 0) {
buffers.push_back(boost::asio::buffer(content_));
}
return buffers;
void HttpRequest::UpdateStartLine() {
start_line_ = method_;
start_line_ += " ";
start_line_ += url_;
start_line_ += " HTTP/1.1";
start_line_ += CRLF;
}
} // namespace webcc

@ -3,9 +3,6 @@
#include <memory>
#include <string>
#include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/http_message.h"
@ -14,9 +11,6 @@ namespace webcc {
class HttpRequest : public HttpMessage {
public:
HttpRequest() = default;
HttpRequest(const HttpRequest&) = default;
HttpRequest& operator=(const HttpRequest&) = default;
virtual ~HttpRequest() = default;
const std::string& method() const { return method_; }
@ -33,14 +27,8 @@ class HttpRequest : public HttpMessage {
// a numeric number (e.g., "9000") and "80" will be used if it's empty.
void SetHost(const std::string& host, const std::string& port);
// Compose start line, etc.
// Must be called before ToBuffers()!
void Build();
// Convert the response into a vector of buffers. The buffers do not own the
// underlying memory blocks, therefore the request object must remain valid
// and not be changed until the write operation has completed.
std::vector<boost::asio::const_buffer> ToBuffers() const;
// Set start line according to HTTP method, URL, etc.
void UpdateStartLine() override;
private:
// HTTP method.

@ -16,75 +16,47 @@ const std::string INTERNAL_SERVER_ERROR =
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";
boost::asio::const_buffer ToBuffer(int status) {
const std::string& ToString(int status) {
switch (status) {
case HttpStatus::kOK:
return boost::asio::buffer(OK);
return OK;
case HttpStatus::kCreated:
return boost::asio::buffer(CREATED);
return CREATED;
case HttpStatus::kAccepted:
return boost::asio::buffer(ACCEPTED);
return ACCEPTED;
case HttpStatus::kNoContent:
return boost::asio::buffer(NO_CONTENT);
return NO_CONTENT;
case HttpStatus::kNotModified:
return boost::asio::buffer(NOT_MODIFIED);
return NOT_MODIFIED;
case HttpStatus::kBadRequest:
return boost::asio::buffer(BAD_REQUEST);
return BAD_REQUEST;
case HttpStatus::kNotFound:
return boost::asio::buffer(NOT_FOUND);
return NOT_FOUND;
case HttpStatus::InternalServerError:
return boost::asio::buffer(INTERNAL_SERVER_ERROR);
return INTERNAL_SERVER_ERROR;
case HttpStatus::kNotImplemented:
return boost::asio::buffer(NOT_IMPLEMENTED);
return NOT_IMPLEMENTED;
case HttpStatus::kServiceUnavailable:
return boost::asio::buffer(SERVICE_UNAVAILABLE);
return SERVICE_UNAVAILABLE;
default:
return boost::asio::buffer(NOT_IMPLEMENTED);
return NOT_IMPLEMENTED;
}
}
} // namespace status_strings
namespace misc_strings {
const char NAME_VALUE_SEPARATOR[] = { ':', ' ' };
const char CRLF[] = { '\r', '\n' };
} // misc_strings
// ATTENTION: The buffers don't hold the memory!
std::vector<boost::asio::const_buffer> HttpResponse::ToBuffers() const {
std::vector<boost::asio::const_buffer> buffers;
// Status line
buffers.push_back(status_strings::ToBuffer(status_));
// Header fields (optional)
for (const HttpHeader& h : headers_) {
buffers.push_back(boost::asio::buffer(h.name));
buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR));
buffers.push_back(boost::asio::buffer(h.value));
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
}
buffers.push_back(boost::asio::buffer(misc_strings::CRLF));
// Content (optional)
if (!content_.empty()) {
buffers.push_back(boost::asio::buffer(content_));
}
return buffers;
void HttpResponse::UpdateStartLine() {
start_line_ = status_strings::ToString(status_);
}
HttpResponse HttpResponse::Fault(HttpStatus::Enum status) {
@ -92,6 +64,7 @@ HttpResponse HttpResponse::Fault(HttpStatus::Enum status) {
HttpResponse response;
response.set_status(status);
response.UpdateStartLine();
return response;
}

@ -3,9 +3,6 @@
#include <memory>
#include <string>
#include <vector>
#include "boost/asio/buffer.hpp" // for const_buffer
#include "webcc/http_message.h"
@ -13,20 +10,19 @@ namespace webcc {
class HttpResponse : public HttpMessage {
public:
HttpResponse() : status_(HttpStatus::kOK) {
}
HttpResponse() : status_(HttpStatus::kOK) {}
~HttpResponse() override = default;
int status() const { return status_; }
void set_status(int status) { status_ = status; }
// Convert the response into a vector of buffers. The buffers do not own the
// underlying memory blocks, therefore the response object must remain valid
// and not be changed until the write operation has completed.
std::vector<boost::asio::const_buffer> ToBuffers() const;
// Set start line according to status code.
void UpdateStartLine() override;
// Get a fault response when HTTP status is not OK.
// TODO: Avoid copy.
static HttpResponse Fault(HttpStatus::Enum status);
private:

@ -10,8 +10,6 @@ HttpResponseParser::HttpResponseParser(HttpResponse* response)
}
bool HttpResponseParser::ParseStartLine(const std::string& line) {
response_->set_start_line(line + "\r\n");
std::size_t off = 0;
std::size_t pos = line.find(' ');

@ -31,7 +31,7 @@ bool RestClient::Request(const std::string& method,
request.SetContent(content);
}
request.Build();
request.UpdateStartLine();
HttpClient http_client;

@ -44,7 +44,7 @@ Error SoapClient::Call(const std::string& operation,
http_request.SetContent(std::move(http_content));
http_request.SetHost(host_, port_);
http_request.SetHeader(kSoapAction, operation);
http_request.Build();
http_request.UpdateStartLine();
HttpResponse http_response;

Loading…
Cancel
Save