Skip to content

Commit c74dda6

Browse files
authored
docs(readme): add project banner to README and docs index (#108)
1 parent ad4f7a4 commit c74dda6

13 files changed

Lines changed: 457 additions & 10 deletions

File tree

.github/workflows/release.yml

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,35 @@ jobs:
161161
compression-level: 6
162162
if-no-files-found: error
163163

164+
- name: Prepare SBOM directory
165+
run: mkdir -p sbom
166+
167+
- name: Generate SBOM (CycloneDX)
168+
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
169+
with:
170+
artifact-name: ${{ env.PACKAGE_NAME }}-${{ needs.prepare.outputs.version }}.cdx.json
171+
format: cyclonedx-json
172+
output-file: sbom/${{ env.PACKAGE_NAME }}-${{ needs.prepare.outputs.version }}.cdx.json
173+
upload-artifact: false
174+
upload-release-assets: false
175+
176+
- name: Generate SBOM (SPDX)
177+
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
178+
with:
179+
artifact-name: ${{ env.PACKAGE_NAME }}-${{ needs.prepare.outputs.version }}.spdx.json
180+
format: spdx-json
181+
output-file: sbom/${{ env.PACKAGE_NAME }}-${{ needs.prepare.outputs.version }}.spdx.json
182+
upload-artifact: false
183+
upload-release-assets: false
184+
185+
- name: Upload SBOM artifacts
186+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
187+
with:
188+
name: sbom
189+
path: sbom/
190+
retention-days: 7
191+
if-no-files-found: error
192+
164193
publish-pypi:
165194
name: Publish to PyPI
166195
runs-on: ubuntu-latest
@@ -201,12 +230,18 @@ jobs:
201230
with:
202231
persist-credentials: false
203232

204-
- name: Download artifacts
233+
- name: Download dist artifacts
205234
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
206235
with:
207236
name: dist
208237
path: dist/
209238

239+
- name: Download SBOM artifacts
240+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
241+
with:
242+
name: sbom
243+
path: sbom/
244+
210245
- name: Create GitHub Release
211246
env:
212247
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -215,7 +250,7 @@ jobs:
215250
REPO: ${{ github.repository }}
216251
run: |-
217252
gh release create "$TAG" \
218-
dist/* \
253+
dist/* sbom/* \
219254
--repo "$REPO" \
220255
--title "$TAG" \
221256
--notes "$NOTES"

.github/workflows/scorecard.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# OpenSSF Scorecard analysis — https://github.com/ossf/scorecard-action
2+
# Publishes results to the OpenSSF API and updates the README badge.
3+
name: OpenSSF Scorecard
4+
5+
on:
6+
branch_protection_rule:
7+
schedule:
8+
- cron: '25 8 * * 1'
9+
push:
10+
branches:
11+
- main
12+
13+
permissions: {}
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
analysis:
21+
name: Scorecard analysis
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 15
24+
permissions:
25+
security-events: write # Required for uploading SARIF results
26+
id-token: write # Required for publishing results to the OpenSSF API
27+
contents: read # Required for repository checkout
28+
actions: read # Required for Token-Permissions check
29+
30+
steps:
31+
- name: Checkout repository
32+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
33+
with:
34+
persist-credentials: false
35+
36+
- name: Run OpenSSF Scorecard analysis
37+
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
38+
with:
39+
results_file: results.sarif
40+
results_format: sarif
41+
publish_results: true
42+
43+
- name: Upload SARIF results artifact
44+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
45+
with:
46+
name: SARIF file
47+
path: results.sarif
48+
retention-days: 5
49+
50+
- name: Upload SARIF results to code scanning
51+
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4
52+
with:
53+
sarif_file: results.sarif

CITATION.cff

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# YAML 1.2
2+
# Citation File Format v1.2.0 — https://citation-file-format.github.io/
3+
cff-version: 1.2.0
4+
message: "If you use httptap in your research or tooling, please cite it using these metadata."
5+
title: httptap
6+
abstract: >-
7+
httptap is a rich-powered CLI that dissects an HTTP request into every
8+
meaningful phase — DNS, TCP connect, TLS handshake, server wait, and
9+
body transfer — and renders the results as a timeline table, compact
10+
summary, or machine-friendly metrics. It is designed for interactive
11+
troubleshooting, regression analysis, and recording of performance
12+
baselines.
13+
type: software
14+
authors:
15+
- family-names: Ozeranskii
16+
given-names: Sergei
17+
alias: ozeranskii
18+
version: 0.4.7
19+
date-released: "2026-03-30"
20+
license: Apache-2.0
21+
url: "https://github.com/ozeranskii/httptap"
22+
repository-code: "https://github.com/ozeranskii/httptap"
23+
repository-artifact: "https://pypi.org/project/httptap/"
24+
keywords:
25+
- http
26+
- https
27+
- performance
28+
- timing
29+
- dns
30+
- tls
31+
- ssl
32+
- networking
33+
- monitoring
34+
- diagnostics
35+
- waterfall
36+
- curl
37+
- httpx
38+
- python
39+
- cli

README.md

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
<p align="center">
2+
<img src="docs/assets/httptap-banner.svg" alt="httptap" width="100%" />
3+
</p>
4+
15
# httptap
26

37
<table>
@@ -22,6 +26,9 @@
2226
<a href="https://github.com/ozeranskii/httptap/actions/workflows/codeql.yml">
2327
<img src="https://github.com/ozeranskii/httptap/actions/workflows/codeql.yml/badge.svg" alt="CodeQL" />
2428
</a><br />
29+
<a href="https://scorecard.dev/viewer/?uri=github.com/ozeranskii/httptap">
30+
<img src="https://api.scorecard.dev/projects/github.com/ozeranskii/httptap/badge" alt="OpenSSF Scorecard" />
31+
</a><br />
2532
<a href="https://codecov.io/github/ozeranskii/httptap">
2633
<img src="https://codecov.io/github/ozeranskii/httptap/graph/badge.svg?token=OFOHOI1X5J" alt="Coverage" />
2734
</a><br />
@@ -50,6 +57,39 @@ performance baselines.
5057

5158
---
5259

60+
## Table of Contents
61+
62+
- [Highlights](#highlights)
63+
- [How it compares](#how-it-compares)
64+
- [Requirements](#requirements)
65+
- [Installation](#installation)
66+
- [Using Homebrew (macOS/Linux)](#using-homebrew-macoslinux)
67+
- [Using `uv`](#using-uv)
68+
- [Using `pip`](#using-pip)
69+
- [From source](#from-source)
70+
- [Shell completions](#shell-completions)
71+
- [Quick Start](#quick-start)
72+
- [Basic GET Request](#basic-get-request)
73+
- [POST Request with Data](#post-request-with-data)
74+
- [Other HTTP Methods](#other-http-methods)
75+
- [Custom Headers](#custom-headers)
76+
- [Redirects and JSON Export](#redirects-and-json-export)
77+
- [Output Modes](#output-modes)
78+
- [Advanced Usage](#advanced-usage)
79+
- [Environment Variables](#environment-variables)
80+
- [Exit Codes](#exit-codes)
81+
- [Releasing](#releasing)
82+
- [Sample Output](#sample-output)
83+
- [JSON Export Structure](#json-export-structure)
84+
- [Metrics-only scripting](#metrics-only-scripting)
85+
- [Advanced Usage](#advanced-usage-1)
86+
- [Development](#development)
87+
- [Contributing](#contributing)
88+
- [License](#license)
89+
- [Acknowledgements](#acknowledgements)
90+
91+
---
92+
5393
## Highlights
5494

5595
- **Phase-by-phase timing** – precise measurements built from httpcore trace hooks (with sane fallbacks when metal-level
@@ -67,6 +107,33 @@ performance baselines.
67107
68108
---
69109

110+
## How it compares
111+
112+
| Feature | `httptap` | `curl -w` | [`httpstat`](https://github.com/reorx/httpstat) | `httpie` |
113+
|------------------------------------------|:---------:|:----------------------:|:-----------------------------------------------:|:-----------------:|
114+
| Phase-by-phase timing (DNS/TCP/TLS/TTFB) || ✅ (format str) |||
115+
| Rich waterfall visualization ||| ⚠️ text bars ||
116+
| Redirect chain with per-step timing |||||
117+
| JSON export (machine-readable) || ✅ (`-w '%{json}'`) | ✅ (`--format json/jsonl`, v1 schema) | ❌ (no metrics) |
118+
| Metrics-only mode for scripting ||| ✅ (`--format json`) ||
119+
| SLO threshold checking ||| ✅ (`--slo total=500,...`) ||
120+
| TLS certificate inspection (CN, expiry) || ⚠️ via `-v` |||
121+
| IPv4/IPv6 reporting | ✅ family | ⚠️ IP via `remote_ip` | ⚠️ IP only (`remote_ip`/`remote_port`) ||
122+
| HTTP/2 support ||| ⚠️ via curl passthrough | ⚠️ plugin only |
123+
| Proxy with source attribution || ⚠️ no attribution | ⚠️ via curl passthrough | ⚠️ no attribution |
124+
| Custom CA bundle ||| ⚠️ via curl passthrough ||
125+
| Extensible Python API || ❌ (pycurl ≠ same API) || ⚠️ via requests |
126+
| Curl-compatible flags ||| ✅ (passes through) ||
127+
| Zero system dependencies ||| needs curl ||
128+
129+
**When to pick what:**
130+
- **`httptap`** — interactive troubleshooting, regression analysis, and scripted baselines with structured JSON.
131+
- **`curl -w`** — one-off shell checks where curl is already the dependency.
132+
- **`httpstat`** — quick visual breakdown on top of an existing curl install.
133+
- **`httpie`** — general-purpose request/response exploration, not latency profiling.
134+
135+
---
136+
70137
## Requirements
71138

72139
- Python 3.10-3.15 (CPython)
@@ -158,7 +225,7 @@ Once completions are installed, you can use `Tab` to autocomplete commands and o
158225
```shell
159226
# Complete command options
160227
httptap --<TAB>
161-
# Shows: --follow, --timeout, --no-http2, --ignore-ssl, --cacert, --proxy, --header, --compact, --metrics-only, --json, --version, --help
228+
# Shows: --method, --data, --follow, --timeout, --no-http2, --ignore-ssl, --cacert, --proxy, --header, --compact, --metrics-only, --json, --version, --help
162229

163230
# Complete after typing partial option
164231
httptap --fol<TAB>
@@ -306,6 +373,54 @@ can confirm what path was used (e.g., `(from arg --proxy)`,
306373

307374
---
308375

376+
## Environment Variables
377+
378+
httptap reads the following environment variables at runtime. All of them are
379+
overridable via CLI flags, and the actual source used for each request is
380+
recorded in the output and JSON export.
381+
382+
| Variable | Purpose | Overridden by |
383+
|---------------------------------------|--------------------------------------------------------------------------------------------------------------|-----------------------|
384+
| `HTTP_PROXY` / `http_proxy` | Proxy URL used for `http://` targets. | `-x/--proxy` |
385+
| `HTTPS_PROXY` / `https_proxy` | Proxy URL used for `https://` targets. | `-x/--proxy` |
386+
| `ALL_PROXY` / `all_proxy` | Fallback proxy URL when scheme-specific variables are unset. | `-x/--proxy` |
387+
| `NO_PROXY` / `no_proxy` | Comma-separated exclusion list (supports `*`, leading `.`, exact matches). Bypassed entries connect direct. | `--proxy ""` |
388+
| `NO_COLOR` | Disables ANSI colors in all Rich output (honors the [NO_COLOR](https://no-color.org) convention). ||
389+
| `FORCE_COLOR` | Forces colored output even when stdout is not a TTY (Rich convention). ||
390+
| `TERM=dumb` | Rich downgrades to plain-text rendering. ||
391+
392+
> Precedence for proxy configuration: explicit `-x/--proxy``--proxy ""`
393+
> (disables env) → `HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY` (scheme-matching) →
394+
> `NO_PROXY` exclusion → direct connection.
395+
396+
---
397+
398+
## Exit Codes
399+
400+
httptap follows the BSD `sysexits.h` convention so it integrates cleanly with
401+
shell pipelines, CI jobs, and systemd services.
402+
403+
| Code | Symbol | Meaning |
404+
|:-----:|-------------------------|------------------------------------------------------------|
405+
| `0` | `EX_OK` | Success. |
406+
| `64` | `EX_USAGE` | Invalid command-line arguments. |
407+
| `70` | `EX_SOFTWARE` | Internal error (unexpected exception, bug). |
408+
| `75` | `EX_TEMPFAIL` | Network / TLS error (partial output may still be rendered). |
409+
| `128 + N` | Signal offset | Killed by signal `N` (e.g., `130` for `SIGINT` / Ctrl-C). |
410+
411+
Example — fail a CI job only on usage errors, tolerating transient network
412+
issues:
413+
414+
```shell
415+
httptap --metrics-only https://api.example.com/health
416+
rc=$?
417+
if [ "$rc" = 64 ] || [ "$rc" = 70 ]; then
418+
exit "$rc"
419+
fi
420+
```
421+
422+
---
423+
309424

310425
## Releasing
311426

@@ -325,8 +440,9 @@ can confirm what path was used (e.g., `(from arg --proxy)`,
325440
- Commit changes and create a git tag
326441
- Run full test suite on the tagged version
327442
- Build wheel and source distribution
443+
- Generate SBOM in CycloneDX and SPDX formats via Syft
328444
- Publish to PyPI via Trusted Publishing (OIDC)
329-
- Create GitHub Release with generated notes
445+
- Create GitHub Release with wheel, sdist, and SBOM assets
330446

331447
---
332448

@@ -577,6 +693,6 @@ details.
577693

578694
- Built on the shoulders of fantastic
579695
libraries: [httpx](https://www.python-httpx.org/), [httpcore](https://github.com/encode/httpcore),
580-
and [Rich](https://github.com/Textualize/rich).
696+
[dnspython](https://www.dnspython.org/), and [Rich](https://github.com/Textualize/rich).
581697
- Inspired by the tooling ecosystem around web performance (e.g., DevTools waterfalls, `curl --trace`).
582698
- Special thanks to everyone who opens issues, shares ideas, or contributes patches.

docs/api/overview.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ analyzer = HTTPTapAnalyzer()
3737
steps = analyzer.analyze_url("https://httpbin.io")
3838
```
3939

40+
`analyze_url` also accepts keyword-only arguments for non-GET requests:
41+
42+
```python
43+
from httptap import HTTPTapAnalyzer
44+
from httptap.constants import HTTPMethod
45+
46+
analyzer = HTTPTapAnalyzer(follow_redirects=True, timeout=10.0)
47+
steps = analyzer.analyze_url(
48+
"https://httpbin.io/post",
49+
method=HTTPMethod.POST,
50+
content=b'{"name": "John"}',
51+
headers={"Content-Type": "application/json"},
52+
)
53+
```
54+
4055
### StepMetrics
4156

4257
Data model representing a single request/response cycle.

0 commit comments

Comments
 (0)