Plans (docs/plans/): - 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design - 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage doc + Makefile, file input (-f), simplification pass, run-summary footer Summaries (docs/summaries/): one per completed feature, recording what was actually built, deviations from the plan, and verification steps Explanations (docs/explanations/): two deep-dives written during review — hxprobe concurrency model and worst-exit-code + render-loop analysis Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md for the full runnable reference Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the latprobe Python package for interview / code-review context CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc, file input, simplification, run-summary footer) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
5.4 KiB
Markdown
103 lines
5.4 KiB
Markdown
# Plan: read target URLs from a file for hxprobe
|
|
|
|
## Context
|
|
|
|
`hxprobe` currently only accepts URLs as positional CLI arguments
|
|
(`hxprobe/hxprobe/cli.py:368`, `nargs="+"`). The user wants a file-based
|
|
input mode too, matching the existing convention `simple.py`/`phases.py`
|
|
already use (`python/simple.py:20-30`'s `load_sites()`: plain text, one URL
|
|
per line, `#`-comments and blank lines skipped, first whitespace-separated
|
|
token taken per line).
|
|
|
|
User-confirmed decisions:
|
|
- **Mutually exclusive** with positional URL args (either pass URLs on the
|
|
command line, or `-f FILE`, never both).
|
|
- **hxprobe only** — `latprobe` is intentionally left untouched.
|
|
- **Add example fixture files** (`hxprobe/configs/*.txt`, mirroring
|
|
`python/configs/*.txt`'s exact set: all-ok, dns-failure,
|
|
connection-refused, timeout, tls-errors, http-errors, mixed) plus one new
|
|
section in the existing `hxprobe/USAGE.md` demonstrating the flag.
|
|
|
|
## Implementation
|
|
|
|
**`hxprobe/hxprobe/cli.py`:**
|
|
- `urls` positional becomes `nargs="*"` (was `nargs="+"`) — no longer
|
|
required on its own, since `-f` is now a second valid source.
|
|
- New flag: `-f, --file PATH` — "read URLs from a file, one per line,
|
|
`#` comments allowed (mutually exclusive with positional url args)".
|
|
Placed right after the `urls` positional definition in the argparse
|
|
block, since the two are the two ways of specifying what to probe.
|
|
- New helper `_load_urls(path: str) -> list[str]`, duplicating (not
|
|
importing) `simple.py`'s `load_sites()` logic — consistent with hxprobe's
|
|
established "imports nothing outside its own directory" rule from the
|
|
standalone-extraction work.
|
|
- After `parser.parse_args()`, manual validation (mirrors the existing
|
|
`--timeout` invalid-value handling style — write to the injected
|
|
`stderr`, `return EXIT_USAGE`, rather than routing through
|
|
`argparse`'s mutually-exclusive-group machinery, which doesn't mix
|
|
cleanly with a variadic positional):
|
|
- both `ns.urls` and `ns.file` given → `parser.error(...)` (usage error,
|
|
consistent with how `_Parser.error()` already handles bad usage)
|
|
- neither given → `parser.error(...)`
|
|
- `ns.file` given but unreadable (`FileNotFoundError`/`OSError`) →
|
|
`stderr.write(...)`; `return EXIT_USAGE`
|
|
- `ns.file` given but yields zero URLs → same treatment
|
|
- otherwise `urls = _load_urls(ns.file)` or `urls = ns.urls`
|
|
|
|
**`hxprobe/tests/test_cli.py`:** new hermetic tests — successful multi-URL
|
|
run from a file, missing-file error, empty-file error, and the
|
|
both-sources-given usage error. Uses a temp file (`tempfile`), no network
|
|
needed for the parsing-error cases.
|
|
|
|
**`hxprobe/configs/*.txt`** (new directory) — same 7 fixtures as
|
|
`python/configs/`, adapted:
|
|
- `all-ok.txt`, `dns-failure.txt`, `connection-refused.txt`,
|
|
`tls-errors.txt` — same URLs, same behavior (DNS/TCP/TLS failures are
|
|
identical regardless of HTTP client sophistication); only the header
|
|
comments change (`hxprobe -f configs/<name>.txt` instead of
|
|
`python3.14 python/simple.py ...`).
|
|
- `timeout.txt` — same two targets (`10.255.255.1`, `192.0.2.1`, RFC 5737
|
|
TEST-NET-1) as the original; "expected exit code" documents normal-network
|
|
behavior (exit 4), same caveat the original file already carries about
|
|
network-dependent behavior.
|
|
- `http-errors.txt` — same 404 URLs; header comment updated to show the
|
|
demo command with `--fail` (hxprobe treats 4xx as success without
|
|
`--fail`, unlike `simple.py`, which always raises on HTTPError) —
|
|
"expected exit code" becomes 6, not `simple.py`'s blanket 1.
|
|
- `mixed.txt` — same mixed set; demo command includes `--fail`; expected
|
|
exit code recalculated as the worst code across the included classes
|
|
(dns=2, connect=3, tls=5, http=6 with `--fail`) → 6.
|
|
- Each header's "Expected exit code" will be verified by actually running
|
|
the fixture through `hxprobe -f ...` during implementation, not assumed
|
|
from the `simple.py` originals — hxprobe's worst-code-wins exit scheme
|
|
(`hxprobe/hxprobe/cli.py:455-481`) differs fundamentally from
|
|
`simple.py`'s blanket 0/1.
|
|
|
|
**`hxprobe/USAGE.md`:** new section "Reading URLs from a file (`-f`)",
|
|
placed after the "Multiple URLs" case (same family of "what to probe"
|
|
examples) — command + real captured output using `configs/all-ok.txt` or
|
|
`configs/mixed.txt`, plus a short list of the other fixture files available
|
|
and what each demonstrates.
|
|
|
|
**Docs housekeeping** (per CLAUDE.md convention): save this plan under
|
|
`docs/plans/`, write a summary under `docs/summaries/` after implementation,
|
|
append a `CHANGELOG.md` entry.
|
|
|
|
## Verification
|
|
|
|
1. `cd hxprobe && uv run python -m hxprobe -f configs/all-ok.txt` — probes
|
|
all 3 URLs, exit 0.
|
|
2. `cd hxprobe && uv run python -m hxprobe -f configs/dns-failure.txt` —
|
|
exit 2; `connection-refused.txt` → exit 3; `tls-errors.txt` → exit 5;
|
|
`--fail -f configs/http-errors.txt` → exit 6; `--fail -f configs/mixed.txt`
|
|
→ exit 6 (confirms the worst-code documented in each header is accurate).
|
|
3. `uv run python -m hxprobe https://example.com -f configs/all-ok.txt` —
|
|
usage error (both sources given).
|
|
4. `uv run python -m hxprobe -f /no/such/file` — usage error, clear message.
|
|
5. `cd hxprobe && make check` — new hermetic tests pass alongside the
|
|
existing 28.
|
|
6. Re-run the new `hxprobe/USAGE.md` section's command and confirm captured
|
|
output matches what's printed in the doc.
|
|
7. `grep -n "import" hxprobe/hxprobe/cli.py` — confirm no new import from
|
|
`python/simple.py` or anywhere outside `hxprobe/`.
|