Skip to content

Commit 406c02b

Browse files
committed
Add lambda runtime support
Summary: This PR adds the support to run the user services on Amazon Lambda
1 parent 01168ff commit 406c02b

File tree

6 files changed

+344
-37
lines changed

6 files changed

+344
-37
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ default = ["http_server", "rand", "uuid", "tracing-span-filter"]
2222
hyper = ["dep:hyper", "http-body-util", "restate-sdk-shared-core/http"]
2323
http_server = ["hyper", "hyper/server", "hyper/http2", "hyper-util", "tokio/net", "tokio/signal", "tokio/macros"]
2424
tracing-span-filter = ["dep:tracing-subscriber"]
25+
lambda = [ "dep:http-serde", "dep:lambda_runtime", "dep:aws_lambda_events"]
2526

2627
[dependencies]
2728
bytes = "1.10"
@@ -44,6 +45,9 @@ tokio = { version = "1.44", default-features = false, features = ["sync"] }
4445
tracing = "0.1"
4546
tracing-subscriber = { version = "0.3", features = ["registry"], optional = true }
4647
uuid = { version = "1.16.0", optional = true }
48+
http-serde = { version = "2.1.1", optional = true }
49+
aws_lambda_events = { version = "0.16.1", optional = true }
50+
lambda_runtime = { version = "0.14.2", optional = true }
4751

4852
[dev-dependencies]
4953
tokio = { version = "1", features = ["full"] }

README.md

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010

1111
## Community
1212

13-
* 🤗️ [Join our online community](https://discord.gg/skW3AZ6uGd) for help, sharing feedback and talking to the community.
14-
* 📖 [Check out our documentation](https://docs.restate.dev) to get quickly started!
15-
* 📣 [Follow us on Twitter](https://twitter.com/restatedev) for staying up to date.
16-
* 🙋 [Create a GitHub issue](https://github.com/restatedev/sdk-java/issues) for requesting a new feature or reporting a problem.
17-
* 🏠 [Visit our GitHub org](https://github.com/restatedev) for exploring other repositories.
13+
- 🤗️ [Join our online community](https://discord.gg/skW3AZ6uGd) for help, sharing feedback and talking to the community.
14+
- 📖 [Check out our documentation](https://docs.restate.dev) to get quickly started!
15+
- 📣 [Follow us on Twitter](https://twitter.com/restatedev) for staying up to date.
16+
- 🙋 [Create a GitHub issue](https://github.com/restatedev/sdk-java/issues) for requesting a new feature or reporting a problem.
17+
- 🏠 [Visit our GitHub org](https://github.com/restatedev) for exploring other repositories.
1818

1919
## Using the SDK
2020

@@ -58,6 +58,75 @@ async fn main() {
5858
}
5959
```
6060

61+
## Running on Lambda
62+
63+
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.
64+
65+
### Setup
66+
67+
First, enable the `lambda` feature in your `Cargo.toml`:
68+
69+
```toml
70+
[dependencies]
71+
restate-sdk = { version = "0.1", features = ["lambda"] }
72+
tokio = { version = "1", features = ["full"] }
73+
```
74+
75+
### Basic Lambda Service
76+
77+
Here's how to create a simple Lambda service:
78+
79+
```rust
80+
use restate_sdk::prelude::*;
81+
82+
#[restate_sdk::service]
83+
trait Greeter {
84+
async fn greet(name: String) -> HandlerResult<String>;
85+
}
86+
87+
struct GreeterImpl;
88+
89+
impl Greeter for GreeterImpl {
90+
async fn greet(&self, _: Context<'_>, name: String) -> HandlerResult<String> {
91+
Ok(format!("Greetings {name}"))
92+
}
93+
}
94+
95+
#[tokio::main]
96+
async fn main() {
97+
// To enable logging/tracing
98+
// check https://docs.aws.amazon.com/lambda/latest/dg/rust-logging.html#rust-logging-tracing
99+
100+
// Build and run the Lambda endpoint
101+
LambdaEndpoint::run(
102+
Endpoint::builder()
103+
.bind(GreeterImpl.serve())
104+
.build(),
105+
)
106+
.await
107+
.unwrap();
108+
}
109+
```
110+
111+
### Deployment
112+
113+
1. Install `cargo-lambda`
114+
```
115+
cargo install cargo-lambda
116+
```
117+
2. Build your Lambda function:
118+
119+
```bash
120+
cargo lambda build --release --arm64 --output-format zip
121+
```
122+
123+
3. Create a Lambda function with the following configuration:
124+
125+
- **Runtime**: Amazon Linux 2023
126+
- **Architecture**: arm64
127+
128+
4. Upload your `zip` file to the Lambda function.
129+
61130
### Logging
62131

63132
The SDK uses tokio's [`tracing`](https://docs.rs/tracing/latest/tracing/) crate to generate logs.
@@ -121,15 +190,15 @@ The Rust SDK is currently in active development, and might break across releases
121190

122191
The compatibility with Restate is described in the following table:
123192

124-
| Restate Server\sdk-rust | 0.0 - 0.2 | 0.3 | 0.4 - 0.5 | 0.6 |
125-
|-------------------------|-----------|-----|-----------|------------------|
126-
| 1.0 | | | ||
127-
| 1.1 | | | ||
128-
| 1.2 | | | ||
129-
| 1.3 | | | | ✅ <sup>(1)</sup> |
130-
| 1.4 | | | ||
193+
| Restate Server\sdk-rust | 0.0 - 0.2 | 0.3 | 0.4 - 0.5 | 0.6 |
194+
| ----------------------- | --------- | --- | --------- | ----------------- |
195+
| 1.0 |||||
196+
| 1.1 |||||
197+
| 1.2 |||||
198+
| 1.3 |||| ✅ <sup>(1)</sup> |
199+
| 1.4 |||||
131200

132-
<sup>(1)</sup> **Note** `bind_with_options` works only from Restate 1.4 onward.
201+
<sup>(1)</sup> **Note** `bind_with_options` works only from Restate 1.4 onward.
133202

134203
## Contributing
135204

src/endpoint/mod.rs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ impl Endpoint {
190190
pub fn handle<B: Body<Data = Bytes, Error: Into<BoxError> + Send> + Send + 'static>(
191191
&self,
192192
req: http::Request<B>,
193-
) -> Result<http::Response<ResponseBody>, Error> {
193+
) -> http::Response<ResponseBody> {
194194
self.handle_with_options(req, HandleOptions::default())
195195
}
196196

@@ -201,13 +201,13 @@ impl Endpoint {
201201
&self,
202202
req: http::Request<B>,
203203
options: HandleOptions,
204-
) -> Result<http::Response<ResponseBody>, Error> {
204+
) -> http::Response<ResponseBody> {
205205
let (parts, body) = req.into_parts();
206206
let path = parts.uri.path();
207207
let headers = parts.headers;
208208

209209
if let Err(e) = self.0.identity_verifier.verify_identity(&headers, path) {
210-
return Err(ErrorInner::IdentityVerification(e).into());
210+
return error_response(ErrorInner::IdentityVerification(e));
211211
}
212212

213213
let parts: Vec<&str> = path.split('/').collect();
@@ -221,17 +221,17 @@ impl Endpoint {
221221

222222
// Parse service name/handler name
223223
let (svc_name, handler_name) = match parts.get(parts.len() - 3..) {
224-
None => return Ok(error_response(ErrorInner::BadPath(path.to_owned()))),
224+
None => return error_response(ErrorInner::BadPath(path.to_owned())),
225225
Some(last_elements) if last_elements[0] != "invoke" => {
226-
return Ok(error_response(ErrorInner::BadPath(path.to_owned())))
226+
return error_response(ErrorInner::BadPath(path.to_owned()))
227227
}
228228
Some(last_elements) => (last_elements[1].to_owned(), last_elements[2].to_owned()),
229229
};
230230

231231
// Prepare vm
232232
let vm = match CoreVM::new(headers, Default::default()) {
233233
Ok(vm) => vm,
234-
Err(e) => return Ok(error_response(e)),
234+
Err(e) => return error_response(e),
235235
};
236236
let ResponseHead {
237237
status_code,
@@ -241,9 +241,7 @@ impl Endpoint {
241241

242242
// Resolve service
243243
if !self.0.svcs.contains_key(&svc_name) {
244-
return Ok(error_response(ErrorInner::UnknownService(
245-
svc_name.to_owned(),
246-
)));
244+
return error_response(ErrorInner::UnknownService(svc_name.to_owned()));
247245
}
248246

249247
// Prepare handle_invocation future
@@ -269,7 +267,7 @@ impl Endpoint {
269267
invocation_response_builder =
270268
invocation_response_builder.header(key.deref(), value.deref());
271269
}
272-
Ok(invocation_response_builder
270+
invocation_response_builder
273271
.body(
274272
Either::Right(InvocationRunnerBody {
275273
fut: Some(handle_invocation_fut),
@@ -278,25 +276,25 @@ impl Endpoint {
278276
})
279277
.into(),
280278
)
281-
.expect("Headers should be valid"))
279+
.expect("Headers should be valid")
282280
}
283281

284-
fn handle_health(&self) -> Result<http::Response<ResponseBody>, Error> {
285-
Ok(simple_response(200, vec![], Bytes::default()))
282+
fn handle_health(&self) -> http::Response<ResponseBody> {
283+
simple_response(200, vec![], Bytes::default())
286284
}
287285

288286
fn handle_discovery(
289287
&self,
290288
headers: http::HeaderMap,
291289
protocol_mode: ProtocolMode,
292-
) -> Result<http::Response<ResponseBody>, Error> {
290+
) -> http::Response<ResponseBody> {
293291
// Extract Accept header from request
294292
let accept_header = match headers
295293
.extract("accept")
296294
.map_err(|e| ErrorInner::Header("accept".to_owned(), Box::new(e)))
297295
{
298296
Ok(h) => h,
299-
Err(e) => return Ok(error_response(e)),
297+
Err(e) => return error_response(e),
300298
};
301299

302300
// Negotiate discovery protocol version
@@ -310,17 +308,15 @@ impl Endpoint {
310308
version = 2;
311309
content_type = DISCOVERY_CONTENT_TYPE_V2;
312310
} else {
313-
return Ok(error_response(ErrorInner::BadDiscoveryVersion(
314-
accept.to_owned(),
315-
)));
311+
return error_response(ErrorInner::BadDiscoveryVersion(accept.to_owned()));
316312
}
317313
}
318314

319315
if let Err(e) = self.validate_discovery_request(version) {
320-
return Ok(error_response(e));
316+
return error_response(e);
321317
}
322318

323-
Ok(simple_response(
319+
simple_response(
324320
200,
325321
vec![Header {
326322
key: "content-type".into(),
@@ -340,7 +336,7 @@ impl Endpoint {
340336
})
341337
.expect("Discovery should be serializable"),
342338
),
343-
))
339+
)
344340
}
345341

346342
fn validate_discovery_request(&self, version: usize) -> Result<(), ErrorInner> {

src/hyper.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::endpoint::{Endpoint, HandleOptions, ProtocolMode};
66
use http::{Request, Response};
77
use hyper::body::Incoming;
88
use hyper::service::Service;
9+
use std::convert::Infallible;
910
use std::future::{ready, Ready};
1011

1112
/// Wraps [`Endpoint`] to implement hyper [`Service`].
@@ -20,15 +21,15 @@ impl HyperEndpoint {
2021

2122
impl Service<Request<Incoming>> for HyperEndpoint {
2223
type Response = Response<endpoint::ResponseBody>;
23-
type Error = endpoint::Error;
24+
type Error = Infallible;
2425
type Future = Ready<Result<Self::Response, Self::Error>>;
2526

2627
fn call(&self, req: Request<Incoming>) -> Self::Future {
27-
ready(self.0.handle_with_options(
28+
ready(Ok(self.0.handle_with_options(
2829
req,
2930
HandleOptions {
3031
protocol_mode: ProtocolMode::BidiStream,
3132
},
32-
))
33+
)))
3334
}
3435
}

0 commit comments

Comments
 (0)