Skip to content

Python Server

Mike J. Renaker / "MikeDEV edited this page Sep 29, 2023 · 3 revisions

Work in progress! This documentation is exclusively for the Python 0.2.0 server.

Server basics

Below is an example of setting up a barebones server. This implements the CL4 protocol and Scratch cloud variables protocol. When connecting to the server, it will only allow you to connect on the same machine on ws://127.0.0.1:3000.

You can make the server CL4 exclusive by commenting out all scratch mentions, or you can make a ready-to-go, self-hosted cloud variable server by commenting out all mentions of clpv4.

# Import the server
from cloudlink import server

# Import protocols
from cloudlink.server.protocols import clpv4, scratch

# Instantiate the server object
server = server()

# Set logging level
server.logging.basicConfig(
    level=server.logging.INFO # See python's logging library for details on logging levels.
)

# Load protocols
clpv4 = clpv4(server)
scratch = scratch(server)

# Start the server!
server.run(ip="127.0.0.1", port=3000)

Securing your server

If you plan on hosting a server for the public, it is recommended to enable SSL for secure websockets. Simply generate an SSL certificate and private key pair using any tool of your choosing (I recommend using Let's Encrypt CertBot), and call upon server.enable_ssl. Once SSL support is enabled, you will connect to the server using wss://your.server.hostname:port/. You will no longer be able to connect to the server using insecure websockets.

server.enable_ssl(certfile="cert.pem", keyfile="privkey.pem")

Reconfiguring your server

There are various configuration settings you can change.

server.max_clients

Integer, Default: -1 (Unlimited).

Set this value to an integer number to limit the maximum number of clients that can connect, regardless of protocol. Leaving the default value, -1, will allow as many clients as (reasonably) possible.

clpv4.warn_if_multiple_username_matches

Boolean, Default: True.

If True, the server will warn users if they are resolving multiple clients for a username search.

clpv4.enable_motd

Boolean, Default: False.

If True, whenever a client sends the handshake command or whenever the client's protocol is identified, the server will send the Message-Of-The-Day from whatever value motd_message is set to.

clpv4.motd_message

String, Default: Blank string.

If enable_mod is True, this string will be sent as the server's Message-Of-The-Day.

clpv4.real_ip_header

String, Default: None.

If you use CloudLink behind a tunneling service or reverse proxy, set this value to whatever IP address-fetching request header to resolve valid IP addresses. When set to None, it will utilize the host's incoming network for resolving IP addresses.

Examples include:

  • x-forwarded-for
  • cf-connecting-ip

server.suppress_websocket_logs

Boolean, Default: True

When True, the server will suppress logging messages relating to the underlying websocket engine, as well as any asyncio-related logging messages. The only messages that will appear in logs will be errors.

Expanding functionality

CloudLink server comes with support for extending its functionality through decorators and classes. A collection of these various decorator functions are referred to as "extensions" or "plugins".

Adding a new command

Adding this chunk of code to your basic server (before calling server.run) will implement a new command, foo. To execute this command, connect to the server and send {"cmd": "foo"}. Each extension to the server must be an asyncio coroutine.

# Example command - client sends { "cmd": "foo" } to the server, this function will execute
@server.on_command(cmd="foo", schema=clpv4.schema)
async def foobar(client, message):
    print("Foobar!")

Exception handling

Invalid Command

This exception is raised when a client sends an invalid command for a protocol.

@server.on_exception(exception_type=server.exceptions.InvalidCommand, schema=clpv4.schema)
async def invalid_command(client, details):
    print(client, "sent an invalid command", details)

Disabled Command

This exception is raised when a client sends a disabled command for a protocol.

@server.on_disabled_command(schema=clpv4.schema)
async def disabled_command(client, details):
    print(client, "sent a disabled command", details)

JSON error

This exception is raised when a client sends invalid/malformed JSON.

@server.on_exception(exception_type=server.exceptions.JSONError, schema=clpv4.schema)
async def json_exception(client, details):
    print(client, details)

Empty message

This exception will be raised when a client sends an empty message after their protocol has been identified.

@server.empty_message(exception_type=server.exceptions.EmptyMessage, schema=clpv4.schema)
async def empty_message(client, details):
    print(client, details)

Events

Client connected

This event will fire whenever a client connects, regardless of protocol.

@server.on_connect
async def on_connect(client):
    print(client, "Connected!")

Client disconnected

This event will fire whenever a client disconnects, regardless of protocol.

@server.on_disconnect
async def on_disconnect(client):
    print(client, "Disconnected!")

Client error

This event will fire when an error has occurred, regardless of protocol.

@server.on_error
async def on_error(client, errors):
    print(client, "encountered an error(s)", errors)

Client protocol identified

This event will fire when the server identifies a client's protocol. This event will fire only once per connection.

@server.on_protocol_identified(schema=clpv4.schema)
async def protocol_identified(client):
    print(client, "is using protocol", clpv4.schema)

Client protocol-specific disconnect

This event will execute whenever a client disconnects. This is protocol-specific.

@server.on_protocol_disconnect(schema=clpv4.schema)
async def protocol_disconnect(client):
    print(client, "Disconnected...")

Sending messages to clients

server.send_packet(obj, message)

You can use this function to send a message to a single client, or a list/set of clients.

Arguments

  • obj- Set of client objects or a single client object.
  • message - Dictionary or string. Contains the message to unicast or multicast.

server.send_packet_unicast(client, message)

This variant will only send a message to a single client.

Arguments

  • client - A client object.
  • message - Dictionary or string. Contains the message to unicast.

server.send_packet_multicast(clients, message)

This variant will only send a message to a list/set of multiple clients.

Arguments

  • clients - A set of client objects.
  • message - Dictionary or string. Contains the message to multicast.

clpv4.send_statuscode(client, code, details=None, message=None, val=None)

This will send a CloudLink statuscode to a client.

Arguments

  • client - A client object.
  • code - Tuple. Specific statuscodes can be obtained by using an attribute of clpv4.statuscodes.
  • details - String (Optional). If provided, it will allow you to provide a client with details on what went wrong, or how to fix an error.
  • message - Dictionary (Optional). Provide the message argument from a callback to detect listeners.
  • val - Any (Optional). Adds the val key to the statuscode. Use if you need to return info for a specific statuscode (ex. returning a message)

Disconnecting clients

server.close_connection(obj, code=1000, reason="")

You can use this function to disconnect a single client or a list/set of clients.

Arguments

  • obj - Set of client objects or a single client object.
  • code - Integer. Defaults to 1000 (Normal close).
  • Reason - String. Defaults to a blank string. Provides a message to the client when the connection is closed.

Extras

clpv4.generate_user_object(obj)

Creates an origin value for messages similar to pmsg, pvar, or direct.

Arguments

  • obj - A client object.

Returns

If the client object has a username set...

{
    "id": String,
    "username": String,
    "uuid": String
}

Otherwise...

{
    "id": String,
    "uuid": String
}

Client/Room management

In CloudLink, each client is a unique object, and upon connection, each client subscribes to the default room. Clients may change rooms at any time, but this requires configuration. These functions make this possible. Using these functions, you may implement custom client/room management functionality.

Clients manager

Methods

Exceptions

All exceptions are subclasses of clients_manager.exceptions.

  • ClientDoesNotExist - This exception is raised when a client object does not exist.
  • ClientAlreadyExists - This exception is raised when attempting to add a client object that is already present.
  • ClientUsernameAlreadySet - This exception is raised when a client attempts to set their friendly username, but it was already set.
  • ClientUsernameNotSet - This exception is raised when a client object has not yet set it's friendly username.
  • NoResultsFound - This exception is raised when there are no results for a client search request.
  • ProtocolAlreadySet - This exception is raised when attempting to change a client's protocol.

Rooms manager