create a http server and upload a file in rust

To create an HTTP server and upload a file in Rust, you can use the Hyper crate. The following code illustrates how to create a basic server that accepts file uploads:

main.rs
extern crate hyper;
extern crate multipart;

use hyper::{Body, Request, Response, Server};
use hyper::rt::Future;
use hyper::service::service_fn;
use multipart::server::{Field, Multipart};

use std::fs::File;
use std::io::{self, Write};
use std::str;

fn main() {
    let addr = ([127, 0, 0, 1], 3000).into();

    let new_service = || {
        service_fn(|req| {
            if req.method() == hyper::Method::POST {
                handle_file_upload(req)
            } else {
                handle_not_found()
            }
        })
    };

    let server = Server::bind(&addr).serve(new_service);

    let server = server.map_err(drop);

    hyper::rt::run(server);
}

fn handle_file_upload(req: Request<Body>) -> impl Future<Item=Response<Body>, Error=hyper::Error> {
    let boundary = extract_boundary(req.headers().get("content-type"));
    let multipart = Multipart::with_body(req.into_body(), boundary);

    let mut response_body = Vec::new();

    let process_field = |field: Field| -> io::Result<()> {
        let filename = match field.filename {
            Some(filename) => filename,
            None => return Ok(()),
        };

        let filepath = format!("./uploads/{}", filename);
        let mut file = File::create(filepath)?;

        io::copy(&mut field.data, &mut file)?;

        response_body.write_all(format!("Uploaded {}", filename).as_bytes())?;
        Ok(())
    };

    multipart.foreach_entry(process_field)
        .map(move |_| {
            Response::builder()
                .status(200)
                .body(response_body.into())
                .unwrap()
        })
        .map_err(|err| {
            eprintln!("Error handling file upload: {}", err);
            hyper::Error::from(err)
        })
}

fn extract_boundary(content_type: Option<&&str>) -> &'static str {
    match content_type {
        Some(value) => {
            let parts: Vec<&str> = value.split("boundary=").collect();
            match parts.last() {
                Some(boundary) => boundary,
                _ => "no-boundary",
            }
        },
        _ => "no-content-type",
    }
}

fn handle_not_found() -> impl Future<Item=Response<Body>, Error=hyper::Error> {
    let response_body = "404 - NOT FOUND";

    futures::future::ok(
        Response::builder()
            .status(404)
            .body(response_body.into())
            .unwrap()
    )
}
2393 chars
90 lines

The above code creates an HTTP server that listens on 127.0.0.1:3000 and accepts file uploads. Uploaded files are saved in the ./uploads/ directory.

You can use a tool like curl to make a file upload request to the server:

main.rs
curl -v -F "file=@/path/to/file.txt" http://localhost:3000/
60 chars
2 lines

This will upload the file.txt file to the server. If the upload is successful, the server will respond with Uploaded file.txt and the file will be saved to ./uploads/file.txt.

gistlibby LogSnag