Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ tracing = "0.1.40"
tracing-core = "0.1"
tracing-opentelemetry = "0.29"
tracing-slog = "0.2"
vergen = "9.0.4"
vergen = { version = "9.0.4", features = ["build", "cargo", "rustc"] }
vergen-gitcl = "1.0.0"
reqwest-eventsource = "0.6.0"

[workspace.dependencies.sentry]
Expand Down
3 changes: 3 additions & 0 deletions engine/packages/api-public/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ tracing.workspace = true
utoipa.workspace = true

[build-dependencies]
anyhow.workspace = true
fs_extra.workspace = true
vergen.workspace = true
vergen-gitcl.workspace = true
13 changes: 12 additions & 1 deletion engine/packages/api-public/build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use anyhow::Result;
use fs_extra::dir;
use std::env;
use std::fs;
use std::path::Path;

fn main() {
fn main() -> Result<()> {
// Configure vergen to emit build metadata
vergen::Emitter::default()
.add_instructions(&vergen::BuildBuilder::all_build()?)?
.add_instructions(&vergen::CargoBuilder::all_cargo()?)?
.add_instructions(&vergen::RustcBuilder::all_rustc()?)?
.add_instructions(&vergen_gitcl::GitclBuilder::all_git()?)?
.emit()?;

let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let out_dir = env::var("OUT_DIR").unwrap();
let ui_dir = Path::new(&out_dir).join("ui");
Expand Down Expand Up @@ -39,4 +48,6 @@ fn main() {

fs::write(index_html_path, index_html_content).expect("Failed to write index.html");
}

Ok(())
}
8 changes: 7 additions & 1 deletion engine/packages/api-public/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ pub async fn get_metadata(Extension(ctx): Extension<ApiCtx>) -> impl IntoRespons

Json(json!({
"runtime": "engine",
"version": env!("CARGO_PKG_VERSION")
"version": env!("CARGO_PKG_VERSION"),
"git_sha": env!("VERGEN_GIT_SHA"),
"build_timestamp": env!("VERGEN_BUILD_TIMESTAMP"),
"rustc_version": env!("VERGEN_RUSTC_SEMVER"),
"rustc_host": env!("VERGEN_RUSTC_HOST_TRIPLE"),
"cargo_target": env!("VERGEN_CARGO_TARGET_TRIPLE"),
"cargo_profile": if env!("VERGEN_CARGO_DEBUG") == "true" { "debug" } else { "release" }
}))
}
2 changes: 2 additions & 0 deletions engine/packages/guard/src/routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub(crate) const X_RIVET_TOKEN: HeaderName = HeaderName::from_static("x-rivet-to
pub(crate) const SEC_WEBSOCKET_PROTOCOL: HeaderName =
HeaderName::from_static("sec-websocket-protocol");
pub(crate) const WS_PROTOCOL_TARGET: &str = "rivet_target.";
pub(crate) const WS_PROTOCOL_ACTOR: &str = "rivet_actor.";
pub(crate) const WS_PROTOCOL_TOKEN: &str = "rivet_token.";

#[derive(Debug, Clone)]
pub struct ActorPathInfo {
Expand Down
61 changes: 40 additions & 21 deletions engine/packages/guard/src/routing/pegboard_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,27 @@ use gas::prelude::*;
use hyper::header::HeaderName;
use rivet_guard_core::proxy_service::{RouteConfig, RouteTarget, RoutingOutput, RoutingTimeout};

use super::SEC_WEBSOCKET_PROTOCOL;
use super::{SEC_WEBSOCKET_PROTOCOL, WS_PROTOCOL_ACTOR, WS_PROTOCOL_TOKEN, X_RIVET_TOKEN};
use crate::{errors, shared_state::SharedState};

const ACTOR_READY_TIMEOUT: Duration = Duration::from_secs(10);
pub const X_RIVET_ACTOR: HeaderName = HeaderName::from_static("x-rivet-actor");
const WS_PROTOCOL_ACTOR: &str = "rivet_actor.";

/// Route requests to actor services using path-based routing
#[tracing::instrument(skip_all)]
pub async fn route_request_path_based(
ctx: &StandaloneCtx,
shared_state: &SharedState,
actor_id_str: &str,
_token: Option<&str>,
token: Option<&str>,
path: &str,
_headers: &hyper::HeaderMap,
_is_websocket: bool,
) -> Result<Option<RoutingOutput>> {
// NOTE: Token validation implemented in EE

// Parse actor ID
let actor_id = Id::parse(actor_id_str).context("invalid actor id in path")?;

route_request_inner(ctx, shared_state, actor_id, path).await
route_request_inner(ctx, shared_state, actor_id, path, token).await
}

/// Route requests to actor services based on headers
Expand All @@ -47,28 +44,39 @@ pub async fn route_request(
return Ok(None);
}

// Extract actor ID from WebSocket protocol or HTTP header
let actor_id_str = if is_websocket {
// Extract actor ID and token from WebSocket protocol or HTTP headers
let (actor_id_str, token) = if is_websocket {
// For WebSocket, parse the sec-websocket-protocol header
headers
let protocols_header = headers
.get(SEC_WEBSOCKET_PROTOCOL)
.and_then(|protocols| protocols.to_str().ok())
.and_then(|protocols| {
// Parse protocols to find actor.{id}
protocols
.split(',')
.map(|p| p.trim())
.find_map(|p| p.strip_prefix(WS_PROTOCOL_ACTOR))
})
.ok_or_else(|| {
crate::errors::MissingHeader {
header: "sec-websocket-protocol".to_string(),
}
.build()
})?;

let protocols: Vec<&str> = protocols_header.split(',').map(|p| p.trim()).collect();

let actor_id = protocols
.iter()
.find_map(|p| p.strip_prefix(WS_PROTOCOL_ACTOR))
.ok_or_else(|| {
crate::errors::MissingHeader {
header: "`rivet_actor.*` protocol in sec-websocket-protocol".to_string(),
}
.build()
})?
})?;

let token = protocols
.iter()
.find_map(|p| p.strip_prefix(WS_PROTOCOL_TOKEN));

(actor_id, token)
} else {
// For HTTP, use the x-rivet-actor header
headers
// For HTTP, use headers
let actor_id = headers
.get(X_RIVET_ACTOR)
.map(|x| x.to_str())
.transpose()
Expand All @@ -78,21 +86,32 @@ pub async fn route_request(
header: X_RIVET_ACTOR.to_string(),
}
.build()
})?
})?;

let token = headers
.get(X_RIVET_TOKEN)
.map(|x| x.to_str())
.transpose()
.context("invalid x-rivet-token header")?;

(actor_id, token)
};

// Find actor to route to
let actor_id = Id::parse(actor_id_str).context("invalid x-rivet-actor header")?;

route_request_inner(ctx, shared_state, actor_id, path).await
route_request_inner(ctx, shared_state, actor_id, path, token).await
}

async fn route_request_inner(
ctx: &StandaloneCtx,
shared_state: &SharedState,
actor_id: Id,
path: &str,
_token: Option<&str>,
) -> Result<Option<RoutingOutput>> {
// NOTE: Token validation implemented in EE

// Route to peer dc where the actor lives
if actor_id.label() != ctx.config().dc_label() {
tracing::debug!(peer_dc_label=?actor_id.label(), "re-routing actor to peer dc");
Expand Down
Loading