lambda_runtime_api_client/
lib.rs1#![deny(clippy::all, clippy::cargo)]
2#![warn(missing_docs, nonstandard_style, rust_2018_idioms)]
3#![allow(clippy::multiple_crate_versions)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6use futures_util::{future::BoxFuture, FutureExt, TryFutureExt};
9use http::{
10 uri::{PathAndQuery, Scheme},
11 Request, Response, Uri,
12};
13use hyper::body::Incoming;
14use hyper_util::client::legacy::connect::HttpConnector;
15use std::{convert::TryInto, fmt::Debug, future};
16
17const USER_AGENT_HEADER: &str = "User-Agent";
18const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION"));
19const CUSTOM_USER_AGENT: Option<&str> = option_env!("LAMBDA_RUNTIME_USER_AGENT");
20
21mod error;
22pub use error::*;
23pub mod body;
24
25#[cfg(feature = "tracing")]
26#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))]
27pub mod tracing;
28
29#[derive(Debug)]
31pub struct Client {
32 pub base: Uri,
34 pub client: hyper_util::client::legacy::Client<HttpConnector, body::Body>,
36}
37
38impl Client {
39 pub fn builder() -> ClientBuilder {
41 ClientBuilder {
42 connector: HttpConnector::new(),
43 uri: None,
44 }
45 }
46}
47
48impl Client {
49 pub fn call(&self, req: Request<body::Body>) -> BoxFuture<'static, Result<Response<Incoming>, BoxError>> {
52 let req = match self.set_origin(req) {
56 Ok(req) => req,
57 Err(err) => return future::ready(Err(err)).boxed(),
58 };
59 self.client.request(req).map_err(Into::into).boxed()
60 }
61
62 fn with(base: Uri, connector: HttpConnector) -> Self {
64 let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
65 .http1_max_buf_size(1024 * 1024)
66 .build(connector);
67 Self { base, client }
68 }
69
70 fn set_origin<B>(&self, req: Request<B>) -> Result<Request<B>, BoxError> {
71 let (mut parts, body) = req.into_parts();
72 let (scheme, authority, base_path) = {
73 let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP);
74 let authority = self.base.authority().expect("Authority not found");
75 let base_path = self.base.path().trim_end_matches('/');
76 (scheme, authority, base_path)
77 };
78 let path = parts.uri.path_and_query().expect("PathAndQuery not found");
79 let pq: PathAndQuery = format!("{base_path}{path}").parse().expect("PathAndQuery invalid");
80
81 let uri = Uri::builder()
82 .scheme(scheme.as_ref())
83 .authority(authority.as_ref())
84 .path_and_query(pq)
85 .build()
86 .map_err(Box::new)?;
87
88 parts.uri = uri;
89 Ok(Request::from_parts(parts, body))
90 }
91}
92
93pub struct ClientBuilder {
95 connector: HttpConnector,
96 uri: Option<http::Uri>,
97}
98
99impl ClientBuilder {
100 pub fn with_connector(self, connector: HttpConnector) -> ClientBuilder {
102 ClientBuilder {
103 connector,
104 uri: self.uri,
105 }
106 }
107
108 pub fn with_endpoint(self, uri: http::Uri) -> Self {
111 Self { uri: Some(uri), ..self }
112 }
113
114 pub fn build(self) -> Result<Client, Error> {
116 let uri = match self.uri {
117 Some(uri) => uri,
118 None => {
119 let uri = std::env::var("AWS_LAMBDA_RUNTIME_API").expect("Missing AWS_LAMBDA_RUNTIME_API env var");
120 uri.try_into().expect("Unable to convert to URL")
121 }
122 };
123 Ok(Client::with(uri, self.connector))
124 }
125}
126
127pub fn build_request() -> http::request::Builder {
133 const USER_AGENT: &str = match CUSTOM_USER_AGENT {
134 Some(value) => value,
135 None => DEFAULT_USER_AGENT,
136 };
137 http::Request::builder().header(USER_AGENT_HEADER, USER_AGENT)
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_set_origin() {
146 let base = "http://localhost:9001";
147 let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap();
148 let req = build_request()
149 .uri("/2018-06-01/runtime/invocation/next")
150 .body(())
151 .unwrap();
152 let req = client.set_origin(req).unwrap();
153 assert_eq!(
154 "http://localhost:9001/2018-06-01/runtime/invocation/next",
155 &req.uri().to_string()
156 );
157 }
158
159 #[test]
160 fn test_set_origin_with_base_path() {
161 let base = "http://localhost:9001/foo";
162 let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap();
163 let req = build_request()
164 .uri("/2018-06-01/runtime/invocation/next")
165 .body(())
166 .unwrap();
167 let req = client.set_origin(req).unwrap();
168 assert_eq!(
169 "http://localhost:9001/foo/2018-06-01/runtime/invocation/next",
170 &req.uri().to_string()
171 );
172
173 let base = "http://localhost:9001/foo/";
174 let client = Client::builder().with_endpoint(base.parse().unwrap()).build().unwrap();
175 let req = build_request()
176 .uri("/2018-06-01/runtime/invocation/next")
177 .body(())
178 .unwrap();
179 let req = client.set_origin(req).unwrap();
180 assert_eq!(
181 "http://localhost:9001/foo/2018-06-01/runtime/invocation/next",
182 &req.uri().to_string()
183 );
184 }
185}