-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DOCS] Added How-to guide for UDP servers
- Loading branch information
1 parent
fc7fea1
commit fa29119
Showing
7 changed files
with
398 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
docs/source/_include/examples/howto/udp_servers/async_server.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
from collections.abc import AsyncGenerator | ||
|
||
from easynetwork.api_async.server import AsyncDatagramClient, AsyncDatagramRequestHandler, AsyncUDPNetworkServer | ||
from easynetwork.protocol import DatagramProtocol | ||
|
||
|
||
class Request: | ||
... | ||
|
||
|
||
class Response: | ||
... | ||
|
||
|
||
class MyRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
request: Request = yield | ||
|
||
... | ||
|
||
await client.send_packet(Response()) | ||
|
||
|
||
# NOTE: The sent packet is "Response" and the received packet is "Request" | ||
class ServerProtocol(DatagramProtocol[Response, Request]): | ||
def __init__(self) -> None: | ||
... | ||
|
||
|
||
async def main() -> None: | ||
host, port = "localhost", 9000 | ||
protocol = ServerProtocol() | ||
handler = MyRequestHandler() | ||
|
||
# Create the server, binding to localhost on port 9000 | ||
async with AsyncUDPNetworkServer(host, port, protocol, handler) as server: | ||
# Activate the server; this will keep running until you | ||
# interrupt the program with Ctrl-C | ||
await server.serve_forever() | ||
|
||
|
||
if __name__ == "__main__": | ||
asyncio.run(main()) |
157 changes: 157 additions & 0 deletions
157
docs/source/_include/examples/howto/udp_servers/request_handler_explanation.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
from __future__ import annotations | ||
|
||
import asyncio | ||
import contextlib | ||
from collections.abc import AsyncGenerator | ||
|
||
from easynetwork.api_async.server import AsyncDatagramClient, AsyncDatagramRequestHandler, AsyncUDPNetworkServer | ||
from easynetwork.exceptions import DatagramProtocolParseError | ||
|
||
|
||
class Request: | ||
... | ||
|
||
|
||
class Response: | ||
... | ||
|
||
|
||
class BadRequest(Response): | ||
... | ||
|
||
|
||
class InternalError(Response): | ||
... | ||
|
||
|
||
class TimedOut(Response): | ||
... | ||
|
||
|
||
class MinimumRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
### Before 'yield' | ||
# Initializes the generator. | ||
# This is the setup part before receiving a request. | ||
# Unlike the stream request handler, the generator is started | ||
# when the datagram is received (but is not parsed yet). | ||
################## | ||
|
||
request: Request = yield | ||
|
||
### After 'yield' | ||
# The received datagram is parsed. | ||
# you can do whatever you want with it and send responses back | ||
# to the client if necessary. | ||
await client.send_packet(Response()) | ||
################# | ||
|
||
### On a 'return' | ||
# When handle() returns, it means that this request handler is finished. | ||
# The server creates a new generator when a new datagram is received. | ||
################# | ||
return | ||
|
||
|
||
class SkipDatagramRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
if not self.should_handle(client): | ||
# By returning before the "yield" statement, you ask the server to discard | ||
# the received datagram. | ||
return | ||
|
||
request: Request = yield | ||
|
||
def should_handle(self, client: AsyncDatagramClient[Response]) -> bool: | ||
return True | ||
|
||
|
||
class ErrorHandlingInRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
try: | ||
# *All* exceptions are thrown through the "yield" statement | ||
# (including BaseException). But you should only catch Exception subclasses. | ||
request: Request = yield | ||
except DatagramProtocolParseError: | ||
await client.send_packet(BadRequest()) | ||
except Exception: | ||
await client.send_packet(InternalError()) | ||
else: | ||
await client.send_packet(Response()) | ||
|
||
|
||
class MultipleYieldInRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
request: Request = yield | ||
|
||
... | ||
|
||
await client.send_packet(Response()) | ||
|
||
if self.need_something_else(request, client): | ||
additional_data: Request = yield | ||
|
||
... | ||
|
||
await client.send_packet(Response()) | ||
|
||
def need_something_else(self, request: Request, client: AsyncDatagramClient[Response]) -> bool: | ||
return True | ||
|
||
|
||
class TimeoutRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
# It is *never* useful to have a timeout for the 1st datagram because the datagram | ||
# is already in the queue. | ||
request: Request = yield | ||
|
||
... | ||
|
||
await client.send_packet(Response()) | ||
|
||
try: | ||
async with asyncio.timeout(30): | ||
# The client has 30 seconds to send the 2nd request to the server. | ||
another_request: Request = yield | ||
except TimeoutError: | ||
await client.send_packet(TimedOut()) | ||
else: | ||
await client.send_packet(Response()) | ||
|
||
|
||
class ServiceInitializationHookRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
async def service_init( | ||
self, | ||
exit_stack: contextlib.AsyncExitStack, | ||
server: AsyncUDPNetworkServer[Request, Response], | ||
) -> None: | ||
exit_stack.callback(self._service_quit) | ||
|
||
self.background_tasks = await exit_stack.enter_async_context(asyncio.TaskGroup()) | ||
|
||
_ = self.background_tasks.create_task(self._service_actions()) | ||
|
||
async def _service_actions(self) -> None: | ||
while True: | ||
await asyncio.sleep(1) | ||
|
||
# Do some stuff each second in background | ||
... | ||
|
||
def _service_quit(self) -> None: | ||
print("Service stopped") |
44 changes: 44 additions & 0 deletions
44
docs/source/_include/examples/howto/udp_servers/simple_request_handler.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from __future__ import annotations | ||
|
||
from collections.abc import AsyncGenerator | ||
|
||
from easynetwork.api_async.server import AsyncDatagramClient, AsyncDatagramRequestHandler | ||
|
||
|
||
class Request: | ||
"""Object representing the client request.""" | ||
|
||
... | ||
|
||
|
||
class Response: | ||
"""Object representing the response to send to the client.""" | ||
|
||
... | ||
|
||
|
||
class MyRequestHandler(AsyncDatagramRequestHandler[Request, Response]): | ||
""" | ||
The request handler class for our server. | ||
It is instantiated once to the server, and must | ||
override the handle() method to implement communication to the | ||
client. | ||
""" | ||
|
||
async def handle( | ||
self, | ||
client: AsyncDatagramClient[Response], | ||
) -> AsyncGenerator[None, Request]: | ||
# "client" a placeholder to have a stream-like API. | ||
# All the datagrams sent by this client are sent | ||
# through the "yield" statement. | ||
request: Request = yield | ||
|
||
# Do some stuff | ||
... | ||
|
||
response = Response() | ||
|
||
# The corresponding call is server_socket.sendto(data, remote_address) | ||
await client.send_packet(response) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,5 +8,6 @@ How-to Guide | |
protocols | ||
serializers | ||
tcp_clients | ||
tcp_servers | ||
udp_clients | ||
tcp_servers | ||
udp_servers |
Oops, something went wrong.