Skip to content

Commit 490c804

Browse files
committed
Less allocation in the router
Signed-off-by: Ryan Levick <ryan.levick@fermyon.com>
1 parent cdd6f6e commit 490c804

File tree

4 files changed

+60
-56
lines changed

4 files changed

+60
-56
lines changed

crates/http/src/routes.rs

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,12 @@ impl Router {
173173
.ok_or_else(|| anyhow!("Cannot match route for path {path}"))?;
174174

175175
let route_handler = best_match.handler();
176+
let captures = best_match.captures();
176177

177178
Ok(RouteMatch {
178179
inner: RouteMatchKind::Real {
179180
route_handler,
180-
best_match,
181+
captures,
181182
path,
182183
},
183184
})
@@ -253,13 +254,12 @@ impl<'router, 'path> RouteMatch<'router, 'path> {
253254
}
254255

255256
/// The matched route, excluding any trailing wildcard, combined with the base.
256-
pub fn based_route_or_prefix(&self) -> String {
257+
pub fn based_route_or_prefix(&self) -> &str {
257258
self.inner
258259
.route_handler()
259260
.based_route
260261
.strip_suffix("/...")
261262
.unwrap_or(&self.inner.route_handler().based_route)
262-
.to_string()
263263
}
264264

265265
/// The matched route, as originally written in the manifest.
@@ -268,22 +268,21 @@ impl<'router, 'path> RouteMatch<'router, 'path> {
268268
}
269269

270270
/// The matched route, excluding any trailing wildcard.
271-
pub fn raw_route_or_prefix(&self) -> String {
271+
pub fn raw_route_or_prefix(&self) -> &str {
272272
self.inner
273273
.route_handler()
274274
.raw_route
275275
.strip_suffix("/...")
276276
.unwrap_or(&self.inner.route_handler().raw_route)
277-
.to_string()
278277
}
279278

280279
/// The named wildcards captured from the path, if any
281-
pub fn named_wildcards(&self) -> HashMap<String, String> {
280+
pub fn named_wildcards(&self) -> HashMap<&str, &str> {
282281
self.inner.named_wildcards()
283282
}
284283

285284
/// The trailing wildcard part of the path, if any
286-
pub fn trailing_wildcard(&self) -> String {
285+
pub fn trailing_wildcard(&self) -> Cow<'_, str> {
287286
self.inner.trailing_wildcard()
288287
}
289288
}
@@ -304,7 +303,7 @@ enum RouteMatchKind<'router, 'path> {
304303
/// The route handler that matched the path.
305304
route_handler: &'router RouteHandler,
306305
/// The best match for the path.
307-
best_match: routefinder::Match<'router, 'path, RouteHandler>,
306+
captures: routefinder::Captures<'router, 'path>,
308307
/// The path that was matched.
309308
path: &'path str,
310309
},
@@ -320,44 +319,37 @@ impl<'router, 'path> RouteMatchKind<'router, 'path> {
320319
}
321320

322321
/// The named wildcards captured from the path, if any
323-
pub fn named_wildcards(&self) -> HashMap<String, String> {
324-
let Self::Real { best_match, .. } = &self else {
322+
pub fn named_wildcards(&self) -> HashMap<&str, &str> {
323+
let Self::Real { captures, .. } = &self else {
325324
return HashMap::new();
326325
};
327-
best_match
328-
.captures()
329-
.iter()
330-
.map(|(k, v)| (k.to_owned(), v.to_owned()))
331-
.collect()
326+
captures.iter().collect()
332327
}
333328

334329
/// The trailing wildcard part of the path, if any
335-
pub fn trailing_wildcard(&self) -> String {
336-
let (best_match, path) = match self {
330+
pub fn trailing_wildcard(&self) -> Cow<'_, str> {
331+
let (captures, path) = match self {
337332
// If we have a synthetic match, we already have the trailing wildcard.
338333
Self::Synthetic {
339334
trailing_wildcard, ..
340-
} => return trailing_wildcard.clone(),
341-
Self::Real {
342-
best_match, path, ..
343-
} => (best_match, path),
335+
} => return trailing_wildcard.into(),
336+
Self::Real { captures, path, .. } => (captures, path),
344337
};
345338

346-
best_match
347-
.captures()
339+
captures
348340
.wildcard()
349341
.map(|s|
350342
// Backward compatibility considerations - Spin has traditionally
351343
// captured trailing slashes, but routefinder does not.
352344
match (s.is_empty(), path.ends_with('/')) {
353345
// route: /foo/..., path: /foo
354-
(true, false) => s.to_owned(),
346+
(true, false) => s.into(),
355347
// route: /foo/..., path: /foo/
356-
(true, true) => "/".to_owned(),
348+
(true, true) => "/".into(),
357349
// route: /foo/..., path: /foo/bar
358-
(false, false) => format!("/{s}"),
350+
(false, false) => format!("/{s}").into(),
359351
// route: /foo/..., path: /foo/bar/
360-
(false, true) => format!("/{s}/"),
352+
(false, true) => format!("/{s}/").into(),
361353
})
362354
.unwrap_or_default()
363355
}

crates/http/src/wagi/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ pub fn build_headers(
105105
// https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.13
106106
headers.insert(
107107
"SCRIPT_NAME".to_owned(),
108-
route_match.based_route_or_prefix(),
108+
route_match.based_route_or_prefix().to_owned(),
109109
);
110110
// PATH_INFO is any path information after SCRIPT_NAME
111111
//
@@ -117,7 +117,10 @@ pub fn build_headers(
117117
// https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.5
118118
let pathsegment = path_info;
119119
let pathinfo = percent_encoding::percent_decode_str(&pathsegment).decode_utf8_lossy();
120-
headers.insert("X_RAW_PATH_INFO".to_owned(), pathsegment.clone());
120+
headers.insert(
121+
"X_RAW_PATH_INFO".to_owned(),
122+
pathsegment.as_ref().to_owned(),
123+
);
121124
headers.insert("PATH_INFO".to_owned(), pathinfo.to_string());
122125
// PATH_TRANSLATED is the url-decoded version of PATH_INFO
123126
// https://datatracker.ietf.org/doc/html/rfc3875#section-4.1.6

crates/trigger-http/src/headers.rs

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use std::{net::SocketAddr, str, str::FromStr};
1+
use std::{
2+
borrow::Cow,
3+
net::SocketAddr,
4+
str::{self, FromStr},
5+
};
26

37
use anyhow::Result;
48
use http::Uri;
@@ -21,23 +25,23 @@ pub const RAW_COMPONENT_ROUTE: [&str; 2] = ["SPIN_RAW_COMPONENT_ROUTE", "X_RAW_C
2125
pub const BASE_PATH: [&str; 2] = ["SPIN_BASE_PATH", "X_BASE_PATH"];
2226
pub const CLIENT_ADDR: [&str; 2] = ["SPIN_CLIENT_ADDR", "X_CLIENT_ADDR"];
2327

24-
pub fn compute_default_headers(
28+
pub fn compute_default_headers<'a>(
2529
uri: &Uri,
2630
host: &str,
27-
route_match: &RouteMatch,
31+
route_match: &'a RouteMatch,
2832
client_addr: SocketAddr,
29-
) -> anyhow::Result<Vec<([String; 2], String)>> {
30-
fn owned(strs: &[&'static str; 2]) -> [String; 2] {
31-
[strs[0].to_owned(), strs[1].to_owned()]
33+
) -> anyhow::Result<Vec<([Cow<'static, str>; 2], Cow<'a, str>)>> {
34+
fn owned(strs: &[&'static str; 2]) -> [Cow<'static, str>; 2] {
35+
[strs[0].into(), strs[1].into()]
3236
}
3337

34-
let owned_full_url: [String; 2] = owned(&FULL_URL);
35-
let owned_path_info: [String; 2] = owned(&PATH_INFO);
36-
let owned_matched_route: [String; 2] = owned(&MATCHED_ROUTE);
37-
let owned_component_route: [String; 2] = owned(&COMPONENT_ROUTE);
38-
let owned_raw_component_route: [String; 2] = owned(&RAW_COMPONENT_ROUTE);
39-
let owned_base_path: [String; 2] = owned(&BASE_PATH);
40-
let owned_client_addr: [String; 2] = owned(&CLIENT_ADDR);
38+
let owned_full_url = owned(&FULL_URL);
39+
let owned_path_info = owned(&PATH_INFO);
40+
let owned_matched_route = owned(&MATCHED_ROUTE);
41+
let owned_component_route = owned(&COMPONENT_ROUTE);
42+
let owned_raw_component_route = owned(&RAW_COMPONENT_ROUTE);
43+
let owned_base_path = owned(&BASE_PATH);
44+
let owned_client_addr = owned(&CLIENT_ADDR);
4145

4246
let mut res = vec![];
4347
let abs_path = uri
@@ -52,21 +56,21 @@ pub fn compute_default_headers(
5256
let full_url = format!("{}://{}{}", scheme, host, abs_path);
5357

5458
res.push((owned_path_info, path_info));
55-
res.push((owned_full_url, full_url));
56-
res.push((owned_matched_route, route_match.based_route().to_string()));
59+
res.push((owned_full_url, full_url.into()));
60+
res.push((owned_matched_route, route_match.based_route().into()));
5761

58-
res.push((owned_base_path, "/".to_string()));
62+
res.push((owned_base_path, "/".into()));
63+
res.push((owned_raw_component_route, route_match.raw_route().into()));
5964
res.push((
60-
owned_raw_component_route,
61-
route_match.raw_route().to_string(),
65+
owned_component_route,
66+
route_match.raw_route_or_prefix().into(),
6267
));
63-
res.push((owned_component_route, route_match.raw_route_or_prefix()));
64-
res.push((owned_client_addr, client_addr.to_string()));
68+
res.push((owned_client_addr, client_addr.to_string().into()));
6569

6670
for (wild_name, wild_value) in route_match.named_wildcards() {
67-
let wild_header = format!("SPIN_PATH_MATCH_{}", wild_name.to_ascii_uppercase()); // TODO: safer
68-
let wild_wagi_header = format!("X_PATH_MATCH_{}", wild_name.to_ascii_uppercase()); // TODO: safer
69-
res.push(([wild_header, wild_wagi_header], wild_value.clone()));
71+
let wild_header = format!("SPIN_PATH_MATCH_{}", wild_name.to_ascii_uppercase()).into();
72+
let wild_wagi_header = format!("X_PATH_MATCH_{}", wild_name.to_ascii_uppercase()).into();
73+
res.push(([wild_header, wild_wagi_header], wild_value.into()));
7074
}
7175

7276
Ok(res)
@@ -110,7 +114,7 @@ pub fn prepare_request_headers(
110114
// In the future, we might want to have this information in a context
111115
// object as opposed to headers.
112116
for (keys, val) in compute_default_headers(req.uri(), host, route_match, client_addr)? {
113-
res.push((prepare_header_key(&keys[0]), val));
117+
res.push((prepare_header_key(&keys[0]), val.into_owned()));
114118
}
115119

116120
Ok(res)
@@ -138,6 +142,8 @@ fn prepare_header_key(key: &str) -> String {
138142

139143
#[cfg(test)]
140144
mod tests {
145+
use std::borrow::Cow;
146+
141147
use super::*;
142148
use anyhow::Result;
143149
use spin_http::routes::Router;
@@ -318,11 +324,14 @@ mod tests {
318324
assert!(req.headers().get("Host").is_some());
319325
}
320326

321-
fn search(keys: &[&str; 2], headers: &[([String; 2], String)]) -> Option<String> {
327+
fn search(
328+
keys: &[&str; 2],
329+
headers: &[([Cow<'static, str>; 2], Cow<'_, str>)],
330+
) -> Option<String> {
322331
let mut res: Option<String> = None;
323332
for (k, v) in headers {
324333
if k[0] == keys[0] && k[1] == keys[1] {
325-
res = Some(v.clone());
334+
res = Some(v.as_ref().to_owned());
326335
}
327336
}
328337

crates/trigger-http/src/wagi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl HttpExecutor for WagiHttpExecutor<'_> {
7171
// `PATH_INFO`, or `X_FULL_URL`).
7272
// Note that this overrides any existing headers previously set by Wagi.
7373
for (keys, val) in compute_default_headers(&parts.uri, host, route_match, client_addr)? {
74-
headers.insert(keys[1].to_string(), val);
74+
headers.insert(keys[1].to_string(), val.into_owned());
7575
}
7676

7777
let stdout = MemoryOutputPipe::new(usize::MAX);

0 commit comments

Comments
 (0)