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:

https.py
 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.