Skip to content

Commit 9d160a9

Browse files
Revert back to using custom HTTP parser instead of Boost.Beast (#6407)
1 parent d143de5 commit 9d160a9

File tree

8 files changed

+446
-88
lines changed

8 files changed

+446
-88
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Unreleased
22
- Changes from 5.27.0
33
- Misc:
4+
- FIXED: Revert back to using custom HTTP parser instead of Boost.Beast. [#6407](https://github.com/Project-OSRM/osrm-backend/pull/6407)
45
- FIXED: Fix bug with large HTTP requests leading to Bad Request in osrm-routed. [#6403](https://github.com/Project-OSRM/osrm-backend/pull/6403)
56
- Routing:
67
- CHANGED: Add support for surface=metal,grass_paver,woodchips in bicyle profile. [#6395](https://github.com/Project-OSRM/osrm-backend/pull/6395)

features/step_definitions/requests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ module.exports = function () {
5555
assert.equal(this.processError.process, binary);
5656
assert.equal(parseInt(this.processError.code), parseInt(code));
5757
});
58-
};
58+
};

fuzz/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ if (ENABLE_FUZZING)
3838
"table_parameters"
3939
"tile_parameters"
4040
"trip_parameters"
41-
"url_parser")
41+
"url_parser"
42+
"request_parser")
4243

4344
foreach (target ${ServerTargets})
4445
add_fuzz_target(${target})

fuzz/request_parser.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include "server/request_parser.hpp"
2+
#include "server/http/request.hpp"
3+
4+
#include "util.hpp"
5+
6+
#include <iterator>
7+
#include <string>
8+
9+
using osrm::server::RequestParser;
10+
using osrm::server::http::request;
11+
12+
extern "C" int LLVMFuzzerTestOneInput(const unsigned char *data, unsigned long size)
13+
{
14+
std::string in(reinterpret_cast<const char *>(data), size);
15+
16+
auto first = begin(in);
17+
auto last = end(in);
18+
19+
RequestParser parser;
20+
request req;
21+
22+
// &(*it) is needed to go from iterator to underlying item to pointer to underlying item
23+
parser.parse(req, &(*first), &(*last));
24+
25+
escape(&req);
26+
27+
return 0;
28+
}

include/server/connection.hpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
#include "server/http/compression_type.hpp"
55
#include "server/http/reply.hpp"
66
#include "server/http/request.hpp"
7+
#include "server/request_parser.hpp"
8+
79
#include <boost/array.hpp>
810
#include <boost/asio.hpp>
9-
#include <boost/beast/core.hpp>
10-
#include <boost/beast/http.hpp>
1111
#include <boost/config.hpp>
1212
#include <boost/version.hpp>
1313

1414
#include <memory>
15-
#include <optional>
1615
#include <vector>
1716

1817
// workaround for incomplete std::shared_ptr compatibility in old boost versions
@@ -48,7 +47,6 @@ class Connection : public std::enable_shared_from_this<Connection>
4847
void start();
4948

5049
private:
51-
using RequestParser = boost::beast::http::request_parser<boost::beast::http::string_body>;
5250
void handle_read(const boost::system::error_code &e, std::size_t bytes_transferred);
5351

5452
/// Handle completion of a write operation.
@@ -62,14 +60,12 @@ class Connection : public std::enable_shared_from_this<Connection>
6260
std::vector<char> compress_buffers(const std::vector<char> &uncompressed_data,
6361
const http::compression_type compression_type);
6462

65-
void fill_request(const RequestParser::value_type &httpMessage, http::request &request);
66-
6763
boost::asio::strand<boost::asio::io_context::executor_type> strand;
6864
boost::asio::ip::tcp::socket TCP_socket;
6965
boost::asio::deadline_timer timer;
7066
RequestHandler &request_handler;
71-
std::optional<RequestParser> http_request_parser;
72-
std::vector<char> incoming_data_buffer;
67+
RequestParser request_parser;
68+
boost::array<char, 8192> incoming_data_buffer;
7369
http::request current_request;
7470
http::reply current_reply;
7571
std::vector<char> compressed_output;

include/server/request_parser.hpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#ifndef REQUEST_PARSER_HPP
2+
#define REQUEST_PARSER_HPP
3+
4+
#include "server/http/compression_type.hpp"
5+
#include "server/http/header.hpp"
6+
7+
#include <tuple>
8+
9+
namespace osrm
10+
{
11+
namespace server
12+
{
13+
14+
namespace http
15+
{
16+
struct request;
17+
}
18+
19+
class RequestParser
20+
{
21+
public:
22+
RequestParser();
23+
24+
enum class RequestStatus : char
25+
{
26+
valid,
27+
invalid,
28+
indeterminate
29+
};
30+
31+
std::tuple<RequestStatus, http::compression_type>
32+
parse(http::request &current_request, char *begin, char *end);
33+
34+
private:
35+
RequestStatus consume(http::request &current_request, const char input);
36+
37+
bool is_char(const int character) const;
38+
39+
bool is_CTL(const int character) const;
40+
41+
bool is_special(const int character) const;
42+
43+
bool is_digit(const int character) const;
44+
45+
enum class internal_state : unsigned char
46+
{
47+
method_start,
48+
method,
49+
uri_start,
50+
uri,
51+
http_version_h,
52+
http_version_t_1,
53+
http_version_t_2,
54+
http_version_p,
55+
http_version_slash,
56+
http_version_major_start,
57+
http_version_major,
58+
http_version_minor_start,
59+
http_version_minor,
60+
expecting_newline_1,
61+
header_line_start,
62+
header_lws,
63+
header_name,
64+
header_value,
65+
expecting_newline_2,
66+
expecting_newline_3
67+
} state;
68+
69+
http::header current_header;
70+
http::compression_type selected_compression;
71+
};
72+
} // namespace server
73+
} // namespace osrm
74+
75+
#endif // REQUEST_PARSER_HPP

src/server/connection.cpp

Lines changed: 33 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,27 @@
11
#include "server/connection.hpp"
22
#include "server/request_handler.hpp"
3+
#include "server/request_parser.hpp"
34

45
#include <boost/algorithm/string/predicate.hpp>
56
#include <boost/bind.hpp>
67
#include <boost/iostreams/filter/gzip.hpp>
78
#include <boost/iostreams/filtering_stream.hpp>
9+
810
#include <vector>
911

1012
namespace osrm
1113
{
1214
namespace server
1315
{
1416

15-
namespace
16-
{
17-
const size_t CHUNK_SIZE = 8192;
18-
19-
} // namespace
2017
Connection::Connection(boost::asio::io_context &io_context, RequestHandler &handler)
2118
: strand(boost::asio::make_strand(io_context)), TCP_socket(strand), timer(strand),
22-
request_handler(handler), http_request_parser(std::make_optional<RequestParser>())
19+
request_handler(handler)
2320
{
24-
http_request_parser->header_limit(std::numeric_limits<std::uint32_t>::max());
2521
}
2622

2723
boost::asio::ip::tcp::socket &Connection::socket() { return TCP_socket; }
2824

29-
namespace
30-
{
31-
32-
http::compression_type select_compression(const boost::beast::http::fields &fields)
33-
{
34-
const auto header_value = fields[boost::beast::http::field::accept_encoding];
35-
/* giving gzip precedence over deflate */
36-
if (boost::icontains(header_value, "gzip"))
37-
{
38-
return http::gzip_rfc1952;
39-
}
40-
if (boost::icontains(header_value, "deflate"))
41-
{
42-
return http::deflate_rfc1951;
43-
}
44-
return http::no_compression;
45-
}
46-
47-
} // namespace
48-
4925
/// Start the first asynchronous operation for the connection.
5026
void Connection::start()
5127
{
@@ -65,8 +41,7 @@ void Connection::start()
6541
}
6642
}
6743

68-
void Connection::handle_read(const boost::system::error_code &error,
69-
std::size_t /*bytes_transferred*/)
44+
void Connection::handle_read(const boost::system::error_code &error, std::size_t bytes_transferred)
7045
{
7146
if (error)
7247
{
@@ -85,48 +60,20 @@ void Connection::handle_read(const boost::system::error_code &error,
8560
timer.expires_from_now(boost::posix_time::seconds(0));
8661
}
8762

88-
boost::beast::error_code ec;
89-
http_request_parser->put(boost::asio::buffer(incoming_data_buffer), ec);
9063
// no error detected, let's parse the request
9164
http::compression_type compression_type(http::no_compression);
92-
93-
if (ec)
94-
{
95-
if (ec == boost::beast::http::error::need_more)
96-
{
97-
const auto current_size = incoming_data_buffer.size();
98-
incoming_data_buffer.resize(incoming_data_buffer.size() + CHUNK_SIZE, 0);
99-
// we don't have a result yet, so continue reading
100-
TCP_socket.async_read_some(
101-
boost::asio::buffer(incoming_data_buffer.data() + current_size, CHUNK_SIZE),
102-
boost::bind(&Connection::handle_read,
103-
this->shared_from_this(),
104-
boost::asio::placeholders::error,
105-
boost::asio::placeholders::bytes_transferred));
106-
}
107-
else
108-
{
109-
// request is not parseable
110-
current_reply = http::reply::stock_reply(http::reply::bad_request);
111-
112-
boost::asio::async_write(TCP_socket,
113-
current_reply.to_buffers(),
114-
boost::bind(&Connection::handle_write,
115-
this->shared_from_this(),
116-
boost::asio::placeholders::error));
117-
}
118-
}
119-
else
65+
RequestParser::RequestStatus result;
66+
std::tie(result, compression_type) =
67+
request_parser.parse(current_request,
68+
incoming_data_buffer.data(),
69+
incoming_data_buffer.data() + bytes_transferred);
70+
71+
// the request has been parsed
72+
if (result == RequestParser::RequestStatus::valid)
12073
{
121-
// the request has been parsed
122-
const auto &message = http_request_parser->get();
123-
compression_type = select_compression(message);
124-
125-
fill_request(message, current_request);
12674

12775
boost::system::error_code ec;
12876
current_request.endpoint = TCP_socket.remote_endpoint(ec).address();
129-
13077
if (ec)
13178
{
13279
util::Log(logDEBUG) << "Socket remote endpoint error: " << ec.message();
@@ -180,6 +127,25 @@ void Connection::handle_read(const boost::system::error_code &error,
180127
this->shared_from_this(),
181128
boost::asio::placeholders::error));
182129
}
130+
else if (result == RequestParser::RequestStatus::invalid)
131+
{ // request is not parseable
132+
current_reply = http::reply::stock_reply(http::reply::bad_request);
133+
134+
boost::asio::async_write(TCP_socket,
135+
current_reply.to_buffers(),
136+
boost::bind(&Connection::handle_write,
137+
this->shared_from_this(),
138+
boost::asio::placeholders::error));
139+
}
140+
else
141+
{
142+
// we don't have a result yet, so continue reading
143+
TCP_socket.async_read_some(boost::asio::buffer(incoming_data_buffer),
144+
boost::bind(&Connection::handle_read,
145+
this->shared_from_this(),
146+
boost::asio::placeholders::error,
147+
boost::asio::placeholders::bytes_transferred));
148+
}
183149
}
184150

185151
/// Handle completion of a write operation.
@@ -192,9 +158,8 @@ void Connection::handle_write(const boost::system::error_code &error)
192158
--processed_requests;
193159
current_request = http::request();
194160
current_reply = http::reply();
195-
http_request_parser.emplace();
196-
http_request_parser->header_limit(std::numeric_limits<std::uint32_t>::max());
197-
incoming_data_buffer.resize(CHUNK_SIZE, 0);
161+
request_parser = RequestParser();
162+
incoming_data_buffer = boost::array<char, 8192>();
198163
output_buffer.clear();
199164
this->start();
200165
}
@@ -255,15 +220,5 @@ std::vector<char> Connection::compress_buffers(const std::vector<char> &uncompre
255220

256221
return compressed_data;
257222
}
258-
259-
void Connection::fill_request(const RequestParser::value_type &http_message,
260-
http::request &current_request)
261-
{
262-
current_request.uri = http_message.target().to_string();
263-
current_request.agent = http_message[boost::beast::http::field::user_agent].to_string();
264-
current_request.referrer = http_message[boost::beast::http::field::referer].to_string();
265-
current_request.connection = http_message[boost::beast::http::field::connection].to_string();
266-
}
267-
268223
} // namespace server
269224
} // namespace osrm

0 commit comments

Comments
 (0)