Skip to content

Commit c9b649b

Browse files
committed
Allocate less when routing
Signed-off-by: Ryan Levick <[email protected]>
1 parent 1abed0c commit c9b649b

File tree

6 files changed

+64
-44
lines changed

6 files changed

+64
-44
lines changed

crates/http/src/routes.rs

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use anyhow::{anyhow, Result};
66
use indexmap::IndexMap;
7-
use std::{collections::HashMap, fmt};
7+
use std::{borrow::Cow, collections::HashMap, fmt};
88

99
use crate::config::HttpTriggerRouteConfig;
1010

@@ -163,37 +163,21 @@ impl Router {
163163
/// If multiple components could potentially handle the same request based on their
164164
/// defined routes, components with matching exact routes take precedence followed
165165
/// by matching wildcard patterns with the longest matching prefix.
166-
pub fn route(&self, p: &str) -> Result<RouteMatch> {
166+
pub fn route<'path, 'router: 'path>(
167+
&'router self,
168+
p: &'path str,
169+
) -> Result<RouteMatch<'router, 'path>> {
167170
let best_match = self
168171
.router
169172
.best_match(p)
170173
.ok_or_else(|| anyhow!("Cannot match route for path {p}"))?;
171174

172-
let route_handler = best_match.handler().clone();
173-
let named_wildcards = best_match
174-
.captures()
175-
.iter()
176-
.map(|(k, v)| (k.to_owned(), v.to_owned()))
177-
.collect();
178-
let trailing_wildcard = best_match.captures().wildcard().map(|s|
179-
// Backward compatibility considerations - Spin has traditionally
180-
// captured trailing slashes, but routefinder does not.
181-
match (s.is_empty(), p.ends_with('/')) {
182-
// route: /foo/..., path: /foo
183-
(true, false) => s.to_owned(),
184-
// route: /foo/..., path: /foo/
185-
(true, true) => "/".to_owned(),
186-
// route: /foo/..., path: /foo/bar
187-
(false, false) => format!("/{s}"),
188-
// route: /foo/..., path: /foo/bar/
189-
(false, true) => format!("/{s}/"),
190-
}
191-
);
175+
let route_handler = best_match.handler();
192176

193177
Ok(RouteMatch {
194-
route_handler,
195-
named_wildcards,
196-
trailing_wildcard,
178+
route_handler: Cow::Borrowed(route_handler),
179+
best_match: Some((p, best_match)),
180+
trailing_wildcard: None,
197181
})
198182
}
199183
}
@@ -235,25 +219,28 @@ impl fmt::Display for ParsedRoute {
235219
}
236220

237221
/// A routing match for a URL.
238-
pub struct RouteMatch {
239-
route_handler: RouteHandler,
240-
named_wildcards: HashMap<String, String>,
222+
pub struct RouteMatch<'router, 'path> {
223+
/// The route handler that matched the path.
224+
route_handler: Cow<'router, RouteHandler>,
225+
/// An optional best match for the path.
226+
best_match: Option<(&'path str, routefinder::Match<'router, 'path, RouteHandler>)>,
227+
/// An optional trailing wildcard part of the path used by the synthetic match.
241228
trailing_wildcard: Option<String>,
242229
}
243230

244-
impl RouteMatch {
231+
impl<'router, 'path> RouteMatch<'router, 'path> {
245232
/// A synthetic match as if the given path was matched against the wildcard route.
246233
/// Used in service chaining.
247-
pub fn synthetic(component_id: &str, path: &str) -> Self {
234+
pub fn synthetic(component_id: String, path: String) -> Self {
248235
Self {
249-
route_handler: RouteHandler {
250-
component_id: component_id.to_string(),
236+
route_handler: Cow::Owned(RouteHandler {
237+
component_id,
251238
based_route: "/...".to_string(),
252239
raw_route: "/...".to_string(),
253240
parsed_based_route: ParsedRoute::TrailingWildcard(String::new()),
254-
},
255-
named_wildcards: Default::default(),
256-
trailing_wildcard: Some(path.to_string()),
241+
}),
242+
best_match: None,
243+
trailing_wildcard: Some(path),
257244
}
258245
}
259246

@@ -291,13 +278,45 @@ impl RouteMatch {
291278
}
292279

293280
/// The named wildcards captured from the path, if any
294-
pub fn named_wildcards(&self) -> &HashMap<String, String> {
295-
&self.named_wildcards
281+
pub fn named_wildcards(&self) -> HashMap<String, String> {
282+
let Some((_, best_match)) = &self.best_match else {
283+
return HashMap::new();
284+
};
285+
best_match
286+
.captures()
287+
.iter()
288+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
289+
.collect()
296290
}
297291

298292
/// The trailing wildcard part of the path, if any
299293
pub fn trailing_wildcard(&self) -> String {
300-
self.trailing_wildcard.clone().unwrap_or_default()
294+
// If we have a trailing wildcard, return it.
295+
if let Some(wildcard) = &self.trailing_wildcard {
296+
return wildcard.clone();
297+
};
298+
299+
// Otherwise, we need to extract it from the best match if we have it.
300+
let Some((path, best_match)) = &self.best_match else {
301+
return String::new();
302+
};
303+
best_match
304+
.captures()
305+
.wildcard()
306+
.map(|s|
307+
// Backward compatibility considerations - Spin has traditionally
308+
// captured trailing slashes, but routefinder does not.
309+
match (s.is_empty(), path.ends_with('/')) {
310+
// route: /foo/..., path: /foo
311+
(true, false) => s.to_owned(),
312+
// route: /foo/..., path: /foo/
313+
(true, true) => "/".to_owned(),
314+
// route: /foo/..., path: /foo/bar
315+
(false, false) => format!("/{s}"),
316+
// route: /foo/..., path: /foo/bar/
317+
(false, true) => format!("/{s}/"),
318+
})
319+
.unwrap_or_default()
301320
}
302321
}
303322

crates/trigger-http/src/outbound_http.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ impl<F: RuntimeFactors> intercept::OutboundHttpInterceptor for OutboundHttpInter
3232
// Handle service chaining requests
3333
if let Some(component_id) = parse_service_chaining_target(request.uri()) {
3434
let req = request.into_hyper_request();
35-
let route_match = RouteMatch::synthetic(&component_id, req.uri().path());
35+
let path = req.uri().path().to_owned();
36+
let route_match = RouteMatch::synthetic(component_id, path);
3637
let resp = self
3738
.server
3839
.handle_trigger_route(req, route_match, Scheme::HTTP, CHAINED_CLIENT_ADDR)

crates/trigger-http/src/server.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
222222
pub async fn handle_trigger_route(
223223
self: &Arc<Self>,
224224
mut req: Request<Body>,
225-
route_match: RouteMatch,
225+
route_match: RouteMatch<'_, '_>,
226226
server_scheme: Scheme,
227227
client_addr: SocketAddr,
228228
) -> anyhow::Result<Response<Body>> {
@@ -471,7 +471,7 @@ pub(crate) trait HttpExecutor {
471471
fn execute<F: RuntimeFactors>(
472472
&self,
473473
instance_builder: TriggerInstanceBuilder<F>,
474-
route_match: &RouteMatch,
474+
route_match: &RouteMatch<'_, '_>,
475475
req: Request<Body>,
476476
client_addr: SocketAddr,
477477
) -> impl Future<Output = anyhow::Result<Response<Body>>>;

crates/trigger-http/src/spin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl HttpExecutor for SpinHttpExecutor {
2424
async fn execute<F: RuntimeFactors>(
2525
&self,
2626
instance_builder: TriggerInstanceBuilder<'_, F>,
27-
route_match: &RouteMatch,
27+
route_match: &RouteMatch<'_, '_>,
2828
req: Request<Body>,
2929
client_addr: SocketAddr,
3030
) -> Result<Response<Body>> {

crates/trigger-http/src/wagi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl HttpExecutor for WagiHttpExecutor<'_> {
2323
async fn execute<F: RuntimeFactors>(
2424
&self,
2525
mut instance_builder: TriggerInstanceBuilder<'_, F>,
26-
route_match: &RouteMatch,
26+
route_match: &RouteMatch<'_, '_>,
2727
req: Request<Body>,
2828
client_addr: SocketAddr,
2929
) -> Result<Response<Body>> {

crates/trigger-http/src/wasi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl HttpExecutor for WasiHttpExecutor<'_> {
2727
async fn execute<F: RuntimeFactors>(
2828
&self,
2929
instance_builder: TriggerInstanceBuilder<'_, F>,
30-
route_match: &RouteMatch,
30+
route_match: &RouteMatch<'_, '_>,
3131
mut req: Request<Body>,
3232
client_addr: SocketAddr,
3333
) -> Result<Response<Body>> {

0 commit comments

Comments
 (0)