-
Notifications
You must be signed in to change notification settings - Fork 440
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
rustdoc: offer web server as a Bazel binary #472
Comments
Looks great to me! But I gotta wonder, could the same thing be implemented in Rust? 😉 |
Yep, that’s a reasonable question. I think the answer is, “yes, but not For instance: how would The runtime performance of the docs web server is not critical, so Since Bazel has native support for Python ( |
I think the compile time cost is a good call out. Do you know how Even though Bazel currently has |
Right,
or replace I guess it’s technically true that you can visit a
Not sure what you mean; please clarify? There’s no
Eh. That’s one interpretation. Another is that that flag was supposed to |
I’ve given this a shot: Rust `zipserver`Cargo.toml[package]
name = "zipserver"
version = "0.1.0"
authors = ["William Chargin <[email protected]>"]
edition = "2018"
[dependencies]
anyhow = "1.0.34"
hyper = "0.13"
mime_guess = "2.0.3"
tokio = { version = "0.2", features = ["full"] }
zip = "0.5.8" src/main.rsuse anyhow::Context;
use hyper::{http, Body, Request, Response, Server};
use std::collections::HashMap;
use std::convert::Infallible;
use std::fs::File;
use std::io::{BufReader, Read};
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use zip::ZipArchive;
struct State {
data: HashMap<String, Vec<u8>>,
root_redirect: String,
}
async fn hello(state: &'static State, req: Request<Body>) -> Result<Response<Body>, http::Error> {
let path = req.uri().path().strip_prefix("/").unwrap_or_default();
if path.is_empty() || path == "/" {
return Ok(Response::builder()
.status(302)
.header("Location", &state.root_redirect)
.body(Body::from(b"302 Found\n".as_ref()))
.unwrap());
}
let data = match state.data.get(path) {
Some(v) => v,
None => {
return Ok(Response::builder()
.status(404)
.body(Body::from(b"404 Not Found\n".as_ref()))
.unwrap());
}
};
let ext = path.rsplit('.').next().unwrap_or("");
let mime = mime_guess::from_ext(ext)
.first()
.map(|x| x.to_string())
.unwrap_or(String::new());
let res = Response::builder()
.header("Content-Type", mime)
.body(Body::from(data.as_slice()))
.unwrap();
Ok(res)
}
fn read_zip(path: &Path) -> anyhow::Result<HashMap<String, Vec<u8>>> {
let mut result = HashMap::new();
let mut zip = ZipArchive::new(BufReader::new(File::open(path)?))?;
for i in 0..zip.len() {
let mut file = zip
.by_index(i)
.with_context(|| format!("zip file entry {}", i))?;
let name = file.name().to_string();
let mut buf = Vec::new();
file.read_to_end(&mut buf)
.with_context(|| format!("reading zip entry {:?}", name))?;
result.insert(name, buf);
}
Ok(result)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut args = std::env::args_os().skip(1).collect::<Vec<_>>();
if args.len() != 2 && args.len() != 3 {
eprintln!("usage: zipserver <zip-file> <root-redirect> [<port>]");
std::process::exit(1);
}
let zip_path = PathBuf::from(std::mem::take(&mut args[0]));
let root_redirect = args[1]
.to_str()
.expect("root redirect must be valid Unicode")
.to_string();
let port: u16 = args
.get(2)
.map(|x| x.to_str().expect("port must be valid Unicode"))
.unwrap_or("8000")
.parse()
.expect("port must be a valid u16");
let state = State {
data: read_zip(&zip_path)?,
root_redirect,
};
eprintln!(
"Read {} files from {}",
state.data.len(),
zip_path.display()
);
let state = &*Box::leak(Box::new(state));
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let make_service = hyper::service::make_service_fn(|_conn| async move {
Ok::<_, Infallible>(hyper::service::service_fn(move |req| hello(state, req)))
});
let server = Server::bind(&addr).serve(make_service);
println!("Serving on port {}", server.local_addr().port());
server.await?;
Ok(())
} With the dependencies fetched but not compiled, this clean-builds in |
(Oh, and that pulls in 68 transitive dependencies.) |
I was suggesting having something like
I feel strongly that a rules repository should adhere to every incompatibility flag. If this is something that's not actually going to be flipped, then someone should ping that issue and ask for the flag to be removed.
There's definitely no discussion to be had around the time difference of a compiled and non-compiled language. Compiling a web server was always going to come at some noticeable cost. I think the clear distinction here is you're looking for something that improves remove developer environments (ssh'ed into another machine) and not necessarily tying to address a gap in functionality between I do feel you've shined light on the issue that viewing the output of |
Cool; that sounds pretty reasonable. Happy to comply. Thanks for your quick responses and the helpful discussion! I’ll start |
A `rust_doc` rule emits its output into a zip archive so that the build graph can be statically known, but this makes it hard to actually view the documentation. This patch adds a `rust_doc_server` macro, a sibling to `rust_doc` that spins up a tiny web server that reads from the zip file. The server can run under `ibazel` and automatically restart when the documentation sources are updated. This is written in Python for a simple cross-platform solution that doesn’t require compiling a Rust web stack, which takes about 20 seconds on my workstation (Xeon W-2135). Fixes bazelbuild#472. Test Plan: From `examples/`, run `bazel run //hello_world:hello_world_doc_server` and note that the server responds properly. Run with `-- --port 0` (extra `--` for `bazel run`) and note that the server properly prints its actual port. wchargin-branch: rustdoc-server wchargin-source: 307dcd532d0f7880b1cceaf0421a51416607b44f
A
rust_doc
rule spits out a zip file, but that’s not convenient foractually consulting the docs. It would be great for
rust_doc
toprovide a Bazel binary that can be run to spin up a web server.
Here is one approach. From the Bazel rule, provide this Python file:
Then generate a
py_binary
build target:Then, users can
bazel run :mylib_doc_server
to start a server, andoptionally pass a port argument. Or, use
ibazel
instead ofbazel
,and then whenever you change one of the Rust sources, the Rustdoc will
automatically recompile and the server will automatically restart.
This depends on #471; without that fix, this will be harder.
Thoughts? Are you open to this change? Is it okay to use Python here?
Note that the above simple server only uses the Python standard library.
Happy to contribute a PR, and I license all this code as Apache 2.0.
The text was updated successfully, but these errors were encountered: