Search code examples
rustrust-axum

How can I use a local variable as key to insert into a `http::HeaderMap`?


I'm writing a HTTP server using axum, and am writing a handler. It calls some external program somecommand, writes some string to its stdin, and reads stdout. The stdout output is formatted like a HTTP request (i.e. Header: Value\r\nHeader2: Value\r\n\r\nBody).

I want to turn that stdout output into an axum response.

async fn handle_request() -> impl IntoResponse {
    let mut child = Command::new("somecommand")
    .stdin(Stdio::piped())
    .spawn()
    .expect("Failed to spawn");

    let mut stdin = child.stdin.take().expect("Failed to open stdin");
    stdin
        .write_all("some string".as_bytes())
        .expect("Could not write to stdin");

    let output = child
        .wait_with_output()
        .expect("Failed to wait with output");

    let stdout = String::from_utf8(output.stdout)
        .expect("failed to parse stdout as utf8")
        .to_string();
    let r = stdout.split("\r\n\r\n").collect::<Vec<&str>>();

    let mut headers = HeaderMap::new();

    let lines: Vec<&str> = r[0].lines().collect();
    lines.iter().for_each(|line| {
        let parts: Vec<&str> = line.splitn(2, ": ").collect();
        headers.insert(
            parts[0],
            parts[1].parse().expect("Could not parse header value"),
        );
    });

    (headers, r[1].to_string())
}

The error I'm getting is stdout does not live long enough, which, if I understand things correctly, is because headers.insert(parts[0] ..., because part[0] lives only in this function scope, but since I'm returning it (as part of the headers map) it must have a longer lifetime.

So how do I increase the lifetime of parts[0] (or stdout) so that I can return it?


Solution

  • The problem is slightly different from what you think, the keys for a HeaderMap::insert must implement IntoHeaderName and only &'static str, HeaderName and &HeaderName do, so you can't use a non static &str to insert a value irrespective of wether you return that map or not. Here is a minimal example that produces the same error:

    use http::HeaderMap;
    fn baz() {
        let header = String::from("header");
        let header = header.as_str();
        let mut headers = HeaderMap::new();
        headers.insert(header, "value".parse().unwrap());
    }
    

    But you can construct a HeaderName yourself:

    use http::{HeaderMap, HeaderName};
    fn baz() {
        let header = String::from("header");
        let header = header.as_str();
        let mut headers = HeaderMap::new();
        headers.insert(
            HeaderName::from_bytes(header.as_bytes()).unwrap(),
            "value".parse().unwrap(),
        );
    }