@@ -12,6 +12,8 @@ use std::task::{Context, Poll};
1212use futures_util:: future;
1313use hyper:: { server:: conn:: AddrIncoming , service:: Service , Body , Request , Response } ;
1414
15+ pub const DEFAULT_WEB_VIEWER_PORT : u16 = 9090 ;
16+
1517#[ cfg( not( feature = "__ci" ) ) ]
1618mod data {
1719 // If you add/remove/change the paths here, also update the include-list in `Cargo.toml`!
@@ -32,6 +34,18 @@ mod data {
3234 pub const VIEWER_WASM_RELEASE : & [ u8 ] = include_bytes ! ( "../web_viewer/re_viewer_bg.wasm" ) ;
3335}
3436
37+ #[ derive( thiserror:: Error , Debug ) ]
38+ pub enum WebViewerServerError {
39+ #[ error( "Could not parse address: {0}" ) ]
40+ AddrParseFailed ( #[ from] std:: net:: AddrParseError ) ,
41+ #[ error( "failed to bind to port {0}: {1}" ) ]
42+ BindFailed ( u16 , hyper:: Error ) ,
43+ #[ error( "failed to join web viewer server task: {0}" ) ]
44+ JoinError ( #[ from] tokio:: task:: JoinError ) ,
45+ #[ error( "failed to serve web viewer: {0}" ) ]
46+ ServeFailed ( hyper:: Error ) ,
47+ }
48+
3549struct Svc {
3650 // NOTE: Optional because it is possible to have the `analytics` feature flag enabled
3751 // while at the same time opting-out of analytics at run-time.
@@ -149,27 +163,72 @@ impl<T> Service<T> for MakeSvc {
149163
150164// ----------------------------------------------------------------------------
151165
152- /// Hosts the Web Viewer Wasm+HTML
166+ /// HTTP host for the Rerun Web Viewer application
167+ /// This serves the HTTP+WASM+JS files that make up the web-viewer.
153168pub struct WebViewerServer {
154169 server : hyper:: Server < AddrIncoming , MakeSvc > ,
155170}
156171
157172impl WebViewerServer {
158- pub fn new ( port : u16 ) -> Self {
159- let bind_addr = format ! ( "0.0.0.0:{port}" ) . parse ( ) . unwrap ( ) ;
160- let server = hyper:: Server :: bind ( & bind_addr) . serve ( MakeSvc ) ;
161- Self { server }
173+ pub fn new ( port : u16 ) -> Result < Self , WebViewerServerError > {
174+ let bind_addr = format ! ( "0.0.0.0:{port}" ) . parse ( ) ?;
175+ let server = hyper:: Server :: try_bind ( & bind_addr)
176+ . map_err ( |e| WebViewerServerError :: BindFailed ( port, e) ) ?
177+ . serve ( MakeSvc ) ;
178+ Ok ( Self { server } )
162179 }
163180
164181 pub async fn serve (
165182 self ,
166183 mut shutdown_rx : tokio:: sync:: broadcast:: Receiver < ( ) > ,
167- ) -> anyhow :: Result < ( ) > {
184+ ) -> Result < ( ) , WebViewerServerError > {
168185 self . server
169186 . with_graceful_shutdown ( async {
170187 shutdown_rx. recv ( ) . await . ok ( ) ;
171188 } )
172- . await ?;
189+ . await
190+ . map_err ( WebViewerServerError :: ServeFailed ) ?;
173191 Ok ( ( ) )
174192 }
175193}
194+
195+ /// Sync handle for the [`WebViewerServer`]
196+ ///
197+ /// When dropped, the server will be shut down.
198+ pub struct WebViewerServerHandle {
199+ port : u16 ,
200+ shutdown_tx : tokio:: sync:: broadcast:: Sender < ( ) > ,
201+ }
202+
203+ impl Drop for WebViewerServerHandle {
204+ fn drop ( & mut self ) {
205+ re_log:: info!( "Shutting down web server on port {}." , self . port) ;
206+ self . shutdown_tx . send ( ( ) ) . ok ( ) ;
207+ }
208+ }
209+
210+ impl WebViewerServerHandle {
211+ /// Create new [`WebViewerServer`] to host the Rerun Web Viewer on a specified port.
212+ ///
213+ /// A port of 0 will let the OS choose a free port.
214+ ///
215+ /// The caller needs to ensure that there is a `tokio` runtime running.
216+ pub fn new ( requested_port : u16 ) -> Result < Self , WebViewerServerError > {
217+ let ( shutdown_tx, shutdown_rx) = tokio:: sync:: broadcast:: channel ( 1 ) ;
218+
219+ let web_server = WebViewerServer :: new ( requested_port) ?;
220+
221+ let port = web_server. server . local_addr ( ) . port ( ) ;
222+
223+ tokio:: spawn ( async move { web_server. serve ( shutdown_rx) . await } ) ;
224+
225+ re_log:: info!( "Started web server on port {}." , port) ;
226+
227+ Ok ( Self { port, shutdown_tx } )
228+ }
229+
230+ /// Get the port where the web assets are hosted
231+ pub fn port ( & self ) -> u16 {
232+ self . port
233+ }
234+ }
0 commit comments