Use Boost.Beast to parse HTTP request (#6294)
This commit is contained in:
committed by
GitHub
parent
00816722dd
commit
e7185b4bcb
+64
-30
@@ -1,12 +1,10 @@
|
||||
#include "server/connection.hpp"
|
||||
#include "server/request_handler.hpp"
|
||||
#include "server/request_parser.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/iostreams/filter/gzip.hpp>
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace osrm
|
||||
@@ -16,12 +14,32 @@ namespace server
|
||||
|
||||
Connection::Connection(boost::asio::io_context &io_context, RequestHandler &handler)
|
||||
: strand(boost::asio::make_strand(io_context)), TCP_socket(strand), timer(strand),
|
||||
request_handler(handler)
|
||||
request_handler(handler), http_request_parser(std::make_optional<RequestParser>())
|
||||
{
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket &Connection::socket() { return TCP_socket; }
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
http::compression_type select_compression(const boost::beast::http::fields &fields)
|
||||
{
|
||||
const auto header_value = fields[boost::beast::http::field::accept_encoding];
|
||||
/* giving gzip precedence over deflate */
|
||||
if (boost::icontains(header_value, "deflate"))
|
||||
{
|
||||
return http::deflate_rfc1951;
|
||||
}
|
||||
if (boost::icontains(header_value, "gzip"))
|
||||
{
|
||||
return http::gzip_rfc1952;
|
||||
}
|
||||
return http::no_compression;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Start the first asynchronous operation for the connection.
|
||||
void Connection::start()
|
||||
{
|
||||
@@ -60,20 +78,45 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t
|
||||
timer.expires_from_now(boost::posix_time::seconds(0));
|
||||
}
|
||||
|
||||
boost::beast::error_code ec;
|
||||
http_request_parser->put(boost::asio::buffer(incoming_data_buffer, bytes_transferred), ec);
|
||||
// no error detected, let's parse the request
|
||||
http::compression_type compression_type(http::no_compression);
|
||||
RequestParser::RequestStatus result;
|
||||
std::tie(result, compression_type) =
|
||||
request_parser.parse(current_request,
|
||||
incoming_data_buffer.data(),
|
||||
incoming_data_buffer.data() + bytes_transferred);
|
||||
|
||||
// the request has been parsed
|
||||
if (result == RequestParser::RequestStatus::valid)
|
||||
if (ec)
|
||||
{
|
||||
if (ec == boost::beast::http::error::need_more)
|
||||
{
|
||||
// we don't have a result yet, so continue reading
|
||||
TCP_socket.async_read_some(boost::asio::buffer(incoming_data_buffer),
|
||||
boost::bind(&Connection::handle_read,
|
||||
this->shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
else
|
||||
{
|
||||
// request is not parseable
|
||||
current_reply = http::reply::stock_reply(http::reply::bad_request);
|
||||
|
||||
boost::asio::async_write(TCP_socket,
|
||||
current_reply.to_buffers(),
|
||||
boost::bind(&Connection::handle_write,
|
||||
this->shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the request has been parsed
|
||||
const auto &message = http_request_parser->get();
|
||||
compression_type = select_compression(message);
|
||||
|
||||
fill_request(message, current_request);
|
||||
|
||||
boost::system::error_code ec;
|
||||
current_request.endpoint = TCP_socket.remote_endpoint(ec).address();
|
||||
|
||||
if (ec)
|
||||
{
|
||||
util::Log(logDEBUG) << "Socket remote endpoint error: " << ec.message();
|
||||
@@ -127,25 +170,6 @@ void Connection::handle_read(const boost::system::error_code &error, std::size_t
|
||||
this->shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
else if (result == RequestParser::RequestStatus::invalid)
|
||||
{ // request is not parseable
|
||||
current_reply = http::reply::stock_reply(http::reply::bad_request);
|
||||
|
||||
boost::asio::async_write(TCP_socket,
|
||||
current_reply.to_buffers(),
|
||||
boost::bind(&Connection::handle_write,
|
||||
this->shared_from_this(),
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't have a result yet, so continue reading
|
||||
TCP_socket.async_read_some(boost::asio::buffer(incoming_data_buffer),
|
||||
boost::bind(&Connection::handle_read,
|
||||
this->shared_from_this(),
|
||||
boost::asio::placeholders::error,
|
||||
boost::asio::placeholders::bytes_transferred));
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle completion of a write operation.
|
||||
@@ -158,7 +182,7 @@ void Connection::handle_write(const boost::system::error_code &error)
|
||||
--processed_requests;
|
||||
current_request = http::request();
|
||||
current_reply = http::reply();
|
||||
request_parser = RequestParser();
|
||||
http_request_parser.emplace();
|
||||
incoming_data_buffer = boost::array<char, 8192>();
|
||||
output_buffer.clear();
|
||||
this->start();
|
||||
@@ -220,5 +244,15 @@ std::vector<char> Connection::compress_buffers(const std::vector<char> &uncompre
|
||||
|
||||
return compressed_data;
|
||||
}
|
||||
|
||||
void Connection::fill_request(const RequestParser::value_type &http_message,
|
||||
http::request ¤t_request)
|
||||
{
|
||||
current_request.uri = http_message.target().to_string();
|
||||
current_request.agent = http_message[boost::beast::http::field::user_agent].to_string();
|
||||
current_request.referrer = http_message[boost::beast::http::field::referer].to_string();
|
||||
current_request.connection = http_message[boost::beast::http::field::connection].to_string();
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace osrm
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
#include "server/request_parser.hpp"
|
||||
|
||||
#include "server/http/compression_type.hpp"
|
||||
#include "server/http/header.hpp"
|
||||
#include "server/http/request.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace osrm
|
||||
{
|
||||
namespace server
|
||||
{
|
||||
|
||||
RequestParser::RequestParser()
|
||||
: state(internal_state::method_start), current_header({"", ""}),
|
||||
selected_compression(http::no_compression)
|
||||
{
|
||||
}
|
||||
|
||||
std::tuple<RequestParser::RequestStatus, http::compression_type>
|
||||
RequestParser::parse(http::request ¤t_request, char *begin, char *end)
|
||||
{
|
||||
while (begin != end)
|
||||
{
|
||||
RequestStatus result = consume(current_request, *begin++);
|
||||
if (result != RequestStatus::indeterminate)
|
||||
{
|
||||
return std::make_tuple(result, selected_compression);
|
||||
}
|
||||
}
|
||||
RequestStatus result = RequestStatus::indeterminate;
|
||||
|
||||
return std::make_tuple(result, selected_compression);
|
||||
}
|
||||
|
||||
RequestParser::RequestStatus RequestParser::consume(http::request ¤t_request,
|
||||
const char input)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case internal_state::method_start:
|
||||
if (!is_char(input) || is_CTL(input) || is_special(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
state = internal_state::method;
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::method:
|
||||
if (input == ' ')
|
||||
{
|
||||
state = internal_state::uri;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (!is_char(input) || is_CTL(input) || is_special(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::uri_start:
|
||||
if (is_CTL(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
state = internal_state::uri;
|
||||
current_request.uri.push_back(input);
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::uri:
|
||||
if (input == ' ')
|
||||
{
|
||||
state = internal_state::http_version_h;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (is_CTL(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
current_request.uri.push_back(input);
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::http_version_h:
|
||||
if (input == 'H')
|
||||
{
|
||||
state = internal_state::http_version_t_1;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_t_1:
|
||||
if (input == 'T')
|
||||
{
|
||||
state = internal_state::http_version_t_2;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_t_2:
|
||||
if (input == 'T')
|
||||
{
|
||||
state = internal_state::http_version_p;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_p:
|
||||
if (input == 'P')
|
||||
{
|
||||
state = internal_state::http_version_slash;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_slash:
|
||||
if (input == '/')
|
||||
{
|
||||
state = internal_state::http_version_major_start;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_major_start:
|
||||
if (is_digit(input))
|
||||
{
|
||||
state = internal_state::http_version_major;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_major:
|
||||
if (input == '.')
|
||||
{
|
||||
state = internal_state::http_version_minor_start;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (is_digit(input))
|
||||
{
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_minor_start:
|
||||
if (is_digit(input))
|
||||
{
|
||||
state = internal_state::http_version_minor;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::http_version_minor:
|
||||
if (input == '\r')
|
||||
{
|
||||
state = internal_state::expecting_newline_1;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (is_digit(input))
|
||||
{
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::expecting_newline_1:
|
||||
if (input == '\n')
|
||||
{
|
||||
state = internal_state::header_line_start;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
case internal_state::header_line_start:
|
||||
if (boost::iequals(current_header.name, "Accept-Encoding"))
|
||||
{
|
||||
/* giving gzip precedence over deflate */
|
||||
if (boost::icontains(current_header.value, "deflate"))
|
||||
{
|
||||
selected_compression = http::deflate_rfc1951;
|
||||
}
|
||||
if (boost::icontains(current_header.value, "gzip"))
|
||||
{
|
||||
selected_compression = http::gzip_rfc1952;
|
||||
}
|
||||
}
|
||||
|
||||
if (boost::iequals(current_header.name, "Referer"))
|
||||
{
|
||||
current_request.referrer = current_header.value;
|
||||
}
|
||||
|
||||
if (boost::iequals(current_header.name, "User-Agent"))
|
||||
{
|
||||
current_request.agent = current_header.value;
|
||||
}
|
||||
|
||||
if (boost::iequals(current_header.name, "Connection"))
|
||||
{
|
||||
current_request.connection = current_header.value;
|
||||
}
|
||||
|
||||
if (input == '\r')
|
||||
{
|
||||
state = internal_state::expecting_newline_3;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (!is_char(input) || is_CTL(input) || is_special(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
state = internal_state::header_name;
|
||||
current_header.clear();
|
||||
current_header.name.push_back(input);
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::header_lws:
|
||||
if (input == '\r')
|
||||
{
|
||||
state = internal_state::expecting_newline_2;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (input == ' ' || input == '\t')
|
||||
{
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (is_CTL(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
state = internal_state::header_value;
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::header_name:
|
||||
if (input == ':')
|
||||
{
|
||||
state = internal_state::header_value;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (!is_char(input) || is_CTL(input) || is_special(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
current_header.name.push_back(input);
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::header_value:
|
||||
if (input == ' ')
|
||||
{
|
||||
state = internal_state::header_value;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (input == '\r')
|
||||
{
|
||||
state = internal_state::expecting_newline_2;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
if (is_CTL(input))
|
||||
{
|
||||
return RequestStatus::invalid;
|
||||
}
|
||||
current_header.value.push_back(input);
|
||||
return RequestStatus::indeterminate;
|
||||
case internal_state::expecting_newline_2:
|
||||
if (input == '\n')
|
||||
{
|
||||
state = internal_state::header_line_start;
|
||||
return RequestStatus::indeterminate;
|
||||
}
|
||||
return RequestStatus::invalid;
|
||||
default: // expecting_newline_3
|
||||
return input == '\n' ? RequestStatus::valid : RequestStatus::invalid;
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestParser::is_char(const int character) const
|
||||
{
|
||||
return character >= 0 && character <= 127;
|
||||
}
|
||||
|
||||
bool RequestParser::is_CTL(const int character) const
|
||||
{
|
||||
return (character >= 0 && character <= 31) || (character == 127);
|
||||
}
|
||||
|
||||
bool RequestParser::is_special(const int character) const
|
||||
{
|
||||
switch (character)
|
||||
{
|
||||
case '(':
|
||||
case ')':
|
||||
case '<':
|
||||
case '>':
|
||||
case '@':
|
||||
case ',':
|
||||
case ';':
|
||||
case ':':
|
||||
case '\\':
|
||||
case '"':
|
||||
case '/':
|
||||
case '[':
|
||||
case ']':
|
||||
case '?':
|
||||
case '=':
|
||||
case '{':
|
||||
case '}':
|
||||
case ' ':
|
||||
case '\t':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RequestParser::is_digit(const int character) const
|
||||
{
|
||||
return character >= '0' && character <= '9';
|
||||
}
|
||||
} // namespace server
|
||||
} // namespace osrm
|
||||
Reference in New Issue
Block a user