httpdbg 0.32.1__tar.gz → 1.1.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.
- {httpdbg-0.32.1 → httpdbg-1.1.0}/PKG-INFO +67 -15
- {httpdbg-0.32.1 → httpdbg-1.1.0}/README.md +65 -13
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/__init__.py +1 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/__main__.py +2 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/args.py +6 -0
- httpdbg-1.1.0/httpdbg/hooks/all.py +44 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/external.py +21 -3
- httpdbg-1.1.0/httpdbg/hooks/flask.py +134 -0
- httpdbg-1.1.0/httpdbg/hooks/http.py +47 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/pytest.py +1 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/socket.py +166 -98
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/template_sitecustomize.py +6 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/unittest.py +5 -5
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/utils.py +1 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/initiator.py +69 -20
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/records.py +57 -23
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/server.py +3 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/api.py +2 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/api.js +33 -30
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/index.htm +2 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/render.js +17 -15
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/style.css +1 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/table.css +4 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/PKG-INFO +67 -15
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/SOURCES.txt +4 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/pyproject.toml +1 -1
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_context_manager.py +14 -14
- httpdbg-1.1.0/tests/test_hook_flask.py +158 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_unittest.py +10 -10
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_initiator.py +3 -3
- httpdbg-0.32.1/tests/test_server.py → httpdbg-1.1.0/tests/test_pyhttpdbg_server.py +7 -6
- httpdbg-1.1.0/tests/test_server.py +51 -0
- httpdbg-0.32.1/httpdbg/hooks/all.py +0 -35
- {httpdbg-0.32.1 → httpdbg-1.1.0}/LICENSE +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/env.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/exception.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/aiohttp.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/httpx.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/requests.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/urllib3.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/log.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/mode_console.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/mode_module.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/mode_script.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/preview.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/utils.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/app.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/setup.cfg +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_api.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_cli.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_httpx.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_pytest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_requests.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_mode_console.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_mode_script.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_multiprocess.py +0 -0
- {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_preview.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version:
|
|
4
|
-
Summary: A very simple tool to debug HTTP(S) client requests
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: A very simple tool to debug HTTP(S) client and server requests.
|
|
5
5
|
Author-email: cle-b <cle@tictac.pm>
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: Source, https://github.com/cle-b/httpdbg/
|
|
@@ -26,7 +26,7 @@ License-File: LICENSE
|
|
|
26
26
|
|
|
27
27
|
# httpdbg
|
|
28
28
|
|
|
29
|
-
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client requests in a Python program.
|
|
29
|
+
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client and server requests in a Python program.
|
|
30
30
|
|
|
31
31
|
To use it, execute your program using the `pyhttpdbg` command instead of `python` and that's it. Open a browser to `http://localhost:4909` to view the requests:
|
|
32
32
|
|
|
@@ -42,6 +42,8 @@ pip install httpdbg
|
|
|
42
42
|
|
|
43
43
|
## usage
|
|
44
44
|
|
|
45
|
+
By default, both client and server requests are recorded. To record only client requests, use the `--only-client` command line argument.
|
|
46
|
+
|
|
45
47
|
### interactive console
|
|
46
48
|
|
|
47
49
|
Open an interactive console using the command `pyhttpdbg`.
|
|
@@ -89,8 +91,32 @@ pyhttpdbg -m pytest [arg1 --arg2 ...]
|
|
|
89
91
|
|
|
90
92
|
In that case, the requests will be grouped by test, and any requests made within a fixture or the setup/teardown methods will be identified by a tag.
|
|
91
93
|
|
|
92
|
-
## Initiators
|
|
93
94
|
|
|
95
|
+
### HTTP server
|
|
96
|
+
|
|
97
|
+
You can trace all HTTP requests received by your HTTP server.
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
pyhttpdbg -m flask --app demoflask run
|
|
101
|
+
.... - - .--. -.. -... --. .... - - .--. -.. -... --. .... - - .--. -.. -... --.
|
|
102
|
+
httpdbg - HTTP(S) requests available at http://localhost:4909/
|
|
103
|
+
.... - - .--. -.. -... --. .... - - .--. -.. -... --. .... - - .--. -.. -... --.
|
|
104
|
+
* Serving Flask app 'demoflask'
|
|
105
|
+
* Debug mode: off
|
|
106
|
+
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
|
107
|
+
* Running on http://127.0.0.1:5000
|
|
108
|
+
Press CTRL+C to quit
|
|
109
|
+
127.0.0.1 - - [14/Mar/2025 17:47:42] "GET / HTTP/1.1" 200 -
|
|
110
|
+
127.0.0.1 - - [14/Mar/2025 17:47:50] "GET /hello/Ben HTTP/1.1" 200 -
|
|
111
|
+
127.0.0.1 - - [14/Mar/2025 17:47:56] "GET /oups HTTP/1.1" 404 -
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
This feature is new and not fully implemented. See [limitations](#limitations) for more details.
|
|
117
|
+
|
|
118
|
+
## initiators and groups
|
|
119
|
+
|
|
94
120
|
An initiator is the function/method that is at the origin of the HTTP requests. By default, we already support some packages but you can add your own initiators.
|
|
95
121
|
|
|
96
122
|
To add a new package in the list of initiators, you can use the `-i` command line argument:
|
|
@@ -101,17 +127,34 @@ pyhttpdbg -i api_client_pck --script my_script.py
|
|
|
101
127
|
|
|
102
128
|
You can use any package as an initiator, this is not limited to HTTP requests.
|
|
103
129
|
|
|
104
|
-
###
|
|
130
|
+
### supported HTTP client
|
|
105
131
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
| _your_package_ | yes, with the arg _-i your_package_ |
|
|
132
|
+
The initiator is the highest-level method used to make the request through the client. The group and the initiator are identical. If a request to a URL generates multiple requests, as in the case of redirection, all these requests will be grouped together.
|
|
133
|
+
|
|
134
|
+
| packages | status | initiator | group |
|
|
135
|
+
|----------------|-------------------------------------|-----------|-----------|
|
|
136
|
+
| requests | supported | yes | yes |
|
|
137
|
+
| urllib3 | supported | yes | yes |
|
|
138
|
+
| httpx | supported | yes | yes |
|
|
139
|
+
| aiohttp | supported | yes | yes |
|
|
140
|
+
| _your_package_ | yes, with the arg _-i your_package_ | yes | yes |
|
|
141
|
+
|
|
142
|
+
### supported HTTP server
|
|
143
|
+
|
|
144
|
+
The initiator is the low-level socket method that receives the data. The group is the endpoint method used to handle the request. A client request made within the endpoint method will be included in this group.
|
|
145
|
+
|
|
146
|
+
| packages | status | initiator | group |
|
|
147
|
+
|----------------|-------------------------------------|-----------|-----------|
|
|
148
|
+
| flask | supported | no | yes |
|
|
149
|
+
|
|
150
|
+
### supported test framework
|
|
151
|
+
|
|
152
|
+
The requests are grouped by tests.
|
|
153
|
+
|
|
154
|
+
| packages | status | initiator | group |
|
|
155
|
+
|----------------|-------------------------------------|-----------|-----------|
|
|
156
|
+
| pytest | supported | no | yes |
|
|
157
|
+
| unittest | supported | no | yes |
|
|
115
158
|
|
|
116
159
|
## configuration
|
|
117
160
|
|
|
@@ -120,7 +163,9 @@ No configuration is necessary to start but some few settings are available for p
|
|
|
120
163
|
### command line
|
|
121
164
|
|
|
122
165
|
```console
|
|
123
|
-
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
166
|
+
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
167
|
+
[--initiator INITIATOR] [--only-client]
|
|
168
|
+
[--keep-up | --force-quit]
|
|
124
169
|
[--console | --module MODULE | --script SCRIPT]
|
|
125
170
|
|
|
126
171
|
httdbg - a very simple tool to debug HTTP(S) client requests
|
|
@@ -132,6 +177,7 @@ options:
|
|
|
132
177
|
--version, -v print the httpdbg version
|
|
133
178
|
--initiator INITIATOR, -i INITIATOR
|
|
134
179
|
add a new initiator (package)
|
|
180
|
+
--only-client record only HTTP client requests
|
|
135
181
|
--keep-up, -k keep the server up even if the requests have been read
|
|
136
182
|
--force-quit, -q stop the server even if the requests have not been read
|
|
137
183
|
--console run a python console (default)
|
|
@@ -167,6 +213,12 @@ The requests:
|
|
|
167
213
|
* are still available in the web page even if the python process stopped (except if you force quit before the requests have been loaded by the web page).
|
|
168
214
|
* are automatically cleaned if a new execution is detected.
|
|
169
215
|
|
|
216
|
+
## limitations
|
|
217
|
+
|
|
218
|
+
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
219
|
+
|
|
220
|
+
Support for recording requests on the server side is new in `httpdbg 1.0.0` and is currently limited. For example, it doesn't work with `FastAPI`, which uses `uvloop`, but it does work with `Uvicorn` if the loop is `asyncio`. Work is in progress to improve this.
|
|
221
|
+
|
|
170
222
|
## documentation
|
|
171
223
|
|
|
172
224
|
https://httpdbg.readthedocs.io
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# httpdbg
|
|
2
2
|
|
|
3
|
-
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client requests in a Python program.
|
|
3
|
+
`httpdbg` is a tool for Python developers to easily debug the HTTP(S) client and server requests in a Python program.
|
|
4
4
|
|
|
5
5
|
To use it, execute your program using the `pyhttpdbg` command instead of `python` and that's it. Open a browser to `http://localhost:4909` to view the requests:
|
|
6
6
|
|
|
@@ -16,6 +16,8 @@ pip install httpdbg
|
|
|
16
16
|
|
|
17
17
|
## usage
|
|
18
18
|
|
|
19
|
+
By default, both client and server requests are recorded. To record only client requests, use the `--only-client` command line argument.
|
|
20
|
+
|
|
19
21
|
### interactive console
|
|
20
22
|
|
|
21
23
|
Open an interactive console using the command `pyhttpdbg`.
|
|
@@ -63,8 +65,32 @@ pyhttpdbg -m pytest [arg1 --arg2 ...]
|
|
|
63
65
|
|
|
64
66
|
In that case, the requests will be grouped by test, and any requests made within a fixture or the setup/teardown methods will be identified by a tag.
|
|
65
67
|
|
|
66
|
-
## Initiators
|
|
67
68
|
|
|
69
|
+
### HTTP server
|
|
70
|
+
|
|
71
|
+
You can trace all HTTP requests received by your HTTP server.
|
|
72
|
+
|
|
73
|
+
```console
|
|
74
|
+
pyhttpdbg -m flask --app demoflask run
|
|
75
|
+
.... - - .--. -.. -... --. .... - - .--. -.. -... --. .... - - .--. -.. -... --.
|
|
76
|
+
httpdbg - HTTP(S) requests available at http://localhost:4909/
|
|
77
|
+
.... - - .--. -.. -... --. .... - - .--. -.. -... --. .... - - .--. -.. -... --.
|
|
78
|
+
* Serving Flask app 'demoflask'
|
|
79
|
+
* Debug mode: off
|
|
80
|
+
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
|
81
|
+
* Running on http://127.0.0.1:5000
|
|
82
|
+
Press CTRL+C to quit
|
|
83
|
+
127.0.0.1 - - [14/Mar/2025 17:47:42] "GET / HTTP/1.1" 200 -
|
|
84
|
+
127.0.0.1 - - [14/Mar/2025 17:47:50] "GET /hello/Ben HTTP/1.1" 200 -
|
|
85
|
+
127.0.0.1 - - [14/Mar/2025 17:47:56] "GET /oups HTTP/1.1" 404 -
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+

|
|
89
|
+
|
|
90
|
+
This feature is new and not fully implemented. See [limitations](#limitations) for more details.
|
|
91
|
+
|
|
92
|
+
## initiators and groups
|
|
93
|
+
|
|
68
94
|
An initiator is the function/method that is at the origin of the HTTP requests. By default, we already support some packages but you can add your own initiators.
|
|
69
95
|
|
|
70
96
|
To add a new package in the list of initiators, you can use the `-i` command line argument:
|
|
@@ -75,17 +101,34 @@ pyhttpdbg -i api_client_pck --script my_script.py
|
|
|
75
101
|
|
|
76
102
|
You can use any package as an initiator, this is not limited to HTTP requests.
|
|
77
103
|
|
|
78
|
-
###
|
|
104
|
+
### supported HTTP client
|
|
79
105
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
| _your_package_ | yes, with the arg _-i your_package_ |
|
|
106
|
+
The initiator is the highest-level method used to make the request through the client. The group and the initiator are identical. If a request to a URL generates multiple requests, as in the case of redirection, all these requests will be grouped together.
|
|
107
|
+
|
|
108
|
+
| packages | status | initiator | group |
|
|
109
|
+
|----------------|-------------------------------------|-----------|-----------|
|
|
110
|
+
| requests | supported | yes | yes |
|
|
111
|
+
| urllib3 | supported | yes | yes |
|
|
112
|
+
| httpx | supported | yes | yes |
|
|
113
|
+
| aiohttp | supported | yes | yes |
|
|
114
|
+
| _your_package_ | yes, with the arg _-i your_package_ | yes | yes |
|
|
115
|
+
|
|
116
|
+
### supported HTTP server
|
|
117
|
+
|
|
118
|
+
The initiator is the low-level socket method that receives the data. The group is the endpoint method used to handle the request. A client request made within the endpoint method will be included in this group.
|
|
119
|
+
|
|
120
|
+
| packages | status | initiator | group |
|
|
121
|
+
|----------------|-------------------------------------|-----------|-----------|
|
|
122
|
+
| flask | supported | no | yes |
|
|
123
|
+
|
|
124
|
+
### supported test framework
|
|
125
|
+
|
|
126
|
+
The requests are grouped by tests.
|
|
127
|
+
|
|
128
|
+
| packages | status | initiator | group |
|
|
129
|
+
|----------------|-------------------------------------|-----------|-----------|
|
|
130
|
+
| pytest | supported | no | yes |
|
|
131
|
+
| unittest | supported | no | yes |
|
|
89
132
|
|
|
90
133
|
## configuration
|
|
91
134
|
|
|
@@ -94,7 +137,9 @@ No configuration is necessary to start but some few settings are available for p
|
|
|
94
137
|
### command line
|
|
95
138
|
|
|
96
139
|
```console
|
|
97
|
-
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
140
|
+
usage: pyhttpdbg [-h] [--host HOST] [--port PORT] [--version]
|
|
141
|
+
[--initiator INITIATOR] [--only-client]
|
|
142
|
+
[--keep-up | --force-quit]
|
|
98
143
|
[--console | --module MODULE | --script SCRIPT]
|
|
99
144
|
|
|
100
145
|
httdbg - a very simple tool to debug HTTP(S) client requests
|
|
@@ -106,6 +151,7 @@ options:
|
|
|
106
151
|
--version, -v print the httpdbg version
|
|
107
152
|
--initiator INITIATOR, -i INITIATOR
|
|
108
153
|
add a new initiator (package)
|
|
154
|
+
--only-client record only HTTP client requests
|
|
109
155
|
--keep-up, -k keep the server up even if the requests have been read
|
|
110
156
|
--force-quit, -q stop the server even if the requests have not been read
|
|
111
157
|
--console run a python console (default)
|
|
@@ -141,6 +187,12 @@ The requests:
|
|
|
141
187
|
* are still available in the web page even if the python process stopped (except if you force quit before the requests have been loaded by the web page).
|
|
142
188
|
* are automatically cleaned if a new execution is detected.
|
|
143
189
|
|
|
190
|
+
## limitations
|
|
191
|
+
|
|
192
|
+
Theoretically, if your HTTP client or server uses a standard Python socket, the HTTP requests will be recorded.
|
|
193
|
+
|
|
194
|
+
Support for recording requests on the server side is new in `httpdbg 1.0.0` and is currently limited. For example, it doesn't work with `FastAPI`, which uses `uvloop`, but it does work with `Uvicorn` if the loop is `asyncio`. Work is in progress to improve this.
|
|
195
|
+
|
|
144
196
|
## documentation
|
|
145
197
|
|
|
146
198
|
https://httpdbg.readthedocs.io
|
|
@@ -29,7 +29,8 @@ def pyhttpdbg(params, subparams, test_mode=False):
|
|
|
29
29
|
sys.path.insert(0, "") # to mimic the default python command behavior
|
|
30
30
|
|
|
31
31
|
with httpdbg_srv(params.host, params.port) as records:
|
|
32
|
-
|
|
32
|
+
records.server = not params.only_client
|
|
33
|
+
with httprecord(records, params.initiator, server=records.server):
|
|
33
34
|
if params.module:
|
|
34
35
|
run_module(subparams)
|
|
35
36
|
elif params.script:
|
|
@@ -36,6 +36,12 @@ def read_args(args: List[str]) -> Tuple[argparse.Namespace, List[str]]:
|
|
|
36
36
|
"--initiator", "-i", action="append", help="add a new initiator (package)"
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--only-client",
|
|
41
|
+
action="store_true",
|
|
42
|
+
help="record only HTTP client requests",
|
|
43
|
+
)
|
|
44
|
+
|
|
39
45
|
server_state = parser.add_mutually_exclusive_group()
|
|
40
46
|
|
|
41
47
|
server_state.add_argument(
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from typing import Generator
|
|
4
|
+
from typing import List
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from httpdbg.hooks.aiohttp import hook_aiohttp
|
|
9
|
+
from httpdbg.hooks.external import watcher_external
|
|
10
|
+
from httpdbg.hooks.generic import hook_generic
|
|
11
|
+
from httpdbg.hooks.http import hook_http
|
|
12
|
+
from httpdbg.hooks.httpx import hook_httpx
|
|
13
|
+
from httpdbg.hooks.pytest import hook_pytest
|
|
14
|
+
from httpdbg.hooks.requests import hook_requests
|
|
15
|
+
from httpdbg.hooks.socket import hook_socket
|
|
16
|
+
from httpdbg.hooks.unittest import hook_unittest
|
|
17
|
+
from httpdbg.hooks.urllib3 import hook_urllib3
|
|
18
|
+
from httpdbg.hooks.flask import hook_flask
|
|
19
|
+
from httpdbg.records import HTTPRecords
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@contextmanager
|
|
23
|
+
def httprecord(
|
|
24
|
+
records: HTTPRecords = None,
|
|
25
|
+
initiators: Union[List[str], None] = None,
|
|
26
|
+
client: bool = True,
|
|
27
|
+
server: bool = False,
|
|
28
|
+
ignore: Union[List[Tuple[str, int]], None] = None,
|
|
29
|
+
) -> Generator[HTTPRecords, None, None]:
|
|
30
|
+
if records is None:
|
|
31
|
+
records = HTTPRecords(client=client, server=server, ignore=ignore)
|
|
32
|
+
|
|
33
|
+
with watcher_external(records, initiators, server):
|
|
34
|
+
with hook_flask(records):
|
|
35
|
+
with hook_socket(records):
|
|
36
|
+
with hook_http(records):
|
|
37
|
+
with hook_httpx(records):
|
|
38
|
+
with hook_requests(records):
|
|
39
|
+
with hook_urllib3(records):
|
|
40
|
+
with hook_aiohttp(records):
|
|
41
|
+
with hook_pytest(records):
|
|
42
|
+
with hook_unittest(records):
|
|
43
|
+
with hook_generic(records, initiators):
|
|
44
|
+
yield records
|
|
@@ -4,10 +4,11 @@ from typing import Generator
|
|
|
4
4
|
import glob
|
|
5
5
|
import os
|
|
6
6
|
import pickle
|
|
7
|
-
import shutil
|
|
8
7
|
import tempfile
|
|
9
8
|
import time
|
|
10
9
|
import threading
|
|
10
|
+
from typing import List
|
|
11
|
+
from typing import Union
|
|
11
12
|
|
|
12
13
|
from httpdbg.env import HTTPDBG_MULTIPROCESS_DIR
|
|
13
14
|
from httpdbg.log import logger
|
|
@@ -15,7 +16,11 @@ from httpdbg.records import HTTPRecords
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@contextmanager
|
|
18
|
-
def watcher_external(
|
|
19
|
+
def watcher_external(
|
|
20
|
+
records: HTTPRecords,
|
|
21
|
+
initiators: Union[List[str], None] = None,
|
|
22
|
+
server: bool = False,
|
|
23
|
+
) -> Generator[HTTPRecords, None, None]:
|
|
19
24
|
if HTTPDBG_MULTIPROCESS_DIR not in os.environ:
|
|
20
25
|
with tempfile.TemporaryDirectory(prefix="httpdbg_") as httpdbg_multiprocess_dir:
|
|
21
26
|
|
|
@@ -31,7 +36,20 @@ def watcher_external(records: HTTPRecords) -> Generator[HTTPRecords, None, None]
|
|
|
31
36
|
)
|
|
32
37
|
sitecustomize = os.path.join(httpdbg_multiprocess_dir, "sitecustomize.py")
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
with open(template) as f:
|
|
40
|
+
template_content = f.read()
|
|
41
|
+
|
|
42
|
+
if initiators:
|
|
43
|
+
template_content = template_content.replace(
|
|
44
|
+
"HTTPDBG_INITIATORS = []", f"HTTPDBG_INITIATORS = {initiators}", 1
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
template_content = template_content.replace(
|
|
48
|
+
"HTTPDBG_RECORD_SERVER = False", f"HTTPDBG_RECORD_SERVER = {server}", 1
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
with open(sitecustomize, "w") as f:
|
|
52
|
+
f.write(template_content)
|
|
35
53
|
|
|
36
54
|
if "PYTHONPATH" in os.environ:
|
|
37
55
|
os.environ["PYTHONPATH"] = (
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import Generator
|
|
6
|
+
|
|
7
|
+
from httpdbg.hooks.utils import getcallargs
|
|
8
|
+
from httpdbg.hooks.utils import decorate
|
|
9
|
+
from httpdbg.hooks.utils import undecorate
|
|
10
|
+
from httpdbg.initiator import httpdbg_endpoint
|
|
11
|
+
from httpdbg.records import HTTPRecords
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def set_hook_flask_endpoint(records: HTTPRecords, method: Callable):
|
|
15
|
+
|
|
16
|
+
@wraps(
|
|
17
|
+
method
|
|
18
|
+
) # to avoid AssertionError: View function mapping is overwriting an existing endpoint function: hook
|
|
19
|
+
def hook(*args, **kwargs):
|
|
20
|
+
|
|
21
|
+
with httpdbg_endpoint(
|
|
22
|
+
records,
|
|
23
|
+
method,
|
|
24
|
+
*args,
|
|
25
|
+
**kwargs,
|
|
26
|
+
):
|
|
27
|
+
ret = method(*args, **kwargs)
|
|
28
|
+
return ret
|
|
29
|
+
|
|
30
|
+
return hook
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# we must not apply the hook more than once on a mapped endpoint function
|
|
34
|
+
# AssertionError: View function mapping is overwriting an existing endpoint function: xxxx
|
|
35
|
+
already_mapped = {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_hook_flask_add_url_rule(records: HTTPRecords, method: Callable):
|
|
39
|
+
|
|
40
|
+
def hook(*args, **kwargs):
|
|
41
|
+
|
|
42
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
43
|
+
|
|
44
|
+
param = "view_func"
|
|
45
|
+
|
|
46
|
+
if param in callargs:
|
|
47
|
+
original_view_func = callargs[param]
|
|
48
|
+
if original_view_func not in already_mapped:
|
|
49
|
+
callargs[param] = decorate(
|
|
50
|
+
records, callargs[param], set_hook_flask_endpoint
|
|
51
|
+
)
|
|
52
|
+
already_mapped[original_view_func] = callargs[param]
|
|
53
|
+
else:
|
|
54
|
+
callargs[param] = already_mapped[original_view_func]
|
|
55
|
+
|
|
56
|
+
args = [callargs[param] if x == original_view_func else x for x in args]
|
|
57
|
+
if param in kwargs:
|
|
58
|
+
kwargs[param] = callargs[param]
|
|
59
|
+
|
|
60
|
+
return method(*args, **kwargs)
|
|
61
|
+
|
|
62
|
+
return hook
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def set_hook_flask_register_error_handler(records: HTTPRecords, method: Callable):
|
|
66
|
+
|
|
67
|
+
def hook(*args, **kwargs):
|
|
68
|
+
|
|
69
|
+
callargs = getcallargs(method, *args, **kwargs)
|
|
70
|
+
|
|
71
|
+
param = "f"
|
|
72
|
+
|
|
73
|
+
if param in callargs:
|
|
74
|
+
original_view_func = callargs[param]
|
|
75
|
+
if original_view_func not in already_mapped:
|
|
76
|
+
callargs[param] = decorate(
|
|
77
|
+
records, callargs[param], set_hook_flask_endpoint
|
|
78
|
+
)
|
|
79
|
+
already_mapped[original_view_func] = callargs[param]
|
|
80
|
+
else:
|
|
81
|
+
callargs[param] = already_mapped[original_view_func]
|
|
82
|
+
|
|
83
|
+
args = [callargs[param] if x == original_view_func else x for x in args]
|
|
84
|
+
if param in kwargs:
|
|
85
|
+
kwargs[param] = callargs[param]
|
|
86
|
+
|
|
87
|
+
return method(*args, **kwargs)
|
|
88
|
+
|
|
89
|
+
return hook
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@contextmanager
|
|
93
|
+
def hook_flask(records: HTTPRecords) -> Generator[None, None, None]:
|
|
94
|
+
hooks = False
|
|
95
|
+
try:
|
|
96
|
+
import flask
|
|
97
|
+
|
|
98
|
+
flask.Flask.add_url_rule = decorate(
|
|
99
|
+
records, flask.Flask.add_url_rule, set_hook_flask_add_url_rule
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
flask.Flask.register_error_handler = decorate(
|
|
103
|
+
records,
|
|
104
|
+
flask.Flask.register_error_handler,
|
|
105
|
+
set_hook_flask_register_error_handler,
|
|
106
|
+
)
|
|
107
|
+
flask.Flask.handle_exception = decorate(
|
|
108
|
+
records, flask.Flask.handle_exception, set_hook_flask_endpoint
|
|
109
|
+
)
|
|
110
|
+
flask.Flask.handle_http_exception = decorate(
|
|
111
|
+
records, flask.Flask.handle_http_exception, set_hook_flask_endpoint
|
|
112
|
+
)
|
|
113
|
+
flask.Flask.handle_user_exception = decorate(
|
|
114
|
+
records, flask.Flask.handle_user_exception, set_hook_flask_endpoint
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
hooks = True
|
|
118
|
+
except ImportError:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
yield
|
|
122
|
+
|
|
123
|
+
if hooks:
|
|
124
|
+
flask.Flask.add_url_rule = undecorate(flask.Flask.add_url_rule)
|
|
125
|
+
flask.Flask.register_error_handler = undecorate(
|
|
126
|
+
flask.Flask.register_error_handler
|
|
127
|
+
)
|
|
128
|
+
flask.Flask.handle_exception = undecorate(flask.Flask.handle_exception)
|
|
129
|
+
flask.Flask.handle_http_exception = undecorate(
|
|
130
|
+
flask.Flask.handle_http_exception
|
|
131
|
+
)
|
|
132
|
+
flask.Flask.handle_user_exception = undecorate(
|
|
133
|
+
flask.Flask.handle_user_exception
|
|
134
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from typing import Generator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from httpdbg.hooks.utils import decorate
|
|
8
|
+
from httpdbg.hooks.utils import undecorate
|
|
9
|
+
from httpdbg.initiator import httpdbg_group
|
|
10
|
+
from httpdbg.records import HTTPRecords
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def set_hook_for_http_server_handle_one_request(records: HTTPRecords, method: Callable):
|
|
14
|
+
def hook(*args, **kwargs):
|
|
15
|
+
|
|
16
|
+
# the group label/full_label will be updated when the endpoint method will be called
|
|
17
|
+
with httpdbg_group(records, "one request", ""):
|
|
18
|
+
ret = method(*args, **kwargs)
|
|
19
|
+
|
|
20
|
+
return ret
|
|
21
|
+
|
|
22
|
+
return hook
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@contextmanager
|
|
26
|
+
def hook_http(records: HTTPRecords) -> Generator[None, None, None]:
|
|
27
|
+
hooks = False
|
|
28
|
+
try:
|
|
29
|
+
import http
|
|
30
|
+
import http.server
|
|
31
|
+
|
|
32
|
+
http.server.BaseHTTPRequestHandler.handle_one_request = decorate(
|
|
33
|
+
records,
|
|
34
|
+
http.server.BaseHTTPRequestHandler.handle_one_request,
|
|
35
|
+
set_hook_for_http_server_handle_one_request,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
hooks = True
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
yield
|
|
43
|
+
|
|
44
|
+
if hooks:
|
|
45
|
+
http.server.BaseHTTPRequestHandler.handle_one_request = undecorate(
|
|
46
|
+
http.server.BaseHTTPRequestHandler.handle_one_request
|
|
47
|
+
)
|
|
@@ -34,7 +34,7 @@ def set_hook_for_pytest_runtest(records: HTTPRecords, method: Callable):
|
|
|
34
34
|
if "item" in callargs:
|
|
35
35
|
full_label = getattr(callargs["item"], "nodeid", "unknown test")
|
|
36
36
|
label = getattr(callargs["item"], "name", "unknown test")
|
|
37
|
-
with httpdbg_group(records, label, full_label):
|
|
37
|
+
with httpdbg_group(records, label, full_label, updatable=False):
|
|
38
38
|
return method(*args, **kwargs)
|
|
39
39
|
else:
|
|
40
40
|
return method(*args, **kwargs)
|