httpdbg 1.0.0__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-1.0.0 → httpdbg-1.1.0}/PKG-INFO +55 -14
- {httpdbg-1.0.0 → httpdbg-1.1.0}/README.md +53 -12
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/__init__.py +1 -1
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/all.py +13 -9
- httpdbg-1.1.0/httpdbg/hooks/flask.py +134 -0
- httpdbg-1.1.0/httpdbg/hooks/http.py +47 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/pytest.py +1 -1
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/unittest.py +5 -5
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/initiator.py +69 -20
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/api.js +33 -30
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/render.js +17 -15
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg.egg-info/PKG-INFO +55 -14
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg.egg-info/SOURCES.txt +3 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/pyproject.toml +1 -1
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_context_manager.py +14 -14
- httpdbg-1.1.0/tests/test_hook_flask.py +158 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_hook_unittest.py +10 -10
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_initiator.py +3 -3
- {httpdbg-1.0.0 → httpdbg-1.1.0}/LICENSE +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/__main__.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/args.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/env.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/exception.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/__init__.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/aiohttp.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/external.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/generic.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/httpx.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/requests.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/socket.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/template_sitecustomize.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/urllib3.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/hooks/utils.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/log.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/mode_console.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/mode_module.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/mode_script.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/preview.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/records.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/server.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/utils.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/__init__.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/api.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/app.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/column.css +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/configuration.css +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/configuration.js +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/detail.css +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/favicon.ico +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/filter.js +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/chevron-direction-top-outline-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/download-install-line-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/engine-gears-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/eye-blind-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/eye-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/files-papers-outline-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/filter-outline-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/information-mark-circle-outline-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/lock-line-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/lock-open-line-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/math-multiplication-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/push-pin-green-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/question-mark-round-line-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/recycle-bin-line-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/icons/wait-sandclock-icon.svg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/index.htm +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/main.js +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/mustache.min.js +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/style.css +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg/webapp/static/table.css +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg.egg-info/dependency_links.txt +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg.egg-info/entry_points.txt +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/httpdbg.egg-info/top_level.txt +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/setup.cfg +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_api.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_cli.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_hook_aiohttp.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_hook_httpx.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_hook_pytest.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_hook_requests.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_hook_urllib3.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_mode_console.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_mode_module_pytest.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_mode_script.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_multiprocess.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_preview.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_pyhttpdbg_server.py +0 -0
- {httpdbg-1.0.0 → httpdbg-1.1.0}/tests/test_server.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: httpdbg
|
|
3
|
-
Version: 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/
|
|
@@ -91,8 +91,32 @@ pyhttpdbg -m pytest [arg1 --arg2 ...]
|
|
|
91
91
|
|
|
92
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.
|
|
93
93
|
|
|
94
|
-
## Initiators
|
|
95
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
|
+
|
|
96
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.
|
|
97
121
|
|
|
98
122
|
To add a new package in the list of initiators, you can use the `-i` command line argument:
|
|
@@ -103,17 +127,34 @@ pyhttpdbg -i api_client_pck --script my_script.py
|
|
|
103
127
|
|
|
104
128
|
You can use any package as an initiator, this is not limited to HTTP requests.
|
|
105
129
|
|
|
106
|
-
###
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
| _your_package_ | yes, with the arg _-i your_package_ |
|
|
130
|
+
### supported HTTP client
|
|
131
|
+
|
|
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 |
|
|
117
158
|
|
|
118
159
|
## configuration
|
|
119
160
|
|
|
@@ -65,8 +65,32 @@ pyhttpdbg -m pytest [arg1 --arg2 ...]
|
|
|
65
65
|
|
|
66
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.
|
|
67
67
|
|
|
68
|
-
## Initiators
|
|
69
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
|
+
|
|
70
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.
|
|
71
95
|
|
|
72
96
|
To add a new package in the list of initiators, you can use the `-i` command line argument:
|
|
@@ -77,17 +101,34 @@ pyhttpdbg -i api_client_pck --script my_script.py
|
|
|
77
101
|
|
|
78
102
|
You can use any package as an initiator, this is not limited to HTTP requests.
|
|
79
103
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
| _your_package_ | yes, with the arg _-i your_package_ |
|
|
104
|
+
### supported HTTP client
|
|
105
|
+
|
|
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 |
|
|
91
132
|
|
|
92
133
|
## configuration
|
|
93
134
|
|
|
@@ -8,12 +8,14 @@ from typing import Union
|
|
|
8
8
|
from httpdbg.hooks.aiohttp import hook_aiohttp
|
|
9
9
|
from httpdbg.hooks.external import watcher_external
|
|
10
10
|
from httpdbg.hooks.generic import hook_generic
|
|
11
|
+
from httpdbg.hooks.http import hook_http
|
|
11
12
|
from httpdbg.hooks.httpx import hook_httpx
|
|
12
13
|
from httpdbg.hooks.pytest import hook_pytest
|
|
13
14
|
from httpdbg.hooks.requests import hook_requests
|
|
14
15
|
from httpdbg.hooks.socket import hook_socket
|
|
15
16
|
from httpdbg.hooks.unittest import hook_unittest
|
|
16
17
|
from httpdbg.hooks.urllib3 import hook_urllib3
|
|
18
|
+
from httpdbg.hooks.flask import hook_flask
|
|
17
19
|
from httpdbg.records import HTTPRecords
|
|
18
20
|
|
|
19
21
|
|
|
@@ -29,12 +31,14 @@ def httprecord(
|
|
|
29
31
|
records = HTTPRecords(client=client, server=server, ignore=ignore)
|
|
30
32
|
|
|
31
33
|
with watcher_external(records, initiators, server):
|
|
32
|
-
with
|
|
33
|
-
with
|
|
34
|
-
with
|
|
35
|
-
with
|
|
36
|
-
with
|
|
37
|
-
with
|
|
38
|
-
with
|
|
39
|
-
with
|
|
40
|
-
|
|
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
|
|
@@ -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)
|
|
@@ -36,7 +36,7 @@ def set_hook_for_unittest_run(records: HTTPRecords, method: Callable):
|
|
|
36
36
|
test_module_path = os.path.relpath(test_module_path)
|
|
37
37
|
full_label = f"{test_module_path}::{full_label}"
|
|
38
38
|
|
|
39
|
-
with httpdbg_group(records, label, full_label):
|
|
39
|
+
with httpdbg_group(records, label, full_label, updatable=False):
|
|
40
40
|
ret = method(*args, **kwargs)
|
|
41
41
|
else:
|
|
42
42
|
ret = method(*args, **kwargs)
|
|
@@ -79,7 +79,7 @@ def set_hook_for_unittest_class_setup(records: HTTPRecords, method: Callable):
|
|
|
79
79
|
test_module_path = os.path.relpath(test_module_path)
|
|
80
80
|
full_label = f"{test_module_path}::{full_label}"
|
|
81
81
|
|
|
82
|
-
with httpdbg_group(records, label, full_label):
|
|
82
|
+
with httpdbg_group(records, label, full_label, updatable=False):
|
|
83
83
|
return method(*args, **kwargs)
|
|
84
84
|
else:
|
|
85
85
|
return method(*args, **kwargs)
|
|
@@ -116,7 +116,7 @@ def set_hook_for_unittest_class_teardown(records: HTTPRecords, method: Callable)
|
|
|
116
116
|
test_module_path = os.path.relpath(test_module_path)
|
|
117
117
|
full_label = f"{test_module_path}::{full_label}"
|
|
118
118
|
|
|
119
|
-
with httpdbg_group(records, label, full_label):
|
|
119
|
+
with httpdbg_group(records, label, full_label, updatable=False):
|
|
120
120
|
return method(*args, **kwargs)
|
|
121
121
|
else:
|
|
122
122
|
return method(*args, **kwargs)
|
|
@@ -147,7 +147,7 @@ def set_hook_for_unittest_module_setup(records: HTTPRecords, method: Callable):
|
|
|
147
147
|
test_module_path = os.path.relpath(test_module_path)
|
|
148
148
|
full_label = f"{test_module_path}::setUpModule"
|
|
149
149
|
|
|
150
|
-
with httpdbg_group(records, label, full_label):
|
|
150
|
+
with httpdbg_group(records, label, full_label, updatable=False):
|
|
151
151
|
return method(*args, **kwargs)
|
|
152
152
|
else:
|
|
153
153
|
return method(*args, **kwargs)
|
|
@@ -191,7 +191,7 @@ def set_hook_for_unittest_module_teardown(records: HTTPRecords, method: Callable
|
|
|
191
191
|
test_module_path = os.path.relpath(test_module_path)
|
|
192
192
|
full_label = f"{test_module_path}::tearDownModule"
|
|
193
193
|
|
|
194
|
-
with httpdbg_group(records, label, full_label):
|
|
194
|
+
with httpdbg_group(records, label, full_label, updatable=False):
|
|
195
195
|
return method(*args, **kwargs)
|
|
196
196
|
else:
|
|
197
197
|
return method(*args, **kwargs)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
import datetime
|
|
5
|
+
import inspect
|
|
5
6
|
import os
|
|
6
7
|
import platform
|
|
7
8
|
import traceback
|
|
@@ -65,10 +66,11 @@ class Initiator:
|
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
class Group:
|
|
68
|
-
def __init__(self, label: str, full_label: str):
|
|
69
|
+
def __init__(self, label: str, full_label: str, updatable: bool):
|
|
69
70
|
self.id: str = get_new_uuid()
|
|
70
71
|
self.label: str = label
|
|
71
72
|
self.full_label: str = full_label
|
|
73
|
+
self.updatable: bool = updatable
|
|
72
74
|
self.tbegin: datetime.datetime = datetime.datetime.now(datetime.timezone.utc)
|
|
73
75
|
|
|
74
76
|
def to_json(self) -> dict:
|
|
@@ -204,6 +206,36 @@ def extract_short_stack_from_file(
|
|
|
204
206
|
return instruction, short_stack
|
|
205
207
|
|
|
206
208
|
|
|
209
|
+
def construct_call_str(original_method, *args, **kwargs):
|
|
210
|
+
|
|
211
|
+
def print_v(v):
|
|
212
|
+
if isinstance(v, str):
|
|
213
|
+
return f'"{v}"'
|
|
214
|
+
else:
|
|
215
|
+
return v
|
|
216
|
+
|
|
217
|
+
callargs = getcallargs(original_method, *args, **kwargs)
|
|
218
|
+
|
|
219
|
+
str_module = (
|
|
220
|
+
f"{original_method.__module__}."
|
|
221
|
+
if hasattr(original_method, "__module__")
|
|
222
|
+
else ""
|
|
223
|
+
)
|
|
224
|
+
str_name = (
|
|
225
|
+
f"{original_method.__name__}" if hasattr(original_method, "__name__") else "***"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if callargs:
|
|
229
|
+
r = f"{str_module}{str_name}(\n"
|
|
230
|
+
for k, v in callargs.items():
|
|
231
|
+
r += f" {k}={print_v(v)},\n"
|
|
232
|
+
r += ")"
|
|
233
|
+
else:
|
|
234
|
+
r = f"{str_module}{str_name}()"
|
|
235
|
+
|
|
236
|
+
return r
|
|
237
|
+
|
|
238
|
+
|
|
207
239
|
@contextmanager
|
|
208
240
|
def httpdbg_initiator(
|
|
209
241
|
records: "HTTPRecords",
|
|
@@ -232,24 +264,10 @@ def httpdbg_initiator(
|
|
|
232
264
|
original_initiator_id = os.environ[HTTPDBG_CURRENT_INITIATOR]
|
|
233
265
|
|
|
234
266
|
# in any case, we create an Initiator
|
|
235
|
-
|
|
236
|
-
callargs = getcallargs(original_method, *args, **kwargs)
|
|
237
267
|
instruction, short_stack, stack = get_current_instruction(extracted_stack)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
f"{original_method.__module__}."
|
|
241
|
-
if hasattr(original_method, "__module__")
|
|
242
|
-
else ""
|
|
243
|
-
)
|
|
244
|
-
str_name = (
|
|
245
|
-
f"{original_method.__name__}"
|
|
246
|
-
if hasattr(original_method, "__name__")
|
|
247
|
-
else "***"
|
|
268
|
+
short_stack += "----------\n" + construct_call_str(
|
|
269
|
+
original_method, *args, **kwargs
|
|
248
270
|
)
|
|
249
|
-
short_stack += f"----------\n{str_module}{str_name}(\n"
|
|
250
|
-
for k, v in callargs.items():
|
|
251
|
-
short_stack += f" {k}={v}\n"
|
|
252
|
-
short_stack += ")"
|
|
253
271
|
|
|
254
272
|
current_initiator = Initiator(instruction, short_stack, stack)
|
|
255
273
|
|
|
@@ -302,7 +320,11 @@ def httpdbg_tag(tag: str) -> Generator[None, None, None]:
|
|
|
302
320
|
|
|
303
321
|
@contextmanager
|
|
304
322
|
def httpdbg_group(
|
|
305
|
-
records: "HTTPRecords",
|
|
323
|
+
records: "HTTPRecords",
|
|
324
|
+
label: str,
|
|
325
|
+
full_label: str,
|
|
326
|
+
update: bool = False,
|
|
327
|
+
updatable: bool = True,
|
|
306
328
|
) -> Generator[Group, None, None]:
|
|
307
329
|
|
|
308
330
|
# A group is considered set if the environment variable exists and the group
|
|
@@ -315,7 +337,7 @@ def httpdbg_group(
|
|
|
315
337
|
|
|
316
338
|
if not group_already_set:
|
|
317
339
|
logger().info("httpdbg_group (new)")
|
|
318
|
-
group = Group(label, full_label)
|
|
340
|
+
group = Group(label, full_label, updatable=updatable)
|
|
319
341
|
# in case of a reentrant call to httpdbg, we force the group id to be the same
|
|
320
342
|
if HTTPDBG_CURRENT_GROUP in os.environ:
|
|
321
343
|
group.id = os.environ[HTTPDBG_CURRENT_GROUP]
|
|
@@ -325,9 +347,13 @@ def httpdbg_group(
|
|
|
325
347
|
else:
|
|
326
348
|
group = records.groups[os.environ[HTTPDBG_CURRENT_GROUP]]
|
|
327
349
|
|
|
350
|
+
if update and group.updatable:
|
|
351
|
+
# Update the label and full_label of an existing group, in case of endpoint.
|
|
352
|
+
group.label = label
|
|
353
|
+
group.full_label = full_label
|
|
328
354
|
try:
|
|
329
355
|
logger().info(
|
|
330
|
-
f"httpdbg_group {
|
|
356
|
+
f"httpdbg_group {group} group_id={os.environ[HTTPDBG_CURRENT_GROUP]} label={label} full_label={full_label}"
|
|
331
357
|
)
|
|
332
358
|
yield group
|
|
333
359
|
except Exception:
|
|
@@ -337,3 +363,26 @@ def httpdbg_group(
|
|
|
337
363
|
|
|
338
364
|
if (not group_already_set) and (HTTPDBG_CURRENT_GROUP in os.environ):
|
|
339
365
|
del os.environ[HTTPDBG_CURRENT_GROUP]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@contextmanager
|
|
369
|
+
def httpdbg_endpoint(
|
|
370
|
+
records: "HTTPRecords", original_method: Callable, *args, **kwargs
|
|
371
|
+
) -> Generator[None, None, None]:
|
|
372
|
+
|
|
373
|
+
filename = inspect.getsourcefile(original_method)
|
|
374
|
+
if filename:
|
|
375
|
+
_, lineno = inspect.getsourcelines(original_method)
|
|
376
|
+
instruction, s_stack = extract_short_stack_from_file(filename, lineno, 0, 8)
|
|
377
|
+
full_label = (
|
|
378
|
+
f'File "{filename}", line {lineno}, in {original_method.__name__}\n'
|
|
379
|
+
)
|
|
380
|
+
full_label += s_stack
|
|
381
|
+
full_label += "----------\n" + construct_call_str(
|
|
382
|
+
original_method, *args, **kwargs
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
with httpdbg_group(records, instruction, full_label, update=True):
|
|
386
|
+
yield
|
|
387
|
+
else:
|
|
388
|
+
yield
|
|
@@ -12,37 +12,40 @@ const global = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function save_request(request_id, request, session_id) {
|
|
15
|
-
request.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
request.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
request.filter = "---";
|
|
22
|
-
}
|
|
23
|
-
request.session_id = session_id;
|
|
24
|
-
request.initiator = global.initiators[request.initiator_id];
|
|
25
|
-
|
|
26
|
-
if (request.in_progress) {
|
|
27
|
-
request.status_code_view = '<img class="icon" src="static/icons/wait-sandclock-icon.svg-+-$**HTTPDBG_VERSION**$" alt="loading"/>';
|
|
28
|
-
} else {
|
|
29
|
-
switch (request.status_code) {
|
|
30
|
-
case 0:
|
|
31
|
-
request.status_code_view = '<img class="icon" src="static/icons/wait-sandclock-icon.svg-+-$**HTTPDBG_VERSION**$/" alt="loading"/>';
|
|
32
|
-
break;
|
|
33
|
-
case -1:
|
|
34
|
-
request.status_code_view = '<img class="icon" src="static/icons/math-multiplication-icon.svg-+-$**HTTPDBG_VERSION**$/" alt="load failed"/>';
|
|
35
|
-
break;
|
|
36
|
-
default:
|
|
37
|
-
request.status_code_view = request.status_code;
|
|
38
|
-
break;
|
|
15
|
+
const initiator = global.initiators[request.initiator_id] ?? null;
|
|
16
|
+
if (initiator !== null) { // the initiator may be missing if the clean list is executed in parrallel
|
|
17
|
+
request.loaded = false;
|
|
18
|
+
request.to_refresh = true;
|
|
19
|
+
if (request.pin == undefined) {
|
|
20
|
+
request.pin = "";
|
|
39
21
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
22
|
+
if (request.filter == undefined) {
|
|
23
|
+
request.filter = "---";
|
|
24
|
+
}
|
|
25
|
+
request.session_id = session_id;
|
|
26
|
+
request.initiator = initiator;
|
|
27
|
+
|
|
28
|
+
if (request.in_progress) {
|
|
29
|
+
request.status_code_view = '<img class="icon" src="static/icons/wait-sandclock-icon.svg-+-$**HTTPDBG_VERSION**$" alt="loading"/>';
|
|
30
|
+
} else {
|
|
31
|
+
switch (request.status_code) {
|
|
32
|
+
case 0:
|
|
33
|
+
request.status_code_view = '<img class="icon" src="static/icons/wait-sandclock-icon.svg-+-$**HTTPDBG_VERSION**$/" alt="loading"/>';
|
|
34
|
+
break;
|
|
35
|
+
case -1:
|
|
36
|
+
request.status_code_view = '<img class="icon" src="static/icons/math-multiplication-icon.svg-+-$**HTTPDBG_VERSION**$/" alt="load failed"/>';
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
request.status_code_view = request.status_code;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
global.requests[request_id] = request;
|
|
45
|
+
|
|
46
|
+
if (!request.pin) {
|
|
47
|
+
get_request(request_id);
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|