|
1 | 1 |
|
2 | 2 | from __future__ import annotations |
3 | 3 |
|
4 | | -from typing_extensions import Final |
| 4 | +import asyncio |
| 5 | +from asyncio import StreamReader, StreamWriter |
| 6 | +from functools import partial |
| 7 | +from typing import Any, Awaitable, Callable, Coroutine, Union |
| 8 | +from typing_extensions import Final, TypeAlias |
5 | 9 |
|
6 | 10 | from . import ProxyProtocol, ProxyProtocolIncompleteError, \ |
7 | 11 | ProxyProtocolWantRead |
8 | 12 | from .result import ProxyResult, ProxyResultUnknown |
| 13 | +from .sock import SocketInfo |
9 | 14 | from .typing import StreamReaderProtocol |
10 | 15 |
|
11 | 16 | __all__ = ['ProxyProtocolReader'] |
12 | 17 |
|
| 18 | +_Callback: TypeAlias = Callable[ |
| 19 | + [StreamReader, StreamWriter], Awaitable[None]] |
| 20 | +_WrappedCallback: TypeAlias = Callable[ |
| 21 | + [StreamReader, StreamWriter, SocketInfo], Coroutine[Any, Any, None]] |
| 22 | + |
13 | 23 |
|
14 | 24 | class ProxyProtocolReader: |
15 | 25 | """Read a PROXY protocol header from a stream. |
@@ -47,7 +57,35 @@ async def read(self, reader: StreamReaderProtocol) -> ProxyResult: |
47 | 57 | return self.pp.unpack(view) |
48 | 58 | except ProxyProtocolIncompleteError as exc: |
49 | 59 | want_read = exc.want_read |
50 | | - try: |
51 | | - data += await self._handle_want(reader, want_read) |
52 | | - except (EOFError, ConnectionResetError) as exc: |
53 | | - return ProxyResultUnknown(exc) |
| 60 | + data += await self._handle_want(reader, want_read) |
| 61 | + |
| 62 | + def get_callback(self, callback: _WrappedCallback, |
| 63 | + timeout: Union[None, int, float] = 3) -> _Callback: |
| 64 | + """Get a callback object for use as the *client_connected_cb* argument |
| 65 | + to :func:`asyncio.start_server`. |
| 66 | +
|
| 67 | + The returned callback will first read the PROXY protocol header before |
| 68 | + starting the provided *callback* as a :class:`~asyncio.Task`. The |
| 69 | + *callback* argument is similar to *client_connected_cb* but with an |
| 70 | + additional positional argument -- the |
| 71 | + :class:`~proxyprotocol.sock.SocketInfo` read from the header. |
| 72 | +
|
| 73 | + Args: |
| 74 | + callback: Async function with arguments ``(reader, writer, |
| 75 | + sock_info)`` called after successfully reading the header. |
| 76 | + timeout: A timeout in seconds to allow for reading the header. |
| 77 | +
|
| 78 | + """ |
| 79 | + return partial(self._read_then_call, callback, timeout) |
| 80 | + |
| 81 | + async def _read_then_call(self, callback: _WrappedCallback, |
| 82 | + timeout: Union[None, int, float], |
| 83 | + reader: StreamReader, writer: StreamWriter) \ |
| 84 | + -> None: |
| 85 | + try: |
| 86 | + result = await asyncio.wait_for(self.read(reader), timeout) |
| 87 | + except Exception as exc: |
| 88 | + writer.close() |
| 89 | + result = ProxyResultUnknown(exc) |
| 90 | + sock_info = SocketInfo.get(writer, result) |
| 91 | + asyncio.create_task(callback(reader, writer, sock_info)) |
0 commit comments