touché

Touché is a low level but fully featured HTTP 1.0/1.1 library.

It tries to mimic hyper, but with a synchronous API.

For now only the server API is implemented.

Hello world

```rust no_run use touche::{Response, Server, StatusCode};

fn main() -> std::io::Result<()> { Server::bind("0.0.0.0:4444").serve(|_req| { Response::builder() .status(StatusCode::OK) .body("Hello World") }) } ```

Features

Comparison with Hyper

Touché shares a lot of similarities with Hyper:

But also has some key differences:

Other examples

Chunked response

```rust no_run use std::{error::Error, thread};

use touche::{Body, Response, Server, StatusCode};

fn main() -> std::io::Result<()> { Server::bind("0.0.0.0:4444").serve(|_req| { let (channel, body) = Body::channel();

    thread::spawn(move || {
        channel.send("chunk1").unwrap();
        channel.send("chunk2").unwrap();
        channel.send("chunk3").unwrap();
    });

    Response::builder()
        .status(StatusCode::OK)
        .body(body)
})

} ```

Streaming files

```rust no_run use std::{fs, io};

use touche::{Body, Response, Server, StatusCode};

fn main() -> std::io::Result<()> { Server::bind("0.0.0.0:4444").serve(|req| { let file = fs::File::open("./examples/file.rs")?; Ok::<_, io::Error>( Response::builder() .status(StatusCode::OK) .body(Body::tryfrom(file)?) .unwrap(), ) }) } ```

Naive routing with pattern matching

```rust no_run use touche::{body::HttpBody, Body, Method, Request, Response, Server, StatusCode};

fn main() -> std::io::Result<()> { Server::builder() .bind("0.0.0.0:4444") .serve(|req: Request| { match (req.method(), req.uri().path()) { (_, "/") => Response::builder() .status(StatusCode::OK) .body(Body::from("Usage: curl -d hello localhost:4444/echo\n")),

            // Responds with the same payload
            (&Method::POST, "/echo") => Response::builder()
                .status(StatusCode::OK)
                .body(req.into_body()),

            // Responds with the reversed payload
            (&Method::POST, "/reverse") => {
                let body = req.into_body().into_bytes().unwrap_or_default();

                match std::str::from_utf8(&body) {
                    Ok(message) => Response::builder()
                        .status(StatusCode::OK)
                        .body(message.chars().rev().collect::<String>().into()),

                    Err(err) => Response::builder()
                        .status(StatusCode::BAD_REQUEST)
                        .body(err.to_string().into()),
                }
            }

            _ => Response::builder()
                .status(StatusCode::NOT_FOUND)
                .body(Body::empty()),
        }
    })

}

```

Response upgrades

```rust no_run use std::io::{BufRead, BufReader, BufWriter, Write};

use touche::{header, upgrade::Upgrade, Body, Connection, Response, Server, StatusCode};

fn main() -> std::io::Result<()> { Server::bind("0.0.0.0:4444").serve(|req| { Response::builder() .status(StatusCode::SWITCHINGPROTOCOLS) .header(header::UPGRADE, "line-protocol") .upgrade(|stream: Connection| { let reader = BufReader::new(stream.clone()); let mut writer = BufWriter::new(stream);

            // Just a simple protocol that will echo every line sent
            for line in reader.lines() {
                match line {
                    Ok(line) if line.as_str() == "quit" => break,
                    Ok(line) => {
                        writer.write_all(format!("{line}\n").as_bytes());
                        writer.flush();
                    }
                    Err(_err) => break,
                };
            }
        })
        .body(Body::empty())
})

} ```

You can find other examples in the examples directory.

Performance

While the primary focus is having a simple and readable implementation, the library shows some decent performance.

A simple benchmark of the hello_world.rs example gives the following result:

```sh $ cat /proc/cpuinfo | grep name | uniq model name : AMD Ryzen 5 5600G with Radeon Graphics

$ wrk --latency -t6 -c 200 -d 10s http://localhost:4444 Running 10s test @ http://localhost:4444 6 threads and 200 connections Thread Stats Avg Stdev Max +/- Stdev Latency 153.37us 391.20us 19.41ms 99.37% Req/Sec 76.11k 13.21k 89.14k 82.67% Latency Distribution 50% 126.00us 75% 160.00us 90% 209.00us 99% 360.00us 4544074 requests in 10.01s, 225.35MB read Requests/sec: 454157.11 Transfer/sec: 22.52MB ```

The result is on par with Hyper's hello world running on the same machine.

Disclaimer

This library is by no means a critique to Hyper or to async Rust. I really love both of them.

The main motivation I had to write this library was to be able to introduce Rust to my co-workers (which are mainly web developers). A synchronous library is way more beginner friendly than an async one, and by having an API that ressembles the "canonical" HTTP Rust library, people can learn Rust concepts in a easier way before adventuring through Hyper and async.