Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ default, and binary responses are protected from accidental terminal output.
See [Output Formatting](docs/output-formatting.md) for pager, color, binary,
clipboard, and file behavior.

Use `--har request.har` to record the final HTTP exchange as a HAR 1.2 sidecar
without changing normal response output. HAR files can contain credentials,
cookies, and bodies and should be treated as sensitive data.

## Documentation

Start with the **[documentation index](docs/README.md)**, or jump directly to:
Expand Down
18 changes: 18 additions & 0 deletions docs/advanced-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,24 @@ Sessions are stored as JSON in the user's cache directory:

## Debugging Network Issues

### HAR Sidecars

Use `--har PATH` to record the final HTTP exchange in HAR 1.2 format while
leaving normal response output unchanged:

```sh
fetch --har request.har https://api.example.com/users
fetch -o response.json --har request.har https://api.example.com/users
```

The destination is reserved before network I/O and atomically installed after
the response completes. It honors `--clobber`. Redirect, retry, and
authentication challenge exchanges are not included; only the final exchange
is recorded. Captures larger than 16 MiB are counted but omitted from the HAR.

HAR files may contain authorization headers, cookies, request bodies, and
response bodies. Store and share them as sensitive data.

### Timing Waterfall

`--timing` (or `-T`) displays a timing waterfall chart after the response, showing how time was spent across DNS resolution, TCP connection setup, TLS handshake, time to first byte, and body download:
Expand Down
22 changes: 22 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,28 @@ Overwrite existing output file (default behavior is to fail if file exists).
fetch -o output.json --clobber example.com/data
```

### `--har PATH`

Write a HAR 1.2 sidecar containing the final HTTP exchange while preserving the
normal response output. The capture includes the effective request, finalized
headers and streamed request body, decoded response body, remote IP, and timing
information. Unary gRPC is supported; protobuf frames are stored as base64.

```sh
fetch --har request.har https://api.example.com/users
fetch -o response.json --har request.har https://api.example.com/users
```

The destination is reserved before the request, is installed atomically after
the response completes, and follows `--clobber`. `PATH` cannot be `-` or match
`--output`. WebSocket, inspection, gRPC discovery, and `--dry-run` modes are not
supported. The initial HAR contains only the final exchange after redirects,
retries, or authentication challenges.

HAR files may contain credentials, cookies, request bodies, and response bodies.
Treat them as sensitive data. Body capture is bounded at 16 MiB; larger bodies
are counted completely but their text is omitted with a truncation comment.

### `--copy`

Copy the response body to the system clipboard. The response is still printed
Expand Down
10 changes: 10 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,16 @@ Use `-O` to save using the filename from the URL:
fetch -O httpbin.org/image/png
```

Record the final HTTP exchange as a HAR 1.2 sidecar without changing response
output:

```sh
fetch --har request.har httpbin.org/json
```

HAR files can contain credentials, cookies, and bodies, so handle them as
sensitive data.

### Viewing Images

`fetch` can render images directly in terminals that support inline images (Kitty, iTerm2), with a block-character fallback for other terminals.
Expand Down
23 changes: 23 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,29 @@ async fn run_inner(cli: &mut Cli) -> Result<i32, FetchError> {
return Err("flag '--remote-header-name' requires '--remote-name'".into());
}

if let Some(path) = cli.har.as_deref() {
if path == "-" {
return Err(
"invalid value '-' for option '--har': stdout is reserved for the response body"
.into(),
);
}
if cli.output.as_deref().is_some_and(|output| {
output != "-" && crate::output::destinations_conflict(path, output)
}) {
return Err("flags '--har' and '--output' cannot use the same path".into());
}
if cli.dry_run {
return Err("flag '--har' cannot be used with '--dry-run'".into());
}
if cli.inspect_dns || cli.inspect_tls {
return Err("flag '--har' cannot be used with inspection modes".into());
}
if cli.has_grpc_discovery() {
return Err("flag '--har' cannot be used with gRPC discovery modes".into());
}
}

if cli.url.is_none() && cli.has_grpc_discovery() && !cli.has_proto_schema() {
return Err("<URL> must be provided unless --proto-file or --proto-desc is set".into());
}
Expand Down
3 changes: 3 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ pub struct Cli {
)]
pub output: Option<String>,

#[arg(long, value_name = "PATH", help = "Write a HAR 1.2 sidecar file")]
pub har: Option<String>,

#[arg(
long = "proto-desc",
value_name = "PATH",
Expand Down
3 changes: 3 additions & 0 deletions src/flag_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ pub(crate) static FLAGS: &[FlagDef] = &[
})
.with_from_curl()
.with_ws_always(),
FlagDef::new("--har", Some(FlagCategory::Response), |c| c.har.is_some())
.with_from_curl()
.with_ws_always(),
FlagDef::new("--remote-name", Some(FlagCategory::Request), |c| {
c.remote_name
})
Expand Down
1 change: 1 addition & 0 deletions src/grpc/reflection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub async fn execute_discovery(cli: &Cli) -> Result<i32, FetchError> {
request_start,
session: session.as_ref(),
connect_timing: Some(&connect_timing),
har: None,
};
let client = crate::http::client::build_client_for_url(cli, &url, &client_build)
.await?
Expand Down
Loading
Loading