Skip to content

Commit e248005

Browse files
TheMarexCopilot
andauthored
Make maximum header size configurable (#7336)
* Make maximum header size configurable * Add changelog entry * Update src/tools/routed.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/server/connection.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update include/server/connection.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update heuristic calculation for feedback * Add unit tests for header size calculation --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 9d837fa commit e248005

File tree

7 files changed

+214
-16
lines changed

7 files changed

+214
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- FIXED: Work around compilation error due to a false-positive of array-bounds check in sol2 [#7317](https://github.com/Project-OSRM/osrm-backend/pull/7317)
1515
- FIXED: Fix compilation with gcc >14 in release with LTO. [#7268](https://github.com/Project-OSRM/osrm-backend/issues/7268)
1616
- Misc:
17+
- ADDED: `--max-header-size` to override the (automatically) configured maximum header size for osrm-routed [#7336](https://github.com/Project-OSRM/osrm-backend/pull/7336)
1718
- CHANGED: Use boost::beast instead of own HTTP code for osrm-routed [#7328](https://github.com/Project-OSRM/osrm-backend/pull/7328)
1819
- ADDED: `SHM_LOCK_DIR` environment variable for shared memory lock file directory [#7312](https://github.com/Project-OSRM/osrm-backend/pull/7312)
1920
- FIXED: Fix JSON rendering of large OSM IDs (avoids scientific notation) and handle NaN/Infinity gracefully [#7016](https://github.com/Project-OSRM/osrm-backend/issues/7016)

include/server/connection.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Connection : public std::enable_shared_from_this<Connection>
2626
public:
2727
explicit Connection(boost::asio::ip::tcp::socket socket,
2828
RequestHandler &handler,
29+
unsigned max_header_size,
2930
short keepalive_timeout);
3031

3132
Connection(const Connection &) = delete;
@@ -58,6 +59,7 @@ class Connection : public std::enable_shared_from_this<Connection>
5859

5960
RequestHandler &request_handler_;
6061

62+
unsigned max_header_size_;
6163
short keepalive_timeout_;
6264
short processed_requests_ = 0;
6365
};

include/server/header_size.hpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
3+
Copyright (c) 2026, Project OSRM contributors
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without modification,
7+
are permitted provided that the following conditions are met:
8+
9+
Redistributions of source code must retain the above copyright notice, this list
10+
of conditions and the following disclaimer.
11+
Redistributions in binary form must reproduce the above copyright notice, this
12+
list of conditions and the following disclaimer in the documentation and/or
13+
other materials provided with the distribution.
14+
15+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
19+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
26+
*/
27+
28+
#ifndef SERVER_HEADER_SIZE_HPP
29+
#define SERVER_HEADER_SIZE_HPP
30+
31+
#include "engine/engine_config.hpp"
32+
33+
#include <algorithm>
34+
35+
namespace osrm::server
36+
{
37+
38+
inline unsigned deriveMaxHeaderSize(const engine::EngineConfig &config)
39+
{
40+
constexpr unsigned MIN_HEADER_SIZE = 8 * 1024;
41+
42+
int max_coordinates = 0;
43+
max_coordinates = std::max(max_coordinates, config.max_locations_trip);
44+
max_coordinates = std::max(max_coordinates, config.max_locations_viaroute);
45+
max_coordinates = std::max(max_coordinates, config.max_locations_distance_table);
46+
max_coordinates = std::max(max_coordinates, config.max_locations_map_matching);
47+
48+
// We estimate the maximum GET line length in bytes:
49+
// coordinates | (2-3 (major) + 1 (dot) + 6 (decimals)) * 2 + 1 semi-colon = 21 chars
50+
// sources/destinations | 4 (digits) + 1 semi-colon = 5 chars
51+
// approaches | 12 (chars) + 1 semi-colon = 13 chars
52+
// radius | 3 (digits) + 1 semi-colon = 4 chars
53+
// Total of 47 chars and we add a generous 1024 chars for the request line
54+
return std::max(MIN_HEADER_SIZE, 48 * static_cast<unsigned>(max_coordinates) + 1024u);
55+
}
56+
57+
} // namespace osrm::server
58+
59+
#endif // SERVER_HEADER_SIZE_HPP

include/server/server.hpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,24 @@ class Server : public std::enable_shared_from_this<Server>
3333
static std::shared_ptr<Server> CreateServer(std::string &ip_address,
3434
int ip_port,
3535
unsigned requested_num_threads,
36-
short keepalive_timeout)
36+
short keepalive_timeout,
37+
unsigned max_header_size)
3738
{
3839
util::Log() << "HTTP/1.1 server using Boost.Beast, compression by zlib " << zlibVersion();
3940
const unsigned hardware_threads = std::max(1u, std::thread::hardware_concurrency());
4041
const unsigned real_num_threads = std::min(hardware_threads, requested_num_threads);
41-
return std::make_shared<Server>(ip_address, ip_port, real_num_threads, keepalive_timeout);
42+
return std::make_shared<Server>(
43+
ip_address, ip_port, real_num_threads, keepalive_timeout, max_header_size);
4244
}
4345

4446
explicit Server(const std::string &address,
4547
const int port,
4648
const unsigned thread_pool_size,
47-
const short keepalive_timeout)
49+
const short keepalive_timeout,
50+
const unsigned max_header_size)
4851
: thread_pool_size(thread_pool_size), keepalive_timeout(keepalive_timeout),
49-
io_context(thread_pool_size), acceptor(boost::asio::make_strand(io_context))
52+
max_header_size(max_header_size), io_context(thread_pool_size),
53+
acceptor(boost::asio::make_strand(io_context))
5054
{
5155
boost::beast::error_code ec;
5256

@@ -175,8 +179,8 @@ class Server : public std::enable_shared_from_this<Server>
175179
if (!ec)
176180
{
177181
// Create the connection and start it
178-
auto connection =
179-
std::make_shared<Connection>(std::move(socket), request_handler, keepalive_timeout);
182+
auto connection = std::make_shared<Connection>(
183+
std::move(socket), request_handler, max_header_size, keepalive_timeout);
180184

181185
connection->start();
182186
}
@@ -195,6 +199,7 @@ class Server : public std::enable_shared_from_this<Server>
195199
RequestHandler request_handler;
196200
unsigned thread_pool_size;
197201
short keepalive_timeout;
202+
unsigned max_header_size;
198203
boost::asio::io_context io_context;
199204
boost::asio::ip::tcp::acceptor acceptor;
200205
};

src/server/connection.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@
1313
namespace osrm::server
1414
{
1515

16-
// Max bytes for request line + headers
17-
static constexpr std::uint32_t REQUEST_HEADER_LIMIT = 64 * 1024;
1816
static constexpr short KEEPALIVE_MAX_REQUESTS = 512;
1917

2018
namespace bhttp = boost::beast::http;
2119
using tcp = boost::asio::ip::tcp;
2220

23-
Connection::Connection(tcp::socket socket, RequestHandler &handler, short keepalive_timeout)
24-
: stream_(std::move(socket)), request_handler_(handler), keepalive_timeout_(keepalive_timeout)
21+
Connection::Connection(tcp::socket socket,
22+
RequestHandler &handler,
23+
unsigned max_header_size,
24+
short keepalive_timeout)
25+
: stream_(std::move(socket)), request_handler_(handler), max_header_size_(max_header_size),
26+
keepalive_timeout_(keepalive_timeout)
2527
{
2628
stream_.expires_after(std::chrono::seconds(keepalive_timeout_));
2729
}
@@ -38,7 +40,9 @@ void Connection::handle_read()
3840

3941
request_ = {};
4042
parser_.emplace();
41-
parser_->header_limit(REQUEST_HEADER_LIMIT);
43+
// Note: The name is a bit of a misnomer, this includes the size of the GET request line.
44+
// Some people parse huge GET requests for table requests, we need to make this configurable.
45+
parser_->header_limit(max_header_size_);
4246

4347
stream_.expires_after(std::chrono::seconds(keepalive_timeout_));
4448

src/tools/routed.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "server/header_size.hpp"
12
#include "server/server.hpp"
23
#include "util/exception_utils.hpp"
34
#include "util/log.hpp"
@@ -108,7 +109,8 @@ inline unsigned generateServerProgramOptions(const int argc,
108109
bool &trial,
109110
EngineConfig &config,
110111
int &requested_thread_num,
111-
short &keepalive_timeout)
112+
short &keepalive_timeout,
113+
unsigned &max_header_size)
112114
{
113115
using boost::program_options::value;
114116
using std::filesystem::path;
@@ -187,7 +189,11 @@ inline unsigned generateServerProgramOptions(const int argc,
187189
"Max. radius size supported in map matching query. Default: unlimited.") //
188190
("default-radius",
189191
value<double>(&config.default_radius)->default_value(-1.0),
190-
"Default radius size for queries. Default: unlimited.");
192+
"Default radius size for queries. Default: unlimited.")(
193+
"max-header-size",
194+
value<unsigned>(&max_header_size)->default_value(0),
195+
"Maximum size of the HTTP headers (including GET request line). Default: auto (based "
196+
"on maximum coordinates).");
191197

192198
// hidden options, will be allowed on command line, but will not be shown to the user
193199
boost::program_options::options_description hidden_options("Hidden options");
@@ -237,6 +243,11 @@ inline unsigned generateServerProgramOptions(const int argc,
237243

238244
boost::program_options::notify(option_variables);
239245

246+
if (max_header_size == 0)
247+
{
248+
max_header_size = server::deriveMaxHeaderSize(config);
249+
}
250+
240251
if (!config.use_shared_memory && option_variables.count("base"))
241252
{
242253
return INIT_OK_START_ENGINE;
@@ -271,6 +282,8 @@ try
271282

272283
int requested_thread_num = 1;
273284
short keepalive_timeout = 5;
285+
// Size of 0 means: Determine automatically based on coordinate limits.
286+
unsigned max_header_size = 0;
274287
const unsigned init_result = generateServerProgramOptions(argc,
275288
argv,
276289
base_path,
@@ -279,7 +292,8 @@ try
279292
trial_run,
280293
config,
281294
requested_thread_num,
282-
keepalive_timeout);
295+
keepalive_timeout,
296+
max_header_size);
283297
if (init_result == INIT_OK_DO_NOT_START_ENGINE)
284298
{
285299
return EXIT_SUCCESS;
@@ -320,6 +334,7 @@ try
320334
util::Log() << "IP address: " << ip_address;
321335
util::Log() << "IP port: " << ip_port;
322336
util::Log() << "Keepalive timeout: " << keepalive_timeout;
337+
util::Log() << "Maximum header size: " << max_header_size;
323338

324339
#ifndef _WIN32
325340
int sig = 0;
@@ -332,8 +347,8 @@ try
332347
#endif
333348

334349
auto service_handler = std::make_unique<server::ServiceHandler>(config);
335-
auto routing_server =
336-
server::Server::CreateServer(ip_address, ip_port, requested_thread_num, keepalive_timeout);
350+
auto routing_server = server::Server::CreateServer(
351+
ip_address, ip_port, requested_thread_num, keepalive_timeout, max_header_size);
337352

338353
routing_server->RegisterServiceHandler(std::move(service_handler));
339354

unit_tests/server/header_size.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#include "server/header_size.hpp"
2+
3+
#include <boost/test/test_tools.hpp>
4+
#include <boost/test/unit_test.hpp>
5+
6+
#include <string>
7+
8+
std::string generateWorstCaseRequest(unsigned num_coordinates)
9+
{
10+
if (num_coordinates == 0)
11+
return "/table/v1/driving/";
12+
13+
std::string request;
14+
request.reserve(50 * num_coordinates);
15+
request += "/table/v1/driving/";
16+
17+
// 1. Coordinates: (2-3 (major) + 1 (dot) + 6 (decimals)) * 2 = 20 chars per coordinate
18+
for (unsigned i = 0; i < num_coordinates; ++i)
19+
{
20+
request += "179.123456,89.123456";
21+
if (i < num_coordinates - 1)
22+
request += ";";
23+
}
24+
25+
request += "?";
26+
27+
// 2. Radiuses: 3 digits
28+
request += "radiuses=";
29+
for (unsigned i = 0; i < num_coordinates; ++i)
30+
{
31+
request += "999";
32+
if (i < num_coordinates - 1)
33+
request += ";";
34+
}
35+
request += "&";
36+
37+
// 3. Sources and Destinations: 4 digits
38+
std::string sources_str = "sources=";
39+
std::string destinations_str = "destinations=";
40+
std::string index_str = "";
41+
for (unsigned i = 0; i < num_coordinates; ++i)
42+
{
43+
index_str += "9999";
44+
if (i < num_coordinates - 1)
45+
{
46+
index_str += ";";
47+
}
48+
}
49+
request += sources_str + index_str + "&" + destinations_str + index_str + "&";
50+
51+
// 4. Approaches: "unrestricted" has 12 chars
52+
request += "approaches=";
53+
for (unsigned i = 0; i < num_coordinates; ++i)
54+
{
55+
request += "unrestricted";
56+
if (i < num_coordinates - 1)
57+
request += ";";
58+
}
59+
60+
return request;
61+
}
62+
63+
BOOST_AUTO_TEST_SUITE(header_size)
64+
65+
using namespace osrm;
66+
using namespace osrm::server;
67+
using namespace osrm::engine;
68+
69+
BOOST_AUTO_TEST_CASE(minimum_header_size)
70+
{
71+
EngineConfig config;
72+
config.max_locations_trip = 0;
73+
config.max_locations_viaroute = 0;
74+
config.max_locations_distance_table = 0;
75+
config.max_locations_map_matching = 0;
76+
77+
unsigned result = deriveMaxHeaderSize(config);
78+
BOOST_CHECK_EQUAL(result, 8 * 1024);
79+
}
80+
81+
BOOST_AUTO_TEST_CASE(negative_values_treated_as_zero)
82+
{
83+
// Test with default EngineConfig values (all -1 for default)
84+
EngineConfig config;
85+
86+
unsigned result = deriveMaxHeaderSize(config);
87+
BOOST_CHECK_EQUAL(result, 8 * 1024);
88+
}
89+
90+
BOOST_AUTO_TEST_CASE(calculation_formula_verification)
91+
{
92+
auto run_test_for_n_coordinates = [](unsigned num_coords)
93+
{
94+
EngineConfig config;
95+
config.max_locations_trip = num_coords;
96+
config.max_locations_viaroute = num_coords;
97+
config.max_locations_distance_table = num_coords;
98+
config.max_locations_map_matching = num_coords;
99+
100+
unsigned calculated_size = deriveMaxHeaderSize(config);
101+
std::string worst_case_request = generateWorstCaseRequest(num_coords);
102+
103+
BOOST_CHECK_GT(calculated_size, worst_case_request.length());
104+
};
105+
106+
run_test_for_n_coordinates(10);
107+
run_test_for_n_coordinates(100);
108+
run_test_for_n_coordinates(1000);
109+
run_test_for_n_coordinates(10000);
110+
}
111+
112+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)