☰
shuttle-rama:在 Shuttle 上用 Rama 框架构建并部署后端服务完整指南
2026/10/15 16:21:36 网站建设 项目流程
  • 云原生
  • 后端
  • DevOps

【免费下载链接】shuttle

Build & ship backends without writing any infrastructure files.

项目地址:https://gitcode.com/gh_mirrors/sh/shuttle
点击查看免费下载

导读

shuttle-rama是 Shuttle 平台为 Rama 框架提供的官方服务集成(service integration),让你无需编写任何基础设施文件,就能把基于 Rama 构建的 HTTP 应用或纯 TCP 传输层服务一键部署到 Shuttle 云平台。阅读本文后,你将掌握shuttle-rama的两种接入形态(Application Service 与 Transport Service)、RamaService的构造与状态注入 API、以及 Shuttle 运行时如何把用户服务绑定到端口并对外提供流量的完整调用链。

什么是 shuttle-rama

shuttle-rama位于仓库的 services/shuttle-rama 目录,其包描述为 "Service implementation to run a rama server on shuttle"(在 Shuttle 上运行 rama 服务器的服务实现),版本与仓库工作区同步为0.57.0,采用 Apache-2.0 许可。它的核心思路与仓库中其他框架集成(如 services/shuttle-axum)一致:提供一个包装类型实现 Shuttle 运行时定义的Servicetrait,从而让用户框架服务能被 Shuttle 平台统一调度、绑定端口并接收流量。

从 Cargo.toml 可以看到该集成仅有两个依赖:

[dependencies] rama = { version = "0.2", features = ["tcp", "http-full"] } shuttle-runtime = { path = "../../runtime", version = "0.57.0", default-features = false }

其中rama使用tcp与http-full两个特性——前者支撑下文要讲的 Transport(TCP 传输层)接入,后者支撑 Application(HTTP 应用层)接入,两者共同决定了RamaService的能力范围。

核心类型与两种服务形态

shuttle-rama的核心是一个通用包装类型RamaService<T, State>(见 services/shuttle-rama/src/lib.rs),它内部保存两层数据:

  • svc: T:用户提供的 RamaService;
  • state: State:随每个请求注入Context的状态,默认为()。

为了让同一类型既能承载「HTTP 应用」又能承载「TCP 传输」两种语义,源码定义了两个私有标记类型:

  • Transport<S>(S):表示服务运行在 TCP 传输层;
  • Application<S>(S):表示服务运行在 HTTP(S) 应用层。

并通过两个公开构造器对外暴露:

构造器服务形态底层运行方式
RamaService::application(svc)HTTP 应用服务HttpServer::http1()监听并处理 HTTP/1.1 请求
RamaService::transport(svc)TCP 传输服务TcpListener接收原始 TCP 连接并交由用户 service 处理

此外还提供了with_state方法用于为服务附加共享状态:

pub fn with_state<State>(self, state: State) -> RamaService<T, State> where State: Clone + Send + Sync + 'static,

该状态会被放进每个请求的Context(对应 Rama 的rama::Context),适用于在应用层与传输层服务中携带配置、连接池等共享数据。

同时,lib.rs 还导出了两个便捷类型别名:

pub type ShuttleRamaTransport<S, State = ()> = Result<RamaService<Transport<S>, State>, Error>; pub type ShuttleRamaApplication<S, State = ()> = Result<RamaService<Application<S>, State>, Error>;

并且pub use shuttle_runtime::{Error as ShuttleError, Service as ShuttleService};让用户可以直接写出文档示例中的impl shuttle_rama::ShuttleService, shuttle_rama::ShuttleError返回类型签名。

接入方式一:Application Service(HTTP 应用服务)

完整示例代码

这是关联文档 services/shuttle-rama/README.md 中给出的官方示例,用于在 Shuttle 上部署一个 HTTP 应用:

use rama::{ Context, Layer, error::ErrorContext, http::{ StatusCode, layer::forwarded::GetForwardedHeaderLayer, service::web::{Router, response::Result}, }, net::forwarded::Forwarded, }; async fn hello_world(ctx: Context<()>) -> Result<String> { Ok(match ctx.get::<Forwarded>() { Some(forwarded) => format!( "Hello cloud user @ {}!", forwarded .client_ip() .context("missing IP information from user") .map_err(|err| (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))? ), None => "Hello local user! Are you developing?".to_owned(), }) } #[shuttle_runtime::main] async fn main() -> Result<impl shuttle_rama::ShuttleService, shuttle_rama::ShuttleError> { let router = Router::new().get("/", hello_world); let app = // Shuttle sits behind a load-balancer, // so in case you want the real IP of the user, // you need to ensure this headers is handled. // // Learn more at <https://docs.shuttle.dev/docs/deployment-environment#https-traffic> GetForwardedHeaderLayer::x_forwarded_for().into_layer(router); Ok(shuttle_rama::RamaService::application(app)) }

逐段拆解

1. 处理器函数:hello_world接收Context<()>,通过ctx.get::<Forwarded>()判断请求中是否携带转发头信息:

  • 有Forwarded信息时,取出forwarded.client_ip()返回客户端真实 IP,并通过ErrorContext将错误上下文包装后映射为500 INTERNAL_SERVER_ERROR;
  • 没有该信息时(典型如本地开发直连),返回"Hello local user! Are you developing?"。

2. 转发头处理(关键部署细节):Shuttle 的负载均衡器位于你的服务之前,HTTPS 请求在负载均衡器处终止,服务实际接收的是负载均衡器转发的流量。因此要拿到用户真实 IP,必须通过GetForwardedHeaderLayer::x_forwarded_for()处理X-Forwarded-For头。示例中该 Layer 通过into_layer(router)包裹在Router之外,作为最外层中间件生效。

3. 入口函数:#[shuttle_runtime::main]是 Shuttle 的过程宏入口(实现在 codegen/src/shuttle_main.rs)。它会把用户函数改名为__shuttle_main,并生成__loader(加载/序列化资源配置)与__runner(反序列化并调用用户函数)两个内部函数,最终构建一个多线程 Tokio Runtime 调用shuttle_runtime::__internals::start启动服务。返回值Result<impl shuttle_rama::ShuttleService, shuttle_rama::ShuttleError>即ShuttleRamaApplication<S, State = ()>的语义。

4. 打包服务:RamaService::application(app)将带 Layer 的 Router 包装为RamaService<Application<Router>, ()>,交给运行时绑定端口并对外服务。

接入方式二:Transport Service(TCP 传输服务)

Rama 是一个通用的服务框架,除了 HTTP 应用层,它还允许你直接操作 TCP 连接。shuttle-rama的transport形态正好覆盖了这一场景,官方示例如下:

use rama::{net, service::service_fn}; use std::convert::Infallible; use tokio::io::AsyncWriteExt; async fn hello_world<S>(mut stream: S) -> Result<(), Infallible> where S: net::stream::Stream + Unpin, { const TEXT: &str = "Hello, Shuttle!"; let resp = [ "HTTP/1.1 200 OK", "Content-Type: text/plain", format!("Content-Length: {}", TEXT.len()).as_str(), "", TEXT, "", ] .join("\r\n"); stream .write_all(resp.as_bytes()) .await .expect("write to stream"); Ok::<_, std::convert::Infallible>(()) } #[shuttle_runtime::main] async fn main() -> Result<impl shuttle_rama::ShuttleService, shuttle_rama::ShuttleError> { Ok(shuttle_rama::RamaService::transport(service_fn( hello_world, ))) }

关键点说明

  • hello_world直接面向一个泛型 TCP 流S: net::stream::Stream + Unpin,手工拼接并写回一段完整的 HTTP/1.1 响应(含Status Line、Content-Type、Content-Length与空行分隔的响应体);
  • service_fn(hello_world)把该异步函数转换为 Rama 的Service;
  • RamaService::transport(...)将其包装为传输层服务,Shuttle 运行时随后会把经过负载均衡器转发的原始 TCP 连接交给它。

这种形态适合需要自己掌控协议细节的场景,例如自定义协议、代理、隧道等,而不局限于 HTTP。

底层原理:RamaService 如何绑定到 Shuttle 端口

shuttle-rama为两种形态分别实现了shuttle_runtime::Service的bind方法(见 services/shuttle-rama/src/lib.rs),这是整个集成落地到 Shuttle 平台的关键。

Transport 形态的 bind 实现

#[shuttle_runtime::async_trait] impl<S, State> shuttle_runtime::Service for RamaService<Transport<S>, State> where S: Service<State, tokio::net::TcpStream>, State: Clone + Send + Sync + 'static, { async fn bind(self, addr: SocketAddr) -> Result<(), Error> { TcpListener::build_with_state(self.state) .bind(addr) .await .map_err(|err| Error::BindPanic(err.to_string()))? .serve(self.svc.0) .await; Ok(()) } }
  • 使用rama::tcp::server::TcpListener绑定运行时下发的SocketAddr;
  • build_with_state(self.state)将状态并入监听器,保证每个连接的服务调用都能拿到共享状态;
  • 绑定失败时映射为Error::BindPanic,随后serve永续运行接收连接。

Application 形态的 bind 实现

#[shuttle_runtime::async_trait] impl<S, State, Response> shuttle_runtime::Service for RamaService<Application<S>, State> where S: Service<State, Request, Response = Response, Error = Infallible>, Response: IntoResponse + Send + 'static, State: Clone + Send + Sync + 'static, { async fn bind(self, addr: SocketAddr) -> Result<(), Error> { // shuttle only supports h1 between load balancer <=> web service, // h2 is terminated by shuttle's load balancer HttpServer::http1() .listen_with_state(self.state, addr, self.svc.0) .await .map_err(|err| CustomError::new(OpaqueError::from_boxed(err)))?; Ok(()) } }

源码注释明确写道:Shuttle 仅在负载均衡器与服务之间支持 h1(HTTP/1.1),h2 由 Shuttle 的负载均衡器终止。因此HttpServer::http1()固定使用 HTTP/1.1 对外服务,错误则包装进CustomError/OpaqueError返回。

运行时的完整调用链

bind方法由 Shuttle 运行时在「运行阶段」调用。整条链路(见 runtime/src/rt.rs)为:

  1. #[shuttle_runtime::main]展开后,start.rs 的start解析环境变量(如SHUTTLE_RUNTIME_IP、SHUTTLE_RUNTIME_PORT);
  2. rt::start进入资源加载/预配阶段(secrets、数据库等),随后运行runner得到用户返回的RamaService;
  3. 运行时调用service.bind(service_addr),此时 lib.rs 中的实现接管,用 Rama 的TcpListener/HttpServer完成实际监听;
  4. 服务正式对外接收流量。

这也解释了为什么用户只需返回一个RamaService,无需关心任何端口、代理或基础设施细节。

如何运行与部署

虽然关联文档本身没有给出运行命令,但结合仓库 runtime/README.md 与 cargo-shuttle/src/args.rs 可以给出完整的实战流程。

本地运行

安装 Shuttle CLI 后,在项目根目录执行:

shuttle run

默认监听localhost:8000(见RunArgs中port的默认值"8000")。可用-p指定其他端口,用--external让服务监听0.0.0.0以支持局域网设备访问。本地开发时没有负载均衡器,因此hello_world示例会命中ctx.get::<Forwarded>()为None的分支,返回"Hello local user! Are you developing?"。

部署上线

shuttle deploy

部署成功后,服务将运行在 Shuttle 分配的*.shuttle.app子域名下。由于线上流量会经过 Shuttle 负载均衡器并携带转发头,示例中的应用会输出"Hello cloud user @ <真实IP>!",这正是文档示例中显式加入GetForwardedHeaderLayer的原因。

初始化模板

Shuttle CLI 内置了Rama初始化模板(见 cargo-shuttle/src/args.rs 的InitTemplateArg::Rama及"rama/hello-world"子目录映射),可通过shuttle init --template rama my-rama-app快速生成项目骨架。从 CHANGELOG.md 可知,shuttle-rama 服务集成与 Rama 初始化模板是在 v0.55.0 版本(2025-05-21)随 "Rama init template arg" 一并引入的。

小结

shuttle-rama用约百行代码把 Rama 这个「可构建代理、服务器与客户端的模块化服务框架」无缝接入 Shuttle 的部署模型:

  • Application 形态适合标准的 HTTP Web 服务,由HttpServer::http1()承载,配合GetForwardedHeaderLayer获取真实客户端 IP;
  • Transport 形态适合自定义协议或直接处理 TCP 流,由TcpListener承载;
  • 两种形态都通过RamaService::with_state支持共享状态注入;
  • 底层的bind实现严格遵循 service/src/lib.rs 定义的Servicetrait 契约,因此与其他框架集成一样,可以享受shuttle run/shuttle deploy的统一开发与部署体验。

如果你正在使用 Rama 构建后端,或希望以「零基础设施配置」的方式托管一个自定义 TCP 服务,shuttle-rama就是现成的官方接入方案。

参考源码路径

  • 集成文档:services/shuttle-rama/README.md
  • 核心实现:services/shuttle-rama/src/lib.rs
  • 依赖清单:services/shuttle-rama/Cargo.toml
  • 运行时入口:runtime/src/start.rs 与 runtime/src/rt.rs
  • Service trait 定义:service/src/lib.rs
  • #[shuttle_runtime::main]宏展开:codegen/src/shuttle_main.rs
  • CLI 运行/部署参数与 Rama 模板:cargo-shuttle/src/args.rs
  • 云原生
  • 后端
  • DevOps

【免费下载链接】shuttle

Build & ship backends without writing any infrastructure files.

项目地址:https://gitcode.com/gh_mirrors/sh/shuttle
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询