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 * Refactor file_body for best practices
* Add http-server-threaded example * Add http-server-threaded example
* Documentation tidying * Documentation tidying
* Various improvements to http_server_fast.cpp
WebSocket: WebSocket:

View File

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