lambda_web/
actix4.rs

1// SPDX-License-Identifier: MIT
2//! Run Actix Web on AWS Lambda
3//!
4//!
5use crate::request::LambdaHttpEvent;
6use core::convert::TryFrom;
7use core::future::Future;
8use lambda_runtime::{Error as LambdaError, LambdaEvent, Service as LambdaService};
9use std::pin::Pin;
10
11/// Run Actix web application on AWS Lambda
12///
13/// ```no_run
14/// use lambda_web::actix_web::{self, get, App, HttpServer, Responder};
15/// use lambda_web::{is_running_on_lambda, run_actix_on_lambda, LambdaError};
16///
17/// #[get("/")]
18/// async fn hello() -> impl Responder {
19///     format!("Hello")
20/// }
21///
22/// #[actix_web::main]
23/// async fn main() -> Result<(),LambdaError> {
24///     let factory = move || {
25///         App::new().service(hello)
26///     };
27///     if is_running_on_lambda() {
28///         // Run on AWS Lambda
29///         run_actix_on_lambda(factory).await?;
30///     } else {
31///         // Run local server
32///         HttpServer::new(factory).bind("127.0.0.1:8080")?.run().await?;
33///     }
34///     Ok(())
35/// }
36/// ```
37///
38pub async fn run_actix_on_lambda<F, I, S, B>(factory: F) -> Result<(), LambdaError>
39where
40    F: Fn() -> I + Send + Clone + 'static,
41    I: actix_service::IntoServiceFactory<S, actix_http::Request>,
42    S: actix_service::ServiceFactory<
43            actix_http::Request,
44            Config = actix_web::dev::AppConfig,
45            Response = actix_web::dev::ServiceResponse<B>,
46            Error = actix_web::Error,
47        > + 'static,
48    S::InitError: std::fmt::Debug,
49    B: actix_web::body::MessageBody,
50    B::Error: std::fmt::Display,
51    <B as actix_web::body::MessageBody>::Error: std::fmt::Debug,
52{
53    // Prepare actix_service::Service
54    let srv = factory().into_factory();
55    let new_svc = srv
56        .new_service(actix_web::dev::AppConfig::default())
57        .await
58        .unwrap();
59
60    lambda_runtime::run(ActixHandler(new_svc)).await?;
61
62    Ok(())
63}
64
65/// Lambda_runtime handler for Actix Web
66struct ActixHandler<S, B>(S)
67where
68    S: actix_service::Service<
69            actix_http::Request,
70            Response = actix_web::dev::ServiceResponse<B>,
71            Error = actix_web::Error,
72        > + 'static,
73    B: actix_web::body::MessageBody,
74    B::Error: std::fmt::Display,
75    <B as actix_web::body::MessageBody>::Error: std::fmt::Debug;
76
77impl<S, B> LambdaService<LambdaEvent<LambdaHttpEvent<'_>>> for ActixHandler<S, B>
78where
79    S: actix_service::Service<
80            actix_http::Request,
81            Response = actix_web::dev::ServiceResponse<B>,
82            Error = actix_web::Error,
83        > + 'static,
84    B: actix_web::body::MessageBody,
85    B::Error: std::fmt::Display,
86    <B as actix_web::body::MessageBody>::Error: std::fmt::Debug,
87{
88    type Response = serde_json::Value;
89    type Error = actix_web::Error;
90    type Future = Pin<Box<dyn Future<Output = Result<serde_json::Value, Self::Error>>>>;
91
92    /// Returns Poll::Ready when servie can process more requrests.
93    fn poll_ready(
94        &mut self,
95        cx: &mut core::task::Context<'_>,
96    ) -> core::task::Poll<Result<(), Self::Error>> {
97        self.0.poll_ready(cx)
98    }
99
100    /// Lambda handler function
101    /// Parse Lambda event as Actix-web request,
102    /// serialize Actix-web response to Lambda JSON response
103    fn call(&mut self, req: LambdaEvent<LambdaHttpEvent<'_>>) -> Self::Future {
104        use serde_json::json;
105
106        let event = req.payload;
107        let _context = req.context;
108
109        // check if web client supports content-encoding: br
110        let client_br = event.client_supports_brotli();
111        // multi-value-headers response format
112        let multi_value = event.multi_value();
113
114        // Parse request
115        let actix_request = actix_http::Request::try_from(event);
116
117        // Call Actix service when request parsing succeeded
118        let svc_call = actix_request.map(|req| self.0.call(req));
119
120        let fut = async move {
121            match svc_call {
122                Ok(svc_fut) => {
123                    // Request parsing succeeded
124                    if let Ok(response) = svc_fut.await {
125                        // Returns as API Gateway response
126                        api_gateway_response_from_actix_web(response, client_br, multi_value)
127                            .await
128                            .or_else(|_err| {
129                                Ok(json!({
130                                    "isBase64Encoded": false,
131                                    "statusCode": 500u16,
132                                    "headers": { "content-type": "text/plain"},
133                                    "body": "Internal Server Error"
134                                }))
135                            })
136                    } else {
137                        // Some Actix web error -> 500 Internal Server Error
138                        Ok(json!({
139                            "isBase64Encoded": false,
140                            "statusCode": 500u16,
141                            "headers": { "content-type": "text/plain"},
142                            "body": "Internal Server Error"
143                        }))
144                    }
145                }
146                Err(_request_err) => {
147                    // Request parsing error
148                    Ok(json!({
149                        "isBase64Encoded": false,
150                        "statusCode": 400u16,
151                        "headers": { "content-type": "text/plain"},
152                        "body": "Bad Request"
153                    }))
154                }
155            }
156        };
157        Box::pin(fut)
158    }
159}
160
161impl TryFrom<LambdaHttpEvent<'_>> for actix_http::Request {
162    type Error = LambdaError;
163
164    /// Actix-web Request from API Gateway event
165    fn try_from(event: LambdaHttpEvent) -> Result<Self, Self::Error> {
166        use actix_web::http::Method;
167
168        // Construct actix_web request
169        let method = Method::try_from(event.method())?;
170        let req = actix_web::test::TestRequest::with_uri(&event.path_query()).method(method);
171
172        // Source IP
173        let req = if let Some(source_ip) = event.source_ip() {
174            req.peer_addr(std::net::SocketAddr::from((source_ip, 0u16)))
175        } else {
176            req
177        };
178
179        // Headers
180        let req = event
181            .headers()
182            .into_iter()
183            .fold(req, |req, (k, v)| req.insert_header((k, &v as &str)));
184
185        // Body
186        let req = req.set_payload(event.body()?);
187
188        Ok(req.to_request())
189    }
190}
191
192impl<B> crate::brotli::ResponseCompression for actix_web::dev::ServiceResponse<B> {
193    /// Content-Encoding header value
194    fn content_encoding<'a>(&'a self) -> Option<&'a str> {
195        self.headers()
196            .get(actix_web::http::header::CONTENT_ENCODING)
197            .and_then(|val| val.to_str().ok())
198    }
199
200    /// Content-Type header value
201    fn content_type<'a>(&'a self) -> Option<&'a str> {
202        self.headers()
203            .get(actix_web::http::header::CONTENT_TYPE)
204            .and_then(|val| val.to_str().ok())
205    }
206}
207
208/// API Gateway response from Actix-web response
209async fn api_gateway_response_from_actix_web<B: actix_web::body::MessageBody>(
210    response: actix_web::dev::ServiceResponse<B>,
211    client_support_br: bool,
212    multi_value: bool,
213) -> Result<serde_json::Value, B::Error> {
214    use crate::brotli::ResponseCompression;
215    use actix_web::http::header::SET_COOKIE;
216    use serde_json::json;
217
218    // HTTP status
219    let status_code = response.status().as_u16();
220
221    // Convert header to JSON map
222    let mut cookies = Vec::<String>::new();
223    let mut headers = serde_json::Map::new();
224    for (k, v) in response.headers() {
225        if let Ok(value_str) = v.to_str() {
226            if multi_value {
227                // REST API format, returns multiValueHeaders
228                if let Some(values) = headers.get_mut(k.as_str()) {
229                    if let Some(value_ary) = values.as_array_mut() {
230                        value_ary.push(json!(value_str));
231                    }
232                } else {
233                    headers.insert(k.as_str().to_string(), json!([value_str]));
234                }
235            } else {
236                // HTTP API v2 format, returns headers
237                if k == SET_COOKIE {
238                    cookies.push(value_str.to_string());
239                } else {
240                    headers.insert(k.as_str().to_string(), json!(value_str));
241                }
242            }
243        }
244    }
245
246    // check if response should be compressed
247    let compress = client_support_br && response.can_brotli_compress();
248    let body_bytes = actix_web::body::to_bytes(response.into_body()).await?;
249    let body_base64 = if compress {
250        if multi_value {
251            headers.insert("content-encoding".to_string(), json!(["br"]));
252        } else {
253            headers.insert("content-encoding".to_string(), json!("br"));
254        }
255        crate::brotli::compress_response_body(&body_bytes)
256    } else {
257        base64::encode(body_bytes)
258    };
259
260    if multi_value {
261        Ok(json!({
262            "isBase64Encoded": true,
263            "statusCode": status_code,
264            "multiValueHeaders": headers,
265            "body": body_base64
266        }))
267    } else {
268        Ok(json!({
269            "isBase64Encoded": true,
270            "statusCode": status_code,
271            "cookies": cookies,
272            "headers": headers,
273            "body": body_base64
274        }))
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use crate::{request::LambdaHttpEvent, test_consts::*};
282
283    // Request JSON to actix_http::Request
284    fn prepare_request(event_str: &str) -> actix_http::Request {
285        let reqjson: LambdaHttpEvent = serde_json::from_str(event_str).unwrap();
286        actix_http::Request::try_from(reqjson).unwrap()
287    }
288
289    #[test]
290    fn test_path_decode() {
291        let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY);
292        assert_eq!(req.uri().path(), "/");
293        let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY);
294        assert_eq!(req.uri().path(), "/stage/");
295
296        let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_NOQUERY);
297        assert_eq!(req.uri().path(), "/somewhere");
298        let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_NOQUERY);
299        assert_eq!(req.uri().path(), "/stage/somewhere");
300
301        let req = prepare_request(API_GATEWAY_V2_GET_SPACEPATH_NOQUERY);
302        assert_eq!(req.uri().path(), "/path%20with/space");
303        let req = prepare_request(API_GATEWAY_REST_GET_SPACEPATH_NOQUERY);
304        assert_eq!(req.uri().path(), "/stage/path%20with/space");
305
306        let req = prepare_request(API_GATEWAY_V2_GET_PERCENTPATH_NOQUERY);
307        assert_eq!(req.uri().path(), "/path%25with/percent");
308        let req = prepare_request(API_GATEWAY_REST_GET_PERCENTPATH_NOQUERY);
309        assert_eq!(req.uri().path(), "/stage/path%25with/percent");
310
311        let req = prepare_request(API_GATEWAY_V2_GET_UTF8PATH_NOQUERY);
312        assert_eq!(
313            req.uri().path(),
314            "/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D"
315        );
316        let req = prepare_request(API_GATEWAY_REST_GET_UTF8PATH_NOQUERY);
317        assert_eq!(
318            req.uri().path(),
319            "/stage/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D"
320        );
321    }
322
323    #[test]
324    fn test_query_decode() {
325        let req = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY);
326        assert_eq!(req.uri().query(), Some("key=value"));
327        let req = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY);
328        assert_eq!(req.uri().query(), Some("key=value"));
329
330        let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_ONEQUERY);
331        assert_eq!(req.uri().query(), Some("key=value"));
332        let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_ONEQUERY);
333        assert_eq!(req.uri().query(), Some("key=value"));
334
335        let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_TWOQUERY);
336        assert_eq!(req.uri().query(), Some("key1=value1&key2=value2"));
337        let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_TWOQUERY);
338        assert!(
339            req.uri().query() == Some("key1=value1&key2=value2")
340                || req.uri().query() == Some("key2=value2&key1=value1")
341        );
342
343        let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_SPACEQUERY);
344        assert_eq!(req.uri().query(), Some("key=value1+value2"));
345        let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_SPACEQUERY);
346        assert_eq!(req.uri().query(), Some("key=value1%20value2"));
347
348        let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_UTF8QUERY);
349        assert_eq!(req.uri().query(), Some("key=%E6%97%A5%E6%9C%AC%E8%AA%9E"));
350        let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_UTF8QUERY);
351        assert_eq!(req.uri().query(), Some("key=%E6%97%A5%E6%9C%AC%E8%AA%9E"));
352    }
353
354    #[test]
355    fn test_remote_ip_decode() {
356        use std::net::IpAddr;
357        use std::str::FromStr;
358
359        let req = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY);
360        assert_eq!(
361            req.peer_addr().unwrap().ip(),
362            IpAddr::from_str("1.2.3.4").unwrap()
363        );
364        let req = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY);
365        assert_eq!(
366            req.peer_addr().unwrap().ip(),
367            IpAddr::from_str("1.2.3.4").unwrap()
368        );
369
370        let req = prepare_request(API_GATEWAY_V2_GET_REMOTE_IPV6);
371        assert_eq!(
372            req.peer_addr().unwrap().ip(),
373            IpAddr::from_str("2404:6800:400a:80c::2004").unwrap()
374        );
375        let req = prepare_request(API_GATEWAY_REST_GET_REMOTE_IPV6);
376        assert_eq!(
377            req.peer_addr().unwrap().ip(),
378            IpAddr::from_str("2404:6800:400a:80c::2004").unwrap()
379        );
380    }
381
382    #[tokio::test]
383    async fn test_form_post() {
384        use actix_web::http::Method;
385
386        let req = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED);
387        assert_eq!(req.method(), Method::POST);
388        let req = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED);
389        assert_eq!(req.method(), Method::POST);
390
391        // Base64 encoded
392        let req = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED_B64);
393        assert_eq!(req.method(), Method::POST);
394        let req = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED_B64);
395        assert_eq!(req.method(), Method::POST);
396    }
397
398    #[test]
399    fn test_parse_header() {
400        let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY);
401        assert_eq!(req.head().headers.get("x-forwarded-port").unwrap(), &"443");
402        assert_eq!(
403            req.head().headers.get("x-forwarded-proto").unwrap(),
404            &"https"
405        );
406        let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY);
407        assert_eq!(req.head().headers.get("x-forwarded-port").unwrap(), &"443");
408        assert_eq!(
409            req.head().headers.get("x-forwarded-proto").unwrap(),
410            &"https"
411        );
412    }
413
414    #[test]
415    fn test_parse_cookies() {
416        let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY);
417        assert_eq!(req.head().headers.get("cookie"), None);
418
419        let req = prepare_request(API_GATEWAY_V2_GET_ONE_COOKIE);
420        assert_eq!(req.head().headers.get("cookie").unwrap(), &"cookie1=value1");
421        let req = prepare_request(API_GATEWAY_REST_GET_ONE_COOKIE);
422        assert_eq!(req.head().headers.get("cookie").unwrap(), &"cookie1=value1");
423
424        let req = prepare_request(API_GATEWAY_V2_GET_TWO_COOKIES);
425        assert!(
426            req.head().headers.get("cookie").unwrap() == &"cookie2=value2; cookie1=value1"
427                || req.head().headers.get("cookie").unwrap() == &"cookie1=value1; cookie2=value2"
428        );
429        let req = prepare_request(API_GATEWAY_REST_GET_TWO_COOKIES);
430        assert!(
431            req.head().headers.get("cookie").unwrap() == &"cookie2=value2; cookie1=value1"
432                || req.head().headers.get("cookie").unwrap() == &"cookie1=value1; cookie2=value2"
433        );
434    }
435}