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.
Files changed (89) hide show
  1. {httpdbg-0.32.1 → httpdbg-1.1.0}/PKG-INFO +67 -15
  2. {httpdbg-0.32.1 → httpdbg-1.1.0}/README.md +65 -13
  3. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/__init__.py +1 -1
  4. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/__main__.py +2 -1
  5. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/args.py +6 -0
  6. httpdbg-1.1.0/httpdbg/hooks/all.py +44 -0
  7. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/external.py +21 -3
  8. httpdbg-1.1.0/httpdbg/hooks/flask.py +134 -0
  9. httpdbg-1.1.0/httpdbg/hooks/http.py +47 -0
  10. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/pytest.py +1 -1
  11. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/socket.py +166 -98
  12. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/template_sitecustomize.py +6 -1
  13. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/unittest.py +5 -5
  14. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/utils.py +1 -1
  15. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/initiator.py +69 -20
  16. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/records.py +57 -23
  17. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/server.py +3 -0
  18. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/api.py +2 -0
  19. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/api.js +33 -30
  20. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/index.htm +2 -1
  21. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/render.js +17 -15
  22. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/style.css +1 -0
  23. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/table.css +4 -0
  24. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/PKG-INFO +67 -15
  25. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/SOURCES.txt +4 -0
  26. {httpdbg-0.32.1 → httpdbg-1.1.0}/pyproject.toml +1 -1
  27. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_context_manager.py +14 -14
  28. httpdbg-1.1.0/tests/test_hook_flask.py +158 -0
  29. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_unittest.py +10 -10
  30. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_initiator.py +3 -3
  31. httpdbg-0.32.1/tests/test_server.py → httpdbg-1.1.0/tests/test_pyhttpdbg_server.py +7 -6
  32. httpdbg-1.1.0/tests/test_server.py +51 -0
  33. httpdbg-0.32.1/httpdbg/hooks/all.py +0 -35
  34. {httpdbg-0.32.1 → httpdbg-1.1.0}/LICENSE +0 -0
  35. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/env.py +0 -0
  36. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/exception.py +0 -0
  37. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/__init__.py +0 -0
  38. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/aiohttp.py +0 -0
  39. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/generic.py +0 -0
  40. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/httpx.py +0 -0
  41. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/requests.py +0 -0
  42. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/hooks/urllib3.py +0 -0
  43. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/log.py +0 -0
  44. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/mode_console.py +0 -0
  45. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/mode_module.py +0 -0
  46. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/mode_script.py +0 -0
  47. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/preview.py +0 -0
  48. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/utils.py +0 -0
  49. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/__init__.py +0 -0
  50. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/app.py +0 -0
  51. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/column.css +0 -0
  52. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/configuration.css +0 -0
  53. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/configuration.js +0 -0
  54. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/detail.css +0 -0
  55. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/favicon.ico +0 -0
  56. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/filter.js +0 -0
  57. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
  58. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
  59. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
  60. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
  61. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
  62. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
  63. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
  64. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
  65. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
  66. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
  67. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
  68. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
  69. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
  70. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
  71. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
  72. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/main.js +0 -0
  73. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg/webapp/static/mustache.min.js +0 -0
  74. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/dependency_links.txt +0 -0
  75. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/entry_points.txt +0 -0
  76. {httpdbg-0.32.1 → httpdbg-1.1.0}/httpdbg.egg-info/top_level.txt +0 -0
  77. {httpdbg-0.32.1 → httpdbg-1.1.0}/setup.cfg +0 -0
  78. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_api.py +0 -0
  79. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_cli.py +0 -0
  80. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_aiohttp.py +0 -0
  81. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_httpx.py +0 -0
  82. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_pytest.py +0 -0
  83. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_requests.py +0 -0
  84. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_hook_urllib3.py +0 -0
  85. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_mode_console.py +0 -0
  86. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_mode_module_pytest.py +0 -0
  87. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_mode_script.py +0 -0
  88. {httpdbg-0.32.1 → httpdbg-1.1.0}/tests/test_multiprocess.py +0 -0
  89. {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: 0.32.1
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
+ ![httpdbg 1.1.0 - pyhttpdbg -m flask --app demoflask run](https://github.com/cle-b/httpdbg/blob/main/ui_server.png?raw=true)
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
- ### Already supported packages
130
+ ### supported HTTP client
105
131
 
106
- | packages | status |
107
- |----------------|-------------------------------------|
108
- | requests | supported |
109
- | urllib3 | supported |
110
- | httpx | supported |
111
- | aiohttp | supported |
112
- | pytest | supported |
113
- | unittest | supported |
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] [--initiator INITIATOR] [--keep-up | --force-quit]
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
+ ![httpdbg 1.1.0 - pyhttpdbg -m flask --app demoflask run](https://github.com/cle-b/httpdbg/blob/main/ui_server.png?raw=true)
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
- ### Already supported packages
104
+ ### supported HTTP client
79
105
 
80
- | packages | status |
81
- |----------------|-------------------------------------|
82
- | requests | supported |
83
- | urllib3 | supported |
84
- | httpx | supported |
85
- | aiohttp | supported |
86
- | pytest | supported |
87
- | unittest | supported |
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] [--initiator INITIATOR] [--keep-up | --force-quit]
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
@@ -3,6 +3,6 @@ from httpdbg.hooks.all import httprecord
3
3
  from httpdbg.records import HTTPRecords
4
4
 
5
5
 
6
- __version__ = "0.32.1"
6
+ __version__ = "1.1.0"
7
7
 
8
8
  __all__ = ["httprecord", "HTTPRecords"]
@@ -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
- with httprecord(records, params.initiator):
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(records: HTTPRecords) -> Generator[HTTPRecords, None, None]:
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
- shutil.copy(template, sitecustomize)
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)