Recommended way of including a webserver in a library (with assets, cross platform)?

I'm working on a tracing subscriber that allows visualizing logs in a web gui. I currently have the frontend ready with a Browse... button where people can load their log files, but that is not necessarily the smoothest experience so I wanted to crate a subscriber to avoid saving the log to a file first.

Now I'm wondering if I include a webserver in a lib, it would be nice if it has all the frontend included, so users don't have to manually download that and click a connect button. Basically, when you run it it says "To inspect the log, open localhost:xxxx" on stdout at the start of the progam/test.

So I need to deliver the assets of the frontend in some way when the library is being compiled, in a cross platform manner.

How would you go about that?

The other option would be a cargo integration like cargo tracing_prism --port xxxx which starts an http server and then the tracing subscriber just sends the log entries to that.

I think the most user-friendly approach would be to embedd assets directly into the binary, using a variant of include_bytes! (with maybe a --assets-path CLI option to allow tweaking&overriding for users and for faster iteration during dev).

Seems like https://docs.rs/include_dir/0.6.0/include_dir/ is an implementation of this approach.

2 Likes

Thanks, that's an interesting approach I hadn't really considered. It sure solves the problem in a self contained way.

I probably go for the cargo plugin approach so that doesn't need to be compiled into everything that wants to use the tracing subscriber. That also makes it simpler if later I want to add another frontend, maybe gtk or something, it doesn't require any version bumps on the subscriber and it will keep peoples binaries smaller.

I marked this as solved, as it works pretty well. For simplicity I create a temp dir and unpack the whole directory like so:

/// Extract a directory to a destination.
//
pub fn unpack_dir( src: &Dir<'_>, dst: &Path ) -> std::io::Result<()>
{
	debug!( "unpacking {}", dst.display() );

	if dst.exists()
	{
		std::fs::remove_dir_all( &dst ).unwrap();
	}

	std::fs::create_dir_all( &dst ).unwrap();

	for file in src.files()
	{
		unpack_file( file, &dst ).unwrap();
	}


	// Subdirectories
	//
	for dir in src.dirs()
	{
		let mut dst_buf = PathBuf::from( dst );

		dst_buf.push( dir.path() );

		unpack_dir( dir, &dst_buf ).unwrap();
	}

	Ok(())
}


fn unpack_file( src: &File<'_>, dst: &Path ) -> std::io::Result<()>
{
	let mut dst = dst.to_path_buf();
	dst.push( src.path().file_name().unwrap() );

	debug!( "unpacking {}", dst.display() );

	std::fs::write( dst, src.contents() ).unwrap();

	Ok(())
}

The File and Dir types come from the include_dir crate. I then serve that temp dir with an http server. It works fine with both tide and warp. scopeguard makes sure the directory get's deleted when main returns. Note that this is draft code, you probably don't want to just unwrap after development phase...

My assets are about 1.2MB and I end up with a release binary of 8.4MB. All very reasonable.