httpcheck 1.4.2__tar.gz → 1.5.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.
- {httpcheck-1.4.2/httpcheck.egg-info → httpcheck-1.5.0}/PKG-INFO +119 -26
- httpcheck-1.4.2/PKG-INFO → httpcheck-1.5.0/README.md +105 -65
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/__init__.py +4 -1
- httpcheck-1.5.0/httpcheck/async_site_checker.py +238 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/cli.py +103 -45
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/common.py +4 -6
- httpcheck-1.5.0/httpcheck/config.py +143 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/file_handler.py +6 -7
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/logger.py +2 -3
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/output_formatter.py +0 -1
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/site_checker.py +70 -38
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/tld_manager.py +48 -25
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/validation.py +12 -12
- httpcheck-1.4.2/README.md → httpcheck-1.5.0/httpcheck.egg-info/PKG-INFO +159 -21
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/SOURCES.txt +4 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/requires.txt +11 -2
- {httpcheck-1.4.2 → httpcheck-1.5.0}/pyproject.toml +20 -6
- httpcheck-1.5.0/tests/test_async_site_checker.py +360 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_cli_integration.py +250 -28
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_common.py +2 -2
- httpcheck-1.5.0/tests/test_config.py +323 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_httpcheck.py +1 -1
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_init.py +2 -2
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_performance.py +22 -4
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_site_checker.py +151 -8
- httpcheck-1.5.0/tests/test_tld_manager.py +357 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_validation.py +5 -1
- httpcheck-1.4.2/tests/test_tld_manager.py +0 -101
- {httpcheck-1.4.2 → httpcheck-1.5.0}/LICENSE +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck/notification.py +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/dependency_links.txt +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/entry_points.txt +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/httpcheck.egg-info/top_level.txt +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/setup.cfg +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_file_handler.py +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_output_formats.py +0 -0
- {httpcheck-1.4.2 → httpcheck-1.5.0}/tests/test_request_customization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpcheck
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: HTTP status code checker with advanced features
|
|
5
5
|
Author-email: Thomas Juul Dyhr <thomas@dyhr.com>
|
|
6
6
|
License: MIT
|
|
@@ -13,19 +13,22 @@ Classifier: Intended Audience :: System Administrators
|
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Topic :: System :: Monitoring
|
|
21
22
|
Classifier: Topic :: System :: Networking :: Monitoring
|
|
22
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.10
|
|
23
24
|
Description-Content-Type: text/markdown
|
|
24
25
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: requests>=2.
|
|
26
|
+
Requires-Dist: requests>=2.33.0
|
|
27
|
+
Requires-Dist: httpx>=0.27.0
|
|
26
28
|
Requires-Dist: tabulate>=0.9.0
|
|
27
29
|
Requires-Dist: tqdm>=4.67.0
|
|
28
30
|
Requires-Dist: validators>=0.22.0
|
|
31
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
29
32
|
Provides-Extra: macos
|
|
30
33
|
Requires-Dist: rumps>=0.4.0; extra == "macos"
|
|
31
34
|
Requires-Dist: pyobjc-framework-Cocoa>=10.0; extra == "macos"
|
|
@@ -37,61 +40,122 @@ Requires-Dist: pytest>=7.0; extra == "dev"
|
|
|
37
40
|
Requires-Dist: pytest-mock>=3.0; extra == "dev"
|
|
38
41
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
39
42
|
Requires-Dist: pytest-benchmark>=5.0; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
40
44
|
Requires-Dist: pylint>=3.1.0; extra == "dev"
|
|
41
|
-
Requires-Dist: mypy
|
|
45
|
+
Requires-Dist: mypy==1.20.2; extra == "dev"
|
|
46
|
+
Requires-Dist: types-requests>=2.31.0; extra == "dev"
|
|
47
|
+
Requires-Dist: types-tabulate>=0.9.0; extra == "dev"
|
|
48
|
+
Requires-Dist: types-tqdm>=4.66.0; extra == "dev"
|
|
49
|
+
Requires-Dist: tomli>=2.0.0; extra == "dev"
|
|
42
50
|
Provides-Extra: build
|
|
43
51
|
Requires-Dist: py2app>=0.28; extra == "build"
|
|
44
52
|
Dynamic: license-file
|
|
45
53
|
|
|
54
|
+
<div align="right">
|
|
55
|
+
|
|
56
|
+
<a href="https://railway.com?referralCode=QhjuBc">
|
|
57
|
+
|
|
58
|
+
<img width="160" src="https://raw.githubusercontent.com/docdyhr/.github/main/assets/railway-corner-v2@2x.png" alt="Deploy on Railway — $20 free credits">
|
|
59
|
+
|
|
60
|
+
</a>
|
|
61
|
+
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
[](https://badge.fury.io/py/httpcheck)
|
|
65
|
+
[](https://pypi.org/project/httpcheck/)
|
|
66
|
+
[](https://pypi.org/project/httpcheck/)
|
|
46
67
|
[](https://github.com/docdyhr/httpcheck/actions/workflows/ci.yml)
|
|
47
68
|
[](https://github.com/pre-commit/pre-commit)
|
|
48
|
-

|
|
49
69
|
[](https://github.com/docdyhr/httpcheck/issues)
|
|
50
70
|

|
|
51
71
|

|
|
72
|
+
[](https://httpcheck.readthedocs.io/)
|
|
52
73
|
|
|
53
74
|
# Check Websites HTTP Status Codes with httpcheck
|
|
54
75
|
|
|
55
76
|
<img src="images/onSiteLogo.png" alt="onSite Logo" width="50%">
|
|
56
77
|
|
|
57
78
|
* **Name**: httpcheck (CLI) / onSite (Menu Bar App)
|
|
58
|
-
* **Current Version**: 1.
|
|
59
|
-
* **
|
|
60
|
-
* **Programming Language**: Python 3.
|
|
79
|
+
* **Current Version**: 1.5.0 (Async I/O & Configuration)
|
|
80
|
+
* **Next Version**: 1.6.0 (Monitoring Mode, target Q1 2027)
|
|
81
|
+
* **Programming Language**: Python 3.10+
|
|
61
82
|
* **Author**: Thomas Juul Dyhr
|
|
62
83
|
* **Purpose**: Advanced HTTP status checker with monitoring capabilities
|
|
63
84
|
* **Development Status**: Active - See [ROADMAP.md](ROADMAP.md) for development plan
|
|
85
|
+
* **PyPI**: https://pypi.org/project/httpcheck/
|
|
64
86
|
|
|
65
87
|
## 🚀 Release Status
|
|
66
88
|
|
|
67
|
-
**httpcheck v1.
|
|
68
|
-
|
|
69
|
-
✅ **NEW in v1.
|
|
70
|
-
- **
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- **
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
**httpcheck v1.5.0 has been released! 🎉**
|
|
90
|
+
|
|
91
|
+
✅ **NEW in v1.5.0:**
|
|
92
|
+
- **Configuration Files**: User-defined defaults in `~/.httpcheck.toml` and
|
|
93
|
+
`./.httpcheck.toml` (the project file overrides the user file); CLI flags always
|
|
94
|
+
take precedence
|
|
95
|
+
- **Async I/O** (experimental): `--async` uses an httpx-based concurrent path. In
|
|
96
|
+
the [benchmark against v1.4.3](docs/development/ASYNC_BENCHMARK.md) it is not
|
|
97
|
+
faster than `-f` at the same `--workers`; for speed, raise `-f --workers`
|
|
98
|
+
- **Python 3.10+**: v1.5.0 drops Python 3.9 support (pip on Python 3.9 keeps
|
|
99
|
+
installing 1.4.3); Python 3.13 and 3.14 are tested in CI
|
|
100
|
+
- **Retry fix**: with `--follow-redirects http-only` or `https-only`, retries now
|
|
101
|
+
work (1.4.3 reported the site as failed without retrying)
|
|
102
|
+
|
|
103
|
+
✅ **v1.4.2-1.4.3 (Published to PyPI):**
|
|
104
|
+
- **Structured Logging**: --debug, --log-file, --log-json flags for production monitoring
|
|
105
|
+
- **Comprehensive Testing**: 297 tests with 88% coverage (CLI: 94%)
|
|
106
|
+
- **Professional Documentation**: 24-page documentation with ReadTheDocs theme
|
|
107
|
+
- **Performance Benchmarks**: 18 automated regression tests
|
|
108
|
+
- **PyPI Published**: Available via `pip install httpcheck`
|
|
109
|
+
- **Automated Publishing**: GitHub Actions with Trusted Publishers (OIDC)
|
|
110
|
+
|
|
111
|
+
✅ **v1.4.0-1.4.1 Foundation:**
|
|
76
112
|
- **Modular Architecture**: 8 specialized modules
|
|
77
|
-
- **Enhanced Security**: Enterprise-grade input validation
|
|
78
|
-
- **Comprehensive Testing**: 88% test coverage with 190 test cases
|
|
113
|
+
- **Enhanced Security**: Enterprise-grade input validation with pip-audit clean
|
|
79
114
|
- **Multiple Output Formats**: JSON and CSV support
|
|
80
115
|
- **Advanced Request Control**: Custom headers, SSL options
|
|
81
|
-
- **Package Installation**: `pip install -e .`
|
|
82
116
|
|
|
83
|
-
|
|
84
|
-
- **Async I/O**: 2-3x performance improvement for large site lists
|
|
85
|
-
- **Configuration Files**: User-defined defaults and settings
|
|
117
|
+
🔭 **Next: v1.6.0 (target Q1 2027)**
|
|
86
118
|
- **Monitoring Mode**: Continuous site monitoring with notifications
|
|
87
119
|
- **Enhanced UX**: Colored output and improved progress reporting
|
|
88
120
|
|
|
89
121
|
See detailed plans in:
|
|
90
|
-
- [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md) - Executive overview and timeline
|
|
91
|
-
- [DEVELOPMENT.md](DEVELOPMENT.md) - Technical implementation guide
|
|
122
|
+
- [DEVELOPMENT_PLAN.md](docs/development/DEVELOPMENT_PLAN.md) - Executive overview and timeline
|
|
123
|
+
- [DEVELOPMENT.md](docs/development/DEVELOPMENT.md) - Technical implementation guide
|
|
92
124
|
- [TODO.md](TODO.md) - Current prioritized task list
|
|
93
125
|
- [ROADMAP.md](ROADMAP.md) - Long-term vision through v2.0.0
|
|
94
126
|
|
|
127
|
+
## 📦 Installation
|
|
128
|
+
|
|
129
|
+
### From PyPI (Recommended)
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install httpcheck
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### From Source
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
git clone https://github.com/docdyhr/httpcheck.git
|
|
139
|
+
cd httpcheck
|
|
140
|
+
pip install -e .
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### With Optional Dependencies
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# For macOS menu bar app
|
|
147
|
+
pip install httpcheck[macos]
|
|
148
|
+
|
|
149
|
+
# For development
|
|
150
|
+
pip install httpcheck[dev]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Upgrade to Latest Version
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install --upgrade httpcheck
|
|
157
|
+
```
|
|
158
|
+
|
|
95
159
|
## usage:
|
|
96
160
|
|
|
97
161
|
```
|
|
@@ -364,6 +428,35 @@ httpcheck --timeout 30 --retries 5 --retry-delay 2.0 slow.example.com
|
|
|
364
428
|
httpcheck -H "API-Key: secret" --output json --no-verify-ssl https://api.example.com
|
|
365
429
|
```
|
|
366
430
|
|
|
431
|
+
### configuration file
|
|
432
|
+
|
|
433
|
+
httpcheck reads defaults from `~/.httpcheck.toml` (user-wide) and/or `./.httpcheck.toml` (project-local). Project-level settings override user-level settings; any CLI flag always takes final precedence.
|
|
434
|
+
|
|
435
|
+
```toml
|
|
436
|
+
# ~/.httpcheck.toml
|
|
437
|
+
|
|
438
|
+
[defaults]
|
|
439
|
+
timeout = 10.0
|
|
440
|
+
retries = 3
|
|
441
|
+
follow_redirects = "always" # always | never | http-only | https-only
|
|
442
|
+
output_format = "table" # table | json | csv
|
|
443
|
+
verify_ssl = true
|
|
444
|
+
workers = 20
|
|
445
|
+
retry_delay = 0.5
|
|
446
|
+
max_redirects = 10
|
|
447
|
+
|
|
448
|
+
[headers]
|
|
449
|
+
User-Agent = "mybot/1.0"
|
|
450
|
+
Accept = "application/json"
|
|
451
|
+
|
|
452
|
+
[notifications]
|
|
453
|
+
enabled = true
|
|
454
|
+
on_failure = true
|
|
455
|
+
sound = "Ping"
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Unknown keys are silently ignored with a `UserWarning`. Invalid TOML falls back to no config (also a `UserWarning`).
|
|
459
|
+
|
|
367
460
|
### file input examples
|
|
368
461
|
|
|
369
462
|
Create a domains file with comments and use it with httpcheck:
|
|
@@ -1,97 +1,108 @@
|
|
|
1
|
-
|
|
2
|
-
Name: httpcheck
|
|
3
|
-
Version: 1.4.2
|
|
4
|
-
Summary: HTTP status code checker with advanced features
|
|
5
|
-
Author-email: Thomas Juul Dyhr <thomas@dyhr.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/docdyhr/httpcheck
|
|
8
|
-
Project-URL: Bug Tracker, https://github.com/docdyhr/httpcheck/issues
|
|
9
|
-
Project-URL: Documentation, https://github.com/docdyhr/httpcheck/blob/master/README.md
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Environment :: Console
|
|
12
|
-
Classifier: Intended Audience :: System Administrators
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Topic :: System :: Monitoring
|
|
21
|
-
Classifier: Topic :: System :: Networking :: Monitoring
|
|
22
|
-
Requires-Python: >=3.9
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: requests>=2.32.5
|
|
26
|
-
Requires-Dist: tabulate>=0.9.0
|
|
27
|
-
Requires-Dist: tqdm>=4.67.0
|
|
28
|
-
Requires-Dist: validators>=0.22.0
|
|
29
|
-
Provides-Extra: macos
|
|
30
|
-
Requires-Dist: rumps>=0.4.0; extra == "macos"
|
|
31
|
-
Requires-Dist: pyobjc-framework-Cocoa>=10.0; extra == "macos"
|
|
32
|
-
Requires-Dist: pyobjc-framework-Foundation>=10.0; extra == "macos"
|
|
33
|
-
Requires-Dist: pyobjc-framework-AppKit>=10.0; extra == "macos"
|
|
34
|
-
Requires-Dist: pyobjc-core>=10.0; extra == "macos"
|
|
35
|
-
Provides-Extra: dev
|
|
36
|
-
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
37
|
-
Requires-Dist: pytest-mock>=3.0; extra == "dev"
|
|
38
|
-
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
39
|
-
Requires-Dist: pytest-benchmark>=5.0; extra == "dev"
|
|
40
|
-
Requires-Dist: pylint>=3.1.0; extra == "dev"
|
|
41
|
-
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
42
|
-
Provides-Extra: build
|
|
43
|
-
Requires-Dist: py2app>=0.28; extra == "build"
|
|
44
|
-
Dynamic: license-file
|
|
1
|
+
<div align="right">
|
|
45
2
|
|
|
3
|
+
<a href="https://railway.com?referralCode=QhjuBc">
|
|
4
|
+
|
|
5
|
+
<img width="160" src="https://raw.githubusercontent.com/docdyhr/.github/main/assets/railway-corner-v2@2x.png" alt="Deploy on Railway — $20 free credits">
|
|
6
|
+
|
|
7
|
+
</a>
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
[](https://badge.fury.io/py/httpcheck)
|
|
12
|
+
[](https://pypi.org/project/httpcheck/)
|
|
13
|
+
[](https://pypi.org/project/httpcheck/)
|
|
46
14
|
[](https://github.com/docdyhr/httpcheck/actions/workflows/ci.yml)
|
|
47
15
|
[](https://github.com/pre-commit/pre-commit)
|
|
48
|
-

|
|
49
16
|
[](https://github.com/docdyhr/httpcheck/issues)
|
|
50
17
|

|
|
51
18
|

|
|
19
|
+
[](https://httpcheck.readthedocs.io/)
|
|
52
20
|
|
|
53
21
|
# Check Websites HTTP Status Codes with httpcheck
|
|
54
22
|
|
|
55
23
|
<img src="images/onSiteLogo.png" alt="onSite Logo" width="50%">
|
|
56
24
|
|
|
57
25
|
* **Name**: httpcheck (CLI) / onSite (Menu Bar App)
|
|
58
|
-
* **Current Version**: 1.
|
|
59
|
-
* **
|
|
60
|
-
* **Programming Language**: Python 3.
|
|
26
|
+
* **Current Version**: 1.5.0 (Async I/O & Configuration)
|
|
27
|
+
* **Next Version**: 1.6.0 (Monitoring Mode, target Q1 2027)
|
|
28
|
+
* **Programming Language**: Python 3.10+
|
|
61
29
|
* **Author**: Thomas Juul Dyhr
|
|
62
30
|
* **Purpose**: Advanced HTTP status checker with monitoring capabilities
|
|
63
31
|
* **Development Status**: Active - See [ROADMAP.md](ROADMAP.md) for development plan
|
|
32
|
+
* **PyPI**: https://pypi.org/project/httpcheck/
|
|
64
33
|
|
|
65
34
|
## 🚀 Release Status
|
|
66
35
|
|
|
67
|
-
**httpcheck v1.
|
|
68
|
-
|
|
69
|
-
✅ **NEW in v1.
|
|
70
|
-
- **
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- **
|
|
74
|
-
|
|
75
|
-
|
|
36
|
+
**httpcheck v1.5.0 has been released! 🎉**
|
|
37
|
+
|
|
38
|
+
✅ **NEW in v1.5.0:**
|
|
39
|
+
- **Configuration Files**: User-defined defaults in `~/.httpcheck.toml` and
|
|
40
|
+
`./.httpcheck.toml` (the project file overrides the user file); CLI flags always
|
|
41
|
+
take precedence
|
|
42
|
+
- **Async I/O** (experimental): `--async` uses an httpx-based concurrent path. In
|
|
43
|
+
the [benchmark against v1.4.3](docs/development/ASYNC_BENCHMARK.md) it is not
|
|
44
|
+
faster than `-f` at the same `--workers`; for speed, raise `-f --workers`
|
|
45
|
+
- **Python 3.10+**: v1.5.0 drops Python 3.9 support (pip on Python 3.9 keeps
|
|
46
|
+
installing 1.4.3); Python 3.13 and 3.14 are tested in CI
|
|
47
|
+
- **Retry fix**: with `--follow-redirects http-only` or `https-only`, retries now
|
|
48
|
+
work (1.4.3 reported the site as failed without retrying)
|
|
49
|
+
|
|
50
|
+
✅ **v1.4.2-1.4.3 (Published to PyPI):**
|
|
51
|
+
- **Structured Logging**: --debug, --log-file, --log-json flags for production monitoring
|
|
52
|
+
- **Comprehensive Testing**: 297 tests with 88% coverage (CLI: 94%)
|
|
53
|
+
- **Professional Documentation**: 24-page documentation with ReadTheDocs theme
|
|
54
|
+
- **Performance Benchmarks**: 18 automated regression tests
|
|
55
|
+
- **PyPI Published**: Available via `pip install httpcheck`
|
|
56
|
+
- **Automated Publishing**: GitHub Actions with Trusted Publishers (OIDC)
|
|
57
|
+
|
|
58
|
+
✅ **v1.4.0-1.4.1 Foundation:**
|
|
76
59
|
- **Modular Architecture**: 8 specialized modules
|
|
77
|
-
- **Enhanced Security**: Enterprise-grade input validation
|
|
78
|
-
- **Comprehensive Testing**: 88% test coverage with 190 test cases
|
|
60
|
+
- **Enhanced Security**: Enterprise-grade input validation with pip-audit clean
|
|
79
61
|
- **Multiple Output Formats**: JSON and CSV support
|
|
80
62
|
- **Advanced Request Control**: Custom headers, SSL options
|
|
81
|
-
- **Package Installation**: `pip install -e .`
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
- **Async I/O**: 2-3x performance improvement for large site lists
|
|
85
|
-
- **Configuration Files**: User-defined defaults and settings
|
|
64
|
+
🔭 **Next: v1.6.0 (target Q1 2027)**
|
|
86
65
|
- **Monitoring Mode**: Continuous site monitoring with notifications
|
|
87
66
|
- **Enhanced UX**: Colored output and improved progress reporting
|
|
88
67
|
|
|
89
68
|
See detailed plans in:
|
|
90
|
-
- [DEVELOPMENT_PLAN.md](DEVELOPMENT_PLAN.md) - Executive overview and timeline
|
|
91
|
-
- [DEVELOPMENT.md](DEVELOPMENT.md) - Technical implementation guide
|
|
69
|
+
- [DEVELOPMENT_PLAN.md](docs/development/DEVELOPMENT_PLAN.md) - Executive overview and timeline
|
|
70
|
+
- [DEVELOPMENT.md](docs/development/DEVELOPMENT.md) - Technical implementation guide
|
|
92
71
|
- [TODO.md](TODO.md) - Current prioritized task list
|
|
93
72
|
- [ROADMAP.md](ROADMAP.md) - Long-term vision through v2.0.0
|
|
94
73
|
|
|
74
|
+
## 📦 Installation
|
|
75
|
+
|
|
76
|
+
### From PyPI (Recommended)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install httpcheck
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### From Source
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/docdyhr/httpcheck.git
|
|
86
|
+
cd httpcheck
|
|
87
|
+
pip install -e .
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### With Optional Dependencies
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# For macOS menu bar app
|
|
94
|
+
pip install httpcheck[macos]
|
|
95
|
+
|
|
96
|
+
# For development
|
|
97
|
+
pip install httpcheck[dev]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Upgrade to Latest Version
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install --upgrade httpcheck
|
|
104
|
+
```
|
|
105
|
+
|
|
95
106
|
## usage:
|
|
96
107
|
|
|
97
108
|
```
|
|
@@ -364,6 +375,35 @@ httpcheck --timeout 30 --retries 5 --retry-delay 2.0 slow.example.com
|
|
|
364
375
|
httpcheck -H "API-Key: secret" --output json --no-verify-ssl https://api.example.com
|
|
365
376
|
```
|
|
366
377
|
|
|
378
|
+
### configuration file
|
|
379
|
+
|
|
380
|
+
httpcheck reads defaults from `~/.httpcheck.toml` (user-wide) and/or `./.httpcheck.toml` (project-local). Project-level settings override user-level settings; any CLI flag always takes final precedence.
|
|
381
|
+
|
|
382
|
+
```toml
|
|
383
|
+
# ~/.httpcheck.toml
|
|
384
|
+
|
|
385
|
+
[defaults]
|
|
386
|
+
timeout = 10.0
|
|
387
|
+
retries = 3
|
|
388
|
+
follow_redirects = "always" # always | never | http-only | https-only
|
|
389
|
+
output_format = "table" # table | json | csv
|
|
390
|
+
verify_ssl = true
|
|
391
|
+
workers = 20
|
|
392
|
+
retry_delay = 0.5
|
|
393
|
+
max_redirects = 10
|
|
394
|
+
|
|
395
|
+
[headers]
|
|
396
|
+
User-Agent = "mybot/1.0"
|
|
397
|
+
Accept = "application/json"
|
|
398
|
+
|
|
399
|
+
[notifications]
|
|
400
|
+
enabled = true
|
|
401
|
+
on_failure = true
|
|
402
|
+
sound = "Ping"
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Unknown keys are silently ignored with a `UserWarning`. Invalid TOML falls back to no config (also a `UserWarning`).
|
|
406
|
+
|
|
367
407
|
### file input examples
|
|
368
408
|
|
|
369
409
|
Create a domains file with comments and use it with httpcheck:
|
|
@@ -5,13 +5,14 @@ A Python CLI tool for checking HTTP status codes of websites with
|
|
|
5
5
|
threading, redirect handling, TLD validation, and system notifications.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "1.
|
|
8
|
+
__version__ = "1.5.0"
|
|
9
9
|
__author__ = "Thomas Juul Dyhr"
|
|
10
10
|
__email__ = "thomas@dyhr.com"
|
|
11
11
|
|
|
12
12
|
# Import specific items for public API
|
|
13
13
|
from .cli import main
|
|
14
14
|
from .common import VERSION, InvalidTLDException, SiteStatus
|
|
15
|
+
from .config import DEFAULT_CONFIG, load_config
|
|
15
16
|
from .file_handler import FileInputHandler, url_validation
|
|
16
17
|
from .notification import notify
|
|
17
18
|
from .output_formatter import (
|
|
@@ -37,6 +38,8 @@ from .validation import (
|
|
|
37
38
|
__all__ = [
|
|
38
39
|
# Core functionality
|
|
39
40
|
"check_site",
|
|
41
|
+
"load_config",
|
|
42
|
+
"DEFAULT_CONFIG",
|
|
40
43
|
"print_format",
|
|
41
44
|
"format_json",
|
|
42
45
|
"format_csv",
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Async HTTP site checking for httpcheck."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Iterable
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .common import STATUS_CODES, VERSION, SiteStatus
|
|
13
|
+
|
|
14
|
+
DEFAULT_LIMITS = httpx.Limits(max_connections=50, max_keepalive_connections=20)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _create_headers(custom_headers: dict | None = None) -> dict:
|
|
18
|
+
"""Create default headers including User-Agent."""
|
|
19
|
+
headers = {"User-Agent": f"httpcheck Async Agent {VERSION}"}
|
|
20
|
+
if custom_headers:
|
|
21
|
+
headers.update(custom_headers)
|
|
22
|
+
return headers
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _extract_domain(url_like: str | httpx.URL, fallback: str) -> str:
|
|
26
|
+
"""Return host component from httpx.URL or string URL."""
|
|
27
|
+
try:
|
|
28
|
+
if isinstance(url_like, httpx.URL):
|
|
29
|
+
return str(url_like.host)
|
|
30
|
+
parsed = urlparse(str(url_like))
|
|
31
|
+
return parsed.hostname or fallback
|
|
32
|
+
except (AttributeError, ValueError):
|
|
33
|
+
return fallback
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _extract_redirects(
|
|
37
|
+
response: httpx.Response,
|
|
38
|
+
) -> tuple[list[tuple[str, int]], list[tuple[str, int, float]]]:
|
|
39
|
+
"""Return redirect chain and timing from an httpx response."""
|
|
40
|
+
chain: list[tuple[str, int]] = []
|
|
41
|
+
timing: list[tuple[str, int, float]] = []
|
|
42
|
+
for hop in response.history:
|
|
43
|
+
chain.append((str(hop.url), hop.status_code))
|
|
44
|
+
timing.append((str(hop.url), hop.status_code, 0.0))
|
|
45
|
+
chain.append((str(response.url), response.status_code))
|
|
46
|
+
timing.append((str(response.url), response.status_code, 0.0))
|
|
47
|
+
return chain, timing
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _should_stop_redirect(follow_redirects: str, redirect_url: httpx.URL) -> bool:
|
|
51
|
+
"""Return True when protocol policy blocks following this redirect."""
|
|
52
|
+
if follow_redirects == "http-only" and redirect_url.scheme == "https":
|
|
53
|
+
return True
|
|
54
|
+
if follow_redirects == "https-only" and redirect_url.scheme == "http":
|
|
55
|
+
return True
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def _manual_redirect_flow(
|
|
60
|
+
client: httpx.AsyncClient,
|
|
61
|
+
url: str,
|
|
62
|
+
*,
|
|
63
|
+
timeout: float,
|
|
64
|
+
max_redirects: int,
|
|
65
|
+
follow_redirects: str,
|
|
66
|
+
) -> tuple[httpx.Response, float, list[tuple[str, int]], list[tuple[str, int, float]]]:
|
|
67
|
+
"""Follow redirects manually to enforce protocol restrictions."""
|
|
68
|
+
chain: list[tuple[str, int]] = []
|
|
69
|
+
timing: list[tuple[str, int, float]] = []
|
|
70
|
+
|
|
71
|
+
current_url: httpx.URL = httpx.URL(url)
|
|
72
|
+
total_start = time.monotonic()
|
|
73
|
+
redirects = 0
|
|
74
|
+
while True:
|
|
75
|
+
hop_start = time.monotonic()
|
|
76
|
+
response = await client.get(
|
|
77
|
+
str(current_url),
|
|
78
|
+
follow_redirects=False,
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
)
|
|
81
|
+
hop_elapsed = time.monotonic() - hop_start
|
|
82
|
+
|
|
83
|
+
chain.append((str(response.url), response.status_code))
|
|
84
|
+
timing.append((str(response.url), response.status_code, hop_elapsed))
|
|
85
|
+
|
|
86
|
+
if not response.is_redirect or "location" not in response.headers:
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
if redirects >= max_redirects:
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
location = response.headers.get("location", "")
|
|
93
|
+
base_url = (
|
|
94
|
+
response.url
|
|
95
|
+
if isinstance(response.url, httpx.URL)
|
|
96
|
+
else httpx.URL(str(response.url))
|
|
97
|
+
)
|
|
98
|
+
next_url = base_url.join(location)
|
|
99
|
+
if _should_stop_redirect(follow_redirects, next_url):
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
redirects += 1
|
|
103
|
+
current_url = next_url
|
|
104
|
+
|
|
105
|
+
total_elapsed = time.monotonic() - total_start
|
|
106
|
+
return response, total_elapsed, chain, timing
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
async def async_check_site(
|
|
110
|
+
site: str,
|
|
111
|
+
timeout: float = 5.0,
|
|
112
|
+
retries: int = 2,
|
|
113
|
+
*,
|
|
114
|
+
follow_redirects: str = "always",
|
|
115
|
+
max_redirects: int = 30,
|
|
116
|
+
custom_headers: dict | None = None,
|
|
117
|
+
verify_ssl: bool = True,
|
|
118
|
+
retry_delay: float = 1.0,
|
|
119
|
+
client: httpx.AsyncClient | None = None,
|
|
120
|
+
) -> SiteStatus:
|
|
121
|
+
"""Async version of check_site with optional protocol-restricted redirects."""
|
|
122
|
+
headers = _create_headers(custom_headers)
|
|
123
|
+
allow_redirects = follow_redirects == "always"
|
|
124
|
+
manual_redirects = follow_redirects in {"http-only", "https-only"}
|
|
125
|
+
|
|
126
|
+
async def _execute_request(
|
|
127
|
+
active_client: httpx.AsyncClient,
|
|
128
|
+
) -> tuple[httpx.Response, float]:
|
|
129
|
+
start = time.monotonic()
|
|
130
|
+
response = await active_client.get(
|
|
131
|
+
site,
|
|
132
|
+
headers=headers,
|
|
133
|
+
timeout=timeout,
|
|
134
|
+
follow_redirects=allow_redirects,
|
|
135
|
+
)
|
|
136
|
+
elapsed = time.monotonic() - start
|
|
137
|
+
return response, elapsed
|
|
138
|
+
|
|
139
|
+
# httpx sets max_redirects per client, not per request: a caller-supplied
|
|
140
|
+
# client keeps its own limit.
|
|
141
|
+
owns_client = client is None
|
|
142
|
+
active_client = client or httpx.AsyncClient(
|
|
143
|
+
verify=verify_ssl,
|
|
144
|
+
limits=DEFAULT_LIMITS,
|
|
145
|
+
headers=headers,
|
|
146
|
+
max_redirects=max_redirects,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
for attempt in range(retries + 1):
|
|
151
|
+
try:
|
|
152
|
+
if manual_redirects:
|
|
153
|
+
(
|
|
154
|
+
response,
|
|
155
|
+
response_time,
|
|
156
|
+
redirect_chain,
|
|
157
|
+
redirect_timing,
|
|
158
|
+
) = await _manual_redirect_flow(
|
|
159
|
+
active_client,
|
|
160
|
+
site,
|
|
161
|
+
timeout=timeout,
|
|
162
|
+
max_redirects=max_redirects,
|
|
163
|
+
follow_redirects=follow_redirects,
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
response, response_time = await _execute_request(active_client)
|
|
167
|
+
redirect_chain, redirect_timing = _extract_redirects(response)
|
|
168
|
+
|
|
169
|
+
domain = _extract_domain(getattr(response, "url", site), site)
|
|
170
|
+
return SiteStatus(
|
|
171
|
+
domain=domain,
|
|
172
|
+
status=str(response.status_code),
|
|
173
|
+
message=STATUS_CODES.get(str(response.status_code), "Unknown"),
|
|
174
|
+
redirect_chain=redirect_chain,
|
|
175
|
+
response_time=response_time,
|
|
176
|
+
redirect_timing=redirect_timing,
|
|
177
|
+
)
|
|
178
|
+
except (httpx.RequestError, httpx.HTTPError, ValueError, OSError) as exc:
|
|
179
|
+
if attempt == retries:
|
|
180
|
+
status = (
|
|
181
|
+
"[timeout]"
|
|
182
|
+
if isinstance(exc, httpx.TimeoutException)
|
|
183
|
+
else "[connection error]"
|
|
184
|
+
)
|
|
185
|
+
domain = site
|
|
186
|
+
if isinstance(exc, httpx.HTTPError):
|
|
187
|
+
try:
|
|
188
|
+
# .request raises RuntimeError when it was never set.
|
|
189
|
+
domain = _extract_domain(exc.request.url, site)
|
|
190
|
+
except RuntimeError:
|
|
191
|
+
pass
|
|
192
|
+
return SiteStatus(domain, status, str(exc))
|
|
193
|
+
if retry_delay > 0:
|
|
194
|
+
await asyncio.sleep(retry_delay)
|
|
195
|
+
# Only reached when retries < 0, as in the sync check_site.
|
|
196
|
+
return SiteStatus(site, "[unknown error]", "All retries failed")
|
|
197
|
+
finally:
|
|
198
|
+
if owns_client:
|
|
199
|
+
await active_client.aclose()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def async_check_sites(
|
|
203
|
+
sites: Iterable[str],
|
|
204
|
+
*,
|
|
205
|
+
timeout: float = 5.0,
|
|
206
|
+
retries: int = 2,
|
|
207
|
+
follow_redirects: str = "always",
|
|
208
|
+
max_redirects: int = 30,
|
|
209
|
+
custom_headers: dict | None = None,
|
|
210
|
+
verify_ssl: bool = True,
|
|
211
|
+
retry_delay: float = 1.0,
|
|
212
|
+
concurrency: int = 50,
|
|
213
|
+
) -> list[SiteStatus]:
|
|
214
|
+
"""Check many sites concurrently with a shared client and semaphore."""
|
|
215
|
+
sem = asyncio.Semaphore(concurrency)
|
|
216
|
+
headers = _create_headers(custom_headers)
|
|
217
|
+
async with httpx.AsyncClient(
|
|
218
|
+
verify=verify_ssl,
|
|
219
|
+
limits=DEFAULT_LIMITS,
|
|
220
|
+
headers=headers,
|
|
221
|
+
max_redirects=max_redirects,
|
|
222
|
+
) as client:
|
|
223
|
+
|
|
224
|
+
async def worker(url: str) -> SiteStatus:
|
|
225
|
+
async with sem:
|
|
226
|
+
return await async_check_site(
|
|
227
|
+
url,
|
|
228
|
+
timeout=timeout,
|
|
229
|
+
retries=retries,
|
|
230
|
+
follow_redirects=follow_redirects,
|
|
231
|
+
max_redirects=max_redirects,
|
|
232
|
+
custom_headers=None, # headers already applied on client
|
|
233
|
+
verify_ssl=verify_ssl,
|
|
234
|
+
retry_delay=retry_delay,
|
|
235
|
+
client=client,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return await asyncio.gather(*[worker(u) for u in sites])
|