Performance serving static files with Rust and Hyper
Initial feedback:


Reading to a string is wasteful if you just want to dump data. A Vec is simpler since you don't care about UTF-8 validity; this will also allow for images to be served.
Returning "Not found" for a failure to read seems like a lie. Should be a HTTP 5xx-series error.
Avoid code duplication by returning the content type from the match and setting it afterward.
text/plain is an unconventional default content type, generally I see application/octet-stream.
Extract a constant for the default content type instead of repeating it.


Turning towards performance, start by performing a base-line performance test of a failure URL:

$ wrk http://127.0.0.1:5000/nothing
Running 10s test @ http://127.0.0.1:5000/nothing
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   290.75us   44.99us   1.50ms   90.46%
    Req/Sec    17.00k   537.99    18.05k    73.76%
  341686 requests in 10.10s, 26.72MB read
  Non-2xx or 3xx responses: 341686
Requests/sec:  33831.33
Transfer/sec:      2.65MB


Then a test with an existing but blank file:

$ wrk http://127.0.0.1:5000/exists
Running 10s test @ http://127.0.0.1:5000/exists
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   432.23us   77.26us   1.67ms   85.00%
    Req/Sec    11.49k   586.77    12.74k    65.84%
  231023 requests in 10.10s, 25.34MB read
Requests/sec:  22874.91
Transfer/sec:      2.51MB


Then a test with a 1 MiB file:

$ wrk http://127.0.0.1:5000/big
Running 10s test @ http://127.0.0.1:5000/big
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    38.58ms    5.60ms  94.66ms   95.67%
    Req/Sec   129.41     16.53   171.00     85.50%
  2595 requests in 10.06s, 2.53GB read
Requests/sec:    257.93
Transfer/sec:    257.96MB


This isn't terrible. The biggest problem I see is that you are using an asynchronous library in a completely synchronous manner. Using a threadpool to offload loading the files increases throughput to ~3x of the previous value:

$ wrk http://127.0.0.1:5000/big
Running 10s test @ http://127.0.0.1:5000/big
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    13.60ms    3.75ms  62.03ms   84.27%
    Req/Sec   370.48     43.96   415.00     89.50%
  7403 requests in 10.04s, 7.23GB read
Requests/sec:    737.26
Transfer/sec:    737.35MB


Its possible caching might help even further, but I didn't attempt it.

extern crate futures;
extern crate futures_spawn;
extern crate futures_threadpool;
extern crate hyper;

use futures::Future;
use futures_spawn::SpawnHelper;
use futures_threadpool::ThreadPool;
use hyper::StatusCode;
use hyper::server::{Http, Request, Response, Service};
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::sync::Arc;

struct Router {
    pool: ThreadPool,
}

impl Service for Router {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box&lt;Future&lt;Item = Self::Response, Error = Self::Error&gt;&gt;;

    fn call(&amp;self, req: Request) -&gt; Self::Future {
        let full_path = "src/static".to_owned() + req.path();

        let data = self.pool.spawn_fn(move || {
            let mut f = File::open(full_path)?;

            let mut data = Vec::new();
            f.read_to_end(&amp;mut data)?;
            Ok(data)
        });

        let mut headers = hyper::header::Headers::new();

        const UNKNOWN_CONTENT_TYPE: &amp;str = "text/plain";
        let content_type = match Path::new(req.path()).extension().and_then(OsStr::to_str) {
            Some(ext) =&gt; match ext {
                "html" =&gt; "text/html",
                "css" =&gt; "text/css",
                "js" =&gt; "application/javascript",
                _ =&gt; UNKNOWN_CONTENT_TYPE,
            },
            None =&gt; UNKNOWN_CONTENT_TYPE,
        };
        headers.set_raw("Content-Type", content_type);

        let r = data.map(|data| {
            Response::new()
                .with_status(StatusCode::Ok)
                .with_headers(headers)
                .with_body(data)
        });

        Box::new(r)
    }
}

fn main() {
    let addr = "127.0.0.1:5000".parse().unwrap();

    let router = Arc::new(Router {
        pool: ThreadPool::new_num_cpus(),
    });

    let server = Http::new()
        .bind(&amp;addr, move || Ok(Arc::clone(&amp;router)))
        .unwrap();

    println!(
        "Listening on http://{} with 1 thread...",
        server.local_addr().unwrap()
    );
    server.run().unwrap();
}