httptap 0.2.1__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.2.1/PKG-INFO ADDED
@@ -0,0 +1,519 @@
1
+ Metadata-Version: 2.3
2
+ Name: httptap
3
+ Version: 0.2.1
4
+ Summary: HTTP request visualizer with detailed timing breakdown (DNS → TCP → TLS → HTTP)
5
+ Keywords: http,https,performance,timing,dns,tls,ssl,networking,monitoring,diagnostics,waterfall,curl,httpx
6
+ Author: Sergei Ozeranskii
7
+ License: Apache-2.0
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Intended Audience :: System Administrators
11
+ Classifier: Topic :: Internet :: WWW/HTTP
12
+ Classifier: Topic :: System :: Networking :: Monitoring
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Classifier: Topic :: Utilities
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Environment :: Console
24
+ Classifier: Operating System :: OS Independent
25
+ Classifier: Operating System :: POSIX
26
+ Classifier: Operating System :: MacOS
27
+ Classifier: Operating System :: Microsoft :: Windows
28
+ Classifier: Typing :: Typed
29
+ Requires-Dist: httpx[http2,socks]>=0.28.1
30
+ Requires-Dist: rich>=14.2.0
31
+ Requires-Dist: dnspython>=2.8.0
32
+ Requires-Dist: argcomplete>=3.6.3 ; extra == 'completion'
33
+ Maintainer: Sergei Ozeranskii
34
+ Requires-Python: >=3.10
35
+ Project-URL: Changelog, https://github.com/ozeranskii/httptap/blob/main/CHANGELOG.md
36
+ Project-URL: Documentation, https://github.com/ozeranskii/httptap?tab=readme-ov-file
37
+ Project-URL: Homepage, https://github.com/ozeranskii/httptap
38
+ Project-URL: Issues, https://github.com/ozeranskii/httptap/issues
39
+ Project-URL: Repository, https://github.com/ozeranskii/httptap.git
40
+ Provides-Extra: completion
41
+ Description-Content-Type: text/markdown
42
+
43
+ # httptap
44
+
45
+ <table>
46
+ <tr>
47
+ <th>Releases</th>
48
+ <th>CI &amp; Analysis</th>
49
+ <th>Project Info</th>
50
+ </tr>
51
+ <tr>
52
+ <td>
53
+ <a href="https://pypi.org/project/httptap/">
54
+ <img src="https://img.shields.io/pypi/v/httptap?color=3775A9&label=PyPI&logo=pypi" alt="PyPI" />
55
+ </a><br />
56
+ <a href="https://pypi.org/project/httptap/">
57
+ <img src="https://img.shields.io/pypi/pyversions/httptap?logo=python" alt="Python Versions" />
58
+ </a>
59
+ </td>
60
+ <td>
61
+ <a href="https://github.com/ozeranskii/httptap/actions/workflows/ci.yml">
62
+ <img src="https://github.com/ozeranskii/httptap/actions/workflows/ci.yml/badge.svg" alt="CI" />
63
+ </a><br />
64
+ <a href="https://github.com/ozeranskii/httptap/actions/workflows/codeql.yml">
65
+ <img src="https://github.com/ozeranskii/httptap/actions/workflows/codeql.yml/badge.svg" alt="CodeQL" />
66
+ </a><br />
67
+ <a href="https://codecov.io/github/ozeranskii/httptap">
68
+ <img src="https://codecov.io/github/ozeranskii/httptap/graph/badge.svg?token=OFOHOI1X5J" alt="Coverage" />
69
+ </a>
70
+ </td>
71
+ <td>
72
+ <a href="https://github.com/astral-sh/uv">
73
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json" alt="Build Tool" />
74
+ </a><br />
75
+ <a href="https://github.com/astral-sh/ruff">
76
+ <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Lint" />
77
+ </a><br />
78
+ <a href="https://github.com/ozeranskii/httptap/blob/main/LICENSE">
79
+ <img src="https://img.shields.io/github/license/ozeranskii/httptap?color=2E7D32" alt="License" />
80
+ </a>
81
+ </td>
82
+ </tr>
83
+ </table>
84
+
85
+ `httptap` is a rich-powered CLI that dissects an HTTP request into every meaningful phase-DNS, TCP connect, TLS
86
+ handshake, server wait, and body transfer and renders the results as a timeline table, compact summary, or
87
+ machine-friendly metrics. It is designed for interactive troubleshooting, regression analysis, and recording of
88
+ performance baselines.
89
+
90
+ ---
91
+
92
+ ## Highlights
93
+
94
+ - **Phase-by-phase timing** – precise measurements built from httpcore trace hooks (with sane fallbacks when metal-level
95
+ data is unavailable).
96
+ - **IPv4/IPv6 aware** – the resolver and TLS inspector report both the address and its family.
97
+ - **TLS insights** – certificate CN, expiry countdown, cipher suite, and protocol version are captured automatically.
98
+ - **Multiple output modes** – rich waterfall view, compact single-line summaries, or `--metrics-only` for scripting.
99
+ - **JSON export** – persist full step data (including redirect chains) for later processing.
100
+ - **Extensible** – clean Protocol interfaces for DNS, TLS, timing, visualization, and export so you can plug in custom
101
+ behavior.
102
+
103
+ > 📣 <strong>Exclusive for httptap users:</strong> Save 50% on <a href="https://gitkraken.cello.so/vY8yybnplsZ"><strong>GitKraken Pro</strong></a>. Bundle GitKraken Client, GitLens for VS Code, and powerful CLI tools to accelerate every repo workflow.
104
+
105
+ ---
106
+
107
+ ## Requirements
108
+
109
+ - Python 3.10-3.14 (CPython)
110
+ - macOS, Linux, or Windows (tested on CPython)
111
+ - No system dependencies beyond standard networking
112
+ - Code must follow the Google Python Style Guide (docstrings, formatting). See
113
+ [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html)
114
+
115
+ ---
116
+
117
+ ## Installation
118
+
119
+ ### Using Homebrew (macOS/Linux)
120
+
121
+ ```shell
122
+ brew install httptap
123
+ ```
124
+
125
+ ### Using `uv`
126
+
127
+ ```shell
128
+ uv pip install httptap
129
+ ```
130
+
131
+ ### Using `pip`
132
+
133
+ ```shell
134
+ pip install httptap
135
+ ```
136
+
137
+ ### From source
138
+
139
+ ```shell
140
+ git clone https://github.com/ozeranskii/httptap.git
141
+ cd httptap
142
+ uv venv
143
+ uv pip install .
144
+ ```
145
+
146
+ ---
147
+
148
+ ### Shell completions
149
+
150
+ #### Homebrew Installation
151
+
152
+ If you installed httptap via Homebrew, shell completions are automatically available after installation. Just restart your shell:
153
+
154
+ ```shell
155
+ # Restart your shell or reload configuration
156
+ exec $SHELL
157
+ ```
158
+
159
+ Homebrew automatically installs completions to:
160
+ - Bash: `$(brew --prefix)/etc/bash_completion.d/`
161
+ - Zsh: `$(brew --prefix)/share/zsh/site-functions/`
162
+
163
+ #### Python Package Installation
164
+
165
+ If you installed httptap via `pip` or `uv`, you need to install the optional completion extras:
166
+
167
+ 1. Install the completion extras:
168
+
169
+ ```shell
170
+ uv pip install "httptap[completion]"
171
+ # or
172
+ pip install "httptap[completion]"
173
+ ```
174
+
175
+ 2. Activate your virtual environment:
176
+
177
+ ```shell
178
+ source .venv/bin/activate
179
+ ```
180
+
181
+ 3. Run the global activation script for argument completions:
182
+
183
+ ```shell
184
+ activate-global-python-argcomplete
185
+ ```
186
+
187
+ 4. Restart your shell. Completions should now work in both bash and zsh.
188
+
189
+ **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.
190
+
191
+ #### Usage Examples
192
+
193
+ Once completions are installed, you can use `Tab` to autocomplete commands and options:
194
+
195
+ ```shell
196
+ # Complete command options
197
+ httptap --<TAB>
198
+ # Shows: --follow, --timeout, --no-http2, --ignore-ssl, --proxy, --header, --compact, --metrics-only, --json, --version, --help
199
+
200
+ # Complete after typing partial option
201
+ httptap --fol<TAB>
202
+ # Completes to: httptap --follow
203
+
204
+ # Complete multiple options
205
+ httptap --follow --time<TAB>
206
+ # Completes to: httptap --follow --timeout
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Quick Start
212
+
213
+ Currently `httptap` issues a `GET` request and streams the entire response body. Other HTTP methods and payloads are not
214
+ supported yet; this keeps the interface simple and avoids exposing sensitive request data in output. If you need to
215
+ profile `POST`/`PUT` workloads, you can wrap `httptap` and override the request executor to plug in custom behavior.
216
+
217
+ Run a single request and display a rich waterfall:
218
+
219
+ ```shell
220
+ httptap https://httpbin.io
221
+ ```
222
+
223
+ Add custom headers (repeat `-H` for multiple values):
224
+
225
+ ```shell
226
+ httptap \
227
+ -H "Accept: application/json" \
228
+ -H "Authorization: Bearer super-secret" \
229
+ https://httpbin.io/bearer
230
+ ```
231
+
232
+ Follow redirect chains and dump metrics to JSON:
233
+
234
+ ```shell
235
+ httptap --follow --json out/report.json https://httpbin.io/redirect/2
236
+ ```
237
+
238
+ Collect compact (single-line) timings suitable for logs:
239
+
240
+ ```shell
241
+ httptap --compact https://httpbin.io/get
242
+ ```
243
+
244
+ Expose raw metrics for scripts:
245
+
246
+ ```shell
247
+ httptap --metrics-only https://httpbin.io/get | tee timings.log
248
+ ```
249
+
250
+ Programmatic users can inject a custom executor for advanced scenarios. The
251
+ default analyzer accepts either a modern `RequestExecutor` implementation or a
252
+ legacy callable wrapped with `CallableRequestExecutor`, so new request flags
253
+ remain backward compatible.
254
+
255
+ Bypass TLS verification when troubleshooting self-signed endpoints:
256
+
257
+ ```shell
258
+ httptap --ignore-ssl https://self-signed.badssl.com
259
+ ```
260
+
261
+ The flag disables certificate validation and relaxes many handshake
262
+ constraints so that legacy endpoints (expired/self-signed/hostname
263
+ mismatches, weak hashes, older TLS versions) still complete. Some
264
+ algorithms removed from modern OpenSSL builds (for example RC4 or
265
+ 3DES) may remain unavailable. Use this mode only on trusted networks.
266
+
267
+ Route traffic through an HTTP/SOCKS proxy (explicit override takes precedence over env vars `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`):
268
+
269
+ ```shell
270
+ httptap --proxy socks5h://proxy.local:1080 https://example.com
271
+ ```
272
+
273
+ The output and JSON export include the proxy URI so you can confirm what
274
+ path was used.
275
+
276
+ ---
277
+
278
+
279
+ ## Releasing
280
+
281
+ ### Prerequisites
282
+
283
+ - GitHub Environment `pypi` must be configured in repository settings
284
+ - PyPI Trusted Publishing configured for `ozeranskii/httptap`
285
+
286
+ ### Steps
287
+
288
+ 1. Trigger the **Release** workflow from GitHub Actions:
289
+ - Provide exact version (e.g., `0.3.0`), OR
290
+ - Select bump type: `patch`, `minor`, or `major`
291
+ 2. The workflow will:
292
+ - Update version in `pyproject.toml` using `uv version`
293
+ - Generate changelog with `git-cliff` and update `CHANGELOG.md`
294
+ - Commit changes and create a git tag
295
+ - Run full test suite on the tagged version
296
+ - Build wheel and source distribution
297
+ - Publish to PyPI via Trusted Publishing (OIDC)
298
+ - Create GitHub Release with generated notes
299
+
300
+ ---
301
+
302
+ ## Sample Output
303
+
304
+ ![sample-output.png](docs/assets/sample-output.png)
305
+
306
+ The redirect summary includes a total row:
307
+ ![sample-follow-redirects-output.png](docs/assets/sample-follow-redirects-output.png)
308
+
309
+ ---
310
+
311
+ ## JSON Export Structure
312
+
313
+ ```json
314
+ {
315
+ "initial_url": "https://httpbin.io/redirect/2",
316
+ "total_steps": 3,
317
+ "steps": [
318
+ {
319
+ "url": "https://httpbin.io/redirect/2",
320
+ "step_number": 1,
321
+ "timing": {
322
+ "dns_ms": 8.947208058089018,
323
+ "connect_ms": 96.97712492197752,
324
+ "tls_ms": 194.56583401188254,
325
+ "ttfb_ms": 445.9513339679688,
326
+ "total_ms": 447.3437919514254,
327
+ "wait_ms": 145.46116697601974,
328
+ "xfer_ms": 1.392457983456552,
329
+ "is_estimated": false
330
+ },
331
+ "network": {
332
+ "ip": "44.211.11.205",
333
+ "ip_family": "IPv4",
334
+ "tls_version": "TLSv1.2",
335
+ "tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
336
+ "cert_cn": "httpbin.io",
337
+ "cert_days_left": 143
338
+ },
339
+ "response": {
340
+ "status": 302,
341
+ "bytes": 0,
342
+ "content_type": null,
343
+ "server": null,
344
+ "date": "2025-10-23T19:20:36+00:00",
345
+ "location": "/relative-redirect/1",
346
+ "headers": {
347
+ "access-control-allow-credentials": "true",
348
+ "access-control-allow-origin": "*",
349
+ "location": "/relative-redirect/1",
350
+ "date": "Thu, 23 Oct 2025 19:20:36 GMT",
351
+ "content-length": "0"
352
+ }
353
+ },
354
+ "error": null,
355
+ "note": null
356
+ },
357
+ {
358
+ "url": "https://httpbin.io/relative-redirect/1",
359
+ "step_number": 2,
360
+ "timing": {
361
+ "dns_ms": 2.6895420160144567,
362
+ "connect_ms": 97.51500003039837,
363
+ "tls_ms": 193.99016606621444,
364
+ "ttfb_ms": 400.2034160075709,
365
+ "total_ms": 400.60841606464237,
366
+ "wait_ms": 106.00870789494365,
367
+ "xfer_ms": 0.4050000570714474,
368
+ "is_estimated": false
369
+ },
370
+ "network": {
371
+ "ip": "44.211.11.205",
372
+ "ip_family": "IPv4",
373
+ "tls_version": "TLSv1.2",
374
+ "tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
375
+ "cert_cn": "httpbin.io",
376
+ "cert_days_left": 143
377
+ },
378
+ "response": {
379
+ "status": 302,
380
+ "bytes": 0,
381
+ "content_type": null,
382
+ "server": null,
383
+ "date": "2025-10-23T19:20:36+00:00",
384
+ "location": "/get",
385
+ "headers": {
386
+ "access-control-allow-credentials": "true",
387
+ "access-control-allow-origin": "*",
388
+ "location": "/get",
389
+ "date": "Thu, 23 Oct 2025 19:20:36 GMT",
390
+ "content-length": "0"
391
+ }
392
+ },
393
+ "error": null,
394
+ "note": null
395
+ },
396
+ {
397
+ "url": "https://httpbin.io/get",
398
+ "step_number": 3,
399
+ "timing": {
400
+ "dns_ms": 2.643457963131368,
401
+ "connect_ms": 97.36416593659669,
402
+ "tls_ms": 197.3062080796808,
403
+ "ttfb_ms": 403.2038329169154,
404
+ "total_ms": 403.9644579170272,
405
+ "wait_ms": 105.89000093750656,
406
+ "xfer_ms": 0.7606250001117587,
407
+ "is_estimated": false
408
+ },
409
+ "network": {
410
+ "ip": "52.70.33.41",
411
+ "ip_family": "IPv4",
412
+ "tls_version": "TLSv1.2",
413
+ "tls_cipher": "ECDHE-RSA-AES128-GCM-SHA256",
414
+ "cert_cn": "httpbin.io",
415
+ "cert_days_left": 143
416
+ },
417
+ "response": {
418
+ "status": 200,
419
+ "bytes": 389,
420
+ "content_type": "application/json; charset=utf-8",
421
+ "server": null,
422
+ "date": "2025-10-23T19:20:37+00:00",
423
+ "location": null,
424
+ "headers": {
425
+ "access-control-allow-credentials": "true",
426
+ "access-control-allow-origin": "*",
427
+ "content-type": "application/json; charset=utf-8",
428
+ "date": "Thu, 23 Oct 2025 19:20:37 GMT",
429
+ "content-length": "389"
430
+ }
431
+ },
432
+ "error": null,
433
+ "note": null
434
+ }
435
+ ],
436
+ "summary": {
437
+ "total_time_ms": 1251.916665933095,
438
+ "final_status": 200,
439
+ "final_url": "https://httpbin.io/get",
440
+ "final_bytes": 389,
441
+ "errors": 0
442
+ }
443
+ }
444
+ ```
445
+
446
+ ## Metrics-only scripting
447
+
448
+ ```shell
449
+ httptap --metrics-only https://httpbin.io/get
450
+ ```
451
+
452
+ ```terminaloutput
453
+ Step 1: dns=30.1 connect=97.3 tls=199.0 ttfb=472.2 total=476.0 status=200 bytes=389 ip=44.211.11.205 family=IPv4
454
+ tls_version=TLSv1.2
455
+ ```
456
+
457
+ ---
458
+
459
+ ## Advanced Usage
460
+
461
+ ### Custom Implementations
462
+
463
+ Swap in your own resolver or TLS inspector (anything satisfying the Protocol from `httptap.interfaces`):
464
+
465
+ ```python
466
+ from httptap import HTTPTapAnalyzer, SystemDNSResolver
467
+
468
+
469
+ class HardcodedDNS(SystemDNSResolver):
470
+ def resolve(self, host, port, timeout):
471
+ return "93.184.216.34", "IPv4", 0.1
472
+
473
+
474
+ analyzer = HTTPTapAnalyzer(dns_resolver=HardcodedDNS())
475
+ steps = analyzer.analyze_url("https://httpbin.io")
476
+ ```
477
+
478
+ ---
479
+
480
+ ## Development
481
+
482
+ ```shell
483
+ git clone https://github.com/ozeranskii/httptap.git
484
+ cd httptap
485
+ uv sync
486
+ uv run pytest
487
+ uv run ruff check
488
+ uv run ruff format .
489
+ ```
490
+
491
+ Tests expect outbound network access; you can mock `SystemDNSResolver` / `SocketTLSInspector` when running offline.
492
+
493
+ ---
494
+
495
+ ## Contributing
496
+
497
+ 1. Fork and clone the repo.
498
+ 2. Create a feature branch.
499
+ 3. Run `pytest` and `ruff` before committing.
500
+ 4. Submit a pull request with a clear description and any relevant screenshots or benchmarks.
501
+
502
+ We welcome bug reports, feature proposals, doc improvements, and creative new visualizations or exporters.
503
+
504
+ ---
505
+
506
+ ## License
507
+
508
+ Apache License 2.0 © Sergei Ozeranskii. See [LICENSE](https://github.com/ozeranskii/httptap/blob/main/LICENSE) for
509
+ details.
510
+
511
+ ---
512
+
513
+ ## Acknowledgements
514
+
515
+ - Built on the shoulders of fantastic
516
+ libraries: [httpx](https://www.python-httpx.org/), [httpcore](https://github.com/encode/httpcore),
517
+ and [Rich](https://github.com/Textualize/rich).
518
+ - Inspired by the tooling ecosystem around web performance (e.g., DevTools waterfalls, `curl --trace`).
519
+ - Special thanks to everyone who opens issues, shares ideas, or contributes patches.