Skip to content

Commit d5d0fd2

Browse files
authored
Merge pull request openwallet-foundation#19 from dbluhm/refactor/backend-interface
refactor: backend interface
2 parents 2c4cb4b + 8b90e44 commit d5d0fd2

11 files changed

Lines changed: 134 additions & 88 deletions

Dockerfile

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
FROM python:3.9-slim-bullseye
1+
FROM python:3.9-slim-bookworm as base
22
WORKDIR /usr/src/app
33

4-
ENV POETRY_VERSION=1.4.2
5-
64
RUN apt-get update && apt-get install -y curl && apt-get clean
7-
RUN pip install "poetry==$POETRY_VERSION"
8-
COPY poetry.lock pyproject.toml README.md ./
9-
RUN mkdir -p socketdock && touch socketdock/__init__.py
10-
RUN poetry config virtualenvs.create false \
11-
&& poetry install --without=dev --no-interaction --no-ansi
5+
ENV POETRY_VERSION=1.5.1
6+
ENV POETRY_HOME=/opt/poetry
7+
RUN curl -sSL https://install.python-poetry.org | python -
8+
9+
ENV PATH="/opt/poetry/bin:$PATH"
10+
RUN poetry config virtualenvs.in-project true
11+
12+
# Setup project
13+
COPY pyproject.toml poetry.lock ./
14+
RUN poetry install --without dev
15+
16+
FROM python:3.9-slim-bookworm
17+
WORKDIR /usr/src/app
18+
COPY --from=base /usr/src/app/.venv /usr/src/app/.venv
19+
ENV PATH="/usr/src/app/.venv/bin:$PATH"
1220

1321
COPY socketdock socketdock
1422

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ version: '3'
22

33
services:
44
websocket-gateway:
5-
build: .
5+
build: ..
66
ports:
77
- "8765:8765"
88
volumes:
9-
- ./server:/code
109
- ./wait-for-tunnel.sh:/wait-for-tunnel.sh:ro,z
1110
entrypoint: /wait-for-tunnel.sh
1211
command: >

demo/docker-compose.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
version: '3'
2+
3+
services:
4+
socketdock:
5+
build: ..
6+
ports:
7+
- "8765:8765"
8+
volumes:
9+
- ../socketdock:/usr/src/app/socketdock:z
10+
command: >
11+
--bindip 0.0.0.0
12+
--backend loopback
13+
--message-uri https://example.com
14+
--disconnect-uri https://example.com
15+
--endpoint http://socketdock:8765
16+
--log-level INFO
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ async def hello():
1414
print(f"< {response}", flush=True)
1515

1616

17-
asyncio.run(hello())
17+
if __name__ == "__main__":
18+
asyncio.run(hello())
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ done
99
WS_ENDPOINT=$(curl --silent "${TUNNEL_ENDPOINT}/start" | python -c "import sys, json; print(json.load(sys.stdin)['url'])" | sed -rn 's#https?://([^/]+).*#\1#p')
1010
echo "fetched hostname and port [$WS_ENDPOINT]"
1111

12-
exec "$@" --externalhostandport ${WS_ENDPOINT}
12+
exec "$@" --externalhostandport ${WS_ENDPOINT}

docker-compose.yaml

Lines changed: 0 additions & 23 deletions
This file was deleted.

socketdock/__main__.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import argparse
55
from sanic import Sanic
66

7-
from .api import api, backend_var, endpoint_var
7+
from .api import api, backend_var
88

99

1010
def config() -> argparse.Namespace:
@@ -34,16 +34,17 @@ def main():
3434
if args.backend == "loopback":
3535
from .testbackend import TestBackend
3636

37-
backend = TestBackend()
37+
backend = TestBackend(args.endpoint)
3838
elif args.backend == "http":
3939
from .httpbackend import HTTPBackend
4040

41-
backend = HTTPBackend(args.connect_uri, args.message_uri, args.disconnect_uri)
41+
backend = HTTPBackend(
42+
args.endpoint, args.connect_uri, args.message_uri, args.disconnect_uri
43+
)
4244
else:
4345
raise ValueError("Invalid backend type")
4446

4547
backend_var.set(backend)
46-
endpoint_var.set(args.endpoint)
4748

4849
logging.basicConfig(level=args.log_level)
4950

socketdock/api.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from .backend import Backend
1010

1111
backend_var: ContextVar[Backend] = ContextVar("backend")
12-
endpoint_var: ContextVar[str] = ContextVar("endpoint")
1312

1413
api = Blueprint("api", url_prefix="/")
1514

@@ -45,7 +44,7 @@ async def status_handler(request: Request):
4544
async def socket_send(request: Request, connectionid: str):
4645
"""Send a message to a connected socket."""
4746
LOGGER.info("Inbound message for %s", connectionid)
48-
LOGGER.info("Existing connections: %s", active_connections.keys())
47+
LOGGER.debug("Existing connections: %s", active_connections.keys())
4948

5049
if connectionid not in active_connections:
5150
return text("FAIL", status=500)
@@ -62,7 +61,7 @@ async def socket_send(request: Request, connectionid: str):
6261
async def socket_disconnect(request: Request, connectionid: str):
6362
"""Disconnect a socket."""
6463
LOGGER.info("Disconnect %s", connectionid)
65-
LOGGER.info("Existing connections: %s", active_connections.keys())
64+
LOGGER.debug("Existing connections: %s", active_connections.keys())
6665

6766
if connectionid not in active_connections:
6867
return text("FAIL", status=500)
@@ -78,37 +77,26 @@ async def socket_handler(request: Request, websocket: Websocket):
7877
global lifetime_connections
7978
backend = backend_var.get()
8079
socket_id = None
81-
endpoint = endpoint_var.get()
82-
send = f"{endpoint}/socket/{socket_id}/send"
83-
disconnect = f"{endpoint_var.get()}/socket/{socket_id}/disconnect"
8480
try:
8581
# register user
8682
LOGGER.info("new client connected")
87-
socket_id = websocket.connection.id.hex
83+
socket_id = websocket.ws_proto.id.hex
8884
active_connections[socket_id] = websocket
8985
lifetime_connections += 1
90-
LOGGER.info("Existing connections: %s", active_connections.keys())
91-
LOGGER.info("Added connection: %s", socket_id)
92-
LOGGER.info("Request headers: %s", dict(request.headers.items()))
86+
LOGGER.debug("Existing connections: %s", active_connections.keys())
87+
LOGGER.debug("Added connection: %s", socket_id)
88+
LOGGER.debug("Request headers: %s", dict(request.headers.items()))
9389

9490
await backend.socket_connected(
95-
{
96-
"connection_id": socket_id,
97-
"headers": dict(request.headers.items()),
98-
"send": send,
99-
"disconnect": disconnect,
100-
},
91+
connection_id=socket_id,
92+
headers=dict(request.headers.items()),
10193
)
10294

10395
async for message in websocket:
10496
if message:
10597
await backend.inbound_socket_message(
106-
{
107-
"connection_id": socket_id,
108-
"send": send,
109-
"disconnect": disconnect,
110-
},
111-
message,
98+
connection_id=socket_id,
99+
message=message,
112100
)
113101
else:
114102
LOGGER.warning("empty message received")
@@ -118,4 +106,4 @@ async def socket_handler(request: Request, websocket: Websocket):
118106
if socket_id:
119107
del active_connections[socket_id]
120108
LOGGER.info("Removed connection: %s", socket_id)
121-
await backend.socket_disconnected({"connection_id": socket_id})
109+
await backend.socket_disconnected(socket_id)

socketdock/backend.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
"""Backend interface for SocketDock."""
22

33
from abc import ABC, abstractmethod
4-
from typing import Union
4+
from typing import Dict, Union
55

66

77
class Backend(ABC):
88
"""Backend interface for SocketDock."""
99

1010
@abstractmethod
11-
async def socket_connected(self, callback_uris: dict):
11+
async def socket_connected(
12+
self,
13+
connection_id: str,
14+
headers: Dict[str, str],
15+
):
1216
"""Handle new socket connections, with calback provided."""
13-
raise NotImplementedError()
1417

1518
@abstractmethod
1619
async def inbound_socket_message(
17-
self, callback_uris: dict, message: Union[str, bytes]
20+
self,
21+
connection_id: str,
22+
message: Union[str, bytes],
1823
):
1924
"""Handle inbound socket message, with calback provided."""
20-
raise NotImplementedError()
2125

2226
@abstractmethod
23-
async def socket_disconnected(self, bundle: dict):
27+
async def socket_disconnected(self, connection_id: str):
2428
"""Handle socket disconnected."""
25-
raise NotImplementedError()

socketdock/httpbackend.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""HTTP backend for SocketDock."""
22

33
import logging
4-
from typing import Union
4+
from typing import Dict, Union
55

66
import aiohttp
77

@@ -14,16 +14,46 @@
1414
class HTTPBackend(Backend):
1515
"""HTTP backend for SocketDock."""
1616

17-
def __init__(self, connect_uri: str, message_uri: str, disconnect_uri: str):
17+
def __init__(
18+
self,
19+
socket_base_uri: str,
20+
connect_uri: str,
21+
message_uri: str,
22+
disconnect_uri: str,
23+
):
1824
"""Initialize HTTP backend."""
1925
self._connect_uri = connect_uri
2026
self._message_uri = message_uri
2127
self._disconnect_uri = disconnect_uri
28+
self.socket_base_uri = socket_base_uri
29+
30+
def send_callback(self, connection_id: str) -> str:
31+
"""Return the callback URI for sending a message to a connected socket."""
32+
return f"{self.socket_base_uri}/socket/{connection_id}/send"
33+
34+
def disconnect_callback(self, connection_id: str) -> str:
35+
"""Return the callback URI for disconnecting a connected socket."""
36+
return f"{self.socket_base_uri}/socket/{connection_id}/disconnect"
2237

23-
async def socket_connected(self, callback_uris: dict):
38+
def callback_uris(self, connection_id: str) -> Dict[str, str]:
39+
"""Return labelled callback URIs."""
40+
return {
41+
"send": self.send_callback(connection_id),
42+
"disconnect": self.disconnect_callback(connection_id),
43+
}
44+
45+
async def socket_connected(
46+
self,
47+
connection_id: str,
48+
headers: Dict[str, str],
49+
):
2450
"""Handle inbound socket message, with calback provided."""
2551
http_body = {
26-
"meta": callback_uris,
52+
"meta": {
53+
**self.callback_uris(connection_id),
54+
"headers": headers,
55+
"connection_id": connection_id,
56+
},
2757
}
2858

2959
if self._connect_uri:
@@ -37,11 +67,16 @@ async def socket_connected(self, callback_uris: dict):
3767
LOGGER.debug("Response: %s", response)
3868

3969
async def inbound_socket_message(
40-
self, callback_uris: dict, message: Union[str, bytes]
70+
self,
71+
connection_id: str,
72+
message: Union[str, bytes],
4173
):
4274
"""Handle inbound socket message, with calback provided."""
4375
http_body = {
44-
"meta": callback_uris,
76+
"meta": {
77+
**self.callback_uris(connection_id),
78+
"connection_id": connection_id,
79+
},
4580
"message": message.decode("utf-8") if isinstance(message, bytes) else message,
4681
}
4782

@@ -54,11 +89,15 @@ async def inbound_socket_message(
5489
else:
5590
LOGGER.debug("Response: %s", response)
5691

57-
async def socket_disconnected(self, bundle: dict):
92+
async def socket_disconnected(self, connection_id: str):
5893
"""Handle socket disconnected."""
5994
async with aiohttp.ClientSession() as session:
60-
LOGGER.info("Notifying of disconnect: %s %s", self._disconnect_uri, bundle)
61-
async with session.post(self._disconnect_uri, json=bundle) as resp:
95+
LOGGER.info(
96+
"Notifying of disconnect: %s %s", self._disconnect_uri, connection_id
97+
)
98+
async with session.post(
99+
self._disconnect_uri, json={"connection_id": connection_id}
100+
) as resp:
62101
response = await resp.text()
63102
if resp.status != 200:
64103
LOGGER.error("Error posting to disconnect uri: %s", response)

0 commit comments

Comments
 (0)