Skip to content

Make use of the new serve_web_viewer function #9547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Apr 10, 2025
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
1 change: 1 addition & 0 deletions crates/store/re_grpc_server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use re_protos::{
},
};

/// Default port of the OSS /proxy server.
pub const DEFAULT_SERVER_PORT: u16 = 9876;
pub const DEFAULT_MEMORY_LIMIT: MemoryLimit = MemoryLimit::UNLIMITED;

Expand Down
6 changes: 3 additions & 3 deletions crates/top/re_sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ pub use self::recording_stream::{
RecordingStreamResult,
};

/// The default port of a Rerun gRPC server.
pub const DEFAULT_SERVER_PORT: u16 = 9876;
/// The default port of a Rerun gRPC /proxy server.
pub const DEFAULT_SERVER_PORT: u16 = re_uri::DEFAULT_PROXY_PORT;

/// The default URL of a Rerun gRPC server.
/// The default URL of a Rerun gRPC /proxy server.
///
/// This isn't used to _host_ the server, only to _connect_ to it.
pub const DEFAULT_CONNECT_URL: &str =
Expand Down
11 changes: 9 additions & 2 deletions crates/top/re_sdk/src/recording_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ impl RecordingStreamBuilder {
/// locally hosted gRPC server.
///
/// The server is hosted on the default IP and port, and may be connected to by any SDK or Viewer
/// at `rerun+http://127.0.0.1:9876/proxy`.
/// at `rerun+http://127.0.0.1:9876/proxy` or by just running `rerun --connect`.
///
/// To configure the gRPC server's IP and port, use [`Self::serve_grpc_opts`] instead.
///
Expand Down Expand Up @@ -619,7 +619,10 @@ impl RecordingStreamBuilder {
//
// # TODO(#5531): keep static data around.
#[cfg(feature = "web_viewer")]
#[deprecated(since = "0.20.0", note = "use serve_web() instead")]
#[deprecated(
since = "0.20.0",
note = "use rec.serve_grpc() with rerun::serve_web_viewer() instead"
)]
pub fn serve(
self,
bind_ip: &str,
Expand Down Expand Up @@ -653,6 +656,8 @@ impl RecordingStreamBuilder {
/// You can limit the amount of data buffered by the gRPC server with the `server_memory_limit` argument.
/// Once reached, the earliest logged data will be dropped. Static data is never dropped.
///
/// Calling `serve_web` is equivalent to calling [`Self::serve_grpc`] followed by [`crate::serve_web_viewer`].
///
/// ## Example
///
/// ```ignore
Expand Down Expand Up @@ -1905,6 +1910,8 @@ impl RecordingStream {
///
/// To configure the gRPC server's IP and port, use [`Self::serve_grpc_opts`] instead.
///
/// You can connect a viewer to it with `rerun --connect`.
///
/// The gRPC server will buffer all log data in memory so that late connecting viewers will get all the data.
/// You can limit the amount of data buffered by the gRPC server with the `server_memory_limit` argument.
/// Once reached, the earliest logged data will be dropped. Static data is never dropped.
Expand Down
17 changes: 9 additions & 8 deletions crates/top/rerun/src/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,15 @@ impl RerunArgs {
let server_memory_limit = re_memory::MemoryLimit::parse(&self.server_memory_limit)
.map_err(|err| anyhow::format_err!("Bad --server-memory-limit: {err}"))?;

let open_browser = true;
let rec = RecordingStreamBuilder::new(application_id).serve_web(
&self.bind,
Default::default(),
Default::default(),
server_memory_limit,
open_browser,
)?;
let rec = RecordingStreamBuilder::new("rerun_example_minimal_serve")
.serve_grpc_opts(&self.bind, crate::DEFAULT_SERVER_PORT, server_memory_limit)?;

crate::serve_web_viewer(crate::web_viewer::WebViewerConfig {
open_browser: true,
connect_to: Some("rerun+http://localhost:9876/proxy".to_owned()),
..Default::default()
})?
.detach();

// Ensure the server stays alive until the end of the program.
let sleep_guard = ServeGuard {
Expand Down
6 changes: 6 additions & 0 deletions crates/utils/re_uri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ pub use self::{
pub mod external {
pub use url;
}

/// The default port of a Rerun gRPC proxy server.
pub const DEFAULT_PROXY_PORT: u16 = 9876;

/// The default port of a redap server.
pub const DEFAULT_REDAP_PORT: u16 = 51234;
86 changes: 67 additions & 19 deletions crates/utils/re_uri/src/origin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,60 @@ impl Origin {

/// Parses a URL and returns the [`crate::Origin`] and the canonical URL (i.e. one that
/// starts with `http://` or `https://`).
pub(crate) fn replace_and_parse(value: &str) -> Result<(Self, url::Url), Error> {
let scheme: Scheme = value.parse()?;
let rewritten = scheme.canonical_url(value);
pub(crate) fn replace_and_parse(
input: &str,
default_localhost_port: Option<u16>,
) -> Result<(Self, url::Url), Error> {
let scheme: Scheme;
let rewritten;

if !input.contains("://") && (input.contains("localhost") || input.contains("127.0.0.1")) {
// Assume `rerun+http://`, because that is the default for localhost
scheme = Scheme::RerunHttp;
rewritten = format!("http://{input}");
} else {
scheme = input.parse()?;
rewritten = scheme.canonical_url(input);
}

// We have to first rewrite the endpoint, because `Url` does not allow
// `.set_scheme()` for non-opaque origins, nor does it return a proper
// `Origin` in that case.
let mut http_url = url::Url::parse(&rewritten)?;

// If we parse a Url from e.g. `https://redap.rerun.io:443`, `port` in the Url struct will
// be `None`. So we need to use `port_or_known_default` to get the port back.
// See also: https://github.com/servo/rust-url/issues/957
if http_url.port_or_known_default().is_none() {
// If no port is specified, we assume the default redap port:
http_url.set_port(Some(51234)).ok();
let default_port = if is_origin_localhost(&http_url.origin()) {
default_localhost_port
} else if rewritten.starts_with("https://") {
Some(443)
} else if rewritten.starts_with("http://") {
Some(80)
} else {
None
};

if let Some(default_port) = default_port {
// Parsing with a non-standard scheme
// is a hack to work around the `url` crate bug
// <https://github.com/servo/rust-url/issues/957>.
let has_port = if let Some(rest) = http_url.to_string().strip_prefix("http://") {
url::Url::parse(&format!("foobarbaz://{rest}"))?
.port()
.is_some()
} else if let Some(rest) = http_url.to_string().strip_prefix("https://") {
url::Url::parse(&format!("foobarbaz://{rest}"))?
.port()
.is_some()
} else {
true // Should not happen.
};

if !has_port {
http_url.set_port(Some(default_port)).ok();
}
}

let url::Origin::Tuple(_, host, port) = http_url.origin() else {
return Err(Error::UnexpectedOpaqueOrigin(value.to_owned()));
return Err(Error::UnexpectedOpaqueOrigin(input.to_owned()));
};

let origin = Self { scheme, host, port };
Expand All @@ -79,7 +114,7 @@ impl std::str::FromStr for Origin {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::replace_and_parse(value).map(|(origin, _)| origin)
Self::replace_and_parse(value, None).map(|(origin, _)| origin)
}
}

Expand All @@ -92,20 +127,33 @@ impl std::fmt::Display for Origin {
}

fn format_host(host: &url::Host<String>) -> String {
let is_loopback_or_unspecified = match host {
url::Host::Domain(_domain) => false,
url::Host::Ipv4(ip) => ip.is_loopback() || ip.is_unspecified(),
url::Host::Ipv6(ip) => ip.is_loopback() || ip.is_unspecified(),
};
if is_loopback_or_unspecified {
// For instance: we cannot connect to "0.0.0.0",
// so we do this trick:
if is_host_unspecified(host) {
// We usually cannot connect to "0.0.0.0" so we swap it for:
"127.0.0.1".to_owned()
} else {
host.to_string()
}
}

fn is_host_unspecified(host: &url::Host) -> bool {
match host {
url::Host::Domain(_domain) => false,
url::Host::Ipv4(ip) => ip.is_unspecified(),
url::Host::Ipv6(ip) => ip.is_unspecified(),
}
}

fn is_origin_localhost(origin: &url::Origin) -> bool {
match origin {
url::Origin::Opaque(_) => false,
url::Origin::Tuple(_, host, _) => match host {
url::Host::Domain(domain) => domain == "localhost",
url::Host::Ipv4(ip) => ip.is_loopback() || ip.is_unspecified(),
url::Host::Ipv6(ip) => ip.is_loopback() || ip.is_unspecified(),
},
}
}

#[test]
fn test_origin_format() {
assert_eq!(
Expand Down
131 changes: 110 additions & 21 deletions crates/utils/re_uri/src/redap_uri.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
use re_log_types::StoreId;

use crate::{CatalogUri, DatasetDataUri, EntryUri, Error, Fragment, Origin, ProxyUri};
use crate::{
CatalogUri, DatasetDataUri, EntryUri, Error, Fragment, Origin, ProxyUri, DEFAULT_PROXY_PORT,
DEFAULT_REDAP_PORT,
};

/// Parsed from `rerun://addr:port/recording/12345` or `rerun://addr:port/catalog`
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum RedapUri {
/// `/catalog` - also the default if there is no /endpoint
Catalog(CatalogUri),

/// `/entry`
Entry(EntryUri),

/// `/dataset`
DatasetData(DatasetDataUri),

/// We use the `/proxy` endpoint to access another _local_ viewer.
Expand Down Expand Up @@ -53,7 +59,14 @@ impl std::str::FromStr for RedapUri {
type Err = Error;

fn from_str(value: &str) -> Result<Self, Self::Err> {
let (origin, http_url) = Origin::replace_and_parse(value)?;
// Hacky, but I don't want to have to memorize ports.
let default_localhost_port = if value.contains("/proxy") {
DEFAULT_PROXY_PORT
} else {
DEFAULT_REDAP_PORT
};

let (origin, http_url) = Origin::replace_and_parse(value, Some(default_localhost_port))?;

// :warning: We limit the amount of segments, which might need to be
// adjusted when adding additional resources.
Expand Down Expand Up @@ -447,6 +460,101 @@ mod tests {
assert_eq!(address.unwrap(), expected);
}

#[test]
fn test_parsing() {
let test_cases = [
(
"rerun://localhost/catalog",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::Rerun,
host: url::Host::Domain("localhost".to_owned()),
port: DEFAULT_REDAP_PORT,
},
}),
),
(
"localhost",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Domain("localhost".to_owned()),
port: DEFAULT_REDAP_PORT,
},
}),
),
(
"localhost/proxy",
RedapUri::Proxy(ProxyUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Domain("localhost".to_owned()),
port: DEFAULT_PROXY_PORT,
},
}),
),
(
"127.0.0.1/proxy",
RedapUri::Proxy(ProxyUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1)),
port: DEFAULT_PROXY_PORT,
},
}),
),
(
"rerun+http://example.com",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Domain("example.com".to_owned()),
port: 80,
},
}),
),
(
"rerun+https://example.com",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::RerunHttps,
host: url::Host::Domain("example.com".to_owned()),
port: 443,
},
}),
),
(
"rerun://example.com",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::Rerun,
host: url::Host::Domain("example.com".to_owned()),
port: 443,
},
}),
),
(
"rerun://example.com:420/catalog",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::Rerun,
host: url::Host::Domain("example.com".to_owned()),
port: 420,
},
}),
),
];

for (url, expected) in test_cases {
assert_eq!(
url.parse::<RedapUri>()
.unwrap_or_else(|err| panic!("Failed to parse url {url:}: {err}")),
expected,
"Url: {url:?}"
);
}
}

#[test]
fn test_catalog_default() {
let url = "rerun://localhost:51234";
Expand All @@ -468,25 +576,6 @@ mod tests {
assert_eq!(address.unwrap(), expected);
}

#[test]
#[ignore]
// TODO(lucasmerlin): This should ideally work but doesn't right now because of a issue in the `url` crate:
// https://github.com/servo/rust-url/issues/957
// See also `replace_and_parse` in `origin.rs`
fn test_default_port() {
let url = "rerun://localhost";

let expected = RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::Rerun,
host: url::Host::Domain("localhost".to_owned()),
port: 51234,
},
});

assert_eq!(url.parse::<RedapUri>().unwrap(), expected);
}

#[test]
fn test_custom_port() {
let url = "rerun://localhost:123";
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_redap_browser/src/servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ impl RedapServers {
);
} else {
// Since we persist the server list on disk this happens quite often.
// E.g. run `pixi run rerun "rerun+http://localhost:51234"` more than once.
// E.g. run `pixi run rerun "rerun+http://localhost"` more than once.
re_log::debug!(
"Tried to add pre-existing server at {:?}",
origin.to_string()
Expand Down
Loading
Loading