From d88a48a10a7411952a22ecf2c4cb8e100cff99e3 Mon Sep 17 00:00:00 2001 From: alihanisarr Date: Mon, 13 Jul 2026 02:40:54 +0500 Subject: [PATCH 1/8] added prometheus metrics --- docker-compose.yml | 12 ++++++++++++ requirements.txt | 1 - server.py | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a625591..63bf446 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,3 +6,15 @@ services: ports: - "5000:5000" + prometheus: + image: prom/prometheus:latest + restart: always + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + command: + - '--config.file=/etc/prometheus/prometheus.yml' + ports: + - "9090:9090" + + + diff --git a/requirements.txt b/requirements.txt index 6bd4692..fc1c223 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,3 @@ typing_extensions==4.15.0 uvicorn==0.49.0 watchfiles==1.2.0 websockets==16.0 - diff --git a/server.py b/server.py index 4867a7e..fa3bb17 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,6 @@ import collections import logging +import collections from fastapi import FastAPI, WebSocket, Request, HTTPException, Response import prometheus_client From 61e19ec775d944f7ff9272e9f6f015493d8cad9b Mon Sep 17 00:00:00 2001 From: evan Date: Sat, 25 Jul 2026 14:21:16 -0700 Subject: [PATCH 2/8] do rebase --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index fa3bb17..2a0c447 100644 --- a/server.py +++ b/server.py @@ -46,7 +46,7 @@ async def webhook(subscription_id: str, request: Request): logger.error("Data sent to websocket client") return {"message":"received"} - + # something else: logger.error("Invalid subscription '%s', connection not accepted", subscription_id) return From 8cf687dbed5704dc9f50fdecc49b23bb898994d3 Mon Sep 17 00:00:00 2001 From: evan Date: Sat, 25 Jul 2026 14:21:57 -0700 Subject: [PATCH 3/8] remove prometheus container --- docker-compose.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 63bf446..a625591 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,15 +6,3 @@ services: ports: - "5000:5000" - prometheus: - image: prom/prometheus:latest - restart: always - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml - command: - - '--config.file=/etc/prometheus/prometheus.yml' - ports: - - "9090:9090" - - - From 62bf751f60e3faf23c4a2745b8b203fd532b44da Mon Sep 17 00:00:00 2001 From: evan Date: Sat, 25 Jul 2026 15:00:42 -0700 Subject: [PATCH 4/8] finally block in websocket disconnect --- README.md | 17 ++++++++++++++--- server.py | 10 +++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f79b7a1..d8c9d86 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,26 @@ The server runs at `http://127.0.0.1:5000`. In one terminal, connect to the WebSocket tunnel: -```powershell -websocat ws://127.0.0.1:5000/tunnel +```sh +websocat ws://127.0.0.1:5000/tunnel/asdf + +# with an api key +websocat \ + --header="X-API-Key:hello" \ + - ws://127.0.0.1:5000/tunnel/asdf ``` In another terminal, send a webhook payload: ### bash ```sh -curl -X POST http://127.0.0.1:5000/webhook \ +curl -X POST http://127.0.0.1:5000/webhook/asdf \ + -H "Content-Type: application/json" \ + -d '{"message":"hello from webhook"}' + +# with an api key +curl -X POST http://127.0.0.1:5000/webhook/asdf \ + -H "X-API-Key: hello" \ -H "Content-Type: application/json" \ -d '{"message":"hello from webhook"}' ``` diff --git a/server.py b/server.py index 2a0c447..6a8532b 100644 --- a/server.py +++ b/server.py @@ -34,6 +34,7 @@ async def webhook(subscription_id: str, request: Request): if subscription_id is not None: header_val = request.headers.get("X-API-Key") if (header_val != "hello"): + logger.error('/webhook recieved invalid X-API-Key of "%s"', header_val) raise HTTPException(status_code=403, detail="API key is not valid ") data = await request.json() @@ -46,7 +47,6 @@ async def webhook(subscription_id: str, request: Request): logger.error("Data sent to websocket client") return {"message":"received"} - # something else: logger.error("Invalid subscription '%s', connection not accepted", subscription_id) return @@ -57,6 +57,7 @@ async def websocket_endpoint(subscription_id: str, websocket: WebSocket): api_key = websocket.headers.get("X-API-Key") if (api_key != "hello"): + logger.error('/tunnel recieved invalid X-API-Key of "%s"', api_key) MetricsHandler.failed_connections.labels( subscription_id=subscription_id, reason="bad_api_key", @@ -76,12 +77,14 @@ async def websocket_endpoint(subscription_id: str, websocket: WebSocket): while True: data = await websocket.receive_text() await websocket.send_text("Message received") - except Exception as e: + except Exception: + logger.exception('ok') MetricsHandler.failed_connections.labels( subscription_id=subscription_id, reason="websocket_receive_failed", ).inc() MetricsHandler.connected_clients.labels(subscription_id).dec() + finally: clients[subscription_id].remove(websocket) if not clients[subscription_id]: clients.pop(subscription_id, None) @@ -104,8 +107,9 @@ def get_metrics(): # metrics_handler referenced by the rest of the file. otherwise, # the thread interacts with an instance different than the one the # server uses +logger.error("!!!!!`") if __name__ == "server": MetricsHandler.init() if __name__ == "__main__": - uvicorn.run("server:app", host="0.0.0.0", port=5000) + uvicorn.run("server:app", host="0.0.0.0", port=5000, reload=True, timeout_graceful_shutdown=1) From 8e2356ed0d27a2757e187829201fb2a5616cd442 Mon Sep 17 00:00:00 2001 From: evan Date: Sat, 25 Jul 2026 15:01:59 -0700 Subject: [PATCH 5/8] add sce network to docker compose yml --- docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a625591..91f5124 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,8 @@ services: build: context: . dockerfile: ./Dockerfile - ports: - - "5000:5000" +networks: + default: + external: + name: sce From 770187cc3e5ed13d4480aa951fb7f0fb5b137355 Mon Sep 17 00:00:00 2001 From: evan Date: Sat, 25 Jul 2026 15:30:04 -0700 Subject: [PATCH 6/8] add sce.sjsu.edu section --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d8c9d86..2d2fad1 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,15 @@ Invoke-RestMethod -Method Post -Uri http://127.0.0.1:5000/webhook -ContentType " ``` The connected WebSocket client should receive the JSON payload. + +## on the real website +```sh +curl -X POST https://sce.sjsu.edu/webhook/asdf \ + -H "X-API-Key: hello" \ + -H "Content-Type: application/json" \ + -d '{"message":"hello from webhook"}' + +websocat \ + --header="X-API-Key:hello" \ + - ws://sce.sjsu.edu/tunnel/asdf +``` From 21ac0e0ba34c280f4035541cba619bff2377b73e Mon Sep 17 00:00:00 2001 From: evan Date: Sun, 26 Jul 2026 17:36:15 -0700 Subject: [PATCH 7/8] add yml file argument, only ask for api key with websocket --- .gitignore | 2 + Dockerfile | 2 +- README.md | 16 ++++++++ args.py | 3 +- docker-compose.yml | 4 ++ server.py | 93 ++++++++++++++++++++++++++++++---------------- 6 files changed, 85 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index f2c8323..37de172 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ __pycache__/ *.egg-info/ build/ dist/ +*.yml +*.yaml diff --git a/Dockerfile b/Dockerfile index bb2755a..8890311 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,4 +3,4 @@ WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . -CMD ["python", "server.py"] +ENTRYPOINT ["python", "server.py"] diff --git a/README.md b/README.md index 2d2fad1..3217077 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,22 @@ Invoke-RestMethod -Method Post -Uri http://127.0.0.1:5000/webhook -ContentType " The connected WebSocket client should receive the JSON payload. ## on the real website +create an file called `config.yml` with content like +```yml +api_key: thesecretofalltime +``` +run the server with +```sh +docker-compose up --build -d +``` + +if you wanna see logs +```sh +docker logs smee2-smee2-1 --tail 300 -f +``` + +to test that its working, use the same api key like: + ```sh curl -X POST https://sce.sjsu.edu/webhook/asdf \ -H "X-API-Key: hello" \ diff --git a/args.py b/args.py index bf5e48f..0f2693a 100644 --- a/args.py +++ b/args.py @@ -8,7 +8,8 @@ def get_args(): "-v", action="count", default=0, - help="increase logging verbosity; can be used multiple times" + help="increase logging verbosity; can be used multiple times like -vvv" ) + parser.add_argument("--config", help="path to yaml file for api key, see readme") return parser.parse_args() diff --git a/docker-compose.yml b/docker-compose.yml index 91f5124..e35dd15 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,10 @@ services: build: context: . dockerfile: ./Dockerfile + volumes: + - ./config.yml:/app/config.yml + command: + - --config=/app/config.yml networks: default: diff --git a/server.py b/server.py index 6a8532b..2bbff51 100644 --- a/server.py +++ b/server.py @@ -5,6 +5,7 @@ from fastapi import FastAPI, WebSocket, Request, HTTPException, Response import prometheus_client import uvicorn +import yaml from args import get_args from metrics import MetricsHandler @@ -20,6 +21,8 @@ ) logging.getLogger("uvicorn.access").setLevel(logging.WARNING) logging.getLogger("uvicorn.error").setLevel(logging.WARNING) +logging.getLogger("asyncio").setLevel(logging.WARNING) +logging.getLogger("watchfiles").setLevel(logging.WARNING) logger = logging.getLogger(__name__) @@ -28,36 +31,45 @@ clients = collections.defaultdict(list) subscribers = {} +# see if __name__ == '__server__' section for setting this value +API_KEY = None @app.post("/webhook/{subscription_id}") async def webhook(subscription_id: str, request: Request): - if subscription_id is not None: - header_val = request.headers.get("X-API-Key") - if (header_val != "hello"): - logger.error('/webhook recieved invalid X-API-Key of "%s"', header_val) - raise HTTPException(status_code=403, detail="API key is not valid ") - - data = await request.json() - logger.debug("Data pushed to webhook %s, received: %s", subscription_id, data) - - subscribers[subscription_id] = data - - for client in clients.get(subscription_id, []): - await client.send_json(data) - - logger.error("Data sent to websocket client") - return {"message":"received"} - else: - logger.error("Invalid subscription '%s', connection not accepted", subscription_id) - return - - + if subscription_id is None: + logger.error( + "Invalid subscription '%s', connection not accepted", subscription_id + ) + MetricsHandler.failed_connections.labels( + subscription_id=subscription_id, + reason="no_subscription_id", + ).inc() + raise HTTPException( + status_code=404, + detail="subscripiton id was empty. expecting soemthing like /webhook/1234", + ) + header_val = request.headers.get("X-API-Key") + + data = await request.json() + logger.debug("Data pushed to webhook %s, received: %s", subscription_id, data) + + subscribers[subscription_id] = data + + for client in clients.get(subscription_id, []): + await client.send_json(data) + + number_of_clients = len(clients.get(subscription_id, [])) + message = f"forwarded to {len(number_of_clients)} client(s) for subscription {subscription_id}" + logger.debug(message) + return {"message": message} + + @app.websocket("/tunnel/{subscription_id}") async def websocket_endpoint(subscription_id: str, websocket: WebSocket): - api_key = websocket.headers.get("X-API-Key") - - if (api_key != "hello"): - logger.error('/tunnel recieved invalid X-API-Key of "%s"', api_key) + api_key_from_header = websocket.headers.get("X-API-Key") + + if API_KEY is not None and api_key_from_header != API_KEY: + logger.error('/tunnel recieved invalid X-API-Key of "%s"', api_key_from_header) MetricsHandler.failed_connections.labels( subscription_id=subscription_id, reason="bad_api_key", @@ -66,19 +78,21 @@ async def websocket_endpoint(subscription_id: str, websocket: WebSocket): return await websocket.accept() - + MetricsHandler.connected_clients.labels(subscription_id).inc() - + clients[subscription_id].append(websocket) - logger.debug(f"Websocket connection successfully established at id: {subscription_id}") - + logger.debug( + f"Websocket connection successfully established at id: {subscription_id}" + ) + try: while True: data = await websocket.receive_text() await websocket.send_text("Message received") except Exception: - logger.exception('ok') + logger.exception("ok") MetricsHandler.failed_connections.labels( subscription_id=subscription_id, reason="websocket_receive_failed", @@ -90,7 +104,7 @@ async def websocket_endpoint(subscription_id: str, websocket: WebSocket): clients.pop(subscription_id, None) logger.debug(f"Websocket connection disconnected at id: {subscription_id}") - + @app.get("/metrics") def get_metrics(): @@ -99,6 +113,7 @@ def get_metrics(): media_type="text/plain", ) + # we have a separate __name__ check here due to how FastAPI starts # a server. the file is first ran (where __name__ == "__main__") # and then calls `uvicorn.run`. the call to run() reruns the file, @@ -107,9 +122,21 @@ def get_metrics(): # metrics_handler referenced by the rest of the file. otherwise, # the thread interacts with an instance different than the one the # server uses -logger.error("!!!!!`") if __name__ == "server": MetricsHandler.init() + try: + with open(args.config, "r") as stream: + data = yaml.safe_load(stream) + API_KEY = data.get("api_key", None) + logger.info(f'loaded api key from {args.config}') + except Exception: + logging.warning("unable to open yaml file, smee2 is not checking for api keys") if __name__ == "__main__": - uvicorn.run("server:app", host="0.0.0.0", port=5000, reload=True, timeout_graceful_shutdown=1) + uvicorn.run( + "server:app", + host="0.0.0.0", + port=5000, + reload=True, + timeout_graceful_shutdown=1, + ) From e813aeef17f1816d9fd1d6eb8182783cd14896ab Mon Sep 17 00:00:00 2001 From: evan Date: Sun, 26 Jul 2026 17:56:22 -0700 Subject: [PATCH 8/8] forwarded to {number_of_clients} client(s) for subscription {subscription_id} --- README.md | 3 +-- server.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3217077..7fc6b7f 100644 --- a/README.md +++ b/README.md @@ -83,11 +83,10 @@ to test that its working, use the same api key like: ```sh curl -X POST https://sce.sjsu.edu/webhook/asdf \ - -H "X-API-Key: hello" \ -H "Content-Type: application/json" \ -d '{"message":"hello from webhook"}' websocat \ --header="X-API-Key:hello" \ - - ws://sce.sjsu.edu/tunnel/asdf + - wss://sce.sjsu.edu/tunnel/asdf ``` diff --git a/server.py b/server.py index 2bbff51..d5b4e71 100644 --- a/server.py +++ b/server.py @@ -59,7 +59,7 @@ async def webhook(subscription_id: str, request: Request): await client.send_json(data) number_of_clients = len(clients.get(subscription_id, [])) - message = f"forwarded to {len(number_of_clients)} client(s) for subscription {subscription_id}" + message = f"forwarded to {number_of_clients} client(s) for subscription {subscription_id}" logger.debug(message) return {"message": message}