Skip to content

Commit 20e79fa

Browse files
tunnel (#127)
1 parent e754088 commit 20e79fa

20 files changed

Lines changed: 5113 additions & 32 deletions

Cargo.toml

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,58 +10,149 @@ rust-version = "1.90.0"
1010
[[example]]
1111
name = "tracing"
1212
path = "examples/tracing.rs"
13-
required-features = ["tracing-span-filter"]
13+
required-features = ["http_server", "tracing-span-filter"]
1414

1515
[[example]]
1616
name = "schema"
1717
path = "examples/schema.rs"
18-
required-features = ["schemars"]
18+
required-features = ["http_server", "schemars"]
19+
20+
[[example]]
21+
name = "counter"
22+
path = "examples/counter.rs"
23+
required-features = ["http_server"]
24+
25+
[[example]]
26+
name = "cron"
27+
path = "examples/cron.rs"
28+
required-features = ["http_server"]
29+
30+
[[example]]
31+
name = "failures"
32+
path = "examples/failures.rs"
33+
required-features = ["http_server"]
34+
35+
[[example]]
36+
name = "fan_out"
37+
path = "examples/fan_out.rs"
38+
required-features = ["http_server"]
39+
40+
[[example]]
41+
name = "greeter"
42+
path = "examples/greeter.rs"
43+
required-features = ["http_server"]
44+
45+
[[example]]
46+
name = "run"
47+
path = "examples/run.rs"
48+
required-features = ["http_server"]
49+
50+
[[example]]
51+
name = "signals"
52+
path = "examples/signals.rs"
53+
required-features = ["http_server"]
1954

2055
[[example]]
2156
name = "ingress_client"
2257
path = "examples/ingress_client.rs"
2358
required-features = ["reqwest-client"]
2459

60+
[[example]]
61+
name = "tunnel"
62+
path = "examples/tunnel.rs"
63+
required-features = ["tunnel", "tracing-span-filter"]
64+
2565
[features]
2666
default = ["http_server", "rand", "uuid", "tracing-span-filter", "rust_crypto"]
2767
reqwest-client = ["dep:reqwest"]
68+
rand = ["dep:rand"]
69+
uuid = ["dep:uuid"]
2870
hyper = ["dep:hyper", "restate-sdk-shared-core/http"]
29-
http_server = ["hyper", "hyper/server", "hyper/http2", "hyper-util", "tokio/net", "tokio/signal", "tokio/macros"]
71+
http_server = [
72+
"hyper",
73+
"hyper/server",
74+
"hyper/http2",
75+
"dep:hyper-util",
76+
"hyper-util/tokio",
77+
"hyper-util/server",
78+
"hyper-util/server-graceful",
79+
"hyper-util/http2",
80+
"tokio/net",
81+
"tokio/signal",
82+
"tokio/macros",
83+
]
3084
tracing-span-filter = ["dep:tracing-subscriber"]
3185
lambda = [ "dep:http-serde", "dep:lambda_runtime", "dep:aws_lambda_events"]
3286
# jsonwebtoken crypto backend pass-through for request identity verification.
3387
# Enable exactly one (or call jsonwebtoken's CryptoProvider::install_default yourself).
34-
rust_crypto = ["restate-sdk-shared-core/rust_crypto"]
35-
aws_lc_rs = ["restate-sdk-shared-core/aws_lc_rs"]
88+
rust_crypto = ["restate-sdk-shared-core/rust_crypto", "jsonwebtoken?/rust_crypto", "rustls?/ring"]
89+
aws_lc_rs = ["restate-sdk-shared-core/aws_lc_rs", "jsonwebtoken?/aws_lc_rs", "rustls?/aws_lc_rs"]
90+
tunnel = [
91+
"hyper",
92+
"hyper/server",
93+
"hyper/http2",
94+
"dep:hickory-resolver",
95+
"dep:hostname",
96+
"dep:hyper-util",
97+
"dep:jsonwebtoken",
98+
"hyper-util/tokio",
99+
"rand",
100+
"dep:rustls",
101+
"dep:rustls-native-certs",
102+
"dep:socket2",
103+
"dep:tokio-rustls",
104+
"dep:tokio-util",
105+
"dep:url",
106+
"tokio/io-util",
107+
"tokio/macros",
108+
"tokio/net",
109+
"tokio/rt",
110+
"tokio/signal",
111+
"uuid",
112+
"uuid/v4",
113+
]
36114

37115
[dependencies]
38116
bytes = "1.11"
39117
futures = "0.3"
118+
hickory-resolver = { version = "0.26", default-features = false, features = ["system-config", "tokio"], optional = true }
119+
hostname = { version = "0.4", optional = true }
40120
http = "1.4"
41121
http-body = "1.0.1"
42122
http-body-util = "0.1"
43123
hyper = { version = "1.8", optional = true}
44-
hyper-util = { version = "0.1", features = ["tokio", "server", "server-graceful", "http2"], optional = true }
124+
hyper-util = { version = "0.1", default-features = false, optional = true }
125+
jsonwebtoken = { version = "10.3", default-features = false, optional = true }
45126
pin-project-lite = "0.2"
46127
percent-encoding = "2.3"
47128
rand = { version = "0.10", optional = true }
48129
regress = "0.10"
49130
reqwest = { version = "0.13", optional = true }
50131
restate-sdk-macros = { version = "0.11", path = "macros" }
51132
restate-sdk-shared-core = { version = "=7.0.3", features = ["request_identity", "sha2_random_seed", "http"] }
133+
rustls = { version = "0.23", default-features = false, features = ["std", "tls12"], optional = true }
134+
rustls-native-certs = { version = "0.8", optional = true }
52135
schemars = { version = "1.2", optional = true }
53136
serde = "1.0"
54137
serde_json = "1.0"
138+
socket2 = { version = "0.6", optional = true }
55139
thiserror = "2.0"
56140
tokio = { version = "1.49", default-features = false, features = ["sync", "time"] }
141+
tokio-rustls = { version = "0.26", default-features = false, features = ["tls12"], optional = true }
142+
tokio-util = { version = "0.7", default-features = false, features = ["rt"], optional = true }
57143
tracing = "0.1"
58144
tracing-subscriber = { version = "0.3", features = ["registry"], optional = true }
145+
url = { version = "2.5", optional = true }
59146
uuid = { version = "1.20", optional = true }
60147
http-serde = { version = "2.1.1", optional = true }
61148
aws_lambda_events = { version = "1.0", optional = true }
62149
lambda_runtime = { version = "1.0", optional = true }
63150

64151
[dev-dependencies]
152+
base64 = "0.22"
153+
bs58 = "0.5"
154+
ed25519-dalek = "2"
155+
hyper = { version = "1.8", default-features = false, features = ["client", "http2"] }
65156
tokio = { version = "1", features = ["full"] }
66157
tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] }
67158
trybuild = "1.0"

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,37 @@ Generated object and workflow ingress clients additionally take their key in `fr
9898
generic `Client<E>` and generated clients are available without `reqwest-client`; implement
9999
`RequestExecutor` to use another buffered HTTP transport.
100100

101+
## Connecting through a Restate Cloud tunnel
102+
103+
The optional Unix-only in-process tunnel lets a service connect outbound to Restate Cloud, so the
104+
service does not need to expose an inbound HTTP port. Enable the `tunnel` feature:
105+
106+
```toml
107+
[dependencies]
108+
restate-sdk = { version = "0.11", features = ["tunnel"] }
109+
tokio = { version = "1", features = ["full"] }
110+
```
111+
112+
When default features are disabled, select a crypto provider explicitly by adding either
113+
`rust_crypto` or `aws_lc_rs` alongside `tunnel`. If both are enabled, `rust_crypto` is selected
114+
deterministically.
115+
116+
The Restate operator injects the discovery and authentication settings, so an operator-managed
117+
deployment needs no application-side tunnel configuration:
118+
119+
```rust
120+
use restate_sdk::prelude::*;
121+
122+
#[tokio::main]
123+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
124+
let endpoint = Endpoint::builder().bind(Greeter).build();
125+
Tunnel::new(endpoint).run().await?;
126+
Ok(())
127+
}
128+
```
129+
130+
See the [complete tunnel example](examples/tunnel.rs).
131+
101132
## Running on Lambda
102133

103134
The Restate Rust SDK supports running services on AWS Lambda using Lambda Function URLs. This allows you to deploy your Restate services as serverless functions.

examples/services/my_service.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ impl MyService {
1313
#[tokio::main]
1414
async fn main() {
1515
tracing_subscriber::fmt::init();
16-
HttpServer::new(Endpoint::builder().bind(MyService).build())
17-
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
18-
.await;
16+
#[cfg(feature = "http_server")]
17+
{
18+
HttpServer::new(Endpoint::builder().bind(MyService).build())
19+
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
20+
.await;
21+
}
1922
}

examples/services/my_virtual_object.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ impl MyVirtualObject {
2222
#[tokio::main]
2323
async fn main() {
2424
tracing_subscriber::fmt::init();
25-
HttpServer::new(Endpoint::builder().bind(MyVirtualObject).build())
26-
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
27-
.await;
25+
#[cfg(feature = "http_server")]
26+
{
27+
HttpServer::new(Endpoint::builder().bind(MyVirtualObject).build())
28+
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
29+
.await;
30+
}
2831
}

examples/services/my_workflow.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ impl MyWorkflow {
2626
#[tokio::main]
2727
async fn main() {
2828
tracing_subscriber::fmt::init();
29-
HttpServer::new(Endpoint::builder().bind(MyWorkflow).build())
30-
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
31-
.await;
29+
#[cfg(feature = "http_server")]
30+
{
31+
HttpServer::new(Endpoint::builder().bind(MyWorkflow).build())
32+
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
33+
.await;
34+
}
3235
}

examples/tunnel.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use restate_sdk::prelude::*;
2+
3+
struct Greeter;
4+
5+
#[service]
6+
impl Greeter {
7+
#[handler]
8+
async fn greet(&self, _ctx: Context<'_>, name: String) -> HandlerResult<String> {
9+
Ok(format!("Greetings {name}"))
10+
}
11+
}
12+
13+
#[tokio::main]
14+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
15+
// Install a subscriber so the tunnel's startup line (including the
16+
// deployment URL to register) is printed.
17+
tracing_subscriber::fmt::init();
18+
19+
let endpoint = Endpoint::builder().bind(Greeter).build();
20+
21+
// The Restate operator supplies the tunnel configuration through the
22+
// RESTATE_INPROC_* environment variables and its projected token Secret.
23+
Tunnel::new(endpoint).run().await?;
24+
25+
Ok(())
26+
}

justfile

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,19 @@ check-fmt:
5252
cargo fmt --all -- --check
5353

5454
clippy: (_target-installed target)
55-
cargo clippy {{ _target-option }} --all-targets --workspace -- -D warnings
55+
cargo clippy {{ _target-option }} --all-targets --all-features --workspace -- -D warnings
5656

5757
# Runs all lints (fmt, clippy, deny)
5858
lint: check-fmt clippy
5959

60+
# Checks the SDK's minimal build and both tunnel crypto-provider configurations.
61+
check-sdk-features: (_target-installed target)
62+
cargo check {{ _target-option }} -p restate-sdk --no-default-features
63+
cargo check {{ _target-option }} -p restate-sdk --no-default-features --features tunnel,rust_crypto
64+
cargo check {{ _target-option }} -p restate-sdk --no-default-features --features tunnel,aws_lc_rs
65+
cargo check {{ _target-option }} -p restate-sdk --features tunnel --example tunnel
66+
cargo tree -p restate-sdk --no-default-features
67+
6068
build *flags: (_target-installed target)
6169
cargo build {{ _target-option }} {{ _features }} {{ flags }}
6270

@@ -66,11 +74,16 @@ print-target:
6674
test: (_target-installed target)
6775
cargo nextest run {{ _target-option }} --all-features --workspace
6876

69-
doctest:
70-
cargo test --doc
77+
test-tunnel: (_target-installed target)
78+
cargo test {{ _target-option }} -p restate-sdk --no-default-features --features tunnel,rust_crypto
79+
cargo test {{ _target-option }} -p restate-sdk --no-default-features --features tunnel,aws_lc_rs
80+
cargo test {{ _target-option }} -p restate-sdk --doc --features tunnel
81+
82+
doctest: (_target-installed target)
83+
cargo test {{ _target-option }} --doc --workspace --all-features
7184

7285
# Runs lints and tests
73-
verify: lint test doctest
86+
verify: lint check-sdk-features test-tunnel test doctest
7487

7588
udeps *flags:
7689
RUSTC_BOOTSTRAP=1 cargo udeps --all-features --all-targets {{ flags }}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Release Notes for Issue #127: In-process Restate Cloud tunnel
2+
3+
## New Feature
4+
5+
The SDK can now serve an endpoint over outbound HTTP/2 connections to Restate Cloud, so a
6+
service no longer needs an inbound HTTP port. It is Unix-only and behind the non-default `tunnel`
7+
feature (plus a crypto backend, `rust_crypto` or `aws_lc_rs`):
8+
9+
```toml
10+
restate-sdk = { version = "0.11", features = ["tunnel"] }
11+
```
12+
13+
The operator supplies the `RESTATE_INPROC_*` environment variables and a projected token Secret, so
14+
the application just runs the tunnel:
15+
16+
```rust
17+
use restate_sdk::prelude::*;
18+
19+
let endpoint = Endpoint::builder().bind(Greeter).build();
20+
Tunnel::new(endpoint).run().await?;
21+
```
22+
23+
`run()` handles SIGINT/SIGTERM: the first signal drains gracefully, a second forces close. For an
24+
application-managed lifecycle, use `connect()` (returns after the first successful handshake):
25+
26+
```rust
27+
let connection = Tunnel::new(endpoint).connect().await?;
28+
println!("{}", connection.info().deployment_url());
29+
connection.shutdown().await?; // graceful drain; or .close() for abrupt
30+
```

0 commit comments

Comments
 (0)