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 12 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
2 changes: 1 addition & 1 deletion crates/top/re_sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub use self::recording_stream::{
};

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

/// The default URL of a Rerun gRPC server.
///
Expand Down
7 changes: 6 additions & 1 deletion crates/top/re_sdk/src/recording_stream.rs
Original file line number Diff line number Diff line change
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
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
3 changes: 3 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,6 @@ 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;
44 changes: 34 additions & 10 deletions crates/utils/re_uri/src/origin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,49 @@ 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) -> 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 mut has_port = true;
{
// Parsing with a non-standard scheme
// is a hack to work around the `url` crate bug
// <https://github.com/servo/rust-url/issues/957>.
if let Some(rest) = http_url.to_string().strip_prefix("http://") {
has_port = url::Url::parse(&format!("foobarbaz://{rest}"))?
.port()
.is_some();
} else if let Some(rest) = http_url.to_string().strip_prefix("https://") {
has_port = url::Url::parse(&format!("foobarbaz://{rest}"))?
.port()
.is_some();
} else {
// Should not happen.
};
}

if !has_port {
// If no port is specified, we assume the default:
http_url.set_port(Some(crate::DEFAULT_PROXY_PORT)).ok();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wild! 🤓

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something here, but maybe we should set the port based on the actual route? I.e. using 9876 for /proxy and 51234 for /recording and /catalog?

Copy link
Member Author

@emilk emilk Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that would be nice, but since Origin doesn't know about that it would require more structural changes (e.g. have Origin parse port to None). I guess I could do an ugly search for /proxy here though 🙈

It would be easier to just switch redap to use the same port as OSS

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't make sense for Origin to even be able to produce a default port. Only RedapUri has sufficient context for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be easier to just switch redap to use the same port as OSS

It can't be on the same port until it's the same service

Copy link
Member Author

@emilk emilk Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be on the same port until it's the same service

They can be the same, as long as we don't run both at the same time on the same machine

Copy link
Member

@jprochazk jprochazk Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as long as we don't run both at the same time on the same machine

Just to clarify, that always happens by default right now. The following:

/dataplatform $ pixi run redap-server
/rerun $ pixi run rerun rerun+http://localhost:51234

will start both a proxy server, and a catalog server. The Viewer starts the proxy server unless it's run with --connect.

So people working on dataplatform are pretty much always running both at the same time on the same machine.

}

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 Down
94 changes: 80 additions & 14 deletions crates/utils/re_uri/src/redap_uri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ mod tests {

#[test]
fn test_localhost_url() {
let url = "rerun+http://localhost:51234/catalog";
let url = "rerun+http://localhost:9876/catalog";
let address: RedapUri = url.parse().unwrap();

assert_eq!(
Expand All @@ -371,7 +371,7 @@ mod tests {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::<String>::Domain("localhost".to_owned()),
port: 51234
port: 9876
}
})
);
Expand All @@ -390,7 +390,7 @@ mod tests {

#[test]
fn test_invalid_path() {
let url = "rerun://0.0.0.0:51234/redap/recordings/12345";
let url = "rerun://0.0.0.0:9876/redap/recordings/12345";
let address: Result<RedapUri, _> = url.parse();

assert!(matches!(
Expand All @@ -401,65 +401,101 @@ mod tests {

#[test]
fn test_proxy_endpoint() {
let url = "rerun://localhost:51234/proxy";
let url = "rerun://localhost:9876/proxy";
let address: Result<RedapUri, _> = url.parse();

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

assert_eq!(address.unwrap(), expected);

let url = "rerun://localhost:51234/proxy/";
let url = "rerun://localhost:9876/proxy/";
let address: Result<RedapUri, _> = url.parse();

assert_eq!(address.unwrap(), expected);
}

#[test]
fn test_catalog_default() {
let url = "rerun://localhost:51234";
let url = "rerun://localhost:9876";
let address: Result<RedapUri, _> = url.parse();

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

assert_eq!(address.unwrap(), expected);

let url = "rerun://localhost:51234/";
let url = "rerun://localhost:9876/";
let address: Result<RedapUri, _> = url.parse();

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,
port: 9876,
},
});

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

#[test]
fn test_default_everything() {
let test_cases = [
(
"localhost",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Domain("localhost".to_owned()),
port: 9876,
},
}),
),
(
"localhost/proxy",
RedapUri::Proxy(ProxyUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Domain("localhost".to_owned()),
port: 9876,
},
}),
),
(
"127.0.0.1/proxy",
RedapUri::Proxy(ProxyUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1)),
port: 9876,
},
}),
),
];

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

#[test]
fn test_custom_port() {
let url = "rerun://localhost:123";
Expand All @@ -474,4 +510,34 @@ mod tests {

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

#[test]
fn test_default_localhost_scheme() {
let test_cases = [
(
"localhost:123",
RedapUri::Catalog(CatalogUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Domain("localhost".to_owned()),
port: 123,
},
}),
),
(
"127.0.0.1:1234/proxy",
RedapUri::Proxy(ProxyUri {
origin: Origin {
scheme: Scheme::RerunHttp,
host: url::Host::Ipv4(Ipv4Addr::new(127, 0, 0, 1)),
port: 1234,
},
}),
),
];

for (url, expected) in test_cases {
assert_eq!(url.parse::<RedapUri>().unwrap(), expected);
}
}
}
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
29 changes: 13 additions & 16 deletions docs/content/reference/sdk/operating-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ Before reading this document, you might want to familiarize yourself with the [R

## Operating modes

The Rerun SDK provides 4 modes of operation: `spawn`, `connect`, `serve`, and `save`.
The Rerun SDK provides 4 modes of operation: `spawn`, `connect`, `serve_grpc`, and `save`.

All four of them are optional: when none of these modes are active, the client will simply buffer the logged data in memory, waiting for one of these modes to be enabled so that it can flush it.

### Spawn
### `spawn`

This is the default behavior you get when running all of our C++/Python/Rust examples, and is generally the most convenient when you're experimenting.

Expand All @@ -30,7 +30,7 @@ Call [`rr.spawn`](https://ref.rerun.io/docs/python/stable/common/initialization_
[`RecordingStream::spawn`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.spawn) spawns a new Rerun Viewer process using an executable available in your PATH, then streams all the data to it via gRPC. If an external Viewer was already running, `spawn` will connect to that one instead of spawning a new one.


### Connect
### `connect`

Connects to a remote Rerun Viewer and streams all the data via gRPC.

Expand All @@ -46,31 +46,28 @@ You will need to start a stand-alone Viewer first by typing `rerun` in your term
[`RecordingStream::connect`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.connect)


### Serve
### `serve_grpc`
Calling `serve_grpc` will start a Rerun gRPC server in your process, and stream logged data to it.
This gRPC server can then be connected to from the Rerun Viewer, e.g. by running `rerun --connect localhost/proxy`.
The gRPC server acts as a proxy, buffering and forwarding log data to the Rerun Viewer.

There are two kinds of `serve` calls:

* `serve_web`
* `serve_grpc`

Both will start a Rerun gRPC server in your process, and stream logged data to it.

The `serve_web` call starts an additional server which hosts the assets required to run the Rerun Viewer in your browser.
You can also connect to the gRPC server from a Rerun Web Viewer.
To host a Rerun Web Viewer, you can use the `serve_web_viewer` function.

#### C++
* [`RecordingStream::serve_grpc`](https://ref.rerun.io/docs/cpp/stable/classrerun_1_1RecordingStream.html).
* `serve_web` is not available.
* `serve_web_viewer` is not available.

#### Python
* [`rr.serve_grpc`](https://ref.rerun.io/docs/python/stable/common/initialization_functions/#rerun.serve_grpc?speculative-link)
* [`rr.serve_web`](https://ref.rerun.io/docs/python/stable/common/initialization_functions/#rerun.serve_web)
* [`rr.serve_web_viewer`](https://ref.rerun.io/docs/python/stable/common/initialization_functions/#rerun.serve_web_viewer?speculative-link)

#### Rust
* [`RecordingStream::serve_grpc`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.serve_grpc?speculative-link)
* [`RecordingStream::serve_web`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.serve_web?speculative-link)
* [`RecordingStream::serve_web_viewer`](https://docs.rs/rerun/latest/rerun/struct.RecordingStream.html#method.serve_web_viewer?speculative-link)


### Save
### `save`

Streams all logging data into an `.rrd` file on disk, which can then be loaded into a stand-alone viewer.

Expand Down
15 changes: 7 additions & 8 deletions examples/rust/minimal_serve/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
use rerun::{demo_util::grid, external::glam};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let open_browser = true;
let rec = rerun::RecordingStreamBuilder::new("rerun_example_minimal_serve").serve_web(
"0.0.0.0",
Default::default(),
Default::default(),
rerun::MemoryLimit::from_fraction_of_total(0.25),
open_browser,
)?;
let rec = rerun::RecordingStreamBuilder::new("rerun_example_minimal_serve").serve_grpc()?;

rerun::serve_web_viewer(rerun::web_viewer::WebViewerConfig {
connect_to: Some("localhost/proxy".to_owned()),
..Default::default()
})?
.detach();

let points = grid(glam::Vec3::splat(-10.0), glam::Vec3::splat(10.0), 10);
let colors = grid(glam::Vec3::ZERO, glam::Vec3::splat(255.0), 10)
Expand Down
Loading
Loading