Cheat sheet
***********


Server
======

* Write a coroutine that handles a single connection. It receives a
  WebSocket protocol instance and the URI path in argument.

  * Call "recv()" and "send()" to receive and send messages at any
    time.

  * When "recv()" or "send()" raises "ConnectionClosed", clean up
    and exit. If you started other "asyncio.Task", terminate them
    before exiting.

  * If you aren't awaiting "recv()", consider awaiting
    "wait_closed()" to detect quickly when the connection is closed.

  * You may "ping()" or "pong()" if you wish but it isn't needed in
    general.

* Create a server with "serve()" which is similar to asyncio's
  "create_server()". You can also use it as an asynchronous context
  manager.

  * The server takes care of establishing connections, then lets the
    handler execute the application logic, and finally closes the
    connection after the handler exits normally or with an exception.

  * For advanced customization, you may subclass
    "WebSocketServerProtocol" and pass either this subclass or a
    factory function as the "create_protocol" argument.


Client
======

* Create a client with "connect()" which is similar to asyncio's
  "create_connection()". You can also use it as an asynchronous
  context manager.

  * For advanced customization, you may subclass
    "WebSocketClientProtocol" and pass either this subclass or a
    factory function as the "create_protocol" argument.

* Call "recv()" and "send()" to receive and send messages at any
  time.

* You may "ping()" or "pong()" if you wish but it isn't needed in
  general.

* If you aren't using "connect()" as a context manager, call
  "close()" to terminate the connection.


Debugging
=========

If you don't understand what "websockets" is doing, enable logging:

   import logging
   logger = logging.getLogger('websockets')
   logger.setLevel(logging.INFO)
   logger.addHandler(logging.StreamHandler())

The logs contain:

* Exceptions in the connection handler at the "ERROR" level

* Exceptions in the opening or closing handshake at the "INFO" level

* All frames at the "DEBUG" level — this can be very verbose

If you're new to "asyncio", you will certainly encounter issues that
are related to asynchronous programming in general rather than to
"websockets" in particular. Fortunately Python's official
documentation provides advice to develop with asyncio. Check it out:
it's invaluable!


Passing additional arguments to the connection handler
======================================================

When writing a server, if you need to pass additional arguments to the
connection handler, you can bind them with "functools.partial()":

   import asyncio
   import functools
   import websockets

   async def handler(websocket, path, extra_argument):
       ...

   bound_handler = functools.partial(handler, extra_argument='spam')
   start_server = websockets.serve(bound_handler, '127.0.0.1', 8765)

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

Another way to achieve this result is to define the "handler"
coroutine in a scope where the "extra_argument" variable exists
instead of injecting it through an argument.
