From f82a381a1a4d487205a8e6b5766d8504dbd94cbc Mon Sep 17 00:00:00 2001 From: Forrest Date: Tue, 18 Jul 2023 15:07:38 -0400 Subject: [PATCH] feat(server): add FastAPI example and custom paths - FastAPI example in docs and in examples/ directory - Add support for custom socket.io paths --- documentation/content/doc/server.md | 53 +++++++++++++++++------------ server/examples/example_fastapi.py | 28 +++++++++++++++ src/core/remote/client.ts | 18 +++++++++- 3 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 server/examples/example_fastapi.py diff --git a/documentation/content/doc/server.md b/documentation/content/doc/server.md index f0fb7cba0..cb55542af 100644 --- a/documentation/content/doc/server.md +++ b/documentation/content/doc/server.md @@ -286,43 +286,52 @@ These are exposed as keyword arguments to `VolViewApi(app, server_kwargs={}, asg ##### FastAPI middleware example -Install `FastAPI` and `uvicorn`, and then create a `server.py` file with the following contents: +FastAPI is an ASGI-compatible web framework. This guide will go through the +FastAPI example found in `examples/example_fastapi.py`. -```python -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware +First install `FastAPI` and `uvicorn[standard]`. -from volview_server import VolViewMiddleware -from custom.user_api import Api +``` +python -m pip install FastAPI 'uvicorn[standard]' +``` -app = FastAPI() +To start the FastAPI server, use `uvicorn` as follows. -app.add_middleware(VolViewMiddleware, ApiClass=Api) -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], -) +``` +uvicorn examples.example_fastapi:app +``` +Edit the VolView `.env` file to point to the FastAPI server: -@app.get("/") -def index(): - return {"hello": "world"} +``` +VITE_REMOTE_SERVER_URL=http://localhost:8000/ ``` -To start the FastAPI server: +Rebuild the VolView viewer app and navigate to the "Remote Functions" tab to +verify that the server works. -``` -uvicorn server:app +###### Changing the socket.io path + +If the default `https://your-host/socket.io/` path conflicts with an existing +route, VolView can be configured to use a different path. In this guide, we will +rename the default `/socket.io/` path to `/my-custom-path/`. + +On the server-side, the VolView middleware must be configured with the new path, +as shown. + +```python +app.add_middlware(volview, asgi_kwargs={"socketio_path": "/my-custom-path"}) ``` -Edit the VolView `.env` file to point to the FastAPI server: +Then, the VolView client server URL must be updated to match. The following sets +the server URL in the `.env` file. ``` -VITE_REMOTE_SERVER_URL=http://localhost:8000/ +VITE_REMOTE_SERVER_URL=http://localhost:8000/my-custom-path ``` -Rebuild the VolView viewer app and navigate to the "Remote Functions" tab to see -that the server works. +Restart both the server and the client to verify that a successful connection is +achieved. #### Python-socketio supported deployment strategies diff --git a/server/examples/example_fastapi.py b/server/examples/example_fastapi.py new file mode 100644 index 000000000..cb9038aae --- /dev/null +++ b/server/examples/example_fastapi.py @@ -0,0 +1,28 @@ +import sys +import os + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from volview_server import VolViewApi + +# Import the VolView example API +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +from example_api import volview + + +app = FastAPI() + +# Adds volview middlware +app.add_middleware(volview) + +# Set CORS configuration +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], +) + + +@app.get("/") +def index(): + return {"hello": "world"} diff --git a/src/core/remote/client.ts b/src/core/remote/client.ts index 7a2bffb30..c65774137 100644 --- a/src/core/remote/client.ts +++ b/src/core/remote/client.ts @@ -12,6 +12,7 @@ import { nanoid } from 'nanoid'; import { Socket, io } from 'socket.io-client'; import { z } from 'zod'; import * as ChunkedParser from '@/src/core/remote/chunkedParser'; +import { URL } from 'whatwg-url'; const CLIENT_ID_SIZE = 24; const RPC_ID_SIZE = 24; @@ -104,6 +105,20 @@ export interface RpcApi { export interface RpcClientOptions { serializers?: Array<(input: any) => any>; deserializers?: Array<(input: any) => any>; + path?: string; +} + +function justHostUrl(url: string) { + const parts = new URL(url); + parts.pathname = ''; + return String(parts); +} + +function getSocketIoPath(url: string) { + const parts = new URL(url); + return parts.pathname.replace(/^\/+$/, '').length === 0 + ? '/socket.io/' + : parts.pathname; } /** @@ -156,7 +171,8 @@ export default class RpcClient { async connect(uri: string) { await this.disconnect(); // @ts-ignore reset socket.io URI - this.socket.io.uri = uri; + this.socket.io.uri = justHostUrl(uri); + this.socket.io.opts.path = getSocketIoPath(uri); let promise = this.waiting.get('connect'); if (!promise) {