How-to — Standalone Servers
Note
This page uses two different API variants:
Synchronous API with classic
def
functions, usable in any context.Asynchronous API with
async def
functions, using an asynchronous framework to perform I/O operations.
All asynchronous API examples assume that you are using asyncio
,
but you can use a different library thanks to the asynchronous backend engine API.
Introduction
Standalone servers are classes that can create ready-to-run servers with no predefined asynchronous context
(i.e., no async
/ await
). They use (and block) a thread to accept requests,
and their methods are meant to be used by other threads for control (e.g. shutdown).
By default, the runner is "asyncio"
, but it can be changed during object creation.
Server Object
1from __future__ import annotations
2
3import asyncio
4from collections.abc import AsyncGenerator
5from contextlib import AsyncExitStack
6from typing import Any
7
8from easynetwork.protocol import StreamProtocol
9from easynetwork.serializers import JSONSerializer
10from easynetwork.servers import StandaloneTCPNetworkServer
11from easynetwork.servers.handlers import AsyncStreamClient, AsyncStreamRequestHandler
12
13
14class JSONProtocol(StreamProtocol[dict[str, Any], dict[str, Any]]):
15 def __init__(self) -> None:
16 super().__init__(JSONSerializer())
17
18
19class MyRequestHandler(AsyncStreamRequestHandler[dict[str, Any], dict[str, Any]]):
20 async def service_init(self, exit_stack: AsyncExitStack, server: Any) -> None:
21 # StandaloneTCPNetworkServer wraps an AsyncTCPNetworkServer instance.
22 # Therefore, "server" is still asynchronous.
23
24 from easynetwork.servers import AsyncTCPNetworkServer
25
26 assert isinstance(server, AsyncTCPNetworkServer)
27
28 async def handle(
29 self,
30 client: AsyncStreamClient[dict[str, Any]],
31 ) -> AsyncGenerator[None, dict[str, Any]]:
32 request: dict[str, Any] = yield
33
34 current_task = asyncio.current_task()
35 assert current_task is not None
36
37 response = {"task": current_task.get_name(), "request": request}
38 await client.send_packet(response)
39
40
41def main() -> None:
42 host, port = "localhost", 9000
43 protocol = JSONProtocol()
44 handler = MyRequestHandler()
45
46 # All the parameters are the same as AsyncTCPNetworkServer.
47 server = StandaloneTCPNetworkServer(host, port, protocol, handler)
48
49 with server:
50 server.serve_forever()
51
52
53if __name__ == "__main__":
54 main()
1from __future__ import annotations
2
3import asyncio
4from collections.abc import AsyncGenerator
5from contextlib import AsyncExitStack
6from typing import Any
7
8from easynetwork.protocol import DatagramProtocol
9from easynetwork.serializers import JSONSerializer
10from easynetwork.servers import StandaloneUDPNetworkServer
11from easynetwork.servers.handlers import AsyncDatagramClient, AsyncDatagramRequestHandler
12
13
14class JSONProtocol(DatagramProtocol[dict[str, Any], dict[str, Any]]):
15 def __init__(self) -> None:
16 super().__init__(JSONSerializer())
17
18
19class MyRequestHandler(AsyncDatagramRequestHandler[dict[str, Any], dict[str, Any]]):
20 async def service_init(self, exit_stack: AsyncExitStack, server: Any) -> None:
21 # StandaloneUDPNetworkServer wraps an AsyncUDPNetworkServer instance.
22 # Therefore, "server" is still asynchronous.
23
24 from easynetwork.servers import AsyncUDPNetworkServer
25
26 assert isinstance(server, AsyncUDPNetworkServer)
27
28 async def handle(
29 self,
30 client: AsyncDatagramClient[dict[str, Any]],
31 ) -> AsyncGenerator[None, dict[str, Any]]:
32 request: dict[str, Any] = yield
33
34 current_task = asyncio.current_task()
35 assert current_task is not None
36
37 response = {"task": current_task.get_name(), "request": request}
38 await client.send_packet(response)
39
40
41def main() -> None:
42 host, port = "localhost", 9000
43 protocol = JSONProtocol()
44 handler = MyRequestHandler()
45
46 # All the parameters are the same as AsyncUDPNetworkServer.
47 server = StandaloneUDPNetworkServer(host, port, protocol, handler)
48
49 with server:
50 server.serve_forever()
51
52
53if __name__ == "__main__":
54 main()
Run Server In Background
Unlike asynchronous tasks, it is mandatory to spawn a separate execution thread. NetworkServerThread
is useful for this case:
1from __future__ import annotations
2
3import asyncio
4import threading
5from collections.abc import AsyncGenerator
6from typing import Any
7
8from easynetwork.clients import TCPNetworkClient
9from easynetwork.protocol import StreamProtocol
10from easynetwork.serializers import JSONSerializer
11from easynetwork.servers import StandaloneTCPNetworkServer
12from easynetwork.servers.handlers import AsyncStreamClient, AsyncStreamRequestHandler
13from easynetwork.servers.threads_helper import NetworkServerThread
14
15
16class JSONProtocol(StreamProtocol[dict[str, Any], dict[str, Any]]):
17 def __init__(self) -> None:
18 super().__init__(JSONSerializer())
19
20
21class MyRequestHandler(AsyncStreamRequestHandler[dict[str, Any], dict[str, Any]]):
22 async def handle(
23 self,
24 client: AsyncStreamClient[dict[str, Any]],
25 ) -> AsyncGenerator[None, dict[str, Any]]:
26 request: dict[str, Any] = yield
27
28 current_task = asyncio.current_task()
29 assert current_task is not None
30
31 response = {
32 "thread": threading.current_thread().name,
33 "task": current_task.get_name(),
34 "request": request,
35 }
36 await client.send_packet(response)
37
38
39def client(host: str, port: int, message: str) -> None:
40 with TCPNetworkClient((host, port), JSONProtocol()) as client:
41 client.send_packet({"message": message})
42 response = client.recv_packet()
43 print(f"From server: {response}")
44
45
46def main() -> None:
47 host, port = "localhost", 9000
48 protocol = JSONProtocol()
49 handler = MyRequestHandler()
50
51 server = StandaloneTCPNetworkServer(host, port, protocol, handler)
52
53 with server:
54 server_thread = NetworkServerThread(server)
55 server_thread.start()
56
57 print(f"Server loop running in thread: {server_thread.name}")
58
59 client(host, port, "Hello world 1")
60 client(host, port, "Hello world 2")
61 client(host, port, "Hello world 3")
62
63 server_thread.join()
64
65
66if __name__ == "__main__":
67 main()
1from __future__ import annotations
2
3import asyncio
4import threading
5from collections.abc import AsyncGenerator
6from typing import Any
7
8from easynetwork.clients import UDPNetworkClient
9from easynetwork.protocol import DatagramProtocol
10from easynetwork.serializers import JSONSerializer
11from easynetwork.servers import StandaloneUDPNetworkServer
12from easynetwork.servers.handlers import AsyncDatagramClient, AsyncDatagramRequestHandler
13from easynetwork.servers.threads_helper import NetworkServerThread
14
15
16class JSONProtocol(DatagramProtocol[dict[str, Any], dict[str, Any]]):
17 def __init__(self) -> None:
18 super().__init__(JSONSerializer())
19
20
21class MyRequestHandler(AsyncDatagramRequestHandler[dict[str, Any], dict[str, Any]]):
22 async def handle(
23 self,
24 client: AsyncDatagramClient[dict[str, Any]],
25 ) -> AsyncGenerator[None, dict[str, Any]]:
26 request: dict[str, Any] = yield
27
28 current_task = asyncio.current_task()
29 assert current_task is not None
30
31 response = {
32 "thread": threading.current_thread().name,
33 "task": current_task.get_name(),
34 "request": request,
35 }
36 await client.send_packet(response)
37
38
39def client(host: str, port: int, message: str) -> None:
40 with UDPNetworkClient((host, port), JSONProtocol()) as client:
41 client.send_packet({"message": message})
42 response = client.recv_packet()
43 print(f"From server: {response}")
44
45
46def main() -> None:
47 host, port = "localhost", 9000
48 protocol = JSONProtocol()
49 handler = MyRequestHandler()
50
51 server = StandaloneUDPNetworkServer(host, port, protocol, handler)
52
53 with server:
54 server_thread = NetworkServerThread(server)
55 server_thread.start()
56
57 print(f"Server loop running in thread: {server_thread.name}")
58
59 client(host, port, "Hello world 1")
60 client(host, port, "Hello world 2")
61 client(host, port, "Hello world 3")
62
63 server_thread.join()
64
65
66if __name__ == "__main__":
67 main()
The output of the example should look something like this:
$ python background_server.py
Server loop running in thread: Thread-1
From server: {'thread': 'Thread-1', 'task': 'Task-6', 'request': {'message': 'Hello world 1'}}
From server: {'thread': 'Thread-1', 'task': 'Task-8', 'request': {'message': 'Hello world 2'}}
From server: {'thread': 'Thread-1', 'task': 'Task-10', 'request': {'message': 'Hello world 3'}}
$ python background_server.py
Server loop running in thread: Thread-1
From server: {'thread': 'Thread-1', 'task': 'Task-5', 'request': {'message': 'Hello world 1'}}
From server: {'thread': 'Thread-1', 'task': 'Task-6', 'request': {'message': 'Hello world 2'}}
From server: {'thread': 'Thread-1', 'task': 'Task-7', 'request': {'message': 'Hello world 3'}}