Deployment
**********


Application server
==================

The author of "websockets" isn't aware of best practices for deploying
network services based on "asyncio", let alone application servers.

You can run a script similar to the server example, inside a
supervisor if you deem that useful.

You can also add a wrapper to daemonize the process. Third-party
libraries provide solutions for that.

If you can share knowledge on this topic, please file an issue.
Thanks!


Graceful shutdown
=================

You may want to close connections gracefully when shutting down the
server, perhaps after executing some cleanup logic. There are two ways
to achieve this with the object returned by "serve()":

* using it as a asynchronous context manager, or

* calling its "close()" method, then waiting for its "wait_closed()"
  method to complete.

On Unix systems, shutdown is usually triggered by sending a signal.

Here's a full example for handling SIGTERM on Unix:

   #!/usr/bin/env python

   import asyncio
   import signal
   import websockets

   async def echo(websocket, path):
       async for message in websocket:
           await websocket.send(message)

   async def echo_server(stop):
       async with websockets.serve(echo, "localhost", 8765):
           await stop

   loop = asyncio.get_event_loop()

   # The stop condition is set when receiving SIGTERM.
   stop = loop.create_future()
   loop.add_signal_handler(signal.SIGTERM, stop.set_result, None)

   # Run the server until the stop condition is met.
   loop.run_until_complete(echo_server(stop))

This example is easily adapted to handle other signals. If you
override the default handler for SIGINT, which raises
"KeyboardInterrupt", be aware that you won't be able to interrupt a
program with Ctrl-C anymore when it's stuck in a loop.

It's more difficult to achieve the same effect on Windows. Some third-
party projects try to help with this problem.

If your server doesn't run in the main thread, look at
"call_soon_threadsafe()".


Memory usage
============

In most cases, memory usage of a WebSocket server is proportional to
the number of open connections. When a server handles thousands of
connections, memory usage can become a bottleneck.

Memory usage of a single connection is the sum of:

1. the baseline amount of memory "websockets" requires for each
   connection,

2. the amount of data held in buffers before the application processes
   it,

3. any additional memory allocated by the application itself.


Baseline
--------

Compression settings are the main factor affecting the baseline amount
of memory used by each connection.

By default "websockets" maximizes compression rate at the expense of
memory usage. If memory usage is an issue, lowering compression
settings can help:

* Context Takeover is necessary to get good performance for almost all
  applications. It should remain enabled.

* Window Bits is a trade-off between memory usage and compression
  rate. It defaults to 15 and can be lowered. The default value isn't
  optimal for small, repetitive messages which are typical of
  WebSocket servers.

* Memory Level is a trade-off between memory usage and compression
  speed. It defaults to 8 and can be lowered. A lower memory level can
  actually increase speed thanks to memory locality, even if the CPU
  does more work!

See this example for how to configure compression settings.

Here's how various compression settings affect memory usage of a
single connection on a 64-bit system, as well a benchmark of
compressed size and compression time for a corpus of small JSON
documents.

+---------------+---------------+----------------+----------------+--------------------+--------------------+
| Compression   | Window Bits   | Memory Level   | Memory usage   | Size vs. default   | Time vs. default   |
|===============|===============|================|================|====================|====================|
| *default*     | 15            | 8              | 325 KiB        | +0%                | +0%                |
|               |               |                |                |                    |                    |
|               |               |                |                |                    |                    |
+---------------+---------------+----------------+----------------+--------------------+--------------------+
|               | 14            | 7              | 181 KiB        | +1.5%              | -5.3%              |
+---------------+---------------+----------------+----------------+--------------------+--------------------+
|               | 13            | 6              | 110 KiB        | +2.8%              | -7.5%              |
+---------------+---------------+----------------+----------------+--------------------+--------------------+
|               | 12            | 5              | 73 KiB         | +4.4%              | -18.9%             |
+---------------+---------------+----------------+----------------+--------------------+--------------------+
|               | 11            | 4              | 55 KiB         | +8.5%              | -18.8%             |
+---------------+---------------+----------------+----------------+--------------------+--------------------+
| *disabled*    | N/A           | N/A            | 22 KiB         | N/A                | N/A                |
+---------------+---------------+----------------+----------------+--------------------+--------------------+

*Don't assume this example is representative! Compressed size and
compression time depend heavily on the kind of messages exchanged by
the application!*

You can run the same benchmark for your application by creating a list
of typical messages and passing it to the "_benchmark" function.

This blog post by Ilya Grigorik provides more details about how
compression settings affect memory usage and how to optimize them.

This experiment by Peter Thorson suggests Window Bits = 11, Memory
Level = 4 as a sweet spot for optimizing memory usage.


Buffers
-------

Under normal circumstances, buffers are almost always empty.

Under high load, if a server receives more messages than it can
process, bufferbloat can result in excessive memory use.

By default "websockets" has generous limits. It is strongly
recommended to adapt them to your application. When you call
"serve()":

* Set "max_size" (default: 1 MiB, UTF-8 encoded) to the maximum size
  of messages your application generates.

* Set "max_queue" (default: 32) to the maximum number of messages your
  application expects to receive faster than it can process them. The
  queue provides burst tolerance without slowing down the TCP
  connection.

Furthermore, you can lower "read_limit" and "write_limit" (default: 64
KiB) to reduce the size of buffers for incoming and outgoing data.

The design document provides more details about buffers.


Port sharing
============

The WebSocket protocol is an extension of HTTP/1.1. It can be tempting
to serve both HTTP and WebSocket on the same port.

The author of "websockets" doesn't think that's a good idea, due to
the widely different operational characteristics of HTTP and
WebSocket.

"websockets" provide minimal support for responding to HTTP requests
with the "process_request()" hook. Typical use cases include health
checks. Here's an example:

   #!/usr/bin/env python

   # WS echo server with HTTP endpoint at /health/

   import asyncio
   import http
   import websockets

   async def health_check(path, request_headers):
       if path == "/health/":
           return http.HTTPStatus.OK, [], b"OK\n"

   async def echo(websocket, path):
       async for message in websocket:
           await websocket.send(message)

   start_server = websockets.serve(
       echo, "localhost", 8765, process_request=health_check
   )

   asyncio.get_event_loop().run_until_complete(start_server)
   asyncio.get_event_loop().run_forever()
