spoof 2.2.0__tar.gz → 2.2.2__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.
- {spoof-2.2.0 → spoof-2.2.2}/CHANGELOG.rst +10 -0
- {spoof-2.2.0 → spoof-2.2.2}/PKG-INFO +128 -93
- {spoof-2.2.0 → spoof-2.2.2}/README.rst +127 -92
- {spoof-2.2.0 → spoof-2.2.2}/src/spoof.egg-info/PKG-INFO +128 -93
- {spoof-2.2.0 → spoof-2.2.2}/src/spoof.py +5 -5
- {spoof-2.2.0 → spoof-2.2.2}/.flake8 +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/.gitignore +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/.readthedocs.yaml +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/LICENSE +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/MANIFEST.in +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/Makefile +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/docs/Makefile +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/docs/conf.py +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/docs/index.rst +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/docs/requirements-docs.txt +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/pyproject.toml +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/requirements-dev.txt +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/setup.cfg +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/src/spoof.egg-info/SOURCES.txt +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/src/spoof.egg-info/dependency_links.txt +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/src/spoof.egg-info/top_level.txt +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/tests/test_spoof_functional.py +0 -0
- {spoof-2.2.0 → spoof-2.2.2}/tests/test_spoof_unit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spoof
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: HTTP server for testing environments
|
|
5
5
|
Author-email: Lex Scarisbrick <lex@scarisbrick.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -57,12 +57,12 @@ Spoof 👻
|
|
|
57
57
|
A test interface for HTTP
|
|
58
58
|
=========================
|
|
59
59
|
Spoof lets you easily create HTTP servers listening on real network
|
|
60
|
-
sockets. Designed for test environments, what responses to
|
|
61
|
-
configured while an HTTP server is running. Requests
|
|
62
|
-
live or after a response is sent.
|
|
60
|
+
sockets. Designed for test environments, what responses to send can be
|
|
61
|
+
configured anytime, including while an HTTP server is running. Requests
|
|
62
|
+
can be inspected live or after a response is sent.
|
|
63
63
|
|
|
64
|
-
Unlike a
|
|
65
|
-
configured in advance, Spoof accepts and
|
|
64
|
+
Unlike a conventional HTTP server, where specific methods and paths are
|
|
65
|
+
configured in advance, Spoof accepts and records *all* requests, sending
|
|
66
66
|
whatever responses are queued, or a default response if the queue is empty.
|
|
67
67
|
|
|
68
68
|
Why would I want this?
|
|
@@ -70,13 +70,12 @@ Why would I want this?
|
|
|
70
70
|
Spoof is all about enabling test-driven development (and refactoring) of
|
|
71
71
|
HTTP client code. Have you ever felt icky patching a client library to
|
|
72
72
|
write tests? Ever been burned by this? Ever wanted to refactor a client
|
|
73
|
-
library, but had no way to
|
|
74
|
-
integration testing? Ever wanted mock
|
|
75
|
-
|
|
73
|
+
library, but had no way to verify behavior apart from doing live
|
|
74
|
+
integration testing? Ever wanted mock for HTTP? If you answered yes to
|
|
75
|
+
any of the above, Spoof might be for you.
|
|
76
76
|
|
|
77
77
|
Installation and Compatibility
|
|
78
78
|
==============================
|
|
79
|
-
|
|
80
79
|
Spoof is available on PyPI:
|
|
81
80
|
|
|
82
81
|
.. code-block:: console
|
|
@@ -91,74 +90,8 @@ Multiple Spoof HTTP servers can be run concurrently, and by default, the port
|
|
|
91
90
|
number is the next available unused port. With OpenSSL installed, Spoof can
|
|
92
91
|
also provide an SSL/TLS HTTP server. HTTP proxying and IPv6 are also supported.
|
|
93
92
|
|
|
94
|
-
Request instances
|
|
95
|
-
=================
|
|
96
|
-
Spoof captures each request as a ``SpoofRequestEnv`` instance with the following
|
|
97
|
-
properties:
|
|
98
|
-
|
|
99
|
-
+-------------------------+----------------------------------------------+
|
|
100
|
-
| Property | Description |
|
|
101
|
-
+=========================+==============================================+
|
|
102
|
-
| content | ``bytes`` object of request content |
|
|
103
|
-
+-------------------------+----------------------------------------------+
|
|
104
|
-
| contentEncoding | Value of Content-Encoding header, if present |
|
|
105
|
-
+-------------------------+----------------------------------------------+
|
|
106
|
-
| contentLength | Value of Content-Length header, if present |
|
|
107
|
-
+-------------------------+----------------------------------------------+
|
|
108
|
-
| contentType | Value of Content-Type header, if present |
|
|
109
|
-
+-------------------------+----------------------------------------------+
|
|
110
|
-
| headers | ``http.client.HTTPMessage`` object of headers|
|
|
111
|
-
+-------------------------+----------------------------------------------+
|
|
112
|
-
| json() | Convenience to call ``json.loads`` on content|
|
|
113
|
-
+-------------------------+----------------------------------------------+
|
|
114
|
-
| method | Request method (e.g. GET, POST, HEAD) |
|
|
115
|
-
+-------------------------+----------------------------------------------+
|
|
116
|
-
| path | Decoded URI path, without query string |
|
|
117
|
-
+-------------------------+----------------------------------------------+
|
|
118
|
-
| protocol | Protocol version (e.g. HTTP/1.0) |
|
|
119
|
-
+-------------------------+----------------------------------------------+
|
|
120
|
-
| queryString | Anything in URI after ``?`` |
|
|
121
|
-
+-------------------------+----------------------------------------------+
|
|
122
|
-
| serverName | Host name of HTTP server |
|
|
123
|
-
+-------------------------+----------------------------------------------+
|
|
124
|
-
| serverPort | Port number of HTTP server |
|
|
125
|
-
+-------------------------+----------------------------------------------+
|
|
126
|
-
| uri | Raw URI path and query string, if present |
|
|
127
|
-
+-------------------------+----------------------------------------------+
|
|
128
|
-
|
|
129
|
-
Example with request properties:
|
|
130
|
-
|
|
131
|
-
.. code-block:: python
|
|
132
|
-
|
|
133
|
-
>>> import requests
|
|
134
|
-
... import spoof
|
|
135
|
-
...
|
|
136
|
-
... with spoof.HTTPServer() as httpd:
|
|
137
|
-
... httpd.defaultResponse = [200, [], None]
|
|
138
|
-
...
|
|
139
|
-
... [requests.get(httpd.url + path) for path in ["/a", "/b", "/c"]]
|
|
140
|
-
... [f"{r.method} {r.path} {r.protocol}" for r in httpd.requests]
|
|
141
|
-
...
|
|
142
|
-
[<Response [200]>, <Response [200]>, <Response [200]>]
|
|
143
|
-
['GET /a HTTP/1.1', 'GET /b HTTP/1.1', 'GET /c HTTP/1.1']
|
|
144
|
-
|
|
145
|
-
Response precedence
|
|
146
|
-
===================
|
|
147
|
-
|
|
148
|
-
Spoof determines what response to send to incoming requests based on
|
|
149
|
-
the following precedence, highest to lowest:
|
|
150
|
-
|
|
151
|
-
#. Oldest response queued in ``.responses`` using first-in, first-out (FIFO) order
|
|
152
|
-
#. Response stored in ``.defaultResponse`` if no responses are queued
|
|
153
|
-
#. Response stored in ``.errorResponse`` if ``.defaultResponse`` is ``None``
|
|
154
|
-
|
|
155
|
-
By default, an HTTP error response will be sent to all requests, because
|
|
156
|
-
newly created Spoof instances have no responses queued, and no default
|
|
157
|
-
response set. This requires non-error responses to be explicitly specified.
|
|
158
|
-
|
|
159
93
|
Response syntax
|
|
160
94
|
===============
|
|
161
|
-
|
|
162
95
|
Spoof expects responses to have the following syntax:
|
|
163
96
|
|
|
164
97
|
.. code-block:: python
|
|
@@ -178,13 +111,31 @@ Spoof expects responses to have the following syntax:
|
|
|
178
111
|
def callback(request):
|
|
179
112
|
return [200, [], request.path]
|
|
180
113
|
|
|
181
|
-
|
|
182
|
-
|
|
114
|
+
Response precedence
|
|
115
|
+
===================
|
|
116
|
+
Spoof determines what response to send to incoming requests based on
|
|
117
|
+
the following precedence, highest to lowest:
|
|
118
|
+
|
|
119
|
+
#. Oldest response queued in ``.responses`` using first-in, first-out (FIFO) order
|
|
120
|
+
#. Response stored in ``.defaultResponse`` if no responses are queued
|
|
121
|
+
#. Response stored in ``.errorResponse`` if ``.defaultResponse`` is ``None``
|
|
122
|
+
|
|
123
|
+
By default, Spoof will respond with an **HTTP 503 Service Unavailable** error,
|
|
124
|
+
because newly created Spoof instances have no responses queued and no default
|
|
125
|
+
response set. This requires non-error HTTP responses to be explicitly specified.
|
|
183
126
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
127
|
+
Response queue
|
|
128
|
+
==============
|
|
129
|
+
Spoof will always try to send a response from ``.responses`` first, before falling
|
|
130
|
+
back to ``.defaultResponse`` if the queue is empty. Backed by a
|
|
131
|
+
`deque <https://docs.python.org/3/library/collections.html#collections.deque>`__
|
|
132
|
+
instance, the ``.responses`` queue supports adding items via ``.responses.append()``
|
|
133
|
+
and ``.responses.extend()``, similar to a regular list.
|
|
134
|
+
|
|
135
|
+
Spoof HTTP servers run in a single background thread, so response order should
|
|
136
|
+
be predictably serial. Tests using Spoof should be able to use the same fixtures,
|
|
137
|
+
in the same order, and get the same results. Example queueing multiple responses,
|
|
138
|
+
verifying content, and request paths:
|
|
188
139
|
|
|
189
140
|
.. code-block:: python
|
|
190
141
|
|
|
@@ -203,9 +154,11 @@ queueing multiple responses, verifying content, and request paths:
|
|
|
203
154
|
assert requests.get(httpd.url + "/oops").status_code == 404
|
|
204
155
|
assert [r.path for r in httpd.requests] == ["/path", "/alt/path", "/oops"]
|
|
205
156
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
157
|
+
Response default
|
|
158
|
+
================
|
|
159
|
+
Spoof will always try to send a response from ``.responses`` first, before falling
|
|
160
|
+
back to ``.defaultResponse`` if the queue is empty. Example setting a callback as
|
|
161
|
+
a default response:
|
|
209
162
|
|
|
210
163
|
.. code-block:: python
|
|
211
164
|
|
|
@@ -217,9 +170,69 @@ Set a callback as the default response (callbacks can also be queued):
|
|
|
217
170
|
|
|
218
171
|
assert requests.get(httpd.url + "/alt").text == "/alt"
|
|
219
172
|
|
|
173
|
+
Request history
|
|
174
|
+
===============
|
|
175
|
+
Spoof records each request and appends it to the ``.requests`` property,
|
|
176
|
+
which is backed by a
|
|
177
|
+
`deque <https://docs.python.org/3/library/collections.html#collections.deque>`__
|
|
178
|
+
instance, the same as the ``.responses`` property. Think of it like a pre-parsed access log. Example
|
|
179
|
+
using request history:
|
|
180
|
+
|
|
181
|
+
.. code-block:: python
|
|
182
|
+
|
|
183
|
+
>>> import requests
|
|
184
|
+
... import spoof
|
|
185
|
+
...
|
|
186
|
+
... with spoof.HTTPServer() as httpd:
|
|
187
|
+
... httpd.defaultResponse = [200, [], None]
|
|
188
|
+
...
|
|
189
|
+
... [requests.get(httpd.url + path) for path in ["/a", "/b", "/c"]]
|
|
190
|
+
... [f"{r.method} {r.path} {r.protocol}" for r in httpd.requests]
|
|
191
|
+
...
|
|
192
|
+
[<Response [200]>, <Response [200]>, <Response [200]>]
|
|
193
|
+
['GET /a HTTP/1.1', 'GET /b HTTP/1.1', 'GET /c HTTP/1.1']
|
|
194
|
+
|
|
195
|
+
Request properties
|
|
196
|
+
==================
|
|
197
|
+
``SpoofRequestEnv`` instances have the following properties:
|
|
198
|
+
|
|
199
|
+
+-------------------------+----------------------------------------------+
|
|
200
|
+
| Property | Description |
|
|
201
|
+
+=========================+==============================================+
|
|
202
|
+
| content | ``bytes`` object of request content |
|
|
203
|
+
+-------------------------+----------------------------------------------+
|
|
204
|
+
| contentEncoding | Value of Content-Encoding header, if present |
|
|
205
|
+
+-------------------------+----------------------------------------------+
|
|
206
|
+
| contentLength | Value of Content-Length header, if present |
|
|
207
|
+
+-------------------------+----------------------------------------------+
|
|
208
|
+
| contentType | Value of Content-Type header, if present |
|
|
209
|
+
+-------------------------+----------------------------------------------+
|
|
210
|
+
| headers | ``http.client.HTTPMessage`` object of headers|
|
|
211
|
+
+-------------------------+----------------------------------------------+
|
|
212
|
+
| json() | Convenience to call ``json.loads`` on content|
|
|
213
|
+
+-------------------------+----------------------------------------------+
|
|
214
|
+
| method | Request method (e.g. GET, POST, HEAD) |
|
|
215
|
+
+-------------------------+----------------------------------------------+
|
|
216
|
+
| path | Decoded URI path, without query string |
|
|
217
|
+
+-------------------------+----------------------------------------------+
|
|
218
|
+
| protocol | Protocol version (e.g. HTTP/1.0) |
|
|
219
|
+
+-------------------------+----------------------------------------------+
|
|
220
|
+
| queryString | Anything in URI after ``?`` |
|
|
221
|
+
+-------------------------+----------------------------------------------+
|
|
222
|
+
| serverName | Host name of HTTP server |
|
|
223
|
+
+-------------------------+----------------------------------------------+
|
|
224
|
+
| serverPort | Port number of HTTP server |
|
|
225
|
+
+-------------------------+----------------------------------------------+
|
|
226
|
+
| uri | Raw URI path and query string, if present |
|
|
227
|
+
+-------------------------+----------------------------------------------+
|
|
228
|
+
|
|
220
229
|
SSL/TLS Mode
|
|
221
230
|
============
|
|
222
|
-
|
|
231
|
+
Spoof supports SSL/TLS connectivity by passing an
|
|
232
|
+
`SSLContext <https://docs.python.org/3/library/ssl.html#ssl-contexts>`__,
|
|
233
|
+
or if OpenSSL command line tools are available, creating an ``SSLContext``
|
|
234
|
+
with a self-signed certificate. Configured correctly, this should not raise
|
|
235
|
+
any warnings or errors:
|
|
223
236
|
|
|
224
237
|
.. code-block:: python
|
|
225
238
|
|
|
@@ -251,8 +264,8 @@ set to the path of the self-signed certificate to silence SSL/TLS errors:
|
|
|
251
264
|
response = requests.get(httpd.url)
|
|
252
265
|
assert response.text == "No self-signed cert warning!"
|
|
253
266
|
|
|
254
|
-
If OpenSSL 3.5.0
|
|
255
|
-
key algorithms can be used:
|
|
267
|
+
If `OpenSSL 3.5.0 <https://openssl-library.org/post/2025-04-08-openssl-35-final-release/>`__
|
|
268
|
+
or later is installed, Post-Quantum Cryptography (PQC) key algorithms can be used:
|
|
256
269
|
|
|
257
270
|
.. code-block:: python
|
|
258
271
|
|
|
@@ -293,8 +306,31 @@ external services. Example usage:
|
|
|
293
306
|
assert proxy.upstream.requests[0].path == "/ayt"
|
|
294
307
|
assert response.text == "I'm here!"
|
|
295
308
|
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
If setting the ``proxies`` option in ``requests`` isn't workable, the
|
|
310
|
+
``https_proxy`` environment variable can be set to the URL of the proxy:
|
|
311
|
+
|
|
312
|
+
.. code-block:: python
|
|
313
|
+
|
|
314
|
+
import os
|
|
315
|
+
import requests
|
|
316
|
+
import spoof
|
|
317
|
+
|
|
318
|
+
with spoof.SelfSignedSSLContext(commonName="example.spoof") as selfSigned:
|
|
319
|
+
with spoof.HTTPServer(sslContext=selfSigned.sslContext, proxy=True) as proxy:
|
|
320
|
+
proxy.upstream.defaultResponse = [200, [], "I'm here!"]
|
|
321
|
+
|
|
322
|
+
os.environ["https_proxy"] = proxy.url
|
|
323
|
+
os.environ["REQUESTS_CA_BUNDLE"] = selfSigned.certFile
|
|
324
|
+
|
|
325
|
+
response = requests.get("https://example.spoof/ayt")
|
|
326
|
+
assert proxy.requests[0].method == "CONNECT"
|
|
327
|
+
assert proxy.requests[0].path == "example.spoof:443"
|
|
328
|
+
assert proxy.upstream.requests[0].method == "GET"
|
|
329
|
+
assert proxy.upstream.requests[0].path == "/ayt"
|
|
330
|
+
assert response.text == "I'm here!"
|
|
331
|
+
|
|
332
|
+
IPv6 Mode
|
|
333
|
+
=========
|
|
298
334
|
Setting the ``host`` attribute to an IPv6 address will work as expected. There
|
|
299
335
|
is also an IPv6-only ``spoof.HTTPServer6`` class that can be used if needed to
|
|
300
336
|
only listen on IPv6 sockets.
|
|
@@ -325,8 +361,8 @@ only listen on IPv6 sockets.
|
|
|
325
361
|
'This is also Spoof on IPv6 👀'
|
|
326
362
|
'http://[::1]:54296'
|
|
327
363
|
|
|
328
|
-
|
|
329
|
-
|
|
364
|
+
Debug mode
|
|
365
|
+
==========
|
|
330
366
|
Setting a callback with a ``breakpoint()`` can allow for live HTTP request
|
|
331
367
|
debugging, including setting custom responses and inspecting requests. Note
|
|
332
368
|
that callbacks can also be queued.
|
|
@@ -351,4 +387,3 @@ that callbacks can also be queued.
|
|
|
351
387
|
(Pdb) response[2] = "content set from pdb"
|
|
352
388
|
(Pdb) c
|
|
353
389
|
'content set from pdb'
|
|
354
|
-
|
|
@@ -30,12 +30,12 @@ Spoof 👻
|
|
|
30
30
|
A test interface for HTTP
|
|
31
31
|
=========================
|
|
32
32
|
Spoof lets you easily create HTTP servers listening on real network
|
|
33
|
-
sockets. Designed for test environments, what responses to
|
|
34
|
-
configured while an HTTP server is running. Requests
|
|
35
|
-
live or after a response is sent.
|
|
33
|
+
sockets. Designed for test environments, what responses to send can be
|
|
34
|
+
configured anytime, including while an HTTP server is running. Requests
|
|
35
|
+
can be inspected live or after a response is sent.
|
|
36
36
|
|
|
37
|
-
Unlike a
|
|
38
|
-
configured in advance, Spoof accepts and
|
|
37
|
+
Unlike a conventional HTTP server, where specific methods and paths are
|
|
38
|
+
configured in advance, Spoof accepts and records *all* requests, sending
|
|
39
39
|
whatever responses are queued, or a default response if the queue is empty.
|
|
40
40
|
|
|
41
41
|
Why would I want this?
|
|
@@ -43,13 +43,12 @@ Why would I want this?
|
|
|
43
43
|
Spoof is all about enabling test-driven development (and refactoring) of
|
|
44
44
|
HTTP client code. Have you ever felt icky patching a client library to
|
|
45
45
|
write tests? Ever been burned by this? Ever wanted to refactor a client
|
|
46
|
-
library, but had no way to
|
|
47
|
-
integration testing? Ever wanted mock
|
|
48
|
-
|
|
46
|
+
library, but had no way to verify behavior apart from doing live
|
|
47
|
+
integration testing? Ever wanted mock for HTTP? If you answered yes to
|
|
48
|
+
any of the above, Spoof might be for you.
|
|
49
49
|
|
|
50
50
|
Installation and Compatibility
|
|
51
51
|
==============================
|
|
52
|
-
|
|
53
52
|
Spoof is available on PyPI:
|
|
54
53
|
|
|
55
54
|
.. code-block:: console
|
|
@@ -64,74 +63,8 @@ Multiple Spoof HTTP servers can be run concurrently, and by default, the port
|
|
|
64
63
|
number is the next available unused port. With OpenSSL installed, Spoof can
|
|
65
64
|
also provide an SSL/TLS HTTP server. HTTP proxying and IPv6 are also supported.
|
|
66
65
|
|
|
67
|
-
Request instances
|
|
68
|
-
=================
|
|
69
|
-
Spoof captures each request as a ``SpoofRequestEnv`` instance with the following
|
|
70
|
-
properties:
|
|
71
|
-
|
|
72
|
-
+-------------------------+----------------------------------------------+
|
|
73
|
-
| Property | Description |
|
|
74
|
-
+=========================+==============================================+
|
|
75
|
-
| content | ``bytes`` object of request content |
|
|
76
|
-
+-------------------------+----------------------------------------------+
|
|
77
|
-
| contentEncoding | Value of Content-Encoding header, if present |
|
|
78
|
-
+-------------------------+----------------------------------------------+
|
|
79
|
-
| contentLength | Value of Content-Length header, if present |
|
|
80
|
-
+-------------------------+----------------------------------------------+
|
|
81
|
-
| contentType | Value of Content-Type header, if present |
|
|
82
|
-
+-------------------------+----------------------------------------------+
|
|
83
|
-
| headers | ``http.client.HTTPMessage`` object of headers|
|
|
84
|
-
+-------------------------+----------------------------------------------+
|
|
85
|
-
| json() | Convenience to call ``json.loads`` on content|
|
|
86
|
-
+-------------------------+----------------------------------------------+
|
|
87
|
-
| method | Request method (e.g. GET, POST, HEAD) |
|
|
88
|
-
+-------------------------+----------------------------------------------+
|
|
89
|
-
| path | Decoded URI path, without query string |
|
|
90
|
-
+-------------------------+----------------------------------------------+
|
|
91
|
-
| protocol | Protocol version (e.g. HTTP/1.0) |
|
|
92
|
-
+-------------------------+----------------------------------------------+
|
|
93
|
-
| queryString | Anything in URI after ``?`` |
|
|
94
|
-
+-------------------------+----------------------------------------------+
|
|
95
|
-
| serverName | Host name of HTTP server |
|
|
96
|
-
+-------------------------+----------------------------------------------+
|
|
97
|
-
| serverPort | Port number of HTTP server |
|
|
98
|
-
+-------------------------+----------------------------------------------+
|
|
99
|
-
| uri | Raw URI path and query string, if present |
|
|
100
|
-
+-------------------------+----------------------------------------------+
|
|
101
|
-
|
|
102
|
-
Example with request properties:
|
|
103
|
-
|
|
104
|
-
.. code-block:: python
|
|
105
|
-
|
|
106
|
-
>>> import requests
|
|
107
|
-
... import spoof
|
|
108
|
-
...
|
|
109
|
-
... with spoof.HTTPServer() as httpd:
|
|
110
|
-
... httpd.defaultResponse = [200, [], None]
|
|
111
|
-
...
|
|
112
|
-
... [requests.get(httpd.url + path) for path in ["/a", "/b", "/c"]]
|
|
113
|
-
... [f"{r.method} {r.path} {r.protocol}" for r in httpd.requests]
|
|
114
|
-
...
|
|
115
|
-
[<Response [200]>, <Response [200]>, <Response [200]>]
|
|
116
|
-
['GET /a HTTP/1.1', 'GET /b HTTP/1.1', 'GET /c HTTP/1.1']
|
|
117
|
-
|
|
118
|
-
Response precedence
|
|
119
|
-
===================
|
|
120
|
-
|
|
121
|
-
Spoof determines what response to send to incoming requests based on
|
|
122
|
-
the following precedence, highest to lowest:
|
|
123
|
-
|
|
124
|
-
#. Oldest response queued in ``.responses`` using first-in, first-out (FIFO) order
|
|
125
|
-
#. Response stored in ``.defaultResponse`` if no responses are queued
|
|
126
|
-
#. Response stored in ``.errorResponse`` if ``.defaultResponse`` is ``None``
|
|
127
|
-
|
|
128
|
-
By default, an HTTP error response will be sent to all requests, because
|
|
129
|
-
newly created Spoof instances have no responses queued, and no default
|
|
130
|
-
response set. This requires non-error responses to be explicitly specified.
|
|
131
|
-
|
|
132
66
|
Response syntax
|
|
133
67
|
===============
|
|
134
|
-
|
|
135
68
|
Spoof expects responses to have the following syntax:
|
|
136
69
|
|
|
137
70
|
.. code-block:: python
|
|
@@ -151,13 +84,31 @@ Spoof expects responses to have the following syntax:
|
|
|
151
84
|
def callback(request):
|
|
152
85
|
return [200, [], request.path]
|
|
153
86
|
|
|
154
|
-
|
|
155
|
-
|
|
87
|
+
Response precedence
|
|
88
|
+
===================
|
|
89
|
+
Spoof determines what response to send to incoming requests based on
|
|
90
|
+
the following precedence, highest to lowest:
|
|
91
|
+
|
|
92
|
+
#. Oldest response queued in ``.responses`` using first-in, first-out (FIFO) order
|
|
93
|
+
#. Response stored in ``.defaultResponse`` if no responses are queued
|
|
94
|
+
#. Response stored in ``.errorResponse`` if ``.defaultResponse`` is ``None``
|
|
95
|
+
|
|
96
|
+
By default, Spoof will respond with an **HTTP 503 Service Unavailable** error,
|
|
97
|
+
because newly created Spoof instances have no responses queued and no default
|
|
98
|
+
response set. This requires non-error HTTP responses to be explicitly specified.
|
|
156
99
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
100
|
+
Response queue
|
|
101
|
+
==============
|
|
102
|
+
Spoof will always try to send a response from ``.responses`` first, before falling
|
|
103
|
+
back to ``.defaultResponse`` if the queue is empty. Backed by a
|
|
104
|
+
`deque <https://docs.python.org/3/library/collections.html#collections.deque>`__
|
|
105
|
+
instance, the ``.responses`` queue supports adding items via ``.responses.append()``
|
|
106
|
+
and ``.responses.extend()``, similar to a regular list.
|
|
107
|
+
|
|
108
|
+
Spoof HTTP servers run in a single background thread, so response order should
|
|
109
|
+
be predictably serial. Tests using Spoof should be able to use the same fixtures,
|
|
110
|
+
in the same order, and get the same results. Example queueing multiple responses,
|
|
111
|
+
verifying content, and request paths:
|
|
161
112
|
|
|
162
113
|
.. code-block:: python
|
|
163
114
|
|
|
@@ -176,9 +127,11 @@ queueing multiple responses, verifying content, and request paths:
|
|
|
176
127
|
assert requests.get(httpd.url + "/oops").status_code == 404
|
|
177
128
|
assert [r.path for r in httpd.requests] == ["/path", "/alt/path", "/oops"]
|
|
178
129
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
130
|
+
Response default
|
|
131
|
+
================
|
|
132
|
+
Spoof will always try to send a response from ``.responses`` first, before falling
|
|
133
|
+
back to ``.defaultResponse`` if the queue is empty. Example setting a callback as
|
|
134
|
+
a default response:
|
|
182
135
|
|
|
183
136
|
.. code-block:: python
|
|
184
137
|
|
|
@@ -190,9 +143,69 @@ Set a callback as the default response (callbacks can also be queued):
|
|
|
190
143
|
|
|
191
144
|
assert requests.get(httpd.url + "/alt").text == "/alt"
|
|
192
145
|
|
|
146
|
+
Request history
|
|
147
|
+
===============
|
|
148
|
+
Spoof records each request and appends it to the ``.requests`` property,
|
|
149
|
+
which is backed by a
|
|
150
|
+
`deque <https://docs.python.org/3/library/collections.html#collections.deque>`__
|
|
151
|
+
instance, the same as the ``.responses`` property. Think of it like a pre-parsed access log. Example
|
|
152
|
+
using request history:
|
|
153
|
+
|
|
154
|
+
.. code-block:: python
|
|
155
|
+
|
|
156
|
+
>>> import requests
|
|
157
|
+
... import spoof
|
|
158
|
+
...
|
|
159
|
+
... with spoof.HTTPServer() as httpd:
|
|
160
|
+
... httpd.defaultResponse = [200, [], None]
|
|
161
|
+
...
|
|
162
|
+
... [requests.get(httpd.url + path) for path in ["/a", "/b", "/c"]]
|
|
163
|
+
... [f"{r.method} {r.path} {r.protocol}" for r in httpd.requests]
|
|
164
|
+
...
|
|
165
|
+
[<Response [200]>, <Response [200]>, <Response [200]>]
|
|
166
|
+
['GET /a HTTP/1.1', 'GET /b HTTP/1.1', 'GET /c HTTP/1.1']
|
|
167
|
+
|
|
168
|
+
Request properties
|
|
169
|
+
==================
|
|
170
|
+
``SpoofRequestEnv`` instances have the following properties:
|
|
171
|
+
|
|
172
|
+
+-------------------------+----------------------------------------------+
|
|
173
|
+
| Property | Description |
|
|
174
|
+
+=========================+==============================================+
|
|
175
|
+
| content | ``bytes`` object of request content |
|
|
176
|
+
+-------------------------+----------------------------------------------+
|
|
177
|
+
| contentEncoding | Value of Content-Encoding header, if present |
|
|
178
|
+
+-------------------------+----------------------------------------------+
|
|
179
|
+
| contentLength | Value of Content-Length header, if present |
|
|
180
|
+
+-------------------------+----------------------------------------------+
|
|
181
|
+
| contentType | Value of Content-Type header, if present |
|
|
182
|
+
+-------------------------+----------------------------------------------+
|
|
183
|
+
| headers | ``http.client.HTTPMessage`` object of headers|
|
|
184
|
+
+-------------------------+----------------------------------------------+
|
|
185
|
+
| json() | Convenience to call ``json.loads`` on content|
|
|
186
|
+
+-------------------------+----------------------------------------------+
|
|
187
|
+
| method | Request method (e.g. GET, POST, HEAD) |
|
|
188
|
+
+-------------------------+----------------------------------------------+
|
|
189
|
+
| path | Decoded URI path, without query string |
|
|
190
|
+
+-------------------------+----------------------------------------------+
|
|
191
|
+
| protocol | Protocol version (e.g. HTTP/1.0) |
|
|
192
|
+
+-------------------------+----------------------------------------------+
|
|
193
|
+
| queryString | Anything in URI after ``?`` |
|
|
194
|
+
+-------------------------+----------------------------------------------+
|
|
195
|
+
| serverName | Host name of HTTP server |
|
|
196
|
+
+-------------------------+----------------------------------------------+
|
|
197
|
+
| serverPort | Port number of HTTP server |
|
|
198
|
+
+-------------------------+----------------------------------------------+
|
|
199
|
+
| uri | Raw URI path and query string, if present |
|
|
200
|
+
+-------------------------+----------------------------------------------+
|
|
201
|
+
|
|
193
202
|
SSL/TLS Mode
|
|
194
203
|
============
|
|
195
|
-
|
|
204
|
+
Spoof supports SSL/TLS connectivity by passing an
|
|
205
|
+
`SSLContext <https://docs.python.org/3/library/ssl.html#ssl-contexts>`__,
|
|
206
|
+
or if OpenSSL command line tools are available, creating an ``SSLContext``
|
|
207
|
+
with a self-signed certificate. Configured correctly, this should not raise
|
|
208
|
+
any warnings or errors:
|
|
196
209
|
|
|
197
210
|
.. code-block:: python
|
|
198
211
|
|
|
@@ -224,8 +237,8 @@ set to the path of the self-signed certificate to silence SSL/TLS errors:
|
|
|
224
237
|
response = requests.get(httpd.url)
|
|
225
238
|
assert response.text == "No self-signed cert warning!"
|
|
226
239
|
|
|
227
|
-
If OpenSSL 3.5.0
|
|
228
|
-
key algorithms can be used:
|
|
240
|
+
If `OpenSSL 3.5.0 <https://openssl-library.org/post/2025-04-08-openssl-35-final-release/>`__
|
|
241
|
+
or later is installed, Post-Quantum Cryptography (PQC) key algorithms can be used:
|
|
229
242
|
|
|
230
243
|
.. code-block:: python
|
|
231
244
|
|
|
@@ -266,8 +279,31 @@ external services. Example usage:
|
|
|
266
279
|
assert proxy.upstream.requests[0].path == "/ayt"
|
|
267
280
|
assert response.text == "I'm here!"
|
|
268
281
|
|
|
269
|
-
|
|
270
|
-
|
|
282
|
+
If setting the ``proxies`` option in ``requests`` isn't workable, the
|
|
283
|
+
``https_proxy`` environment variable can be set to the URL of the proxy:
|
|
284
|
+
|
|
285
|
+
.. code-block:: python
|
|
286
|
+
|
|
287
|
+
import os
|
|
288
|
+
import requests
|
|
289
|
+
import spoof
|
|
290
|
+
|
|
291
|
+
with spoof.SelfSignedSSLContext(commonName="example.spoof") as selfSigned:
|
|
292
|
+
with spoof.HTTPServer(sslContext=selfSigned.sslContext, proxy=True) as proxy:
|
|
293
|
+
proxy.upstream.defaultResponse = [200, [], "I'm here!"]
|
|
294
|
+
|
|
295
|
+
os.environ["https_proxy"] = proxy.url
|
|
296
|
+
os.environ["REQUESTS_CA_BUNDLE"] = selfSigned.certFile
|
|
297
|
+
|
|
298
|
+
response = requests.get("https://example.spoof/ayt")
|
|
299
|
+
assert proxy.requests[0].method == "CONNECT"
|
|
300
|
+
assert proxy.requests[0].path == "example.spoof:443"
|
|
301
|
+
assert proxy.upstream.requests[0].method == "GET"
|
|
302
|
+
assert proxy.upstream.requests[0].path == "/ayt"
|
|
303
|
+
assert response.text == "I'm here!"
|
|
304
|
+
|
|
305
|
+
IPv6 Mode
|
|
306
|
+
=========
|
|
271
307
|
Setting the ``host`` attribute to an IPv6 address will work as expected. There
|
|
272
308
|
is also an IPv6-only ``spoof.HTTPServer6`` class that can be used if needed to
|
|
273
309
|
only listen on IPv6 sockets.
|
|
@@ -298,8 +334,8 @@ only listen on IPv6 sockets.
|
|
|
298
334
|
'This is also Spoof on IPv6 👀'
|
|
299
335
|
'http://[::1]:54296'
|
|
300
336
|
|
|
301
|
-
|
|
302
|
-
|
|
337
|
+
Debug mode
|
|
338
|
+
==========
|
|
303
339
|
Setting a callback with a ``breakpoint()`` can allow for live HTTP request
|
|
304
340
|
debugging, including setting custom responses and inspecting requests. Note
|
|
305
341
|
that callbacks can also be queued.
|
|
@@ -324,4 +360,3 @@ that callbacks can also be queued.
|
|
|
324
360
|
(Pdb) response[2] = "content set from pdb"
|
|
325
361
|
(Pdb) c
|
|
326
362
|
'content set from pdb'
|
|
327
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spoof
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: HTTP server for testing environments
|
|
5
5
|
Author-email: Lex Scarisbrick <lex@scarisbrick.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -57,12 +57,12 @@ Spoof 👻
|
|
|
57
57
|
A test interface for HTTP
|
|
58
58
|
=========================
|
|
59
59
|
Spoof lets you easily create HTTP servers listening on real network
|
|
60
|
-
sockets. Designed for test environments, what responses to
|
|
61
|
-
configured while an HTTP server is running. Requests
|
|
62
|
-
live or after a response is sent.
|
|
60
|
+
sockets. Designed for test environments, what responses to send can be
|
|
61
|
+
configured anytime, including while an HTTP server is running. Requests
|
|
62
|
+
can be inspected live or after a response is sent.
|
|
63
63
|
|
|
64
|
-
Unlike a
|
|
65
|
-
configured in advance, Spoof accepts and
|
|
64
|
+
Unlike a conventional HTTP server, where specific methods and paths are
|
|
65
|
+
configured in advance, Spoof accepts and records *all* requests, sending
|
|
66
66
|
whatever responses are queued, or a default response if the queue is empty.
|
|
67
67
|
|
|
68
68
|
Why would I want this?
|
|
@@ -70,13 +70,12 @@ Why would I want this?
|
|
|
70
70
|
Spoof is all about enabling test-driven development (and refactoring) of
|
|
71
71
|
HTTP client code. Have you ever felt icky patching a client library to
|
|
72
72
|
write tests? Ever been burned by this? Ever wanted to refactor a client
|
|
73
|
-
library, but had no way to
|
|
74
|
-
integration testing? Ever wanted mock
|
|
75
|
-
|
|
73
|
+
library, but had no way to verify behavior apart from doing live
|
|
74
|
+
integration testing? Ever wanted mock for HTTP? If you answered yes to
|
|
75
|
+
any of the above, Spoof might be for you.
|
|
76
76
|
|
|
77
77
|
Installation and Compatibility
|
|
78
78
|
==============================
|
|
79
|
-
|
|
80
79
|
Spoof is available on PyPI:
|
|
81
80
|
|
|
82
81
|
.. code-block:: console
|
|
@@ -91,74 +90,8 @@ Multiple Spoof HTTP servers can be run concurrently, and by default, the port
|
|
|
91
90
|
number is the next available unused port. With OpenSSL installed, Spoof can
|
|
92
91
|
also provide an SSL/TLS HTTP server. HTTP proxying and IPv6 are also supported.
|
|
93
92
|
|
|
94
|
-
Request instances
|
|
95
|
-
=================
|
|
96
|
-
Spoof captures each request as a ``SpoofRequestEnv`` instance with the following
|
|
97
|
-
properties:
|
|
98
|
-
|
|
99
|
-
+-------------------------+----------------------------------------------+
|
|
100
|
-
| Property | Description |
|
|
101
|
-
+=========================+==============================================+
|
|
102
|
-
| content | ``bytes`` object of request content |
|
|
103
|
-
+-------------------------+----------------------------------------------+
|
|
104
|
-
| contentEncoding | Value of Content-Encoding header, if present |
|
|
105
|
-
+-------------------------+----------------------------------------------+
|
|
106
|
-
| contentLength | Value of Content-Length header, if present |
|
|
107
|
-
+-------------------------+----------------------------------------------+
|
|
108
|
-
| contentType | Value of Content-Type header, if present |
|
|
109
|
-
+-------------------------+----------------------------------------------+
|
|
110
|
-
| headers | ``http.client.HTTPMessage`` object of headers|
|
|
111
|
-
+-------------------------+----------------------------------------------+
|
|
112
|
-
| json() | Convenience to call ``json.loads`` on content|
|
|
113
|
-
+-------------------------+----------------------------------------------+
|
|
114
|
-
| method | Request method (e.g. GET, POST, HEAD) |
|
|
115
|
-
+-------------------------+----------------------------------------------+
|
|
116
|
-
| path | Decoded URI path, without query string |
|
|
117
|
-
+-------------------------+----------------------------------------------+
|
|
118
|
-
| protocol | Protocol version (e.g. HTTP/1.0) |
|
|
119
|
-
+-------------------------+----------------------------------------------+
|
|
120
|
-
| queryString | Anything in URI after ``?`` |
|
|
121
|
-
+-------------------------+----------------------------------------------+
|
|
122
|
-
| serverName | Host name of HTTP server |
|
|
123
|
-
+-------------------------+----------------------------------------------+
|
|
124
|
-
| serverPort | Port number of HTTP server |
|
|
125
|
-
+-------------------------+----------------------------------------------+
|
|
126
|
-
| uri | Raw URI path and query string, if present |
|
|
127
|
-
+-------------------------+----------------------------------------------+
|
|
128
|
-
|
|
129
|
-
Example with request properties:
|
|
130
|
-
|
|
131
|
-
.. code-block:: python
|
|
132
|
-
|
|
133
|
-
>>> import requests
|
|
134
|
-
... import spoof
|
|
135
|
-
...
|
|
136
|
-
... with spoof.HTTPServer() as httpd:
|
|
137
|
-
... httpd.defaultResponse = [200, [], None]
|
|
138
|
-
...
|
|
139
|
-
... [requests.get(httpd.url + path) for path in ["/a", "/b", "/c"]]
|
|
140
|
-
... [f"{r.method} {r.path} {r.protocol}" for r in httpd.requests]
|
|
141
|
-
...
|
|
142
|
-
[<Response [200]>, <Response [200]>, <Response [200]>]
|
|
143
|
-
['GET /a HTTP/1.1', 'GET /b HTTP/1.1', 'GET /c HTTP/1.1']
|
|
144
|
-
|
|
145
|
-
Response precedence
|
|
146
|
-
===================
|
|
147
|
-
|
|
148
|
-
Spoof determines what response to send to incoming requests based on
|
|
149
|
-
the following precedence, highest to lowest:
|
|
150
|
-
|
|
151
|
-
#. Oldest response queued in ``.responses`` using first-in, first-out (FIFO) order
|
|
152
|
-
#. Response stored in ``.defaultResponse`` if no responses are queued
|
|
153
|
-
#. Response stored in ``.errorResponse`` if ``.defaultResponse`` is ``None``
|
|
154
|
-
|
|
155
|
-
By default, an HTTP error response will be sent to all requests, because
|
|
156
|
-
newly created Spoof instances have no responses queued, and no default
|
|
157
|
-
response set. This requires non-error responses to be explicitly specified.
|
|
158
|
-
|
|
159
93
|
Response syntax
|
|
160
94
|
===============
|
|
161
|
-
|
|
162
95
|
Spoof expects responses to have the following syntax:
|
|
163
96
|
|
|
164
97
|
.. code-block:: python
|
|
@@ -178,13 +111,31 @@ Spoof expects responses to have the following syntax:
|
|
|
178
111
|
def callback(request):
|
|
179
112
|
return [200, [], request.path]
|
|
180
113
|
|
|
181
|
-
|
|
182
|
-
|
|
114
|
+
Response precedence
|
|
115
|
+
===================
|
|
116
|
+
Spoof determines what response to send to incoming requests based on
|
|
117
|
+
the following precedence, highest to lowest:
|
|
118
|
+
|
|
119
|
+
#. Oldest response queued in ``.responses`` using first-in, first-out (FIFO) order
|
|
120
|
+
#. Response stored in ``.defaultResponse`` if no responses are queued
|
|
121
|
+
#. Response stored in ``.errorResponse`` if ``.defaultResponse`` is ``None``
|
|
122
|
+
|
|
123
|
+
By default, Spoof will respond with an **HTTP 503 Service Unavailable** error,
|
|
124
|
+
because newly created Spoof instances have no responses queued and no default
|
|
125
|
+
response set. This requires non-error HTTP responses to be explicitly specified.
|
|
183
126
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
127
|
+
Response queue
|
|
128
|
+
==============
|
|
129
|
+
Spoof will always try to send a response from ``.responses`` first, before falling
|
|
130
|
+
back to ``.defaultResponse`` if the queue is empty. Backed by a
|
|
131
|
+
`deque <https://docs.python.org/3/library/collections.html#collections.deque>`__
|
|
132
|
+
instance, the ``.responses`` queue supports adding items via ``.responses.append()``
|
|
133
|
+
and ``.responses.extend()``, similar to a regular list.
|
|
134
|
+
|
|
135
|
+
Spoof HTTP servers run in a single background thread, so response order should
|
|
136
|
+
be predictably serial. Tests using Spoof should be able to use the same fixtures,
|
|
137
|
+
in the same order, and get the same results. Example queueing multiple responses,
|
|
138
|
+
verifying content, and request paths:
|
|
188
139
|
|
|
189
140
|
.. code-block:: python
|
|
190
141
|
|
|
@@ -203,9 +154,11 @@ queueing multiple responses, verifying content, and request paths:
|
|
|
203
154
|
assert requests.get(httpd.url + "/oops").status_code == 404
|
|
204
155
|
assert [r.path for r in httpd.requests] == ["/path", "/alt/path", "/oops"]
|
|
205
156
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
157
|
+
Response default
|
|
158
|
+
================
|
|
159
|
+
Spoof will always try to send a response from ``.responses`` first, before falling
|
|
160
|
+
back to ``.defaultResponse`` if the queue is empty. Example setting a callback as
|
|
161
|
+
a default response:
|
|
209
162
|
|
|
210
163
|
.. code-block:: python
|
|
211
164
|
|
|
@@ -217,9 +170,69 @@ Set a callback as the default response (callbacks can also be queued):
|
|
|
217
170
|
|
|
218
171
|
assert requests.get(httpd.url + "/alt").text == "/alt"
|
|
219
172
|
|
|
173
|
+
Request history
|
|
174
|
+
===============
|
|
175
|
+
Spoof records each request and appends it to the ``.requests`` property,
|
|
176
|
+
which is backed by a
|
|
177
|
+
`deque <https://docs.python.org/3/library/collections.html#collections.deque>`__
|
|
178
|
+
instance, the same as the ``.responses`` property. Think of it like a pre-parsed access log. Example
|
|
179
|
+
using request history:
|
|
180
|
+
|
|
181
|
+
.. code-block:: python
|
|
182
|
+
|
|
183
|
+
>>> import requests
|
|
184
|
+
... import spoof
|
|
185
|
+
...
|
|
186
|
+
... with spoof.HTTPServer() as httpd:
|
|
187
|
+
... httpd.defaultResponse = [200, [], None]
|
|
188
|
+
...
|
|
189
|
+
... [requests.get(httpd.url + path) for path in ["/a", "/b", "/c"]]
|
|
190
|
+
... [f"{r.method} {r.path} {r.protocol}" for r in httpd.requests]
|
|
191
|
+
...
|
|
192
|
+
[<Response [200]>, <Response [200]>, <Response [200]>]
|
|
193
|
+
['GET /a HTTP/1.1', 'GET /b HTTP/1.1', 'GET /c HTTP/1.1']
|
|
194
|
+
|
|
195
|
+
Request properties
|
|
196
|
+
==================
|
|
197
|
+
``SpoofRequestEnv`` instances have the following properties:
|
|
198
|
+
|
|
199
|
+
+-------------------------+----------------------------------------------+
|
|
200
|
+
| Property | Description |
|
|
201
|
+
+=========================+==============================================+
|
|
202
|
+
| content | ``bytes`` object of request content |
|
|
203
|
+
+-------------------------+----------------------------------------------+
|
|
204
|
+
| contentEncoding | Value of Content-Encoding header, if present |
|
|
205
|
+
+-------------------------+----------------------------------------------+
|
|
206
|
+
| contentLength | Value of Content-Length header, if present |
|
|
207
|
+
+-------------------------+----------------------------------------------+
|
|
208
|
+
| contentType | Value of Content-Type header, if present |
|
|
209
|
+
+-------------------------+----------------------------------------------+
|
|
210
|
+
| headers | ``http.client.HTTPMessage`` object of headers|
|
|
211
|
+
+-------------------------+----------------------------------------------+
|
|
212
|
+
| json() | Convenience to call ``json.loads`` on content|
|
|
213
|
+
+-------------------------+----------------------------------------------+
|
|
214
|
+
| method | Request method (e.g. GET, POST, HEAD) |
|
|
215
|
+
+-------------------------+----------------------------------------------+
|
|
216
|
+
| path | Decoded URI path, without query string |
|
|
217
|
+
+-------------------------+----------------------------------------------+
|
|
218
|
+
| protocol | Protocol version (e.g. HTTP/1.0) |
|
|
219
|
+
+-------------------------+----------------------------------------------+
|
|
220
|
+
| queryString | Anything in URI after ``?`` |
|
|
221
|
+
+-------------------------+----------------------------------------------+
|
|
222
|
+
| serverName | Host name of HTTP server |
|
|
223
|
+
+-------------------------+----------------------------------------------+
|
|
224
|
+
| serverPort | Port number of HTTP server |
|
|
225
|
+
+-------------------------+----------------------------------------------+
|
|
226
|
+
| uri | Raw URI path and query string, if present |
|
|
227
|
+
+-------------------------+----------------------------------------------+
|
|
228
|
+
|
|
220
229
|
SSL/TLS Mode
|
|
221
230
|
============
|
|
222
|
-
|
|
231
|
+
Spoof supports SSL/TLS connectivity by passing an
|
|
232
|
+
`SSLContext <https://docs.python.org/3/library/ssl.html#ssl-contexts>`__,
|
|
233
|
+
or if OpenSSL command line tools are available, creating an ``SSLContext``
|
|
234
|
+
with a self-signed certificate. Configured correctly, this should not raise
|
|
235
|
+
any warnings or errors:
|
|
223
236
|
|
|
224
237
|
.. code-block:: python
|
|
225
238
|
|
|
@@ -251,8 +264,8 @@ set to the path of the self-signed certificate to silence SSL/TLS errors:
|
|
|
251
264
|
response = requests.get(httpd.url)
|
|
252
265
|
assert response.text == "No self-signed cert warning!"
|
|
253
266
|
|
|
254
|
-
If OpenSSL 3.5.0
|
|
255
|
-
key algorithms can be used:
|
|
267
|
+
If `OpenSSL 3.5.0 <https://openssl-library.org/post/2025-04-08-openssl-35-final-release/>`__
|
|
268
|
+
or later is installed, Post-Quantum Cryptography (PQC) key algorithms can be used:
|
|
256
269
|
|
|
257
270
|
.. code-block:: python
|
|
258
271
|
|
|
@@ -293,8 +306,31 @@ external services. Example usage:
|
|
|
293
306
|
assert proxy.upstream.requests[0].path == "/ayt"
|
|
294
307
|
assert response.text == "I'm here!"
|
|
295
308
|
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
If setting the ``proxies`` option in ``requests`` isn't workable, the
|
|
310
|
+
``https_proxy`` environment variable can be set to the URL of the proxy:
|
|
311
|
+
|
|
312
|
+
.. code-block:: python
|
|
313
|
+
|
|
314
|
+
import os
|
|
315
|
+
import requests
|
|
316
|
+
import spoof
|
|
317
|
+
|
|
318
|
+
with spoof.SelfSignedSSLContext(commonName="example.spoof") as selfSigned:
|
|
319
|
+
with spoof.HTTPServer(sslContext=selfSigned.sslContext, proxy=True) as proxy:
|
|
320
|
+
proxy.upstream.defaultResponse = [200, [], "I'm here!"]
|
|
321
|
+
|
|
322
|
+
os.environ["https_proxy"] = proxy.url
|
|
323
|
+
os.environ["REQUESTS_CA_BUNDLE"] = selfSigned.certFile
|
|
324
|
+
|
|
325
|
+
response = requests.get("https://example.spoof/ayt")
|
|
326
|
+
assert proxy.requests[0].method == "CONNECT"
|
|
327
|
+
assert proxy.requests[0].path == "example.spoof:443"
|
|
328
|
+
assert proxy.upstream.requests[0].method == "GET"
|
|
329
|
+
assert proxy.upstream.requests[0].path == "/ayt"
|
|
330
|
+
assert response.text == "I'm here!"
|
|
331
|
+
|
|
332
|
+
IPv6 Mode
|
|
333
|
+
=========
|
|
298
334
|
Setting the ``host`` attribute to an IPv6 address will work as expected. There
|
|
299
335
|
is also an IPv6-only ``spoof.HTTPServer6`` class that can be used if needed to
|
|
300
336
|
only listen on IPv6 sockets.
|
|
@@ -325,8 +361,8 @@ only listen on IPv6 sockets.
|
|
|
325
361
|
'This is also Spoof on IPv6 👀'
|
|
326
362
|
'http://[::1]:54296'
|
|
327
363
|
|
|
328
|
-
|
|
329
|
-
|
|
364
|
+
Debug mode
|
|
365
|
+
==========
|
|
330
366
|
Setting a callback with a ``breakpoint()`` can allow for live HTTP request
|
|
331
367
|
debugging, including setting custom responses and inspecting requests. Note
|
|
332
368
|
that callbacks can also be queued.
|
|
@@ -351,4 +387,3 @@ that callbacks can also be queued.
|
|
|
351
387
|
(Pdb) response[2] = "content set from pdb"
|
|
352
388
|
(Pdb) c
|
|
353
389
|
'content set from pdb'
|
|
354
|
-
|
|
@@ -303,7 +303,7 @@ class HTTPServer(object):
|
|
|
303
303
|
return handlerClass
|
|
304
304
|
|
|
305
305
|
def start(self):
|
|
306
|
-
"""Starts HTTP server thread."""
|
|
306
|
+
"""Starts HTTP server thread(s)."""
|
|
307
307
|
if self.server is not None:
|
|
308
308
|
message = "server at {0} already started".format(self.url)
|
|
309
309
|
raise RuntimeError(message)
|
|
@@ -505,10 +505,10 @@ class SSLContext(object):
|
|
|
505
505
|
"""Creates and returns file paths to self-signed certificate and key
|
|
506
506
|
via OpenSSL command line tool.
|
|
507
507
|
|
|
508
|
-
:commonName:
|
|
509
|
-
:bits:
|
|
510
|
-
:days:
|
|
511
|
-
:openssl:
|
|
508
|
+
:commonName: string of hostname for X509 certificate
|
|
509
|
+
:bits: RSA public key length in bits
|
|
510
|
+
:days: length in days certificate is valid
|
|
511
|
+
:openssl: name/path string of openssl command
|
|
512
512
|
:keyAlgorithm: key algorithm to use (e.g. mldsa65); ignores ``bits`` arg
|
|
513
513
|
"""
|
|
514
514
|
if keyAlgorithm is None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|