summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRutger Broekhoff2026-09-10 10:59:59 +0200
committerRutger Broekhoff2026-09-10 10:59:59 +0200
commita4408b5ac4ec10421bf9a7d5bf277946ec9f786e (patch)
tree0a5214580a1557b3eb85f0a3ab0e15c5c9ec9f50
parent4031e77edd2abd694114be698312d086bce6b425 (diff)
downloadroutemon-main.tar.gz
routemon-main.zip
Timing and slow locale negotation fixesHEADmain
-rw-r--r--server/src/http_server.cppm20
-rw-r--r--server/src/locale.cpp121
-rw-r--r--server/src/locale.cppm65
-rw-r--r--server/src/main.cpp3
-rw-r--r--server/src/srv.cpp88
5 files changed, 178 insertions, 119 deletions
diff --git a/server/src/http_server.cppm b/server/src/http_server.cppm
index 12c83db..2bf3c63 100644
--- a/server/src/http_server.cppm
+++ b/server/src/http_server.cppm
@@ -200,7 +200,7 @@ auto cors_middleware(std::vector<std::string> const& allow_origins)
200} 200}
201 201
202template <class Ctx> 202template <class Ctx>
203auto trace_id_middleware( 203auto expose_trace_id_middleware(
204 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr, 204 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr,
205 next_handler_t<Ctx> next) -> net::awaitable<presponse> 205 next_handler_t<Ctx> next) -> net::awaitable<presponse>
206{ 206{
@@ -214,6 +214,24 @@ auto trace_id_middleware(
214 co_return std::move(prersp); 214 co_return std::move(prersp);
215} 215}
216 216
217template <class Ctx>
218auto expose_content_language_middleware(
219 Ctx ctx, bhttp::request_header<bhttp::fields>& req_hdr,
220 next_handler_t<Ctx> next) -> net::awaitable<presponse>
221{
222 std::ignore = req_hdr;
223 auto prersp = co_await next(std::move(ctx));
224 if (prersp.header().find(bhttp::field::content_language)
225 == prersp.header().end()
226 && std::has_facet<locale::bcp47_tag>(ctx.locale))
227 {
228 prersp.header().set(
229 bhttp::field::content_language,
230 std::use_facet<locale::bcp47_tag>(ctx.locale).tag());
231 }
232 co_return std::move(prersp);
233}
234
217struct keep_alive 235struct keep_alive
218{ 236{
219 bool value; 237 bool value;
diff --git a/server/src/locale.cpp b/server/src/locale.cpp
index 652349c..92bc33b 100644
--- a/server/src/locale.cpp
+++ b/server/src/locale.cpp
@@ -69,6 +69,55 @@ public:
69 ~icu_priority_locale_vec_iterator() override = default; 69 ~icu_priority_locale_vec_iterator() override = default;
70}; 70};
71 71
72auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string>
73{
74 auto const& locale_info = std::use_facet<blocale::info>(locale);
75 auto ec = UErrorCode::U_ZERO_ERROR;
76 auto bcp47_lang_tag =
77 icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec);
78 if (U_FAILURE(ec))
79 return std::nullopt;
80 return bcp47_lang_tag;
81}
82
83auto bcp47_tag::id = std::locale::id{};
84
85bcp47_tag::bcp47_tag(std::locale locale)
86{
87 auto mtag = to_bcp47_lang_tag(locale);
88 if (!mtag)
89 throw std::invalid_argument{
90 "locale name cannot be expressed as a BCP 47 language tag"
91 };
92 tag_ = *mtag;
93}
94
95auto bcp47_tag::tag() const -> std::string_view { return tag_; }
96
97auto selector::make_matcher(std::vector<std::locale> const& supported_locales)
98 -> icu::LocaleMatcher
99{
100 auto builder = icu::LocaleMatcher::Builder{};
101 for (auto const& supported_locale : supported_locales)
102 {
103 auto const& supported_locale_info =
104 std::use_facet<blocale::info>(supported_locale);
105 auto supported_icu_locale =
106 icu::Locale{supported_locale_info.name().c_str()};
107 if (supported_icu_locale.isBogus())
108 throw std::runtime_error{
109 "supported locale gives rise to bogus ICU locale"
110 };
111 builder.addSupportedLocale(supported_icu_locale);
112 }
113 builder.setNoDefaultLocale();
114 auto ec = UErrorCode::U_ZERO_ERROR;
115 auto matcher = builder.build(ec);
116 if (U_FAILURE(ec))
117 throw std::runtime_error{"failed to build icu::LocaleMatcher"};
118 return matcher;
119}
120
72// Trimming optional whitespace as defined in RFC 9110, § 12.4.2. 121// Trimming optional whitespace as defined in RFC 9110, § 12.4.2.
73auto selector::ltrim_ows(std::string_view s) -> std::string_view 122auto selector::ltrim_ows(std::string_view s) -> std::string_view
74{ 123{
@@ -87,59 +136,6 @@ auto selector::trim_ows(std::string_view s) -> std::string_view
87 return rtrim_ows(ltrim_ows(s)); 136 return rtrim_ows(ltrim_ows(s));
88} 137}
89 138
90auto selector::from_icu_locale(icu::Locale const& l) const -> std::locale
91{
92 auto posix_name = std::string{l.getLanguage()};
93 if (l.getScript() && std::strlen(l.getScript()) > 0)
94 {
95 posix_name += "_";
96 posix_name += l.getScript();
97 }
98 if (l.getCountry() && std::strlen(l.getCountry()) > 0)
99 {
100 posix_name += "_";
101 posix_name += l.getCountry();
102 }
103 posix_name += ".UTF-8";
104 auto added_at = false;
105 if (l.getVariant() && std::strlen(l.getVariant()) > 0)
106 {
107 added_at = true;
108 posix_name += "@";
109 posix_name += l.getVariant();
110 }
111 auto ec = UErrorCode::U_ZERO_ERROR;
112 auto* keywords = l.createKeywords(ec);
113 if (U_FAILURE(ec))
114 throw std::runtime_error{"failed to create keywords"};
115 if (keywords)
116 {
117 std::int32_t kw_len = 0;
118 char const* kw = nullptr;
119 while (kw = keywords->next(&kw_len, ec), !U_FAILURE(ec) && kw)
120 {
121 auto value =
122 l.getKeywordValue<std::string>(icu::StringPiece(kw, kw_len), ec);
123 if (!added_at)
124 {
125 posix_name += "@";
126 added_at = true;
127 }
128 else
129 {
130 posix_name += ";";
131 }
132 posix_name += kw;
133 posix_name += "=";
134 posix_name += value;
135 }
136 if (U_FAILURE(ec))
137 throw std::runtime_error{"failed to iterate over keywords"};
138 delete keywords;
139 }
140 return lgen_->generate(posix_name);
141}
142
143auto selector::select(std::string_view accept_language) const -> std::locale 139auto selector::select(std::string_view accept_language) const -> std::locale
144{ 140{
145 using namespace std::literals::string_view_literals; 141 using namespace std::literals::string_view_literals;
@@ -194,21 +190,10 @@ auto selector::select(std::string_view accept_language) const -> std::locale
194 auto res = matcher_.getBestMatchResult(it, ec); 190 auto res = matcher_.getBestMatchResult(it, ec);
195 if (U_FAILURE(ec)) 191 if (U_FAILURE(ec))
196 return default_; 192 return default_;
197 auto resolved = res.makeResolvedLocale(ec); // TODO: maybe don't? 193 auto i = res.getSupportedIndex();
198 if (U_FAILURE(ec)) 194 if (i == -1)
199 return from_icu_locale(*res.getSupportedLocale()); 195 return default_;
200 return from_icu_locale(resolved); 196 return supported_[util::size_from_int(i).value()];
201}
202
203auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string>
204{
205 auto const& locale_info = std::use_facet<blocale::info>(locale);
206 auto ec = UErrorCode::U_ZERO_ERROR;
207 auto bcp47_lang_tag =
208 icu::Locale{locale_info.name().c_str()}.toLanguageTag<std::string>(ec);
209 if (U_FAILURE(ec))
210 return std::nullopt;
211 return bcp47_lang_tag;
212} 197}
213 198
214#ifdef LOCALEDIR 199#ifdef LOCALEDIR
diff --git a/server/src/locale.cppm b/server/src/locale.cppm
index 263de1d..00a3d36 100644
--- a/server/src/locale.cppm
+++ b/server/src/locale.cppm
@@ -26,65 +26,52 @@ concept locale_input_range =
26 && std:: 26 && std::
27 same_as<std::locale const&, std::ranges::range_const_reference_t<T>>; 27 same_as<std::locale const&, std::ranges::range_const_reference_t<T>>;
28 28
29class bcp47_tag : public std::locale::facet
30{
31 std::string tag_;
32
33public:
34 static std::locale::id id;
35
36 explicit bcp47_tag(std::locale locale);
37 ~bcp47_tag() override = default;
38
39 auto tag() const -> std::string_view;
40};
41
29// Helps select a locale based on the Accept-Language header in an 42// Helps select a locale based on the Accept-Language header in an
30// HTTP request. 43// HTTP request.
31export class selector 44export class selector
32{ 45{
46 std::vector<std::locale> supported_;
33 std::locale default_; 47 std::locale default_;
34 icu::LocaleMatcher matcher_; 48 icu::LocaleMatcher matcher_;
35 std::shared_ptr<blocale::generator const> lgen_;
36 49
37 auto make_matcher( 50 static auto make_matcher(std::vector<std::locale> const& supported_locales)
38 locale_input_range auto supported_locales, std::locale default_locale) 51 -> icu::LocaleMatcher;
39 {
40 auto builder = icu::LocaleMatcher::Builder{};
41 for (auto const& supported_locale : supported_locales)
42 {
43 auto const& supported_locale_info =
44 std::use_facet<blocale::info>(supported_locale);
45 auto supported_icu_locale =
46 icu::Locale{supported_locale_info.name().c_str()};
47 if (supported_icu_locale.isBogus())
48 throw std::runtime_error{
49 "supported locale gives rise to bogus ICU locale"
50 };
51 builder.addSupportedLocale(supported_icu_locale);
52 }
53 auto const& default_locale_info =
54 std::use_facet<blocale::info>(default_locale);
55 auto default_icu_locale = icu::Locale{default_locale_info.name().c_str()};
56 if (default_icu_locale.isBogus())
57 throw std::runtime_error{"default locale gives rise to bogus ICU locale"};
58 builder.setDefaultLocale(&default_icu_locale);
59 auto ec = UErrorCode::U_ZERO_ERROR;
60 auto matcher = builder.build(ec);
61 if (U_FAILURE(ec))
62 throw std::runtime_error{"failed to build icu::LocaleMatcher"};
63 return matcher;
64 }
65 52
66 // Trimming optional whitespace as defined in RFC 9110, § 12.4.2. 53 // Trimming optional whitespace as defined in RFC 9110, § 12.4.2.
67 static auto ltrim_ows(std::string_view s) -> std::string_view; 54 static auto ltrim_ows(std::string_view s) -> std::string_view;
68 static auto rtrim_ows(std::string_view s) -> std::string_view; 55 static auto rtrim_ows(std::string_view s) -> std::string_view;
69 static auto trim_ows(std::string_view s) -> std::string_view; 56 static auto trim_ows(std::string_view s) -> std::string_view;
70 57
71 auto from_icu_locale(icu::Locale const& l) const -> std::locale;
72
73public: 58public:
74 // Note: lgen must live at least as long as the selector constructed here! 59 explicit selector(locale_input_range auto supported, std::locale default_)
75 // It is unfortunately not possible to copy/move a blocale::generator. 60 : supported_{
76 explicit selector( 61 std::from_range,
77 locale_input_range auto locales, std::locale default_, 62 supported
78 std::shared_ptr<blocale::generator const> lgen) 63 | std::views::transform(
79 : default_{default_}, matcher_{make_matcher(locales, default_)}, lgen_{lgen} 64 [](std::locale const& locale) -> std::locale
65 { return std::locale{locale, new bcp47_tag{locale}}; })
66 },
67 default_{std::locale{default_, new bcp47_tag{default_}}},
68 matcher_{make_matcher(supported_)}
80 { 69 {
81 } 70 }
82 71
83 auto select(std::string_view accept_language) const -> std::locale; 72 auto select(std::string_view accept_language) const -> std::locale;
84}; 73};
85 74
86auto to_bcp47_lang_tag(std::locale locale) -> std::optional<std::string>;
87
88export auto make_generator() -> std::shared_ptr<blocale::generator const>; 75export auto make_generator() -> std::shared_ptr<blocale::generator const>;
89 76
90} // namespace routemon::locale 77} // namespace routemon::locale
diff --git a/server/src/main.cpp b/server/src/main.cpp
index 99f19da..3ef45c6 100644
--- a/server/src/main.cpp
+++ b/server/src/main.cpp
@@ -33,10 +33,9 @@ auto real_main(std::span<char const*> args) -> exit_status
33 auto locales = { 33 auto locales = {
34 default_locale, 34 default_locale,
35 lgen->generate("nl_NL.UTF-8"), 35 lgen->generate("nl_NL.UTF-8"),
36 lgen->generate("de_DE.UTF-8"),
37 lgen->generate("en_GB.UTF-8"), 36 lgen->generate("en_GB.UTF-8"),
38 }; 37 };
39 auto lsel = routemon::locale::selector{locales, default_locale, lgen}; 38 auto lsel = routemon::locale::selector{locales, default_locale};
40 39
41 auto ioc = net::io_context{1 /* concurrency hint */}; 40 auto ioc = net::io_context{1 /* concurrency hint */};
42 41
diff --git a/server/src/srv.cpp b/server/src/srv.cpp
index f79da33..adfef18 100644
--- a/server/src/srv.cpp
+++ b/server/src/srv.cpp
@@ -51,12 +51,43 @@ auto gpx_parse_error() noexcept -> std::error_code
51 51
52class gpx_parse_result 52class gpx_parse_result
53{ 53{
54 chrono::steady_clock::duration init_parse_dur_;
55 chrono::steady_clock::duration put_parse_dur_;
56 chrono::steady_clock::duration finish_parse_dur_;
54 std::variant<std::exception_ptr, gpx::file> res_; 57 std::variant<std::exception_ptr, gpx::file> res_;
55 58
56public: 59public:
57 auto set_exception(std::exception_ptr ex) noexcept { res_ = ex; } 60 auto set_exception(std::exception_ptr ex) noexcept { res_ = ex; }
58 auto set_gpx_file(gpx::file&& f) noexcept { res_ = std::move(f); } 61 auto set_gpx_file(gpx::file&& f) noexcept { res_ = std::move(f); }
59 62
63 auto init_parse_duration(chrono::steady_clock::duration dur) noexcept
64 {
65 init_parse_dur_ = dur;
66 }
67 [[nodiscard]] auto init_parse_duration() const noexcept
68 -> chrono::steady_clock::duration
69 {
70 return init_parse_dur_;
71 }
72 auto put_parse_duration(chrono::steady_clock::duration dur) noexcept
73 {
74 put_parse_dur_ = dur;
75 }
76 [[nodiscard]] auto put_parse_duration() const noexcept
77 -> chrono::steady_clock::duration
78 {
79 return put_parse_dur_;
80 }
81 auto finish_parse_duration(chrono::steady_clock::duration dur) noexcept
82 {
83 finish_parse_dur_ = dur;
84 }
85 [[nodiscard]] auto finish_parse_duration() const noexcept
86 -> chrono::steady_clock::duration
87 {
88 return finish_parse_dur_;
89 }
90
60 auto unwrap() -> gpx::file&& 91 auto unwrap() -> gpx::file&&
61 { 92 {
62 return std::visit( 93 return std::visit(
@@ -82,11 +113,15 @@ struct readable_gpx_body
82 { 113 {
83 gpx::reader r_; 114 gpx::reader r_;
84 util::not_null<value_type*> res_; 115 util::not_null<value_type*> res_;
116 std::inplace_vector<char, 4096> buf_;
85 117
86 public: 118 public:
87 template <bool isRequest, bhttp::concepts::fields Fields> 119 template <bool isRequest, bhttp::concepts::fields Fields>
88 explicit reader(bhttp::header<isRequest, Fields>&, value_type& v) : res_{&v} 120 explicit reader(bhttp::header<isRequest, Fields>&, value_type& v) : res_{&v}
89 { 121 {
122 res_->init_parse_duration({});
123 res_->put_parse_duration({});
124 res_->finish_parse_duration({});
90 } 125 }
91 126
92 // The following methods (which are called by Beast) are marked 127 // The following methods (which are called by Beast) are marked
@@ -99,6 +134,7 @@ struct readable_gpx_body
99 init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept 134 init(boost::optional<std::uint64_t> /* n */, beast::error_code& ec) noexcept
100 -> void 135 -> void
101 { 136 {
137 auto const init_start = chrono::steady_clock::now();
102 try 138 try
103 { 139 {
104 r_.init(); 140 r_.init();
@@ -109,23 +145,38 @@ struct readable_gpx_body
109 res_->set_exception(std::current_exception()); 145 res_->set_exception(std::current_exception());
110 ec = gpx_parse_error(); 146 ec = gpx_parse_error();
111 } 147 }
148 res_->init_parse_duration(chrono::steady_clock::now() - init_start);
112 } 149 }
113 150
114 auto 151 auto
115 put(beast::concepts::const_buffer_sequence auto b, 152 put(beast::concepts::const_buffer_sequence auto b,
116 beast::error_code& ec) noexcept -> std::size_t 153 beast::error_code& ec) noexcept -> std::size_t
117 { 154 {
155 auto const put_start = chrono::steady_clock::now();
118 auto total = 0uz; 156 auto total = 0uz;
119 try 157 try
120 { 158 {
121 for (auto it = net::buffer_sequence_begin(b); 159 for (auto it = net::buffer_sequence_begin(b);
122 it != net::buffer_sequence_end(b); it++) 160 it != net::buffer_sequence_end(b); it++)
123 { 161 {
124 r_.put( 162 auto cur_in_buf = net::const_buffer{*it};
125 std::string_view{ 163 while (cur_in_buf.size() > 0)
126 static_cast<char const*>(it->data()), it->size() 164 {
127 }); 165 auto to_read =
128 total += it->size(); 166 std::min(cur_in_buf.size(), buf_.max_size() - buf_.size());
167 buf_.append_range(
168 std::span{
169 static_cast<char const*>(cur_in_buf.data()), to_read
170 });
171 cur_in_buf += to_read;
172 total += to_read;
173
174 if (buf_.size() == buf_.max_size())
175 {
176 r_.put(std::string_view{buf_});
177 buf_.clear();
178 }
179 }
129 } 180 }
130 ec = {}; 181 ec = {};
131 } 182 }
@@ -134,13 +185,23 @@ struct readable_gpx_body
134 res_->set_exception(std::current_exception()); 185 res_->set_exception(std::current_exception());
135 ec = gpx_parse_error(); 186 ec = gpx_parse_error();
136 } 187 }
188 res_->put_parse_duration(
189 res_->put_parse_duration()
190 + (chrono::steady_clock::now() - put_start));
137 return total; 191 return total;
138 } 192 }
139 193
140 auto finish(beast::error_code& ec) noexcept 194 auto finish(beast::error_code& ec) noexcept
141 { 195 {
196 auto const finish_start = chrono::steady_clock::now();
142 try 197 try
143 { 198 {
199 if (buf_.size() > 0)
200 {
201 r_.put(std::string_view{buf_});
202 buf_.clear();
203 }
204
144 res_->set_gpx_file(r_.finish()); 205 res_->set_gpx_file(r_.finish());
145 ec = {}; 206 ec = {};
146 } 207 }
@@ -149,6 +210,7 @@ struct readable_gpx_body
149 res_->set_exception(std::current_exception()); 210 res_->set_exception(std::current_exception());
150 ec = gpx_parse_error(); 211 ec = gpx_parse_error();
151 } 212 }
213 res_->finish_parse_duration(chrono::steady_clock::now() - finish_start);
152 } 214 }
153 }; 215 };
154}; 216};
@@ -168,12 +230,18 @@ auto handler::handle_process_gpx(l0_ctx ctx, http::readable_request r)
168 auto const before_read_gpx = chrono::steady_clock::now(); 230 auto const before_read_gpx = chrono::steady_clock::now();
169 auto req = 231 auto req =
170 co_await http::read_request<readable_gpx_body>(ctx, std::move(r)); 232 co_await http::read_request<readable_gpx_body>(ctx, std::move(r));
171 gpx_file = std::move(req->body().unwrap());
172 l.debug( 233 l.debug(
173 "Read GPX request body in {}", 234 "Read GPX request body in {}; init took {}, actual parsing {}, "
235 "finishing {}",
174 chrono::duration<double, std::milli>{ 236 chrono::duration<double, std::milli>{
175 chrono::steady_clock::now() - before_read_gpx 237 chrono::steady_clock::now() - before_read_gpx
238 },
239 chrono::duration<double, std::milli>{req->body().init_parse_duration()},
240 chrono::duration<double, std::milli>{req->body().put_parse_duration()},
241 chrono::duration<double, std::milli>{
242 req->body().finish_parse_duration()
176 }); 243 });
244 gpx_file = std::move(req->body().unwrap());
177 } 245 }
178 catch (std::exception& ex) 246 catch (std::exception& ex)
179 { 247 {
@@ -251,8 +319,10 @@ auto server::make_global_middleware(config::http_server const& cfg)
251{ 319{
252 return http:: 320 return http::
253 middleware_compose<http::base_ctx, http::base_ctx, http::base_ctx>( 321 middleware_compose<http::base_ctx, http::base_ctx, http::base_ctx>(
254 http::trace_id_middleware<http::base_ctx>, 322 http::expose_content_language_middleware<http::base_ctx>,
255 http::cors_middleware<http::base_ctx>(cfg.allow_origins)); 323 middleware_compose<http::base_ctx, http::base_ctx, http::base_ctx>(
324 http::expose_trace_id_middleware<http::base_ctx>,
325 http::cors_middleware<http::base_ctx>(cfg.allow_origins)));
256} 326}
257 327
258server::server( 328server::server(