The Main Package poorconn
¶
The main package provides generically useful functions that can be used in any Python code.
Basic Usage¶
The main package poorconn
includes a list of simulation functions, each of which modifies a
socket
object so that it behaves poorly as if it were under some kind of poor network conditions. To
use one of these simulation functions, always ensure that the socket
object is patchable (so that it
can be modified, which will be explained in How Does It Work?) by calling make_socket_patchable()
first,
then call the simulation function that you would like to use.
For example, consider delay_before_sending()
, a simulation function that delays a socket
object
every time when it tries to send a message. The following code snippet achieves this effect on the
socket
object s
:
1 2 3 4 5 6 | from socket import socket
from poorconn import delay_before_sending, make_socket_patchable
s = socket()
s = make_socket_patchable(s)
delay_before_sending(s, 2, 1024)
|
The code snippet above turns s
to delay 2 seconds for sending every 1024 bytes of messages. Line 5 calls
make_socket_patchable()
so that s
becomes patchable. Line 6 calls delay_before_sending()
so that s
’s
sending methods are patched so that extra code that delays the sending is in place. As the example in Quickstart
shows, simulation functions can also be applied to socket objects that are used in HTTP server objects.
How Does It Work?¶
The main package simulates poor network conditions by monkey patching (“patching” for short) methods in
socket
. It wraps the original socket
methods with code that simulates the intended
effects. For example, delay_before_sending()
replaces send()
and
sendall()
with a different implementation. This implementation chops the message into certain
number of bytes (1024 in the example above), delays a certain amount of time (2 seconds in the example above), and then
calls the original send()
/sendall()
function.
However, not every socket
object can be patched by default. For example, if you use CPython,
socket.socket.send()
is not patchable:
>>> from socket import socket
>>> s = socket()
>>> s.send = lambda: None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'socket' object attribute 'send' is read-only
This is why we provide make_socket_patchable()
. This function first detects whether pertinent methods (such as
send()
) of an object are patchable. If yes, it does nothing and returns the object itself. If not,
it would detach the underlying C socket object and attach it to a newly created PatchableSocket
object. A
PatchableSocket
object is almost the same as a socket.socket
object, except that all pertinent methods
are made patchable. Therefore, it is recommended to always call make_socket_patchable()
before calling any
simulation functions.
Advanced Usage¶
Stacking Simulation Functions¶
Thanks to the mechanism in How Does It Work?, it is possible to stack simulation functions with other functions
that modify socket
objects. For example, an SSL wrapper (ssl.SSLContext.wrap_socket()
) is usually
used to create an HTTPS server. poorconn.close_upon_acceptance()
makes a listening socket object close the
connection immediately after accepting this connection. Stacking wrap_socket()
and
close_upon_acceptance()
combines these two effects—It can be used to create an HTTPS server that always accepts
incoming connections but immediately closes the connections afterwards:
1 2 3 4 5 6 7 8 9 10 11 12 13 | from http.server import HTTPServer, SimpleHTTPRequestHandler
from poorconn import close_upon_acceptance, make_socket_patchable
from ssl import PROTOCOL_TLS_SERVER, SSLContext
with HTTPServer(("localhost", 8888), SimpleHTTPRequestHandler) as httpd:
context = SSLContext(PROTOCOL_TLS_SERVER)
# Replace with your cert and key files
context.load_cert_chain(certfile="/path/to/server.pem", keyfile="/path/to/server.key")
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
httpd.socket = make_socket_patchable(httpd.socket)
close_upon_acceptance(httpd.socket)
print(f"Start serving at https://{httpd.server_address[0]}:{httpd.server_address[1]}")
httpd.serve_forever()
|
(Download https.py
)
After running this script, connections from a client would establish but fail to communicate subsequently:
$ wget --no-check-certificate -t 1 https://localhost:8888
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:8888... failed: Connection refused.
Connecting to localhost (localhost)|127.0.0.1|:8888... connected.
WARNING: The certificate of ‘localhost’ is not trusted.
WARNING: The certificate of ‘localhost’ doesn't have a known issuer.
HTTP request sent, awaiting response... Read error (Success.) in headers.
Giving up.