Skip to content

Commit 8e6ae59

Browse files
hugobastnmoutschencalavera
authored
feat(lambda-http): accept http_body::Body in responses (#491)
* feat!(lambda-http): accept http_body::Body in responses Co-authored-by: Nicolas Moutschen <[email protected]> Co-authored-by: David Calavera <[email protected]>
1 parent ff948fa commit 8e6ae59

File tree

12 files changed

+270
-73
lines changed

12 files changed

+270
-73
lines changed

Makefile

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ INTEG_EXTENSIONS := extension-fn extension-trait logs-trait
66
# Using musl to run extensions on both AL1 and AL2
77
INTEG_ARCH := x86_64-unknown-linux-musl
88

9+
define uppercase
10+
$(shell sed -r 's/(^|-)(\w)/\U\2/g' <<< $(1))
11+
endef
12+
913
pr-check:
1014
cargo +1.54.0 check --all
1115
cargo +stable fmt --all -- --check
@@ -15,7 +19,7 @@ pr-check:
1519

1620
integration-tests:
1721
# Build Integration functions
18-
cross build --release --target $(INTEG_ARCH) -p lambda_integration_tests
22+
cargo zigbuild --release --target $(INTEG_ARCH) -p lambda_integration_tests
1923
rm -rf ./build
2024
mkdir -p ./build
2125
${MAKE} ${MAKEOPTS} $(foreach function,${INTEG_FUNCTIONS_BUILD}, build-integration-function-${function})
@@ -37,7 +41,7 @@ build-integration-function-%:
3741

3842
build-integration-extension-%:
3943
mkdir -p ./build/$*/extensions
40-
cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/extensions/$*
44+
cp -v ./target/$(INTEG_ARCH)/release/$* ./build/$*/extensions/$(call uppercase,$*)
4145

4246
invoke-integration-function-%:
4347
aws lambda invoke --function-name $$(aws cloudformation describe-stacks --stack-name $(INTEG_STACK_NAME) \
@@ -56,4 +60,3 @@ invoke-integration-api-%:
5660
curl -X POST -d '{"command": "hello"}' $(API_URL)/trait/post
5761
curl -X POST -d '{"command": "hello"}' $(API_URL)/al2/post
5862
curl -X POST -d '{"command": "hello"}' $(API_URL)/al2-trait/post
59-

examples/http-basic-lambda/src/main.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
use lambda_http::{run, service_fn, Error, IntoResponse, Request, Response};
1+
use lambda_http::{run, service_fn, Body, Error, Request, Response};
22

33
/// This is the main body for the function.
44
/// Write your code inside it.
55
/// There are some code examples in the Runtime repository:
66
/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples
7-
async fn function_handler(_event: Request) -> Result<impl IntoResponse, Error> {
7+
async fn function_handler(_event: Request) -> Result<Response<Body>, Error> {
88
// Extract some useful information from the request
99

1010
// Return something that implements IntoResponse.
1111
// It will be serialized to the right response event automatically by the runtime
1212
let resp = Response::builder()
1313
.status(200)
1414
.header("content-type", "text/html")
15-
.body("Hello AWS Lambda HTTP request")
15+
.body("Hello AWS Lambda HTTP request".into())
1616
.map_err(Box::new)?;
1717
Ok(resp)
1818
}

examples/http-cors/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ async fn main() -> Result<(), Error> {
2929

3030
async fn func(event: Request) -> Result<Response<Body>, Error> {
3131
Ok(match event.query_string_parameters().first("first_name") {
32-
Some(first_name) => format!("Hello, {}!", first_name).into_response(),
32+
Some(first_name) => format!("Hello, {}!", first_name).into_response().await,
3333
_ => Response::builder()
3434
.status(400)
3535
.body("Empty first name".into())

examples/http-query-parameters/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Res
77
async fn function_handler(event: Request) -> Result<impl IntoResponse, Error> {
88
// Extract some useful information from the request
99
Ok(match event.query_string_parameters().first("first_name") {
10-
Some(first_name) => format!("Hello, {}!", first_name).into_response(),
10+
Some(first_name) => format!("Hello, {}!", first_name).into_response().await,
1111
_ => Response::builder()
1212
.status(400)
1313
.body("Empty first name".into())

examples/http-raw-path/src/main.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ async fn main() -> Result<(), Error> {
1515
}
1616

1717
async fn func(event: Request) -> Result<impl IntoResponse, Error> {
18-
let res = format!("The raw path for this request is: {}", event.raw_http_path()).into_response();
18+
let res = format!("The raw path for this request is: {}", event.raw_http_path())
19+
.into_response()
20+
.await;
1921

2022
Ok(res)
2123
}

examples/http-shared-resource/src/main.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ async fn main() -> Result<(), Error> {
2929
// Define a closure here that makes use of the shared client.
3030
let handler_func_closure = move |event: Request| async move {
3131
Result::<Response<Body>, Error>::Ok(match event.query_string_parameters().first("first_name") {
32-
Some(first_name) => shared_client_ref
33-
.response(event.lambda_context().request_id, first_name)
34-
.into_response(),
32+
Some(first_name) => {
33+
shared_client_ref
34+
.response(event.lambda_context().request_id, first_name)
35+
.into_response()
36+
.await
37+
}
3538
_ => Response::builder()
3639
.status(400)
3740
.body("Empty first name".into())

lambda-http/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ base64 = "0.13.0"
2424
bytes = "1"
2525
http = "0.2"
2626
http-body = "0.4"
27+
hyper = "0.14"
2728
lambda_runtime = { path = "../lambda-runtime", version = "0.5" }
2829
serde = { version = "^1", features = ["derive"] }
2930
serde_json = "^1"
3031
serde_urlencoded = "0.7.0"
3132
query_map = { version = "0.5", features = ["url-query"] }
33+
mime = "0.3.16"
34+
encoding_rs = "0.8.31"
3235

3336
[dependencies.aws_lambda_events]
3437
version = "^0.6.3"

lambda-http/src/lib.rs

+20-9
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ extern crate maplit;
6767
pub use http::{self, Response};
6868
use lambda_runtime::LambdaEvent;
6969
pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service};
70+
use request::RequestFuture;
71+
use response::ResponseFuture;
7072

7173
pub mod ext;
7274
pub mod request;
@@ -91,9 +93,9 @@ pub type Request = http::Request<Body>;
9193
///
9294
/// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function.
9395
#[doc(hidden)]
94-
pub struct TransformResponse<'a, R, E> {
95-
request_origin: RequestOrigin,
96-
fut: Pin<Box<dyn Future<Output = Result<R, E>> + 'a>>,
96+
pub enum TransformResponse<'a, R, E> {
97+
Request(RequestOrigin, RequestFuture<'a, R, E>),
98+
Response(RequestOrigin, ResponseFuture),
9799
}
98100

99101
impl<'a, R, E> Future for TransformResponse<'a, R, E>
@@ -103,11 +105,19 @@ where
103105
type Output = Result<LambdaResponse, E>;
104106

105107
fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll<Self::Output> {
106-
match self.fut.as_mut().poll(cx) {
107-
Poll::Ready(result) => Poll::Ready(
108-
result.map(|resp| LambdaResponse::from_response(&self.request_origin, resp.into_response())),
109-
),
110-
Poll::Pending => Poll::Pending,
108+
match *self {
109+
TransformResponse::Request(ref mut origin, ref mut request) => match request.as_mut().poll(cx) {
110+
Poll::Ready(Ok(resp)) => {
111+
*self = TransformResponse::Response(origin.clone(), resp.into_response());
112+
self.poll(cx)
113+
}
114+
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
115+
Poll::Pending => Poll::Pending,
116+
},
117+
TransformResponse::Response(ref mut origin, ref mut response) => match response.as_mut().poll(cx) {
118+
Poll::Ready(resp) => Poll::Ready(Ok(LambdaResponse::from_response(origin, resp))),
119+
Poll::Pending => Poll::Pending,
120+
},
111121
}
112122
}
113123
}
@@ -153,7 +163,8 @@ where
153163
let request_origin = req.payload.request_origin();
154164
let event: Request = req.payload.into();
155165
let fut = Box::pin(self.service.call(event.with_lambda_context(req.context)));
156-
TransformResponse { request_origin, fut }
166+
167+
TransformResponse::Request(request_origin, fut)
157168
}
158169
}
159170

lambda-http/src/request.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use http::header::HeaderName;
1717
use query_map::QueryMap;
1818
use serde::Deserialize;
1919
use serde_json::error::Error as JsonError;
20+
use std::future::Future;
21+
use std::pin::Pin;
2022
use std::{io::Read, mem};
2123

2224
/// Internal representation of an Lambda http event from
@@ -56,9 +58,12 @@ impl LambdaRequest {
5658
}
5759
}
5860

61+
/// RequestFuture type
62+
pub type RequestFuture<'a, R, E> = Pin<Box<dyn Future<Output = Result<R, E>> + 'a>>;
63+
5964
/// Represents the origin from which the lambda was requested from.
6065
#[doc(hidden)]
61-
#[derive(Debug)]
66+
#[derive(Debug, Clone)]
6267
pub enum RequestOrigin {
6368
/// API Gateway request origin
6469
#[cfg(feature = "apigw_rest")]

0 commit comments

Comments
 (0)