Skip to content

Commit d4b9d6d

Browse files
authored
Add tcp readiness check (#86)
1 parent 1c13c79 commit d4b9d6d

File tree

2 files changed

+66
-22
lines changed

2 files changed

+66
-22
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,14 @@ After passing readiness check, Lambda Web Adapter will start Lambda Runtime and
6767

6868
The readiness check port/path and traffic port can be configured using environment variables. These environment variables can be defined either within docker file or as Lambda function configuration.
6969

70-
| Environment Variable | Description | Default |
71-
|----------------------|----------------------------------------------------------------------|---------|
72-
| PORT | traffic port | "8080" |
73-
| READINESS_CHECK_PORT | readiness check port, default to the traffic port | PORT |
74-
| READINESS_CHECK_PATH | readiness check path | "/" |
75-
| ASYNC_INIT | enable asynchronous initialization for long initialization functions | "false" |
76-
| REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None |
70+
| Environment Variable | Description | Default |
71+
|--------------------------|----------------------------------------------------------------------|---------|
72+
| PORT | traffic port | "8080" |
73+
| READINESS_CHECK_PORT | readiness check port, default to the traffic port | PORT |
74+
| READINESS_CHECK_PATH | readiness check path | "/" |
75+
| READINESS_CHECK_PROTOCOL | readiness check protocol: "http" or "tcp", default is "http" | "http" |
76+
| ASYNC_INIT | enable asynchronous initialization for long initialization functions | "false" |
77+
| REMOVE_BASE_PATH | (optional) the base path to be removed from request path | None |
7778

7879
**ASYNC_INIT** Lambda managed runtimes offer up to 10 seconds for function initialization. During this period of time, Lambda functions have burst of CPU to accelerate initialization, and it is free.
7980
If a lambda function couldn't complete the initialization within 10 seconds, Lambda will restart the function, and bill for the initialization.

src/lib.rs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,42 @@ use std::{
1616
},
1717
time::Duration,
1818
};
19+
use tokio::net::TcpStream;
1920
use tokio::time::timeout;
2021
use tokio_retry::{strategy::FixedInterval, Retry};
2122
use tower::Service;
2223

2324
pub use lambda_http::Error;
2425

26+
#[derive(Debug, Clone, Copy)]
27+
pub enum Protocol {
28+
Http,
29+
Tcp,
30+
}
31+
32+
impl Default for Protocol {
33+
fn default() -> Self {
34+
Protocol::Http
35+
}
36+
}
37+
38+
impl From<&str> for Protocol {
39+
fn from(value: &str) -> Self {
40+
match value.to_lowercase().as_str() {
41+
"http" => Protocol::Http,
42+
"tcp" => Protocol::Tcp,
43+
_ => Protocol::Http,
44+
}
45+
}
46+
}
47+
2548
#[derive(Default)]
2649
pub struct AdapterOptions {
2750
host: String,
2851
port: String,
2952
readiness_check_port: String,
3053
readiness_check_path: String,
54+
readiness_check_protocol: Protocol,
3155
base_path: Option<String>,
3256
async_init: bool,
3357
}
@@ -40,6 +64,10 @@ impl AdapterOptions {
4064
readiness_check_port: env::var("READINESS_CHECK_PORT")
4165
.unwrap_or_else(|_| env::var("PORT").unwrap_or_else(|_| "8080".to_string())),
4266
readiness_check_path: env::var("READINESS_CHECK_PATH").unwrap_or_else(|_| "/".to_string()),
67+
readiness_check_protocol: env::var("READINESS_CHECK_PROTOCOL")
68+
.unwrap_or_else(|_| "HTTP".to_string())
69+
.as_str()
70+
.into(),
4371
base_path: env::var("REMOVE_BASE_PATH").ok(),
4472
async_init: env::var("ASYNC_INIT")
4573
.unwrap_or_else(|_| "false".to_string())
@@ -51,7 +79,8 @@ impl AdapterOptions {
5179

5280
pub struct Adapter {
5381
client: Arc<Client>,
54-
healthcheck_url: String,
82+
healthcheck_url: Url,
83+
healthcheck_protocol: Protocol,
5584
async_init: bool,
5685
ready_at_init: Arc<AtomicBool>,
5786
domain: Url,
@@ -72,13 +101,16 @@ impl Adapter {
72101
let healthcheck_url = format!(
73102
"http://{}:{}{}",
74103
options.host, options.readiness_check_port, options.readiness_check_path
75-
);
104+
)
105+
.parse()
106+
.unwrap();
76107

77108
let domain = format!("http://{}:{}", options.host, options.port).parse().unwrap();
78109

79110
Adapter {
80111
client: Arc::new(client),
81112
healthcheck_url,
113+
healthcheck_protocol: options.readiness_check_protocol,
82114
domain,
83115
base_path: options.base_path.clone(),
84116
async_init: options.async_init,
@@ -126,7 +158,8 @@ impl Adapter {
126158

127159
async fn check_readiness(&self) -> bool {
128160
let url = self.healthcheck_url.clone();
129-
is_web_ready(&url).await
161+
let protocol = self.healthcheck_protocol;
162+
is_web_ready(&url, &protocol).await
130163
}
131164

132165
/// Run the adapter to take events from Lambda.
@@ -151,6 +184,7 @@ impl Service<Request> for Adapter {
151184
let client = self.client.clone();
152185
let ready_at_init = self.ready_at_init.clone();
153186
let healthcheck_url = self.healthcheck_url.clone();
187+
let healthcheck_protocol = self.healthcheck_protocol;
154188
let domain = self.domain.clone();
155189
let base_path = self.base_path.clone();
156190

@@ -162,24 +196,27 @@ impl Service<Request> for Adapter {
162196
base_path,
163197
domain,
164198
healthcheck_url,
199+
healthcheck_protocol,
165200
event,
166201
)
167202
.await
168203
})
169204
}
170205
}
171206

207+
#[allow(clippy::too_many_arguments)]
172208
async fn fetch_response(
173209
async_init: bool,
174210
ready_at_init: Arc<AtomicBool>,
175211
client: Arc<Client>,
176212
base_path: Option<String>,
177213
domain: Url,
178-
healthcheck_url: String,
214+
healthcheck_url: Url,
215+
healthcheck_protocol: Protocol,
179216
event: Request,
180217
) -> Result<Response<Body>, Error> {
181218
if async_init && !ready_at_init.load(Ordering::SeqCst) {
182-
is_web_ready(&healthcheck_url).await;
219+
is_web_ready(&healthcheck_url, &healthcheck_protocol).await;
183220
ready_at_init.store(true, Ordering::SeqCst);
184221
}
185222

@@ -221,27 +258,33 @@ async fn fetch_response(
221258
Ok(resp)
222259
}
223260

224-
async fn is_web_ready(url: &str) -> bool {
225-
Retry::spawn(FixedInterval::from_millis(10), || check_web_readiness(url))
261+
async fn is_web_ready(url: &Url, protocol: &Protocol) -> bool {
262+
Retry::spawn(FixedInterval::from_millis(10), || check_web_readiness(url, protocol))
226263
.await
227264
.is_ok()
228265
}
229266

230-
async fn check_web_readiness(url: &str) -> Result<(), i8> {
231-
match reqwest::get(url).await {
232-
Ok(response) if { response.status().is_success() } => Ok(()),
233-
_ => Err(-1),
267+
async fn check_web_readiness(url: &Url, protocol: &Protocol) -> Result<(), i8> {
268+
match protocol {
269+
Protocol::Http => match reqwest::get(url.as_str()).await {
270+
Ok(response) if { response.status().is_success() } => Ok(()),
271+
_ => Err(-1),
272+
},
273+
Protocol::Tcp => match TcpStream::connect(format!("{}:{}", url.host().unwrap(), url.port().unwrap())).await {
274+
Ok(_) => Ok(()),
275+
Err(_) => Err(-1),
276+
},
234277
}
235278
}
236279

237280
async fn convert_body(app_response: reqwest::Response) -> Result<Body, Error> {
238281
if app_response.headers().get(http::header::CONTENT_ENCODING).is_some() {
239282
let content = app_response.bytes().await?;
240-
if content.is_empty() {
241-
return Ok(Body::Empty);
283+
return if content.is_empty() {
284+
Ok(Body::Empty)
242285
} else {
243-
return Ok(Body::Binary(content.to_vec()));
244-
}
286+
Ok(Body::Binary(content.to_vec()))
287+
};
245288
}
246289

247290
match app_response.headers().get(http::header::CONTENT_TYPE) {

0 commit comments

Comments
 (0)