Skip to content

Commit 9fbc065

Browse files
committed
Private endpoints
Signed-off-by: itowlson <[email protected]>
1 parent 57610ec commit 9fbc065

File tree

5 files changed

+101
-29
lines changed

5 files changed

+101
-29
lines changed

crates/http/src/config.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,32 @@ pub struct HttpTriggerConfig {
77
/// Component ID to invoke
88
pub component: String,
99
/// HTTP route the component will be invoked for
10-
pub route: String,
10+
pub route: HttpTriggerRouteConfig,
1111
/// The HTTP executor the component requires
1212
#[serde(default)]
1313
pub executor: Option<HttpExecutorType>,
1414
}
1515

16+
/// An HTTP trigger route
17+
#[derive(Clone, Debug, Deserialize, Serialize)]
18+
#[serde(untagged)]
19+
pub enum HttpTriggerRouteConfig {
20+
Route(String),
21+
IsRoutable(bool),
22+
}
23+
24+
impl Default for HttpTriggerRouteConfig {
25+
fn default() -> Self {
26+
Self::Route(Default::default())
27+
}
28+
}
29+
30+
impl<T: Into<String>> From<T> for HttpTriggerRouteConfig {
31+
fn from(value: T) -> Self {
32+
Self::Route(value.into())
33+
}
34+
}
35+
1636
/// The executor for the HTTP component.
1737
/// The component can either implement the Spin HTTP interface,
1838
/// the `wasi-http` interface, or the Wagi CGI interface.

crates/http/src/routes.rs

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use http::Uri;
77
use indexmap::IndexMap;
88
use std::{borrow::Cow, fmt};
99

10+
use crate::config::HttpTriggerRouteConfig;
11+
1012
/// Router for the HTTP trigger.
1113
#[derive(Clone, Debug)]
1214
pub struct Router {
@@ -15,6 +17,7 @@ pub struct Router {
1517
}
1618

1719
/// A detected duplicate route.
20+
#[derive(Debug)] // Needed to call `expect_err` on `Router::build`
1821
pub struct DuplicateRoute {
1922
/// The duplicated route pattern.
2023
pub route: RoutePattern,
@@ -28,14 +31,23 @@ impl Router {
2831
/// Builds a router based on application configuration.
2932
pub fn build<'a>(
3033
base: &str,
31-
component_routes: impl IntoIterator<Item = (&'a str, &'a str)>,
34+
component_routes: impl IntoIterator<Item = (&'a str, &'a HttpTriggerRouteConfig)>,
3235
) -> Result<(Self, Vec<DuplicateRoute>)> {
3336
let mut routes = IndexMap::new();
3437
let mut duplicates = vec![];
3538

36-
let routes_iter = component_routes.into_iter().map(|(component_id, route)| {
37-
(RoutePattern::from(base, route), component_id.to_string())
38-
});
39+
let routes_iter = component_routes
40+
.into_iter()
41+
.filter_map(|(component_id, route)| {
42+
match route {
43+
HttpTriggerRouteConfig::Route(r) => {
44+
Some(Ok((RoutePattern::from(base, r), component_id.to_string())))
45+
}
46+
HttpTriggerRouteConfig::IsRoutable(false) => None,
47+
HttpTriggerRouteConfig::IsRoutable(true) => Some(Err(anyhow!("route must be a string pattern or 'false': component '{component_id}' has route = 'true'"))),
48+
}
49+
})
50+
.collect::<Result<Vec<_>>>()?;
3951

4052
for (route, component_id) in routes_iter {
4153
let replaced = routes.insert(route.clone(), component_id.clone());
@@ -417,10 +429,10 @@ mod route_tests {
417429
let (routes, duplicates) = Router::build(
418430
"/",
419431
vec![
420-
("/", "/"),
421-
("/foo", "/foo"),
422-
("/bar", "/bar"),
423-
("/whee/...", "/whee/..."),
432+
("/", &"/".into()),
433+
("/foo", &"/foo".into()),
434+
("/bar", &"/bar".into()),
435+
("/whee/...", &"/whee/...".into()),
424436
],
425437
)
426438
.unwrap();
@@ -434,10 +446,10 @@ mod route_tests {
434446
let (routes, _) = Router::build(
435447
"/",
436448
vec![
437-
("/", "/"),
438-
("/foo", "/foo"),
439-
("/bar", "/bar"),
440-
("/whee/...", "/whee/..."),
449+
("/", &"/".into()),
450+
("/foo", &"/foo".into()),
451+
("/bar", &"/bar".into()),
452+
("/whee/...", &"/whee/...".into()),
441453
],
442454
)
443455
.unwrap();
@@ -453,10 +465,10 @@ mod route_tests {
453465
let (routes, duplicates) = Router::build(
454466
"/",
455467
vec![
456-
("/", "/"),
457-
("first /foo", "/foo"),
458-
("second /foo", "/foo"),
459-
("/whee/...", "/whee/..."),
468+
("/", &"/".into()),
469+
("first /foo", &"/foo".into()),
470+
("second /foo", &"/foo".into()),
471+
("/whee/...", &"/whee/...".into()),
460472
],
461473
)
462474
.unwrap();
@@ -470,10 +482,10 @@ mod route_tests {
470482
let (routes, duplicates) = Router::build(
471483
"/",
472484
vec![
473-
("/", "/"),
474-
("first /foo", "/foo"),
475-
("second /foo", "/foo"),
476-
("/whee/...", "/whee/..."),
485+
("/", &"/".into()),
486+
("first /foo", &"/foo".into()),
487+
("second /foo", &"/foo".into()),
488+
("/whee/...", &"/whee/...".into()),
477489
],
478490
)
479491
.unwrap();
@@ -482,4 +494,37 @@ mod route_tests {
482494
assert_eq!("first /foo", duplicates[0].replaced_id);
483495
assert_eq!("second /foo", duplicates[0].effective_id);
484496
}
497+
498+
#[test]
499+
fn unroutable_routes_are_skipped() {
500+
let (routes, _) = Router::build(
501+
"/",
502+
vec![
503+
("/", &"/".into()),
504+
("/foo", &"/foo".into()),
505+
("private", &HttpTriggerRouteConfig::IsRoutable(false)),
506+
("/whee/...", &"/whee/...".into()),
507+
],
508+
)
509+
.unwrap();
510+
511+
assert_eq!(3, routes.routes.len());
512+
assert!(!routes.routes.values().any(|c| c == "private"));
513+
}
514+
515+
#[test]
516+
fn unroutable_routes_have_to_be_unroutable_thats_just_common_sense() {
517+
let e = Router::build(
518+
"/",
519+
vec![
520+
("/", &"/".into()),
521+
("/foo", &"/foo".into()),
522+
("bad component", &HttpTriggerRouteConfig::IsRoutable(true)),
523+
("/whee/...", &"/whee/...".into()),
524+
],
525+
)
526+
.expect_err("should not have accepted a 'route = true'");
527+
528+
assert!(e.to_string().contains("bad component"));
529+
}
485530
}

crates/testing/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ use spin_app::{
1616
AppComponent, Loader,
1717
};
1818
use spin_core::{Component, StoreBuilder};
19-
use spin_http::config::{HttpExecutorType, HttpTriggerConfig, WagiTriggerConfig};
19+
use spin_http::config::{
20+
HttpExecutorType, HttpTriggerConfig, HttpTriggerRouteConfig, WagiTriggerConfig,
21+
};
2022
use spin_trigger::{HostComponentInitData, RuntimeConfig, TriggerExecutor, TriggerExecutorBuilder};
2123
use tokio::fs;
2224

@@ -68,7 +70,7 @@ impl HttpTestConfig {
6870
self.module_path(Path::new(TEST_PROGRAM_PATH).join(name))
6971
}
7072

71-
pub fn http_spin_trigger(&mut self, route: impl Into<String>) -> &mut Self {
73+
pub fn http_spin_trigger(&mut self, route: impl Into<HttpTriggerRouteConfig>) -> &mut Self {
7274
self.http_trigger_config = HttpTriggerConfig {
7375
component: "test-component".to_string(),
7476
route: route.into(),
@@ -79,7 +81,7 @@ impl HttpTestConfig {
7981

8082
pub fn http_wagi_trigger(
8183
&mut self,
82-
route: impl Into<String>,
84+
route: impl Into<HttpTriggerRouteConfig>,
8385
wagi_config: WagiTriggerConfig,
8486
) -> &mut Self {
8587
self.http_trigger_config = HttpTriggerConfig {

crates/trigger-http/src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use spin_core::{Engine, OutboundWasiHttpHandler};
2929
use spin_http::{
3030
app_info::AppInfo,
3131
body,
32-
config::{HttpExecutorType, HttpTriggerConfig},
32+
config::{HttpExecutorType, HttpTriggerConfig, HttpTriggerRouteConfig},
3333
routes::{RoutePattern, Router},
3434
};
3535
use spin_outbound_networking::{
@@ -119,7 +119,7 @@ impl TriggerExecutor for HttpTrigger {
119119

120120
let component_routes = engine
121121
.trigger_configs()
122-
.map(|(_, config)| (config.component.as_str(), config.route.as_str()));
122+
.map(|(_, config)| (config.component.as_str(), &config.route));
123123

124124
let (router, duplicate_routes) = Router::build(&base, component_routes)?;
125125

@@ -257,14 +257,19 @@ impl HttpTrigger {
257257

258258
let executor = trigger.executor.as_ref().unwrap_or(&HttpExecutorType::Http);
259259

260+
let raw_route = match &trigger.route {
261+
HttpTriggerRouteConfig::Route(r) => r.as_str(),
262+
HttpTriggerRouteConfig::IsRoutable(_) => "/...",
263+
};
264+
260265
let res = match executor {
261266
HttpExecutorType::Http => {
262267
HttpHandlerExecutor
263268
.execute(
264269
self.engine.clone(),
265270
component_id,
266271
&self.base,
267-
&trigger.route,
272+
raw_route,
268273
req,
269274
addr,
270275
)
@@ -279,7 +284,7 @@ impl HttpTrigger {
279284
self.engine.clone(),
280285
component_id,
281286
&self.base,
282-
&trigger.route,
287+
raw_route,
283288
req,
284289
addr,
285290
)

tests/runtime-tests/tests/internal-http/spin.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ source = "%{source=internal-http-front}"
1414
allowed_outbound_hosts = ["http://middle.spin.internal"]
1515

1616
[[trigger.http]]
17-
route = "/middle/..."
17+
route = false
1818
component = "middle"
1919

2020
[component.middle]

0 commit comments

Comments
 (0)