aws / aws/aws-lambda-rust-runtime

[lambda_http] Fallible response body panics during buffered conversion instead of propagating the body error

未关闭
#1,165 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Rust
星标
3.6k
派生
396
PR 合并指标
30 天内没有已合并 PR

描述

## Description

`lambda_http` accepts response body types whose `HttpBody::Error` is fallible, but the buffered response conversion calls `expect()` when collecting the body.

If the body returns an error—for example, when a proxied HTTP/1.1 chunked response is truncated—`IntoResponse::into_response()` panics instead of propagating the body error. This affects both the text and binary conversion paths.

When the handler runs under `lambda_http::run`, the panic may be caught by `lambda_runtime`'s `CatchPanicService` and reported as an invocation error, so the process can survive when built with the default `panic = "unwind"` strategy.

However, this relies on `catch_unwind` as the error channel instead of propagating the body error:

- With `panic = "abort"`, which may be used for size-optimized Lambda builds, the process terminates.
- The diagnostic says that the user handler panicked even though the panic originates inside `lambda_http`.
- The structured body error is flattened into a panic message.
- Outside `lambda_http::run`, such as in custom runtimes, tests, or other adapters, calling `IntoResponse::into_response()` directly panics the process, as demonstrated below.

## Versions

- `lambda_http`: 1.3.0
- No modifications to `lambda_http`
- The same behavior is present in earlier releases
- `rustc`: 1.88.0 (6b00bc388 2025-06-23)
- `cargo`: 1.88.0 (873a06493 2025-05-10)
- OS: Debian GNU/Linux 12 (bookworm)
- Architecture: aarch64
- Reproduction environment: official `rust:1.88-bookworm` Docker image

## Minimal reproduction

`Cargo.toml`:

```toml
[package]
name = "lambda-http-body-error-repro"
version = "0.1.0"
edition = "2021"

[dependencies]
lambda_http = "=1.3.0"
bytes = "1"
futures-util = "0.3"
http-body = "1"
http-body-util = "0.1"
tokio = { version = "1", features = ["macros", "rt"] }
```

`src/main.rs`:

```rust
use bytes::Bytes;
use futures_util::stream;
use http_body::Frame;
use http_body_util::StreamBody;
use lambda_http::{IntoResponse, Response};
use std::io::{self, ErrorKind};

#[tokio::main(flavor = "current_thread")]
async fn main() {
let frames = vec![
Ok(Frame::data(Bytes::from_static(b"partial response"))),
Err(io::Error::new(
ErrorKind::UnexpectedEof,
"simulated truncated response body",
)),
];
let body = StreamBody::new(stream::iter(frames));

let response = Response::builder()
.header("content-type", "text/plain; charset=utf-8")
.body(body)
.unwrap();

// Panics in lambda_http::response::convert_to_text().
let _ = response.into_response().await;
}
```

Run:

```bash
cargo run
```

## Actual behavior

The process exits with status 101 and produces the following panic:

```text
thread 'main' panicked at /usr/local/cargo/registry/src/index.crates.io-1949cf8c6b5b557f/lambda_http-1.3.0/src/response.rs:412:42:
unable to read bytes from body: Custom { kind: UnexpectedEof, error: "simulated truncated response body" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

A real transport-level example is an HTTP proxy returning a `Response` where the upstream server closes an HTTP/1.1 chunked response before completing the current chunk.

Hyper exposes the premature closure as a body error. That error reaches the same `expect()` call in the buffered response conversion and is converted into a panic.

## Expected behavior

A response body error should be propagated as an error rather than surfaced through `panic!` and `catch_unwind`, so that:

- the failure is reported as an invocation error regardless of the configured panic strategy;
- the original body error is preserved in the diagnostic instead of being flattened into a panic message attributed to the user handler;
- callers of `IntoResponse` outside `lambda_http::run` are not taken down by a request-level I/O failure.

If the existing `IntoResponse` API cannot propagate the error without an API change, returning a deterministic error response from the conversion would still be preferable to panicking.

## Affected code

`ConvertBody` explicitly accepts fallible body types, with bounds including:

```rust
B: HttpBody + Unpin + Send + 'static,
B::Data: Send,
B::Error: fmt::Debug,
```

However, both buffered conversion paths use `expect()`:

```rust
body.collect()
.await
.expect("unable to read bytes from body")
```

Source at the `lambda_http-v1.3.0` tag:

- [`convert_to_binary` and `convert_to_text`](https://github.com/aws/aws-lambda-rust-runtime/blob/lambda_http-v1.3.0/lambda-http/src/response.rs#L377-L420)
- [`expect()` in `convert_to_binary`](https://github.com/aws/aws-lambda-rust-runtime/blob/lambda_http-v1.3.0/lambda-http/src/response.rs#L387)
- [`expect()` in `convert_to_text`](https://github.com/aws/aws-lambda-rust-runtime/blob/lambda_http-v1.3.0/lambda-http/src/response.rs#L412)

## Impact

A single malformed or prematurely closed upstream response turns a request-level I/O failure into a panic inside the runtime's response conversion path.

This is especially relevant for HTTP adapters and reverse proxies that return a live, fallible `HttpBody`, because transport-level failures are expected to be represented and handled as normal body errors.

In environments using `panic = "abort"`, this can also terminate the runtime process instead of failing only the affected invocation.

## Related issue

This appears to be different from #1051.

That issue concerns an early client closure in the streaming runtime path involving `lambda_runtime` and `send_data().unwrap()` under local Lambda emulation.

This report concerns `lambda_http::IntoResponse` collecting a fallible application response body in the buffered conversion path.

The minimal reproduction does not require:

- Lambda response streaming;
- a client disconnect;
- a Lambda emulator;
- an actual HTTP server.

It only requires a valid `HttpBody` implementation that returns an error while its frames are being collected.

贡献指南

打开贡献指南

调研方向

从 lambda-http/src/response.rs 开始阅读 convert_to_binary 和 convert_to_text,重点关注 body.collect() 调用及其 expect() 处理。使用 cargo run 运行最小复现,以观察 panic。当一个可能失败的 body 在两个带缓冲的转换路径中都能报告其原始错误且不会触发 panic 时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
rust
领域
api, backend
Issue 类型
缺陷
难度
3/5
预计耗时
1-2 天
活跃度
活跃
描述清晰度
描述清楚
新手友好度
74/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。