Various improvements to http_server_fast.cpp:

fix #578

- Receive request in a single read
- Use fields_alloc for response
- Fix command line usage information
- Add command line option to spin the io_service
This commit is contained in:
chriskohlhoff
2017-07-03 23:03:01 +10:00
committed by Vinnie Falco
parent a281ca5384
commit 7e61a48607
2 changed files with 49 additions and 53 deletions

View File

@@ -8,6 +8,7 @@ HTTP:
* Refactor file_body for best practices
* Add http-server-threaded example
* Documentation tidying
* Various improvements to http_server_fast.cpp
WebSocket:

View File

@@ -16,6 +16,7 @@
#include <boost/filesystem.hpp>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <list>
@@ -34,11 +35,7 @@ public:
http_worker(tcp::acceptor& acceptor, const std::string& doc_root) :
acceptor_(acceptor),
doc_root_(doc_root),
socket_(acceptor.get_io_service()),
alloc_(8192),
request_deadline_(acceptor.get_io_service(),
std::chrono::steady_clock::time_point::max())
doc_root_(doc_root)
{
}
@@ -49,6 +46,7 @@ public:
}
private:
using alloc_t = fields_alloc<char>;
using request_body_t = http::basic_dynamic_body<beast::static_buffer_n<1024 * 1024>>;
// The acceptor used to listen for incoming connections.
@@ -58,24 +56,26 @@ private:
std::string doc_root_;
// The socket for the currently connected client.
tcp::socket socket_;
tcp::socket socket_{acceptor_.get_io_service()};
// The buffer for performing reads
beast::static_buffer_n<8192> buffer_;
// The allocator used for the fields in the request and reply.
alloc_t alloc_{8192};
// The parser for reading the requests
using alloc_type = fields_alloc<char>;
alloc_type alloc_;
boost::optional<http::request_parser<request_body_t, alloc_type>> parser_;
boost::optional<http::request_parser<request_body_t, alloc_t>> parser_;
// The timer putting a time limit on requests.
boost::asio::basic_waitable_timer<std::chrono::steady_clock> request_deadline_;
boost::asio::basic_waitable_timer<std::chrono::steady_clock> request_deadline_{
acceptor_.get_io_service(), (std::chrono::steady_clock::time_point::max)()};
// The response message.
http::response<http::string_body> response_;
boost::optional<http::response<http::string_body, http::basic_fields<alloc_t>>> response_;
// The response serializer.
boost::optional<http::response_serializer<http::string_body>> serializer_;
boost::optional<http::response_serializer<http::string_body, http::basic_fields<alloc_t>>> serializer_;
void accept()
{
@@ -98,12 +98,12 @@ private:
request_deadline_.expires_from_now(
std::chrono::seconds(60));
read_header();
read_request();
}
});
}
void read_header()
void read_request()
{
// On each read the parser needs to be destroyed and
// recreated. We store it in a boost::optional to
@@ -121,21 +121,6 @@ private:
std::make_tuple(),
std::make_tuple(alloc_));
http::async_read_header(
socket_,
buffer_,
*parser_,
[this](beast::error_code ec)
{
if (ec)
accept();
else
read_body();
});
}
void read_body()
{
http::async_read(
socket_,
buffer_,
@@ -149,29 +134,32 @@ private:
});
}
void process_request(http::request<request_body_t, http::basic_fields<alloc_type>> const& req)
void process_request(http::request<request_body_t, http::basic_fields<alloc_t>> const& req)
{
response_.version = 11;
response_.set(http::field::connection, "close");
response_.emplace(
std::piecewise_construct,
std::make_tuple(),
std::make_tuple(alloc_));
response_->set(http::field::connection, "close");
switch (req.method())
{
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
response_->result(http::status::ok);
response_->set(http::field::server, "Beast");
load_file(req.target());
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain");
response_.body = "Invalid request-method '" + req.method_string().to_string() + "'";
response_->result(http::status::bad_request);
response_->set(http::field::content_type, "text/plain");
response_->body = "Invalid request-method '" + req.method_string().to_string() + "'";
break;
}
response_.prepare_payload();
write_response();
}
@@ -180,9 +168,9 @@ private:
// Request path must be absolute and not contain "..".
if (target.empty() || target[0] != '/' || target.find("..") != std::string::npos)
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
response_.body = "File not found\r\n";
response_->result(http::status::not_found);
response_->set(http::field::content_type, "text/plain");
response_->body = "File not found\r\n";
return;
}
@@ -193,22 +181,23 @@ private:
std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
if (!is)
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
response_.body = "File not found\r\n";
response_->result(http::status::not_found);
response_->set(http::field::content_type, "text/plain");
response_->body = "File not found\r\n";
return;
}
// Fill out the reply to be sent to the client.
response_.set(http::field::content_type, mime_type(target.to_string()));
response_.body.clear();
response_->set(http::field::content_type, mime_type(target.to_string()));
response_->body.clear();
for (char buf[2048]; is.read(buf, sizeof(buf)).gcount() > 0;)
response_.body.append(buf, static_cast<std::size_t>(is.gcount()));
response_->body.append(buf, static_cast<std::size_t>(is.gcount()));
}
void write_response()
{
serializer_.emplace(response_);
response_->prepare_payload();
serializer_.emplace(*response_);
http::async_write(
socket_,
@@ -216,6 +205,8 @@ private:
[this](beast::error_code ec)
{
socket_.shutdown(tcp::socket::shutdown_send, ec);
serializer_.reset();
response_.reset();
accept();
});
}
@@ -247,13 +238,13 @@ int main(int argc, char* argv[])
try
{
// Check command line arguments.
if (argc != 5)
if (argc != 6)
{
std::cerr << "Usage: http_server <address> <port> <doc_root> <num_workers>\n";
std::cerr << "Usage: http_server_fast <address> <port> <doc_root> <num_workers> {spin|block}\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80 . 100\n";
std::cerr << " http_server_fast 0.0.0.0 80 . 100 block\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80 . 100\n";
std::cerr << " http_server_fast 0::0 80 . 100 block\n";
return EXIT_FAILURE;
}
@@ -261,6 +252,7 @@ int main(int argc, char* argv[])
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
std::string doc_root = argv[3];
int num_workers = std::atoi(argv[4]);
bool spin = (std::strcmp(argv[5], "spin") == 0);
boost::asio::io_service ios{1};
tcp::acceptor acceptor{ios, {address, port}};
@@ -272,7 +264,10 @@ int main(int argc, char* argv[])
workers.back().start();
}
ios.run();
if (spin)
for (;;) ios.poll();
else
ios.run();
}
catch (const std::exception& e)
{