httptap 0.6.2__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httptap-0.6.2 → httptap-0.7.0}/PKG-INFO +118 -57
- {httptap-0.6.2 → httptap-0.7.0}/README.md +109 -53
- {httptap-0.6.2 → httptap-0.7.0}/httptap/__init__.py +4 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/_pkgmeta.py +15 -2
- {httptap-0.6.2 → httptap-0.7.0}/httptap/analyzer.py +78 -16
- httptap-0.7.0/httptap/cli.py +1030 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/constants.py +10 -2
- {httptap-0.6.2 → httptap-0.7.0}/httptap/exporter.py +22 -1
- {httptap-0.6.2 → httptap-0.7.0}/httptap/formatters.py +44 -5
- httptap-0.7.0/httptap/http_client.py +1282 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/implementations/dns.py +91 -24
- {httptap-0.6.2 → httptap-0.7.0}/httptap/implementations/timing.py +6 -2
- {httptap-0.6.2 → httptap-0.7.0}/httptap/implementations/tls.py +18 -22
- {httptap-0.6.2 → httptap-0.7.0}/httptap/interfaces.py +9 -2
- {httptap-0.6.2 → httptap-0.7.0}/httptap/models.py +13 -5
- httptap-0.7.0/httptap/otlp.py +191 -0
- httptap-0.7.0/httptap/prometheus.py +122 -0
- httptap-0.7.0/httptap/py.typed +0 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/render.py +29 -5
- {httptap-0.6.2 → httptap-0.7.0}/httptap/request_executor.py +12 -6
- {httptap-0.6.2 → httptap-0.7.0}/httptap/slo.py +34 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/tls_inspector.py +77 -13
- httptap-0.7.0/httptap/utils.py +591 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/visualizer.py +28 -6
- {httptap-0.6.2 → httptap-0.7.0}/pyproject.toml +44 -8
- {httptap-0.6.2 → httptap-0.7.0}/pyproject.toml.orig +38 -7
- httptap-0.6.2/httptap/cli.py +0 -604
- httptap-0.6.2/httptap/http_client.py +0 -715
- httptap-0.6.2/httptap/utils.py +0 -358
- {httptap-0.6.2 → httptap-0.7.0}/LICENSE +0 -0
- {httptap-0.6.2 → httptap-0.7.0}/httptap/implementations/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httptap
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: HTTP request visualizer with detailed timing breakdown (DNS → TCP → TLS → HTTP)
|
|
5
5
|
Keywords: http,https,performance,timing,dns,tls,ssl,networking,monitoring,diagnostics,waterfall,curl,httpx
|
|
6
6
|
Author: Sergei Ozeranskii
|
|
@@ -14,13 +14,13 @@ Classifier: Topic :: System :: Networking :: Monitoring
|
|
|
14
14
|
Classifier: Topic :: Software Development :: Testing
|
|
15
15
|
Classifier: Topic :: Utilities
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.15
|
|
23
22
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
24
24
|
Classifier: Environment :: Console
|
|
25
25
|
Classifier: Operating System :: OS Independent
|
|
26
26
|
Classifier: Operating System :: POSIX
|
|
@@ -28,17 +28,22 @@ Classifier: Operating System :: MacOS
|
|
|
28
28
|
Classifier: Operating System :: Microsoft :: Windows
|
|
29
29
|
Classifier: Typing :: Typed
|
|
30
30
|
Requires-Dist: httpx[http2,socks]>=0.28.1
|
|
31
|
+
Requires-Dist: httpcore>=1.0.0
|
|
31
32
|
Requires-Dist: rich>=14.3.2
|
|
32
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: cryptography>=50.0.2
|
|
33
34
|
Requires-Dist: argcomplete>=3.7.2 ; extra == 'completion'
|
|
35
|
+
Requires-Dist: opentelemetry-api>=1.30.0 ; extra == 'otel'
|
|
36
|
+
Requires-Dist: opentelemetry-sdk>=1.30.0 ; extra == 'otel'
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.30.0 ; extra == 'otel'
|
|
34
38
|
Maintainer: Sergei Ozeranskii
|
|
35
|
-
Requires-Python: >=3.
|
|
39
|
+
Requires-Python: >=3.11
|
|
36
40
|
Project-URL: Homepage, https://github.com/ozeranskii/httptap
|
|
37
41
|
Project-URL: Documentation, https://docs.httptap.dev
|
|
38
42
|
Project-URL: Repository, https://github.com/ozeranskii/httptap.git
|
|
39
43
|
Project-URL: Issues, https://github.com/ozeranskii/httptap/issues
|
|
40
44
|
Project-URL: Changelog, https://github.com/ozeranskii/httptap/blob/main/CHANGELOG.md
|
|
41
45
|
Provides-Extra: completion
|
|
46
|
+
Provides-Extra: otel
|
|
42
47
|
Description-Content-Type: text/markdown
|
|
43
48
|
|
|
44
49
|
<p align="center">
|
|
@@ -160,13 +165,21 @@ performance baselines.
|
|
|
160
165
|
data is unavailable).
|
|
161
166
|
- **All HTTP methods** – GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS with request body support.
|
|
162
167
|
- **Request body support** – send JSON, XML, or any data inline or from file with automatic Content-Type detection.
|
|
163
|
-
- **IPv4/IPv6 aware** – the resolver and TLS inspector report both the address and its family
|
|
168
|
+
- **IPv4/IPv6 aware** – the resolver and TLS inspector report both the address and its family; `-4`/`-6` restrict
|
|
169
|
+
resolution to one family, and `--resolve HOST:PORT:ADDR` pins a host to an address while keeping the original `Host`
|
|
170
|
+
header and TLS SNI.
|
|
164
171
|
- **TLS insights** – certificate CN, SANs, issuer, serial, validity window and expiry countdown, plus cipher suite and
|
|
165
172
|
protocol version, are captured automatically from the live connection (no extra handshake).
|
|
166
173
|
- **Multiple output modes** – rich waterfall view, compact single-line summaries, or `--metrics-only` for scripting.
|
|
167
|
-
- **JSON export** – persist full step data (including redirect chains) for later processing
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
- **JSON export** – persist full step data (including redirect chains) for later processing, or stream it to stdout with
|
|
175
|
+
`--json -`.
|
|
176
|
+
- **Prometheus and OpenTelemetry export** – `--prometheus PATH` writes a node_exporter textfile; `--otlp ENDPOINT` sends
|
|
177
|
+
per-phase spans to an OTLP/HTTP collector (requires `httptap[otel]`).
|
|
178
|
+
- **SLO threshold checking** – `--slo total=500,ttfb=200` (or thresholds read from a file with `--slo-file`) gates CI
|
|
179
|
+
jobs, cron probes, and readiness checks on per-phase latency budgets; non-zero exit on violation while still rendering
|
|
180
|
+
the full report.
|
|
181
|
+
- **Scriptable exit codes** – `-f/--fail` exits `22` on HTTP 4xx/5xx responses, and SLO violations, network errors, and
|
|
182
|
+
the redirect limit each have their own [exit code](#exit-codes).
|
|
170
183
|
- **Extensible** – clean Protocol interfaces for DNS, TLS, timing, visualization, and export so you can plug in custom
|
|
171
184
|
behavior.
|
|
172
185
|
|
|
@@ -203,7 +216,7 @@ performance baselines.
|
|
|
203
216
|
|
|
204
217
|
## Requirements
|
|
205
218
|
|
|
206
|
-
- Python 3.
|
|
219
|
+
- Python 3.11-3.15 (CPython)
|
|
207
220
|
- macOS, Linux, or Windows (tested on CPython)
|
|
208
221
|
- No system dependencies beyond standard networking
|
|
209
222
|
- Code must follow the Google Python Style Guide (docstrings, formatting). See
|
|
@@ -299,6 +312,20 @@ If you installed httptap via `pip` or `uv`, you need to install the optional com
|
|
|
299
312
|
|
|
300
313
|
**Note:** The global activation script provides argument completions for bash and zsh only. Other shells are not covered by the script and must be configured separately.
|
|
301
314
|
|
|
315
|
+
#### Isolated installs (`uv tool`, `pipx`)
|
|
316
|
+
|
|
317
|
+
`uv tool install` and `pipx install` put only the `httptap` command on your `PATH`, not argcomplete's helper scripts. Expose them at install time, then register completion for `httptap` in your shell startup file (e.g. `~/.bashrc` or `~/.zshrc`):
|
|
318
|
+
|
|
319
|
+
```shell
|
|
320
|
+
uv tool install --with-executables-from argcomplete "httptap[completion]"
|
|
321
|
+
# or
|
|
322
|
+
pipx install --include-resources-from argcomplete "httptap[completion]"
|
|
323
|
+
|
|
324
|
+
eval "$(register-python-argcomplete httptap)"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Older pipx releases without `--include-resources-from` can use `--include-deps` instead.
|
|
328
|
+
|
|
302
329
|
#### Usage Examples
|
|
303
330
|
|
|
304
331
|
Once completions are installed, you can use `Tab` to autocomplete commands and options:
|
|
@@ -306,7 +333,7 @@ Once completions are installed, you can use `Tab` to autocomplete commands and o
|
|
|
306
333
|
```shell
|
|
307
334
|
# Complete command options
|
|
308
335
|
httptap --<TAB>
|
|
309
|
-
# Shows: --method
|
|
336
|
+
# Shows: --help --version --request --method --data --location --follow --max-time --timeout --no-http2 --http1.1 --fail --ipv4 --ipv6 --resolve --insecure --ignore-ssl --cacert --ca-bundle --proxy --header --compact --metrics-only --json --prometheus --otlp --slo --slo-file
|
|
310
337
|
|
|
311
338
|
# Complete after typing partial option
|
|
312
339
|
httptap --fol<TAB>
|
|
@@ -339,7 +366,7 @@ httptap https://httpbin.io/post --data '{"name": "John", "email": "john@example.
|
|
|
339
366
|
|
|
340
367
|
**Note:** When `--data` is provided without `--method`, httptap automatically switches to POST (similar to curl).
|
|
341
368
|
|
|
342
|
-
**Curl-compatible flags:** httptap accepts the most common curl syntax, so you can often replace `curl` with `httptap` directly. Aliases include `-X/--request` for `--method`, `-L/--location` for `--follow`, `-m/--max-time` for `--timeout`, `-k/--insecure` for `--ignore-ssl`, `-x` for `--proxy`, and `--http1.1` for `--no-http2`. (Not every curl option is supported—stick to these shared flags when swapping commands.)
|
|
369
|
+
**Curl-compatible flags:** httptap accepts the most common curl syntax, so you can often replace `curl` with `httptap` directly. Aliases include `-X/--request` for `--method`, `-L/--location` for `--follow`, `-m/--max-time` for `--timeout`, `-k/--insecure` for `--ignore-ssl`, `-x` for `--proxy`, and `--http1.1` for `--no-http2`. `-f/--fail`, `-4/--ipv4`, `-6/--ipv6`, and `--resolve HOST:PORT:ADDR` use the same names as in curl. (Not every curl option is supported—stick to these shared flags when swapping commands.)
|
|
343
370
|
|
|
344
371
|
Load data from file:
|
|
345
372
|
|
|
@@ -384,6 +411,9 @@ httptap \
|
|
|
384
411
|
https://httpbin.io/bearer
|
|
385
412
|
```
|
|
386
413
|
|
|
414
|
+
Header names must be HTTP tokens and values printable ASCII (spaces and tabs allowed). Anything else, such as CR/LF or
|
|
415
|
+
non-ASCII text, is rejected with exit code `64` before any request is made.
|
|
416
|
+
|
|
387
417
|
### Redirects and JSON Export
|
|
388
418
|
|
|
389
419
|
Follow redirect chains and dump metrics to JSON:
|
|
@@ -392,6 +422,10 @@ Follow redirect chains and dump metrics to JSON:
|
|
|
392
422
|
httptap --follow --json out/report.json https://httpbin.io/redirect/2
|
|
393
423
|
```
|
|
394
424
|
|
|
425
|
+
A redirect to a URL that cannot be requested (invalid port, missing host, non-HTTP scheme) ends the chain with a failed
|
|
426
|
+
step, `Invalid redirect target: …`, and exit code `75`. Credentials in `Location` URLs are masked in the output and JSON
|
|
427
|
+
export.
|
|
428
|
+
|
|
395
429
|
### Output Modes
|
|
396
430
|
|
|
397
431
|
Collect compact (single-line) timings suitable for logs:
|
|
@@ -450,7 +484,9 @@ httptap --proxy "" https://httpbin.io/get
|
|
|
450
484
|
|
|
451
485
|
The output and JSON export include the proxy URI and its source so you
|
|
452
486
|
can confirm what path was used (e.g., `(from arg --proxy)`,
|
|
453
|
-
`(from env HTTPS_PROXY)`, `(bypassed by env no_proxy)`).
|
|
487
|
+
`(from env HTTPS_PROXY)`, `(bypassed by env no_proxy)`). Proxy credentials
|
|
488
|
+
are masked (`http://user:****@proxy:3128`). A malformed `--proxy` value is
|
|
489
|
+
rejected with exit code `64`.
|
|
454
490
|
|
|
455
491
|
---
|
|
456
492
|
|
|
@@ -499,23 +535,28 @@ Full specification, evaluation rules, and recipes:
|
|
|
499
535
|
|
|
500
536
|
## Environment Variables
|
|
501
537
|
|
|
502
|
-
httptap reads the following environment variables at runtime.
|
|
503
|
-
|
|
504
|
-
recorded in the output and JSON
|
|
538
|
+
httptap reads the following environment variables at runtime. The proxy
|
|
539
|
+
variables can be overridden with `-x/--proxy` or ignored with `--proxy ""`, and
|
|
540
|
+
the proxy source used for each request is recorded in the output and JSON
|
|
541
|
+
export. The color variables have no CLI equivalent.
|
|
505
542
|
|
|
506
543
|
| Variable | Purpose | Overridden by |
|
|
507
544
|
|---------------------------------------|--------------------------------------------------------------------------------------------------------------|-----------------------|
|
|
508
545
|
| `HTTP_PROXY` / `http_proxy` | Proxy URL used for `http://` targets. | `-x/--proxy` |
|
|
509
546
|
| `HTTPS_PROXY` / `https_proxy` | Proxy URL used for `https://` targets. | `-x/--proxy` |
|
|
510
547
|
| `ALL_PROXY` / `all_proxy` | Fallback proxy URL when scheme-specific variables are unset. | `-x/--proxy` |
|
|
511
|
-
| `NO_PROXY` / `no_proxy` | Comma-separated exclusion list
|
|
548
|
+
| `NO_PROXY` / `no_proxy` | Comma-separated exclusion list: `*` matches every host, `example.com` the host and its subdomains, `.example.com` subdomains only. No CIDR ranges. Bypassed hosts connect direct. | `-x/--proxy`, `--proxy ""` |
|
|
512
549
|
| `NO_COLOR` | Disables ANSI colors in all Rich output (honors the [NO_COLOR](https://no-color.org) convention). | — |
|
|
513
550
|
| `FORCE_COLOR` | Forces colored output even when stdout is not a TTY (Rich convention). | — |
|
|
514
551
|
| `TERM=dumb` | Rich downgrades to plain-text rendering. | — |
|
|
515
552
|
|
|
516
|
-
> Precedence for proxy configuration: explicit `-x/--proxy`
|
|
517
|
-
> (disables env) → `
|
|
518
|
-
> `
|
|
553
|
+
> Precedence for proxy configuration: explicit `-x/--proxy` or `--proxy ""`
|
|
554
|
+
> (disables env) → `NO_PROXY` exclusion (direct connection) → the variable
|
|
555
|
+
> matching the URL scheme (`HTTPS_PROXY` or `HTTP_PROXY`) → `ALL_PROXY` →
|
|
556
|
+
> direct connection. Lowercase variables take priority over uppercase ones.
|
|
557
|
+
> A proxy variable without a scheme (`proxy.local:3128`) is treated as
|
|
558
|
+
> `http://`; one that is not a valid proxy URL fails the request with a
|
|
559
|
+
> network error (exit code `75`).
|
|
519
560
|
|
|
520
561
|
---
|
|
521
562
|
|
|
@@ -528,11 +569,20 @@ shell pipelines, CI jobs, and systemd services.
|
|
|
528
569
|
|:-----:|-------------------------|------------------------------------------------------------|
|
|
529
570
|
| `0` | `EX_OK` | Success. |
|
|
530
571
|
| `4` | — | SLO threshold violation (request succeeded but too slow). |
|
|
572
|
+
| `22` | — | HTTP 4xx/5xx response when `-f` / `--fail` is used. |
|
|
573
|
+
| `47` | — | Maximum redirects followed. |
|
|
531
574
|
| `64` | `EX_USAGE` | Invalid command-line arguments. |
|
|
532
575
|
| `70` | `EX_SOFTWARE` | Internal error (unexpected exception, bug). |
|
|
576
|
+
| `73` | `EX_CANTCREAT` | `--json` output file could not be written. |
|
|
533
577
|
| `75` | `EX_TEMPFAIL` | Network / TLS error (partial output may still be rendered). |
|
|
534
578
|
| `128 + N` | Signal offset | Killed by signal `N` (e.g., `130` for `SIGINT` / Ctrl-C). |
|
|
535
579
|
|
|
580
|
+
When several conditions apply, the highest-priority code wins:
|
|
581
|
+
`70` > `47` > `75` > `73` > `22` > `4` > `0`. Invalid arguments (`64`) are
|
|
582
|
+
reported before any request is made. See
|
|
583
|
+
[SLO exit codes](https://docs.httptap.dev/usage/slo/#exit-codes) for the full
|
|
584
|
+
precedence table.
|
|
585
|
+
|
|
536
586
|
Example — fail a CI job only on usage errors, tolerating transient network
|
|
537
587
|
issues:
|
|
538
588
|
|
|
@@ -549,26 +599,20 @@ fi
|
|
|
549
599
|
|
|
550
600
|
## Releasing
|
|
551
601
|
|
|
552
|
-
|
|
602
|
+
Releases are cut by the manually triggered **Release** workflow (GitHub Actions → **Release** → **Run workflow**) with
|
|
603
|
+
either an exact version (e.g., `0.3.0`) or a `patch`/`minor`/`major` bump:
|
|
553
604
|
|
|
554
|
-
|
|
555
|
-
-
|
|
605
|
+
1. **Prepare** – bumps the version with `uv version`, refreshes `uv.lock`, prepends the `git-cliff` changelog entry to
|
|
606
|
+
`CHANGELOG.md`, and creates a gitsign-signed release commit and tag locally. Nothing is pushed yet.
|
|
607
|
+
2. **Build** – runs the full test suite on the unpushed tag, builds the wheel and sdist, generates the SBOMs (CycloneDX,
|
|
608
|
+
SPDX), OpenVEX document, and man page, and attests build provenance.
|
|
609
|
+
3. **Push** – only after the build and attestation succeed, fast-forwards `main` to the release commit and pushes the
|
|
610
|
+
tag. If `main` moved during the release, the push fails and nothing is published.
|
|
611
|
+
4. **Publish** – uploads to TestPyPI and then PyPI via Trusted Publishing (OIDC), pushes the signed multi-arch container
|
|
612
|
+
image to GHCR, and creates the GitHub Release with the wheel, sdist, SBOM, VEX, and man page.
|
|
556
613
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
1. Trigger the **Release** workflow from GitHub Actions:
|
|
560
|
-
- Provide exact version (e.g., `0.3.0`), OR
|
|
561
|
-
- Select bump type: `patch`, `minor`, or `major`
|
|
562
|
-
2. The workflow will:
|
|
563
|
-
- Update version in `pyproject.toml` using `uv version`
|
|
564
|
-
- Generate changelog with `git-cliff` and update `CHANGELOG.md`
|
|
565
|
-
- Commit changes and create a git tag
|
|
566
|
-
- Run full test suite on the tagged version
|
|
567
|
-
- Build wheel and source distribution
|
|
568
|
-
- Generate SBOM in CycloneDX and SPDX formats via Syft
|
|
569
|
-
- Attach the current OpenVEX document (`.vex/httptap.openvex.json`)
|
|
570
|
-
- Publish to PyPI via Trusted Publishing (OIDC)
|
|
571
|
-
- Create GitHub Release with wheel, sdist, SBOM, and VEX assets
|
|
614
|
+
Prerequisites (GitHub environments, Trusted Publishing, deploy key) and job details are in the
|
|
615
|
+
[release process documentation](https://docs.httptap.dev/development/release/).
|
|
572
616
|
|
|
573
617
|
---
|
|
574
618
|
|
|
@@ -585,6 +629,9 @@ The redirect summary includes a total row:
|
|
|
585
629
|
|
|
586
630
|
```json
|
|
587
631
|
{
|
|
632
|
+
"schema_version": 1,
|
|
633
|
+
"httptap_version": "0.6.3",
|
|
634
|
+
"timestamp": "2026-09-18T08:00:00Z",
|
|
588
635
|
"initial_url": "https://httpbin.io/redirect/2",
|
|
589
636
|
"total_steps": 3,
|
|
590
637
|
"steps": [
|
|
@@ -613,14 +660,14 @@ The redirect summary includes a total row:
|
|
|
613
660
|
"tls_version": "TLSv1.2",
|
|
614
661
|
"tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
|
|
615
662
|
"cert_cn": "httpbin.io",
|
|
616
|
-
"cert_days_left":
|
|
663
|
+
"cert_days_left": 41,
|
|
617
664
|
"cert_sans": ["httpbin.io", "*.httpbin.io"],
|
|
618
665
|
"cert_issuer": "WE1",
|
|
619
666
|
"cert_serial": "05BB0F0AA84C8FECE0E72D805BA7A5D2B",
|
|
620
|
-
"cert_not_before": "
|
|
621
|
-
"cert_not_after": "
|
|
667
|
+
"cert_not_before": "2026-08-01T00:00:00+00:00",
|
|
668
|
+
"cert_not_after": "2026-10-30T00:00:00+00:00",
|
|
622
669
|
"tls_verified": true,
|
|
623
|
-
"tls_custom_ca":
|
|
670
|
+
"tls_custom_ca": false,
|
|
624
671
|
"proxy_url": null,
|
|
625
672
|
"proxy_source": null
|
|
626
673
|
},
|
|
@@ -629,18 +676,19 @@ The redirect summary includes a total row:
|
|
|
629
676
|
"bytes": 0,
|
|
630
677
|
"content_type": null,
|
|
631
678
|
"server": null,
|
|
632
|
-
"date": "
|
|
679
|
+
"date": "2026-09-18T07:59:59+00:00",
|
|
633
680
|
"location": "/relative-redirect/1",
|
|
634
681
|
"headers": {
|
|
635
682
|
"access-control-allow-credentials": "true",
|
|
636
683
|
"access-control-allow-origin": "*",
|
|
637
684
|
"location": "/relative-redirect/1",
|
|
638
|
-
"date": "
|
|
685
|
+
"date": "Fri, 18 Sep 2026 07:59:59 GMT",
|
|
639
686
|
"content-length": "0"
|
|
640
687
|
}
|
|
641
688
|
},
|
|
642
689
|
"error": null,
|
|
643
690
|
"note": null,
|
|
691
|
+
"redirect_limit_reached": false,
|
|
644
692
|
"proxy": null
|
|
645
693
|
},
|
|
646
694
|
{
|
|
@@ -668,14 +716,14 @@ The redirect summary includes a total row:
|
|
|
668
716
|
"tls_version": "TLSv1.2",
|
|
669
717
|
"tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
|
|
670
718
|
"cert_cn": "httpbin.io",
|
|
671
|
-
"cert_days_left":
|
|
719
|
+
"cert_days_left": 41,
|
|
672
720
|
"cert_sans": ["httpbin.io", "*.httpbin.io"],
|
|
673
721
|
"cert_issuer": "WE1",
|
|
674
722
|
"cert_serial": "05BB0F0AA84C8FECE0E72D805BA7A5D2B",
|
|
675
|
-
"cert_not_before": "
|
|
676
|
-
"cert_not_after": "
|
|
723
|
+
"cert_not_before": "2026-08-01T00:00:00+00:00",
|
|
724
|
+
"cert_not_after": "2026-10-30T00:00:00+00:00",
|
|
677
725
|
"tls_verified": true,
|
|
678
|
-
"tls_custom_ca":
|
|
726
|
+
"tls_custom_ca": false,
|
|
679
727
|
"proxy_url": null,
|
|
680
728
|
"proxy_source": null
|
|
681
729
|
},
|
|
@@ -684,18 +732,19 @@ The redirect summary includes a total row:
|
|
|
684
732
|
"bytes": 0,
|
|
685
733
|
"content_type": null,
|
|
686
734
|
"server": null,
|
|
687
|
-
"date": "
|
|
735
|
+
"date": "2026-09-18T07:59:59+00:00",
|
|
688
736
|
"location": "/get",
|
|
689
737
|
"headers": {
|
|
690
738
|
"access-control-allow-credentials": "true",
|
|
691
739
|
"access-control-allow-origin": "*",
|
|
692
740
|
"location": "/get",
|
|
693
|
-
"date": "
|
|
741
|
+
"date": "Fri, 18 Sep 2026 07:59:59 GMT",
|
|
694
742
|
"content-length": "0"
|
|
695
743
|
}
|
|
696
744
|
},
|
|
697
745
|
"error": null,
|
|
698
746
|
"note": null,
|
|
747
|
+
"redirect_limit_reached": false,
|
|
699
748
|
"proxy": null
|
|
700
749
|
},
|
|
701
750
|
{
|
|
@@ -723,14 +772,14 @@ The redirect summary includes a total row:
|
|
|
723
772
|
"tls_version": "TLSv1.2",
|
|
724
773
|
"tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
|
|
725
774
|
"cert_cn": "httpbin.io",
|
|
726
|
-
"cert_days_left":
|
|
775
|
+
"cert_days_left": 41,
|
|
727
776
|
"cert_sans": ["httpbin.io", "*.httpbin.io"],
|
|
728
777
|
"cert_issuer": "WE1",
|
|
729
778
|
"cert_serial": "05BB0F0AA84C8FECE0E72D805BA7A5D2B",
|
|
730
|
-
"cert_not_before": "
|
|
731
|
-
"cert_not_after": "
|
|
779
|
+
"cert_not_before": "2026-08-01T00:00:00+00:00",
|
|
780
|
+
"cert_not_after": "2026-10-30T00:00:00+00:00",
|
|
732
781
|
"tls_verified": true,
|
|
733
|
-
"tls_custom_ca":
|
|
782
|
+
"tls_custom_ca": false,
|
|
734
783
|
"proxy_url": null,
|
|
735
784
|
"proxy_source": null
|
|
736
785
|
},
|
|
@@ -739,18 +788,19 @@ The redirect summary includes a total row:
|
|
|
739
788
|
"bytes": 389,
|
|
740
789
|
"content_type": "application/json; charset=utf-8",
|
|
741
790
|
"server": null,
|
|
742
|
-
"date": "
|
|
791
|
+
"date": "2026-09-18T08:00:00+00:00",
|
|
743
792
|
"location": null,
|
|
744
793
|
"headers": {
|
|
745
794
|
"access-control-allow-credentials": "true",
|
|
746
795
|
"access-control-allow-origin": "*",
|
|
747
796
|
"content-type": "application/json; charset=utf-8",
|
|
748
|
-
"date": "
|
|
797
|
+
"date": "Fri, 18 Sep 2026 08:00:00 GMT",
|
|
749
798
|
"content-length": "389"
|
|
750
799
|
}
|
|
751
800
|
},
|
|
752
801
|
"error": null,
|
|
753
802
|
"note": null,
|
|
803
|
+
"redirect_limit_reached": false,
|
|
754
804
|
"proxy": null
|
|
755
805
|
}
|
|
756
806
|
],
|
|
@@ -795,6 +845,11 @@ analyzer = HTTPTapAnalyzer(dns_resolver=HardcodedDNS())
|
|
|
795
845
|
steps = analyzer.analyze_url("https://httpbin.io")
|
|
796
846
|
```
|
|
797
847
|
|
|
848
|
+
`HardcodedDNS` overrides only `resolve()`, so httptap calls it instead of the inherited `resolve_all()` and connects to
|
|
849
|
+
that single address. Override `resolve_all()` as well to return several addresses for connection fallback, and raise
|
|
850
|
+
`DNSResolutionError` when a name cannot be resolved; see
|
|
851
|
+
[Protocol Interfaces](https://docs.httptap.dev/api/interfaces/#dnsresolver).
|
|
852
|
+
|
|
798
853
|
---
|
|
799
854
|
|
|
800
855
|
## Development
|
|
@@ -808,7 +863,13 @@ uv run ruff check
|
|
|
808
863
|
uv run ruff format .
|
|
809
864
|
```
|
|
810
865
|
|
|
811
|
-
|
|
866
|
+
The test suite does not need outbound network access: HTTP calls are mocked with `pytest-httpx`, and TLS and proxy tests use local servers.
|
|
867
|
+
|
|
868
|
+
The end-to-end tests in `tests/e2e` run the `httptap` CLI as a subprocess against local HTTP, TLS, proxy and OTLP servers, so they need no outbound network access either. A plain `uv run pytest` skips them; run them with:
|
|
869
|
+
|
|
870
|
+
```shell
|
|
871
|
+
uv run --extra otel pytest tests/e2e --no-cov -n auto
|
|
872
|
+
```
|
|
812
873
|
|
|
813
874
|
---
|
|
814
875
|
|
|
@@ -834,7 +895,7 @@ details.
|
|
|
834
895
|
|
|
835
896
|
- Built on the shoulders of fantastic
|
|
836
897
|
libraries: [httpx](https://www.python-httpx.org/), [httpcore](https://github.com/encode/httpcore),
|
|
837
|
-
[
|
|
898
|
+
[cryptography](https://cryptography.io/), and [Rich](https://github.com/Textualize/rich).
|
|
838
899
|
- Inspired by the tooling ecosystem around web performance (e.g., DevTools waterfalls, `curl --trace`).
|
|
839
900
|
- Special thanks to everyone who opens issues, shares ideas, or contributes patches.
|
|
840
901
|
|