1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# Condure
Condure is a service that manages network connections on behalf of server applications, in order to allow controlling the connections from multiple processes. Applications communicate with Condure over [ZeroMQ](https://zeromq.org/).
Condure can only manage connections for protocols it knows about. Currently this is HTTP/1 and WebSockets. See [Supported protocols](#supported-protocols).
The project was inspired by [Mongrel2](https://mongrel2.org/).
## Use cases
* Pass connection ownership from one process to another.
* Restart an application without its connections getting disconnected.
* Balance connection ownership among multiple processes.
## Basic usage
Start the server:
```
$ condure --listen 8000 --zclient-stream ipc://client
```
Connect a handler to it, such as this simple Python program:
```py
# this handler responds to every request with "hello world"
import os
import time
import tnetstring
import zmq
instance_id = 'basichandler.{}'.format(os.getpid()).encode()
ctx = zmq.Context()
in_sock = ctx.socket(zmq.PULL)
in_sock.connect('ipc://client-out')
out_sock = ctx.socket(zmq.PUB)
out_sock.connect('ipc://client-in')
# await subscription
time.sleep(0.01)
while True:
m_raw = in_sock.recv()
req = tnetstring.loads(m_raw[1:])
print('IN {}'.format(req))
resp = {}
resp[b'from'] = instance_id
resp[b'id'] = req[b'id']
resp[b'code'] = 200
resp[b'reason'] = b'OK'
resp[b'headers'] = [[b'Content-Type', b'text/plain']]
resp[b'body'] = b'hello world\n'
print('OUT {}'.format(resp))
out_sock.send(req[b'from'] + b' T' + tnetstring.dumps(resp))
```
A client request:
```
$ curl -i http://localhost:8000
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12
hello world
```
The process that receives the request doesn't need to be the same one that responds! For example, here's a program that outputs request IDs to stdout:
```py
# this handler just outputs the request ID
import tnetstring
import zmq
ctx = zmq.Context()
sock = ctx.socket(zmq.PULL)
sock.connect('ipc://client-out')
while True:
m = sock.recv_multipart()
req = tnetstring.loads(m[0][1:])
print('{} {}'.format(req[b'from'].decode(), req[b'id'].decode()))
```
We can see request ID information when a client request is made:
```
$ python examples/printreq.py
condure 0-0-0
```
From another shell we can respond using a program like this:
```py
# this program sends a response to a certain request ID
import sys
import time
import tnetstring
import zmq
body = sys.argv[1]
addr = sys.argv[2].encode()
rid = sys.argv[3].encode()
ctx = zmq.Context()
sock = ctx.socket(zmq.PUB)
sock.connect('ipc://client-in')
# await subscription
time.sleep(0.01)
resp = {}
resp[b'from'] = b'sendresp'
resp[b'id'] = rid
resp[b'code'] = 200
resp[b'reason'] = b'OK'
resp[b'headers'] = [[b'Content-Type', b'text/plain']]
resp[b'body'] = '{}\n'.format(body).encode()
m = [addr + b' T' + tnetstring.dumps(resp)]
sock.send_multipart(m)
```
For example:
```
$ python examples/sendresp.py "responding from another process" condure 0-0-0
```
The client sees:
```
$ curl -i http://localhost:8000
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 32
responding from another process
```
For easy testing, the programs can be piped together:
```
$ python -u examples/printreq.py | xargs -n 2 python examples/sendresp.py "responding from another process"
```
## Suspending and resuming connections
When passing control of a connection from one process to another, it is important to suspend the connection first. This is done by sending a `handoff-start` message and waiting for a `handoff-proceed` message. At that point, the connection information can be given to another process, and the connection can be resumed by sending any message (such as `keep-alive`). See the [ZHTTP spec](https://rfc.zeromq.org/spec/33/).
## REQ mode
In addition to the stream mode which uses PUSH/ROUTER/SUB sockets, there is a "REQ" mode available which uses a DEALER socket. To enable it, set `req` as the mode on a listen port. This mode can be handy for implementing simple request/response servers using ZeroMQ.
## Supported protocols
Condure supports HTTP/1 and WebSockets.
Condure manages connections at layer 7 and only supports protocols it knows about. This is to simplify its usage. Handling arbitrary protocols would require applications to build protocol stacks capable of suspending/resuming sessions at arbitrary byte positions in TCP streams, making Condure usage prohibitive. Instead, Condure is protocol-aware, and provides parsed frames to applications, so that applications are only required to support suspending/resuming sessions at frame boundaries.
## Performance
Condure was built for high performance. It uses numerous optimization techniques, including minimal heap allocations, ring buffers, vectored I/O, hierarchical timing wheels, and fast data structures (e.g. slabs). Over 1M concurrent connections have been tested on a single instance using just 2 workers (4 threads total). More detailed benchmarks to come.
## Comparison to Mongrel2
* Condure supports multiple cores.
* Condure supports listening on multiple ports without requiring multiple processes.
* Condure does not support multiple routes and is not intended to be a shared server. Each application that wants to keep connections in a separate process should spawn its own Condure instance.
* Condure has no config file. Configuration is supplied using command line arguments.
* Condure uses a different ZeroMQ-based protocol, [ZHTTP](https://rfc.zeromq.org/spec/33/), which is easier to use than Mongrel2's protocol and more reliable.
## Future plans
* TLS
* HTTP/2
* HTTP/3
|