
Leptos 多服务器多客户端架构实战基于 Nginx 反向代理实现单域名下多 SSR 应用与共享服务通信【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos本指南以仓库中的 nginx-mpmc 项目为蓝本完整讲解如何用 Nginx 作为反向代理让多个 Leptos SSR 应用App-1、App-2与多个共享服务器Shared-Server-1、Shared-Server-2在同一个域名localhost:80下协同工作。读完本文你将掌握 Nginx 多upstream路由拆分、cargo leptos serve多实例启动、服务器函数server functions跨服务器调用以及create_resource与create_local_resource在 SSR 多服务器场景下的关键差异。一、场景概述为什么要做多服务器多客户端在实际部署中一个业务系统往往由多个独立服务组成前端 SSR 渲染层、多个后端 API 服务、共享的业务逻辑服务器等。如果每个服务都暴露独立端口和域名会导致 CORS 配置复杂、域名管理混乱、客户端跨域请求频繁出错。nginx-mpmc 示例解决的核心问题就是在只有一个域名localhost:80的前提下让多个客户端与多个服务器互相通信由 Nginx 统一入口做路径转发。整体拓扑如下组件角色监听端口访问路径NginxDocker 容器反向代理统一入口80localhost默认App-1Leptos SSR 应用3000localhost默认/App-2Leptos SSR 应用3001localhost/app2Shared-Server-1服务器函数服务3002localhost/api_sharedShared-Server-2服务器函数服务3003localhost/api_shared2两个共享服务器不承担页面渲染仅注册并响应 Leptos 服务器函数供两个 App 通过 action 或本地资源local resource调用。二、项目结构与四个服务nginx-mpmc 示例位于 projects/nginx-mpmc目录结构如下projects/nginx-mpmc/ ├── app-1/ # Leptos SSR 应用 1cargo leptos serve ├── app-2/ # Leptos SSR 应用 2cargo leptos serve ├── shared-server-1/ # 纯服务器函数服务 1cargo run ├── shared-server-2/ # 纯服务器函数服务 2cargo run ├── nginx.conf # macOS/Docker Desktop 使用的 Nginx 配置 ├── nginx_linux.conf # Linux 下使用 host 网络模式时使用的配置 ├── run.sh # 一键启动脚本macOS/Docker Desktop ├── run_linux.sh # 一键启动脚本Linux └── kill.sh # 一键清理脚本2.1 两个 SSR 客户端应用App-1 与 App-2App-1 与 App-2 都是标准 Leptos SSR 应用由cargo leptos serve启动。它们的差异体现在端口不同App-1 监听127.0.0.1:3000App-2 监听127.0.0.1:3001见 app-1/Cargo.toml 与 app-2/Cargo.toml 中的site-addr配置。静态资源目录不同这是本示例的关键改动。App-2 在 Cargo.toml 中将site-pkg-dir从默认的pkg改为pkg2以避免与 App-1 的 WASM/JS/CSS 产物在同一域名下冲突对应的App-2 页面中的样式引用路径为/pkg2/app-2.css见 app-2/src/app.rs而 App-1 使用/pkg/app-1.css。路由挂载路径不同App-2 的Route pathapp2使其页面挂在/app2路径下见 app-2/src/app.rs。两个 App 都通过 Cargo 依赖以本地路径引入两个共享服务器 crateshared-server { path ../shared-server, default-features false } shared-server-2 { path ../shared-server-2, default-features false }并且在ssr与hydrate两个 feature 中分别开启共享服务器的对应 feature确保服务器函数在服务端注册、在客户端以可调用形式编译。2.2 两个纯服务器函数服务Shared-Server-1 与 Shared-Server-2两个共享服务器是「只有服务器、没有客户端配对」的 Leptos 服务它们不输出 WASM也不做 hydration只通过leptos_axum::handle_server_fns暴露服务器函数端点。以 Shared-Server-1 为例服务器函数定义在 shared-server-1/src/lib.rsuse leptos::*; #[cfg(feature ssr)] #[derive(Clone)] pub struct SharedServerState; #[tracing::instrument] #[server(prefix /api_shared, endpoint /a)] pub async fn shared_server_function() - ResultString, ServerFnError { tracing::debug!(SHARED SERVER 1); let _: axum::ExtensionSharedServerState leptos_axum::extract().await?; Ok(This message is from the shared server..to_string()) }这里有两个要点#[server(prefix /api_shared, endpoint /a)]显式指定了服务器函数的路由前缀为/api_shared端点路径为/a。这正好与 Nginx 中location /api_shared的转发规则对应。Shared-Server-2 则使用prefix /api_shared2对应 Nginx 的location /api_shared2。服务器函数通过leptos_axum::extract()取出axum::ExtensionSharedServerState状态说明它可以依赖由共享服务器主程序注入的 axum 扩展状态实际业务中可替换为数据库连接池、会话状态等。主程序在 shared-server-1/src/main.rs 中注册路由let app Router::new() .route(/api_shared/*fn_name, post(leptos_axum::handle_server_fns)) .layer(tower_http::trace::TraceLayer::new_for_http()) .layer(axum::Extension(shared_server::SharedServerState));注意route(/api_shared/*fn_name, ...)使用通配符*fn_name捕获任意服务器函数名统一交给leptos_axum::handle_server_fns处理。Shared-Server-2 与之对称监听127.0.0.1:3003注册/api_shared2/*fn_name。由于这两个服务没有客户端配对main函数在非ssrfeature 下为空实现注释明确说明No hydrate function on a server function only server因此只通过cargo run --features ssr对应cargo run前的 feature 配置运行。三、Nginx 反向代理配置详解Nginx 是整套架构的统一入口。仓库提供了两份几乎一致的配置区别仅在于 upstream 地址的写法nginx.conf适用于 macOS / Docker Desktopupstream 指向host.docker.internal:端口从容器内访问宿主机端口。nginx_linux.conf适用于 Linux配合--networkhost网络模式upstream 直接指向127.0.0.1:端口。3.1 upstream 定义端口别名upstream app_server { server host.docker.internal:3000; } upstream app_2_server { server host.docker.internal:3001; } upstream shared_server { server host.docker.internal:3002; } upstream shared_server_2 { server host.docker.internal:3003; }四个 upstream 分别代表四个后端服务Nginx 通过路径匹配将请求转发到对应 upstream。Linux 版本将host.docker.internal全部替换为127.0.0.1。3.2 location 路由规则核心server { listen 80; # /app2 提供 app2 的客户端页面任何客户端都可以通过 /app2/api 调用其 API location /app2 { proxy_pass http://app_2_server; } # 需要为 app2 配置独立的 pkg 目录并在此转发 location /pkg2 { proxy_pass http://app_2_server; } # /api_shared 调用 shared_server 上注册的服务器函数 location /api_shared { proxy_pass http://shared_server; } # /api_shared_2 调用 shared_server_2 上注册的服务器函数 location /api_shared2 { proxy_pass http://shared_server_2; } # 默认提供 app-1 的客户端 location / { proxy_pass http://app_server; } }各 location 的作用总结location转发目标用途/app2app_2_server3001App-2 的页面与路由请求/pkg2app_2_server3001App-2 的 WASM/JS/CSS 静态资源对应其site-pkg-dir pkg2/api_sharedshared_server3002Shared-Server-1 的服务器函数调用/api_shared2shared_server_23003Shared-Server-2 的服务器函数调用/app_server3000默认入口App-1 的页面与资源配置文件中的注释也直接点明了设计意图/app2 will serve the client for app2, and any client can call the api by calling /app2/api以及We need to set app2 to have a different pkg directory, and to forward on that——即 App-2 必须使用独立 pkg 目录Nginx 再按该目录路径单独转发。四、一键启动与清理脚本4.1 启动脚本 run.shmacOS / Docker Desktop原文档给出的启动方式./run.shrun.sh 的内容解析# 保存 pwd 变量 # 将 pwd 追加到 nginx.conf 前缀 # 使用新的 nginx.conf 路径运行该命令 (cd app-1 cargo leptos serve) \ (cd app-2 cargo leptos serve) \ (cd shared-server-1 cargo run) \ (cd shared-server-2 cargo run) \ ( current_dir$(pwd) \ docker run --rm -v $current_dir/nginx.conf:/etc/nginx/nginx.conf:ro -p 80:80 nginx)脚本依次在后台启动四个服务并通过 Docker 运行 Nginx(cd app-1 cargo leptos serve)以开发模式启动 App-1端口 3000。(cd app-2 cargo leptos serve)以开发模式启动 App-2端口 3001。(cd shared-server-1 cargo run)启动共享服务器 1端口 3002。(cd shared-server-2 cargo run)启动共享服务器 2端口 3003。docker run --rm -v $current_dir/nginx.conf:/etc/nginx/nginx.conf:ro -p 80:80 nginx以只读方式挂载项目内的nginx.conf到容器内映射宿主机 80 端口。注意这里通过current_dir$(pwd)拼接出 nginx.conf 的绝对路径因为 Docker 挂载卷不接受相对路径。4.2 Linux 启动脚本 run_linux.sh./run_linux.shrun_linux.sh 与 run.sh 的唯一区别在 Nginx 启动命令docker run --rm -v $current_dir/nginx_linux.conf:/etc/nginx/nginx.conf:ro -p 80:80 --networkhost nginx它挂载nginx_linux.conf并追加--networkhost参数让容器直接使用宿主机网络栈因此配置中可以使用127.0.0.1直连四个后端端口。4.3 清理脚本 kill.sh原文档强调结束示例时不要只按 Ctrl-C因为「多次按 Ctrl-C 无法关闭所有已打开的程序」需要运行./kill.shkill.sh 通过lsof找到占用各端口的进程并结束lsof -ti :3000 | xargs kill \ lsof -ti :3001 | xargs kill \ lsof -ti :3002 | xargs kill \ lsof -ti :3003 | xargs kill \ lsof -ti :80 | xargs kill即按顺序清理 3000、3001、3002、3003 四个服务端口以及 80 端口的 Nginx 进程。4.4 启动后的访问验证启动完成后浏览器访问http://localhost→ 得到 App-1 的页面浏览器访问http://localhost/app2→ 得到 App-2 的页面在两个页面上点击「request from shared server 1 / 2」按钮即可通过 action 向两个共享服务器发起请求并展示返回消息。五、客户端如何跨服务器调用Action 与 Local Resource5.1 通过 Server Action 调用共享服务器App-1 的 app.rs 展示了标准做法use shared_server::SharedServerFunction; use shared_server_2::SharedServerFunction2; let hello_1_action Action::SharedServerFunction, _::server(); let hello_2_action Action::SharedServerFunction2, _::server(); let value_1 create_rw_signal(String::from(waiting for update from shared server.)); let value_2 create_rw_signal(String::from(waiting for update from shared server 2.)); create_effect(move |_| { if let Some(Ok(msg)) hello_1_action.value().get() { value_1.set(msg) } }); create_effect(move |_| { if let Some(Ok(msg)) hello_2_action.value().get() { value_2.set(msg) } });按钮点击时通过dispatch触发服务器调用button on:clickmove |_| hello_1_action.dispatch(SharedServerFunction{}) request from shared server 1 /button {move || value_1.get()}由于服务器函数上通过#[server(prefix /api_shared, ...)]声明了前缀客户端编译时会自动把请求发往 Nginx 的/api_shared路径再被 Nginx 转发到 Shared-Server-1。App-2 的 app.rs 采用了完全相同的 action 模式证明「任何客户端都可以调用任一共享服务器」。5.2 关键陷阱create_resource在 SSR 下不适用于跨服务器调用这是原文档特别强调、也是本示例最有价值的一条经验create_resource在尝试与不同服务器通信时无法按预期工作。它会尝试在你正在提供 SSR 内容的服务器上运行服务器函数。如果你的服务器函数依赖该服务器上不存在的状态就会导致错误。原因在于使用 SSR 时create_resource会在渲染服务器端初始化并执行资源见 app-1/src/app.rs 中的注释A resource is initialized on the rendering server when using SSR。也就是说如果你在 App-1 页面里写create_resource(..., shared_server::shared_server_function)Leptos 会在 App-13000 端口服务端尝试执行这个函数——而该函数注册在 Shared-Server-13002 端口App-1 内部并无对应路由与状态因此会失败。正确的替代方案有两个使用create_local_resourcecreate_local_resource会等待客户端加载完成后才在客户端初始化执行见 app-1/src/app.rs 的注释A local resource will wait for the client to load before attempting to initialize从而让请求真正从浏览器发出、经由 Nginx 路由到正确的共享服务器。示例代码// A local resource will wait for the client to load before attempting to initialize. let hello_1 create_local_resource(move || (), |_| shared_server::shared_server_function()); // 这样不行let hello_1 create_resource(move || (), |_| shared_server::shared_server_function());使用 Server Action推荐如上文所示action 天然在客户端触发与渲染服务器无关最适合跨服务器调用。5.3 Suspense 展示本地资源结果App-1 用Suspense包裹本地资源以呈现加载与成功状态Suspense fallbackmove || view! { pLoading (Suspense Fallback).../p } {move || { hello_1.get().map(|data| match data { Err(_) view! { preError/pre }.into_view(), Ok(hello) hello.into_view(), }) }} /Suspensecreate_local_resource返回响应式资源配合Suspense可以在 SSR 页面中优雅地处理异步数据的加载态与错误态。而两个 action 的响应则通过create_effect监听action.value()写入信号驱动界面更新——这形成了「本地资源 Action 响应式信号」三种机制协作的完整跨服务器通信方案。六、源码验证要点与常见问题排查6.1 关键实现位置速查Nginx 路由拆分nginx.confmacOS/Docker Desktop与 nginx_linux.confLinux共享服务器函数声明与路由前缀shared-server-1/src/lib.rs、shared-server-2/src/lib.rs共享服务器 axum 路由注册与状态注入shared-server-1/src/main.rs、shared-server-2/src/main.rsApp-1 的跨服务器调用示例local resource actionapp-1/src/app.rsApp-2 的跨服务器调用示例纯 actionapp-2/src/app.rsApp-2 独立 pkg 目录配置site-pkg-dir pkg2见 app-2/Cargo.toml6.2 常见问题排查80 端口被占用先确认本机 80 端口未被其他进程占用否则 Docker 映射失败。可用lsof -i :80检查。macOS 上访问共享服务器 404确认使用的是 nginx.confhost.docker.internal。若误用了 Linux 版配置127.0.0.1Docker 容器内访问不到宿主机服务。Linux 上容器内无法连接后端确认使用run_linux.sh并以--networkhost启动否则127.0.0.1在容器内指向容器自身。App-2 样式或 WASM 加载失败检查是否同时满足两点——App-2 的 Cargo.toml 中site-pkg-dir pkg2且 Nginx 中存在location /pkg2转发规则二者缺一不可。create_resource调用共享服务器报错按本文 5.2 节改用create_local_resource或 Server Action。多次 Ctrl-C 后端口仍被占用按原文档建议运行./kill.sh清理 3000/3001/3002/3003/80 端口。七、结语本示例的可复用架构价值nginx-mpmc 展示了一套可直接迁移到生产环境的 Leptos 多服务部署范式入口统一单一域名 Nginx 路径路由天然规避 CORS 与多域名证书问题前后端解耦SSR 渲染层与纯服务器函数服务分离共享服务器可以独立扩展、独立部署、复用状态注入axum::Extension资源隔离通过site-pkg-dir为每个 SSR 应用分配独立静态资源路径避免 WASM/JS 产物互相覆盖通信纪律跨服务器调用必须走客户端侧触发机制Action /create_local_resource这是 SSR 多服务器场景下避免「在错误服务器上执行函数」的关键约束。无论你是要在单机 Docker 环境中模拟微服务架构还是规划 Leptos 应用的模块化部署都可以以此为起点按需增加upstream与location规则、为每个新服务分配独立端口与 pkg 目录即可将任意数量的客户端与服务器纳入同一域名体系。【免费下载链接】leptosBuild fast web applications with Rust.项目地址: https://gitcode.com/GitHub_Trending/le/leptos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考