Skip to content

Commit 963237d

Browse files
authored
Merge pull request #120 from G-Core/feat/redefine-http-status-code-returned-by-fastedge-as-gcore-cdn-component
feat: redefine HTTP status codes and add internal status header
2 parents 5f84ea7 + f07b123 commit 963237d

3 files changed

Lines changed: 69 additions & 11 deletions

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ fastedge-run http \
182182
--max-duration 5000 # 5-second execution timeout
183183
```
184184

185+
### Internal status codes
186+
187+
On every 5xx error response the runtime adds an `X-CDN-Internal-Status` response header with a
188+
numeric code in the range **3000–3999** that identifies the exact failure reason:
189+
190+
| Code | HTTP status | Meaning |
191+
|------|-------------|---------|
192+
| `3000` | 530 | Context setup error — failed to instantiate the Wasm executor |
193+
| `3001` | 530 | Generic execute error (unclassified internal failure) |
194+
| `3002` | 533 | App exited with a non-zero exit code |
195+
| `3003` | 533 | Wasm trap — unknown or unclassified trap |
196+
| `3010` | 532 | Execution timeout — Wasm interrupt trap (`Trap::Interrupt`) |
197+
| `3011` | 532 | Execution timeout — async deadline exceeded (`tokio::Elapsed`) |
198+
| `3012` | 532 | Execution timeout — deadline elapsed (string-matched error) |
199+
| `3020` | 531 | Out of memory — `Trap::UnreachableCodeReached` |
200+
185201
### Dotenv support
186202

187203
Pass `--dotenv` (optionally with a directory path; defaults to the current directory) to load

crates/http-service/src/executor/http.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ mod tests {
178178
use crate::executor::http::HttpExecutorImpl;
179179
use crate::{
180180
ContextHeaders, ExecutorFactory, HttpService, FASTEDGE_EXECUTION_TIMEOUT,
181-
FASTEDGE_OUT_OF_MEMORY,
181+
FASTEDGE_OUT_OF_MEMORY, INTERNAL_STATUS_OUT_OF_MEMORY, INTERNAL_STATUS_TIMEOUT_ELAPSED,
182+
INTERNAL_STATUS_TIMEOUT_INTERRUPT, X_CDN_INTERNAL_STATUS,
182183
};
183184
use bytes::Bytes;
184185
use claims::*;
@@ -474,13 +475,23 @@ mod tests {
474475
let res = assert_ok!(http_service.handle_request("2".to_smolstr(), req).await);
475476
assert_eq!(FASTEDGE_EXECUTION_TIMEOUT, res.status());
476477
let headers = res.headers();
477-
assert_eq!(3, headers.len());
478+
assert_eq!(4, headers.len());
478479
assert_eq!(
479480
"*",
480481
assert_some!(headers.get("access-control-allow-origin"))
481482
);
482483
assert_eq!("no-store", assert_some!(headers.get("cache-control")));
483484
assert_eq!("03", assert_some!(headers.get("RES_HEADER_03")));
485+
let internal_status = assert_some!(headers.get(X_CDN_INTERNAL_STATUS))
486+
.to_str()
487+
.unwrap()
488+
.parse::<u16>()
489+
.unwrap();
490+
assert!(
491+
internal_status == INTERNAL_STATUS_TIMEOUT_INTERRUPT
492+
|| internal_status == INTERNAL_STATUS_TIMEOUT_ELAPSED,
493+
"expected timeout internal status code, got {internal_status}"
494+
);
484495
}
485496

486497
#[tokio::test]
@@ -525,13 +536,19 @@ mod tests {
525536
let res = assert_ok!(http_service.handle_request("3".to_smolstr(), req).await);
526537
assert_eq!(FASTEDGE_OUT_OF_MEMORY, res.status());
527538
let headers = res.headers();
528-
assert_eq!(3, headers.len());
539+
assert_eq!(4, headers.len());
529540
assert_eq!(
530541
"*",
531542
assert_some!(headers.get("access-control-allow-origin"))
532543
);
533544
assert_eq!("no-store", assert_some!(headers.get("cache-control")));
534545
assert_eq!("03", assert_some!(headers.get("RES_HEADER_03")));
546+
assert_eq!(
547+
INTERNAL_STATUS_OUT_OF_MEMORY.to_string(),
548+
assert_some!(headers.get(X_CDN_INTERNAL_STATUS))
549+
.to_str()
550+
.unwrap()
551+
);
535552
}
536553

537554
#[tokio::test]

crates/http-service/src/lib.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub mod executor;
3131
pub mod state;
3232

3333
pub(crate) static TRACEPARENT: &str = "traceparent";
34+
pub(crate) static X_CDN_INTERNAL_STATUS: &str = "x-cdn-internal-status";
3435

3536
#[cfg(target_family = "unix")]
3637
type OwnedFd = std::os::fd::OwnedFd;
@@ -45,6 +46,16 @@ const FASTEDGE_OUT_OF_MEMORY: u16 = 531;
4546
const FASTEDGE_EXECUTION_TIMEOUT: u16 = 532;
4647
const FASTEDGE_EXECUTION_PANIC: u16 = 533;
4748

49+
/// Internal status codes returned in `X-CDN-Internal-Status` response header (range 3000–3999).
50+
pub(crate) const INTERNAL_STATUS_CONTEXT_ERROR: u16 = 3000;
51+
pub(crate) const INTERNAL_STATUS_EXECUTE_ERROR: u16 = 3001;
52+
pub(crate) const INTERNAL_STATUS_APP_EXIT_ERROR: u16 = 3002;
53+
pub(crate) const INTERNAL_STATUS_WASM_TRAP_OTHER: u16 = 3003;
54+
pub(crate) const INTERNAL_STATUS_TIMEOUT_INTERRUPT: u16 = 3010;
55+
pub(crate) const INTERNAL_STATUS_TIMEOUT_ELAPSED: u16 = 3011;
56+
pub(crate) const INTERNAL_STATUS_TIMEOUT_DEADLINE: u16 = 3012;
57+
pub(crate) const INTERNAL_STATUS_OUT_OF_MEMORY: u16 = 3020;
58+
4859
#[derive(Default)]
4960
pub struct HttpConfig {
5061
pub all_interfaces: bool,
@@ -310,7 +321,7 @@ where
310321
tracing::warn!(cause=?error, app=%app_name,
311322
"failure on getting context"
312323
);
313-
return internal_fastedge_error("context error");
324+
return internal_fastedge_error("context error", INTERNAL_STATUS_CONTEXT_ERROR);
314325
}
315326
};
316327

@@ -331,7 +342,7 @@ where
331342
}
332343
Err(error) => {
333344
tracing::warn!(cause=?error, "execute");
334-
let (status_code, fail_reason, msg) = map_err(error);
345+
let (status_code, fail_reason, msg, internal_code) = map_err(error);
335346
stats.status_code(status_code);
336347
stats.fail_reason(fail_reason as u32);
337348
tracing::debug!(?fail_reason, ?request_id, "stats");
@@ -344,7 +355,9 @@ where
344355
None,
345356
);
346357

347-
let builder = hyper::Response::builder().status(status_code);
358+
let builder = hyper::Response::builder()
359+
.status(status_code)
360+
.header(X_CDN_INTERNAL_STATUS, internal_code);
348361
let res_headers = app_res_headers(cfg);
349362
let builder = res_headers
350363
.iter()
@@ -357,15 +370,16 @@ where
357370
}
358371
}
359372

360-
fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody) {
373+
fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody, u16) {
361374
let root_cause = error.root_cause();
362-
let (status_code, fail_reason, msg) =
375+
let (status_code, fail_reason, msg, internal_code) =
363376
if let Some(exit) = root_cause.downcast_ref::<wasi_common::I32Exit>() {
364377
if exit.0 == 0 {
365378
(
366379
StatusCode::OK.as_u16(),
367380
AppResult::SUCCESS,
368381
Empty::new().map_err(|never| match never {}).boxed(),
382+
0,
369383
)
370384
} else {
371385
(
@@ -374,6 +388,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody) {
374388
Full::new(Bytes::from("fastedge: App failed"))
375389
.map_err(|never| match never {})
376390
.boxed(),
391+
INTERNAL_STATUS_APP_EXIT_ERROR,
377392
)
378393
}
379394
} else if let Some(trap) = root_cause.downcast_ref::<wasmtime::Trap>() {
@@ -384,20 +399,23 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody) {
384399
Full::new(Bytes::from("fastedge: Execution timeout"))
385400
.map_err(|never| match never {})
386401
.boxed(),
402+
INTERNAL_STATUS_TIMEOUT_INTERRUPT,
387403
),
388404
wasmtime::Trap::UnreachableCodeReached => (
389405
FASTEDGE_OUT_OF_MEMORY,
390406
AppResult::OOM,
391407
Full::new(Bytes::from("fastedge: Out of memory"))
392408
.map_err(|never| match never {})
393409
.boxed(),
410+
INTERNAL_STATUS_OUT_OF_MEMORY,
394411
),
395412
_ => (
396413
FASTEDGE_EXECUTION_PANIC,
397414
AppResult::OTHER,
398415
Full::new(Bytes::from("fastedge: App failed"))
399416
.map_err(|never| match never {})
400417
.boxed(),
418+
INTERNAL_STATUS_WASM_TRAP_OTHER,
401419
),
402420
}
403421
} else if let Some(_elapsed) = root_cause.downcast_ref::<Elapsed>() {
@@ -407,6 +425,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody) {
407425
Full::new(Bytes::from("fastedge: Execution timeout"))
408426
.map_err(|never| match never {})
409427
.boxed(),
428+
INTERNAL_STATUS_TIMEOUT_ELAPSED,
410429
)
411430
} else if root_cause.to_string().ends_with("deadline has elapsed") {
412431
(
@@ -415,6 +434,7 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody) {
415434
Full::new(Bytes::from("fastedge: Execution timeout"))
416435
.map_err(|never| match never {})
417436
.boxed(),
437+
INTERNAL_STATUS_TIMEOUT_DEADLINE,
418438
)
419439
} else {
420440
(
@@ -423,9 +443,10 @@ fn map_err(error: Error) -> (u16, AppResult, HyperOutgoingBody) {
423443
Full::new(Bytes::from("fastedge: Execute error"))
424444
.map_err(|never| match never {})
425445
.boxed(),
446+
INTERNAL_STATUS_EXECUTE_ERROR,
426447
)
427448
};
428-
(status_code, fail_reason, msg)
449+
(status_code, fail_reason, msg, internal_code)
429450
}
430451

431452
fn remote_traceparent(req: &hyper::Request<hyper::body::Incoming>) -> SmolStr {
@@ -436,10 +457,14 @@ fn remote_traceparent(req: &hyper::Request<hyper::body::Incoming>) -> SmolStr {
436457
.unwrap_or(nanoid::nanoid!().to_smolstr())
437458
}
438459

439-
/// Creates an HTTP 500 response.
440-
fn internal_fastedge_error(msg: &'static str) -> Result<hyper::Response<HyperOutgoingBody>> {
460+
/// Creates an HTTP 530 response with an `X-CDN-Internal-Status` header.
461+
fn internal_fastedge_error(
462+
msg: &'static str,
463+
internal_code: u16,
464+
) -> Result<hyper::Response<HyperOutgoingBody>> {
441465
Ok(hyper::Response::builder()
442466
.status(FASTEDGE_INTERNAL_ERROR)
467+
.header(X_CDN_INTERNAL_STATUS, internal_code)
443468
.body(
444469
Full::new(Bytes::from(format!("fastedge: {}", msg)))
445470
.map_err(|never| match never {})

0 commit comments

Comments
 (0)