├── .gitignore ├── examples ├── echo.rs ├── single-thread.rs ├── file.rs ├── hello-world.rs ├── websocket.rs ├── connection.rs ├── trailers.rs ├── chunked.rs ├── unix-socket.rs ├── sse.html ├── expect-continue.rs ├── compress.rs ├── upgrade.rs ├── websocket-chat.html ├── routes.rs ├── sse-chat.html ├── websocket-tokio.rs ├── tls.rs ├── sse.rs ├── tls │ ├── cert.pem │ └── key.pem ├── tunnel.rs ├── websocket-chat.rs └── sse-chat.rs ├── src ├── lib.rs ├── upgrade.rs ├── tls.rs ├── read_queue.rs ├── body │ └── http_body.rs ├── connection.rs ├── client.rs ├── request.rs ├── body.rs ├── server.rs └── response.rs ├── LICENSE ├── Cargo.toml ├── .github └── workflows │ └── ci.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /examples/unis-socket.socket 4 | -------------------------------------------------------------------------------- /examples/echo.rs: -------------------------------------------------------------------------------- 1 | use touche::{Request, Response, Server, StatusCode}; 2 | 3 | fn main() -> std::io::Result<()> { 4 | Server::bind("0.0.0.0:4444").serve(|req: Request<_>| { 5 | Response::builder() 6 | .status(StatusCode::OK) 7 | .body(req.into_body()) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /examples/single-thread.rs: -------------------------------------------------------------------------------- 1 | use touche::{Response, Server, StatusCode}; 2 | 3 | fn main() -> std::io::Result<()> { 4 | let mut counter = 0; 5 | Server::bind("0.0.0.0:4444").serve_single_thread(|_| { 6 | counter += 1; 7 | Response::builder() 8 | .status(StatusCode::OK) 9 | .body(format!("Request count: {}", counter)) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /examples/file.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use touche::{Body, Response, Server, StatusCode}; 4 | 5 | fn main() -> std::io::Result<()> { 6 | Server::bind("0.0.0.0:4444").serve(|_req| { 7 | match Body::try_from(PathBuf::from("./examples/file.rs")) { 8 | Ok(file) => Response::builder().status(StatusCode::OK).body(file), 9 | Err(_) => Response::builder() 10 | .status(StatusCode::NOT_FOUND) 11 | .body(Body::empty()), 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /examples/hello-world.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use touche::{Response, Server, StatusCode}; 4 | 5 | fn main() -> std::io::Result<()> { 6 | let threads = match env::var("THREADS") { 7 | Ok(threads) => threads.parse::().expect("Invalid THREADS value"), 8 | Err(_) => 100, 9 | }; 10 | 11 | Server::builder() 12 | .max_threads(threads) 13 | .bind("0.0.0.0:4444") 14 | .serve(|_req| { 15 | Response::builder() 16 | .status(StatusCode::OK) 17 | .body("Hello, world!") 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![doc = include_str!("../README.md")] 3 | 4 | pub mod body; 5 | #[cfg(feature = "client")] 6 | pub mod client; 7 | mod connection; 8 | mod read_queue; 9 | mod request; 10 | mod response; 11 | #[cfg(feature = "server")] 12 | pub mod server; 13 | #[cfg(feature = "rustls")] 14 | mod tls; 15 | pub mod upgrade; 16 | 17 | pub use body::Body; 18 | pub use body::HttpBody; 19 | #[cfg(feature = "client")] 20 | pub use client::Client; 21 | pub use connection::Connection; 22 | #[doc(hidden)] 23 | pub use http; 24 | #[doc(no_inline)] 25 | pub use http::HeaderMap; 26 | pub use http::{header, Method, Request, Response, StatusCode, Uri, Version}; 27 | #[cfg(feature = "server")] 28 | pub use server::Server; 29 | -------------------------------------------------------------------------------- /examples/websocket.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use touche::{upgrade::Upgrade, Body, Connection, Request, Server}; 4 | use tungstenite::{protocol::Role, WebSocket}; 5 | 6 | fn main() -> std::io::Result<()> { 7 | Server::bind("0.0.0.0:4444").serve(|req: Request| { 8 | let res = tungstenite::handshake::server::create_response(&req.map(|_| ()))?; 9 | 10 | Ok::<_, Box>(res.upgrade(|stream: Connection| { 11 | let mut ws = WebSocket::from_raw_socket(stream, Role::Server, None); 12 | 13 | while let Ok(msg) = ws.read() { 14 | if msg.is_text() && ws.send(msg).is_err() { 15 | break; 16 | } 17 | } 18 | })) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /examples/connection.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use touche::{Connection, Response, Server, StatusCode}; 4 | 5 | fn main() -> std::io::Result<()> { 6 | Server::builder() 7 | .bind("0.0.0.0:4444") 8 | // The explicit type is necessary due a regression 9 | // See: https://github.com/rust-lang/rust/issues/81511 10 | .make_service(move |_conn: &Connection| { 11 | // We are now allowed to have mutable state inside this connection 12 | let mut counter = 0; 13 | 14 | Ok::<_, Infallible>(move |_req| { 15 | counter += 1; 16 | 17 | Response::builder() 18 | .status(StatusCode::OK) 19 | .body(format!("Requests on this connection: {counter}")) 20 | }) 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /examples/trailers.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, thread}; 2 | 3 | use touche::{Body, Response, Server, StatusCode}; 4 | 5 | fn main() -> std::io::Result<()> { 6 | Server::bind("0.0.0.0:4444").serve(|_req| { 7 | let (channel, body) = Body::channel(); 8 | 9 | thread::spawn(move || { 10 | let mut md5 = md5::Context::new(); 11 | for chunk in ["chunk1", "chunk2", "chunk3"] { 12 | channel.send(chunk)?; 13 | md5.consume(chunk); 14 | } 15 | channel.send_trailer("content-md5", base64::encode(*md5.compute()))?; 16 | Ok::<_, Box>(()) 17 | }); 18 | 19 | Response::builder() 20 | .status(StatusCode::OK) 21 | .header("trailers", "content-md5") 22 | .body(body) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /examples/chunked.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, thread, time::Duration}; 2 | 3 | use touche::{Body, Response, Server, StatusCode}; 4 | 5 | fn main() -> std::io::Result<()> { 6 | Server::bind("0.0.0.0:4444").serve(|_req| { 7 | let (channel, body) = Body::channel(); 8 | 9 | thread::spawn(move || { 10 | channel.send("chunk1")?; 11 | thread::sleep(Duration::from_secs(1)); 12 | channel.send("chunk2")?; 13 | thread::sleep(Duration::from_secs(1)); 14 | channel.send("chunk3")?; 15 | Ok::<_, Box>(()) 16 | }); 17 | 18 | Response::builder() 19 | .status(StatusCode::OK) 20 | // Disable buffering on Chrome 21 | .header("X-Content-Type-Options", "nosniff") 22 | .body(body) 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /examples/unix-socket.rs: -------------------------------------------------------------------------------- 1 | // Run with: curl --unix-socket examples/unix-socket.socket http://localhost 2 | #[cfg(feature = "unix-sockets")] 3 | fn main() -> std::io::Result<()> { 4 | use std::{fs, os::unix::net::UnixListener}; 5 | use touche::{Response, Server, StatusCode}; 6 | 7 | fs::remove_file("./examples/unix-socket.socket").ok(); 8 | 9 | let listener = UnixListener::bind("./examples/unix-socket.socket")?; 10 | 11 | Server::builder() 12 | .max_threads(100) 13 | .from_connections(listener.incoming().filter_map(|conn| conn.ok())) 14 | .serve(|_req| { 15 | Response::builder() 16 | .status(StatusCode::OK) 17 | .body("Hello from Unix socket!") 18 | }) 19 | } 20 | 21 | #[cfg(not(feature = "unix-sockets"))] 22 | fn main() { 23 | println!("This example requires the unix-sockets feature to be enabled"); 24 | } 25 | -------------------------------------------------------------------------------- /examples/sse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/expect-continue.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use headers::HeaderMapExt; 4 | use touche::{server::Service, Body, Request, Response, Server, StatusCode}; 5 | 6 | #[derive(Clone)] 7 | struct UploadService { 8 | max_length: u64, 9 | } 10 | 11 | impl Service for UploadService { 12 | type Body = &'static str; 13 | type Error = Infallible; 14 | 15 | fn call(&mut self, _req: Request) -> Result, Self::Error> { 16 | Ok(Response::builder() 17 | .status(StatusCode::OK) 18 | .body("Thanks for the info!") 19 | .unwrap()) 20 | } 21 | 22 | fn should_continue(&mut self, req: &Request) -> StatusCode { 23 | match req.headers().typed_get::() { 24 | Some(len) if len.0 <= self.max_length => StatusCode::CONTINUE, 25 | _ => StatusCode::EXPECTATION_FAILED, 26 | } 27 | } 28 | } 29 | 30 | fn main() -> std::io::Result<()> { 31 | Server::bind("0.0.0.0:4444").serve(UploadService { max_length: 1024 }) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Rodrigo Navarro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/compress.rs: -------------------------------------------------------------------------------- 1 | use touche::{ 2 | body::{ChunkIterator, HttpBody}, 3 | Response, Server, StatusCode, 4 | }; 5 | 6 | use flate2::read::GzEncoder; 7 | use flate2::Compression; 8 | 9 | struct Compressed(Body); 10 | 11 | impl HttpBody for Compressed 12 | where 13 | Body: HttpBody, 14 | Body::Reader: 'static, 15 | { 16 | type Reader = GzEncoder; 17 | type Chunks = ChunkIterator; 18 | 19 | fn len(&self) -> Option { 20 | None 21 | } 22 | 23 | fn into_reader(self) -> Self::Reader { 24 | GzEncoder::new(self.0.into_reader(), Compression::fast()) 25 | } 26 | 27 | fn into_chunks(self) -> Self::Chunks { 28 | ChunkIterator::from_reader(self.into_reader(), None) 29 | } 30 | } 31 | 32 | fn main() -> std::io::Result<()> { 33 | Server::bind("0.0.0.0:4444").serve(|_req| { 34 | Response::builder() 35 | .status(StatusCode::OK) 36 | .header("content-type", "text/plain") 37 | .header("content-encoding", "gzip") 38 | .body(Compressed(include_bytes!("./compress.rs").as_ref())) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /examples/upgrade.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, BufReader, BufWriter, Write}; 2 | 3 | use touche::{header, upgrade::Upgrade, Body, Connection, Response, Server, StatusCode}; 4 | 5 | fn main() -> std::io::Result<()> { 6 | Server::bind("0.0.0.0:4444").serve(|_req| { 7 | Response::builder() 8 | .status(StatusCode::SWITCHING_PROTOCOLS) 9 | .header(header::UPGRADE, "line-protocol") 10 | .upgrade(|stream: Connection| { 11 | let reader = BufReader::new(stream.clone()); 12 | let mut writer = BufWriter::new(stream); 13 | 14 | // Just a simple protocol that will echo every line sent 15 | for line in reader.lines() { 16 | match line { 17 | Ok(line) if line.as_str() == "quit" => break, 18 | Ok(line) => { 19 | writer.write_all(format!("{line}\n").as_bytes()).unwrap(); 20 | writer.flush().unwrap(); 21 | } 22 | Err(_err) => break, 23 | }; 24 | } 25 | }) 26 | .body(Body::empty()) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "touche" 3 | version = "0.0.15" 4 | edition = "2021" 5 | authors = ["Rodrigo Navarro "] 6 | license = "MIT" 7 | description = "Synchronous HTTP library" 8 | repository = "https://github.com/reu/touche" 9 | categories = ["network-programming", "web-programming"] 10 | keywords = ["web", "http", "protocol"] 11 | 12 | [features] 13 | default = ["server", "threadpool"] 14 | full = ["client", "server"] 15 | threadpool = ["dep:threadpool"] 16 | server = [] 17 | unix-sockets = [] 18 | client = [] 19 | 20 | [package.metadata.docs.rs] 21 | all-features = true 22 | rustdoc-args = ["--cfg", "docsrs"] 23 | 24 | [dependencies] 25 | bytes = { version = "1", optional = true } 26 | headers = "0.4" 27 | http = "1" 28 | httparse = "1.9" 29 | rustls = { version = "0.23", optional = true, default-features = false, features = ["std"] } 30 | thiserror = "1.0.31" 31 | threadpool = { version = "1.8.1", optional = true, default-features = false } 32 | 33 | [dev-dependencies] 34 | base64 = "0.13.0" 35 | flate2 = "1.0.24" 36 | futures = "0.3.28" 37 | indoc = "1.0.6" 38 | md5 = "0.7.0" 39 | rustls = { version = "0.23", default-features = false, features = ["std", "ring"] } 40 | rustls-pki-types= "1.10" 41 | serde = { version = "1", features = ["derive"] } 42 | serde_json = "1" 43 | tokio = { version = "1.43", features = ["full"] } 44 | tokio-tungstenite = "0.26" 45 | tungstenite = "0.26" 46 | -------------------------------------------------------------------------------- /examples/websocket-chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 35 | 36 | 37 | 38 |
39 | 40 | 41 |
42 | 43 |
    44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/routes.rs: -------------------------------------------------------------------------------- 1 | use touche::{body::HttpBody, Body, Method, Request, Response, Server, StatusCode}; 2 | 3 | fn main() -> std::io::Result<()> { 4 | Server::builder() 5 | .bind("0.0.0.0:4444") 6 | .serve(|req: Request| { 7 | match (req.method(), req.uri().path()) { 8 | (_, "/") => Response::builder() 9 | .status(StatusCode::OK) 10 | .body(Body::from("Usage: curl -d hello localhost:4444/echo\n")), 11 | 12 | // Responds with the same payload 13 | (&Method::POST, "/echo") => Response::builder() 14 | .status(StatusCode::OK) 15 | .body(req.into_body()), 16 | 17 | // Responds with the reversed payload 18 | (&Method::POST, "/reverse") => { 19 | let body = req.into_body().into_bytes().unwrap_or_default(); 20 | 21 | match std::str::from_utf8(&body) { 22 | Ok(message) => Response::builder() 23 | .status(StatusCode::OK) 24 | .body(message.chars().rev().collect::().into()), 25 | 26 | Err(err) => Response::builder() 27 | .status(StatusCode::BAD_REQUEST) 28 | .body(err.to_string().into()), 29 | } 30 | } 31 | 32 | _ => Response::builder() 33 | .status(StatusCode::NOT_FOUND) 34 | .body(Body::empty()), 35 | } 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /examples/sse-chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 37 | 38 | 39 | 40 |
    41 | 42 | 43 |
    44 | 45 |
      46 | 47 | 48 | -------------------------------------------------------------------------------- /src/upgrade.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use thiserror::Error; 4 | 5 | use crate::connection::Connection; 6 | 7 | pub trait UpgradeHandler: Sync + Send { 8 | fn handle(&self, stream: Connection); 9 | } 10 | 11 | impl UpgradeHandler for F { 12 | fn handle(&self, stream: Connection) { 13 | self(stream) 14 | } 15 | } 16 | 17 | #[derive(Clone)] 18 | pub(crate) struct UpgradeExtension { 19 | pub(crate) handler: Arc, 20 | } 21 | 22 | pub trait Upgrade { 23 | fn upgrade(self, handle: impl UpgradeHandler + 'static) -> Self; 24 | } 25 | 26 | impl Upgrade for http::response::Builder { 27 | fn upgrade(self, handle: impl UpgradeHandler + 'static) -> Self { 28 | self.extension(UpgradeExtension { 29 | handler: Arc::new(handle), 30 | }) 31 | } 32 | } 33 | 34 | impl Upgrade for http::Response { 35 | fn upgrade(mut self, handle: impl UpgradeHandler + 'static) -> Self { 36 | self.extensions_mut().insert(UpgradeExtension { 37 | handler: Arc::new(handle), 38 | }); 39 | self 40 | } 41 | } 42 | 43 | #[derive(Debug, Error)] 44 | pub enum ClientUpgradeError { 45 | #[error("connection not upgradable")] 46 | ConnectionNotUpgradable, 47 | } 48 | 49 | pub trait ClientUpgrade { 50 | fn into_upgraded(self) -> Result; 51 | } 52 | 53 | impl ClientUpgrade for http::Response { 54 | fn into_upgraded(mut self) -> Result { 55 | self.extensions_mut() 56 | .remove() 57 | .ok_or(ClientUpgradeError::ConnectionNotUpgradable) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/websocket-tokio.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use futures::{stream::StreamExt, SinkExt}; 4 | use tokio::{net::TcpStream, runtime}; 5 | use tokio_tungstenite::{tungstenite::protocol::Role, WebSocketStream}; 6 | use touche::{upgrade::Upgrade, Body, Connection, Request, Server}; 7 | 8 | fn main() -> std::io::Result<()> { 9 | let tokio_runtime = runtime::Builder::new_multi_thread().enable_all().build()?; 10 | let tokio_handle = tokio_runtime.handle(); 11 | 12 | Server::builder() 13 | .bind("0.0.0.0:4444") 14 | // We can handle multiple websockets even with a single thread server, since the websocket 15 | // connections will be handled by Tokio and not by Touche. 16 | .serve_single_thread(move |req: Request| { 17 | let tokio_handle = tokio_handle.clone(); 18 | 19 | let res = tungstenite::handshake::server::create_response(&req.map(|_| ()))?; 20 | 21 | Ok::<_, Box>(res.upgrade(move |stream: Connection| { 22 | let stream = stream.downcast::().unwrap(); 23 | stream.set_nonblocking(true).unwrap(); 24 | 25 | tokio_handle.spawn(async move { 26 | let stream = TcpStream::from_std(stream).unwrap(); 27 | let mut ws = WebSocketStream::from_raw_socket(stream, Role::Server, None).await; 28 | 29 | while let Some(Ok(msg)) = ws.next().await { 30 | if msg.is_text() && ws.send(msg).await.is_err() { 31 | break; 32 | } 33 | } 34 | }); 35 | })) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /examples/tls.rs: -------------------------------------------------------------------------------- 1 | // Run with: curl --insecure https://localhost:4444 2 | #[cfg(feature = "rustls")] 3 | fn main() -> Result<(), Box> { 4 | use std::{net::TcpListener, sync::Arc}; 5 | 6 | use rustls::{crypto, ServerConfig, ServerConnection, StreamOwned}; 7 | use rustls_pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer}; 8 | use touche::{Response, Server, StatusCode}; 9 | 10 | crypto::ring::default_provider().install_default().ok(); 11 | 12 | let listener = TcpListener::bind("0.0.0.0:4444")?; 13 | 14 | let tls_cfg = { 15 | let certs = CertificateDer::pem_file_iter("examples/tls/cert.pem")? 16 | .filter_map(|cert| cert.ok()) 17 | .collect(); 18 | 19 | let key = PrivateKeyDer::from_pem_file("examples/tls/key.pem")?; 20 | 21 | let cfg = ServerConfig::builder() 22 | .with_no_client_auth() 23 | .with_single_cert(certs, key)?; 24 | 25 | Arc::new(cfg) 26 | }; 27 | 28 | let connections = listener 29 | .incoming() 30 | .filter_map(|conn| conn.ok()) 31 | .filter_map(|conn| { 32 | Some(StreamOwned::new( 33 | ServerConnection::new(tls_cfg.clone()).ok()?, 34 | conn, 35 | )) 36 | }); 37 | 38 | Server::builder() 39 | .max_threads(100) 40 | .from_connections(connections) 41 | .serve(|_req| { 42 | Response::builder() 43 | .status(StatusCode::OK) 44 | .body("Hello from TLS") 45 | })?; 46 | 47 | Ok(()) 48 | } 49 | 50 | #[cfg(not(feature = "rustls"))] 51 | fn main() { 52 | println!("This example requires the rustls feature to be enabled"); 53 | } 54 | -------------------------------------------------------------------------------- /examples/sse.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, thread, time::Duration}; 2 | 3 | use indoc::indoc; 4 | use touche::{header::ACCEPT, Body, Request, Response, Server, StatusCode}; 5 | 6 | fn main() -> std::io::Result<()> { 7 | Server::bind("0.0.0.0:4444").serve(|req: Request<_>| { 8 | match req.headers().get(ACCEPT).and_then(|a| a.to_str().ok()) { 9 | Some(accept) if accept.contains("text/event-stream") => { 10 | let (sender, body) = Body::channel(); 11 | 12 | thread::spawn(move || { 13 | sender.send(indoc! {r#" 14 | event: userconnect 15 | data: {"name": "sasha"} 16 | 17 | "#})?; 18 | 19 | for _ in 1..10 { 20 | thread::sleep(Duration::from_secs(1)); 21 | sender.send(indoc! {r#" 22 | event: usermessage 23 | data: {"name": "sasha", "message": "message"} 24 | 25 | "#})?; 26 | } 27 | 28 | thread::sleep(Duration::from_secs(1)); 29 | sender.send(indoc! {r#" 30 | event: userdisconnect 31 | data: {"name": "sasha"} 32 | 33 | "#})?; 34 | 35 | Ok::<_, Box>(()) 36 | }); 37 | 38 | Response::builder() 39 | .status(StatusCode::OK) 40 | .header("content-type", "text/event-stream") 41 | .body(body) 42 | } 43 | 44 | _ => Response::builder() 45 | .status(StatusCode::OK) 46 | .header("content-type", "text/html") 47 | .body(include_str!("sse.html").into()), 48 | } 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /examples/tls/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFRzCCAy+gAwIBAgIUaE/lhHtrvA3huXlQfVIu722Q2a8wDQYJKoZIhvcNAQEL 3 | BQAwMzELMAkGA1UEBhMCRU4xEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAoM 4 | BlRvdWNoZTAeFw0yMjA3MjgxNjI1MjRaFw0yMzA3MjgxNjI1MjRaMDMxCzAJBgNV 5 | BAYTAkVOMRMwEQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQKDAZUb3VjaGUwggIi 6 | MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzqGmsksOfqA59f5UYbXPQIr+K 7 | GaIPZ9iwKZNEn0PEFMEjVEv0auM7bK4s4e0mZcnt1aqiNPDUT1dyR9lZRnY8mTOB 8 | 8yVJRHWFonofx4VR1cVygVyWtGf8KdpeSIkeEYTfUo6A7QvZtjXIfn/yNkR4sdJX 9 | E5B/f0CwRixf/fv99ydNYfTf46PlLWRl37j35WKbvQBk6tMYcqaGwig3Y/l/XDku 10 | oHD7hR6tMMrN4OACYisACCGDmiTwP9KtbW3YlI8+u/8aweOyt1xVNomNJ/d9mw6z 11 | OrdodQaQr1S2zLtjBIK1uHhd8+KwHjQE5VeNy2/QWMDhM/fIy+hPQ9brw/dxMnZi 12 | uxxLbEVnPbbr5/2qh1gFGGXmooQTrsMB4bWEliavsZSBtq1aje1/OfGCgifRWLjF 13 | G3XsyVrWzNJkJ/YKccyWuYL5jYFDoc5XTWj5pTglLCMBVC4JUNSJf/XOgf6a0ktJ 14 | WlyOYyb97BrlO6AD1UbrV3ys9cIEvfYrHpb4H8xqU1+593KSz5tyigl7P93satdf 15 | rSGKIwnxtXyvqHUUXO1bKgHYbij3g7T0YmYTmpYzUB1THAe67UustzzwI3/T/W// 16 | VdKsAj7CkB3Ik1i/HuZKHH3RRjBcSYLOcsfoMtGz4g5yozNhTOIs9LPwSJnae7gJ 17 | DI/KQ5LbfaLRwZwo+wIDAQABo1MwUTAdBgNVHQ4EFgQURfHikHExDk808AF+IoOB 18 | QMYTFS4wHwYDVR0jBBgwFoAURfHikHExDk808AF+IoOBQMYTFS4wDwYDVR0TAQH/ 19 | BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAgK5jLLjqhEYFiy3khhydYROG1x0c 20 | KP84qdRaai8Y4ybWgCXrOSU0YDeGI2P+2VB+1W1nRaMWk0m4bn8y8oW7XYAAwEZM 21 | xcHU8O/YorALIQnAunG9rltaQTeUmNZ13jJW9hvcYdggtQGy0WYClekKSPlP+/Ou 22 | G+2x20eiPjA2jhcd6DUCbUL92d924NC3U0CuAEmwnEWLCQEXvZsjRup5maecQTcU 23 | LpNFXDApinAHelh9AxxdgW+Rqq1xs3WeRhG7zhOAFO5TiO0+ImnB7C/hxtOFRNQg 24 | a1H5KSB5uNvWwnaVWBarzFxHWlusLw4sUBujPLkKqFvtahF6n/nrPf1ZJlt3S6Sj 25 | 6mSV16jK5a8NYR6F2AJ8C/hyACnbY7BPoMjsE1nk7ZgLwP4EhVoLDgyP6WnVuNn6 26 | 231cfJCV752p3eBuQ7BmRzQdWmMYwGvURi6T4Knm6tO6rVqvDE/PNAyj5/0x92+z 27 | E4r0IFH6HmkzPgxm4Mrbmnw+CI8ONxRGFyUZA/gybN8B+ikQJNCK1coju2cpS+Xz 28 | a8gjLc8q1O+0g2uYikGrZN9q3n3VrUe52SZJN9vsXs0PS8TFAvKGoEmYmcnBYDNX 29 | IcSy1443ZuvWTd5qDVPvLpuUqugtHdDJIaLB4gpqzfd6zgwud34rcFOkCpZA5Juq 30 | cxcUTr/uN8DVRMQ= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /examples/tunnel.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, 3 | net::{Shutdown, TcpStream}, 4 | thread, 5 | }; 6 | 7 | use touche::{upgrade::Upgrade, Body, Connection, Method, Request, Response, Server, StatusCode}; 8 | 9 | // Try with: curl --proxy http://localhost:4444 https://en.wikipedia.org/wiki/HTTP_tunnel 10 | fn main() -> io::Result<()> { 11 | Server::bind("0.0.0.0:4444").serve(|req: Request<_>| { 12 | if req.method() != Method::CONNECT { 13 | return Response::builder() 14 | .status(StatusCode::METHOD_NOT_ALLOWED) 15 | .header("allow", "connect") 16 | .body(Body::empty()); 17 | } 18 | 19 | if let Some(address) = req.uri().authority().map(|a| a.to_string()) { 20 | Response::builder() 21 | .status(StatusCode::OK) 22 | .upgrade(move |conn: Connection| { 23 | if let Ok(server) = TcpStream::connect(&address) { 24 | match tunnel(conn.downcast().unwrap(), server) { 25 | Ok((w, r)) => eprintln!("Tunneled bytes: {r} (read) {w} (write)"), 26 | Err(err) => eprintln!("Tunnel error: {err}"), 27 | }; 28 | } else { 29 | eprintln!("Could not connect to address: {address}"); 30 | } 31 | }) 32 | .body(Body::empty()) 33 | } else { 34 | Response::builder() 35 | .status(StatusCode::BAD_REQUEST) 36 | .body(Body::from("Invalid address")) 37 | } 38 | }) 39 | } 40 | 41 | fn tunnel(mut client: TcpStream, mut server: TcpStream) -> io::Result<(u64, u64)> { 42 | let mut client_writer = client.try_clone()?; 43 | let mut server_writer = server.try_clone()?; 44 | 45 | let client_to_server = thread::spawn(move || { 46 | let bytes = io::copy(&mut client, &mut server_writer)?; 47 | server_writer.shutdown(Shutdown::Both)?; 48 | io::Result::Ok(bytes) 49 | }); 50 | 51 | let server_to_client = thread::spawn(move || { 52 | let bytes = io::copy(&mut server, &mut client_writer)?; 53 | client_writer.shutdown(Shutdown::Both)?; 54 | io::Result::Ok(bytes) 55 | }); 56 | 57 | Ok(( 58 | client_to_server.join().unwrap()?, 59 | server_to_client.join().unwrap()?, 60 | )) 61 | } 62 | -------------------------------------------------------------------------------- /src/tls.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Read, Write}, 3 | net::{SocketAddr, TcpStream}, 4 | sync::{Arc, Mutex}, 5 | time::Duration, 6 | }; 7 | 8 | use rustls::{ServerConnection, StreamOwned}; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct RustlsConnection(Arc>>); 12 | 13 | impl RustlsConnection { 14 | pub(crate) fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { 15 | let stream = self.0.lock().unwrap(); 16 | stream.get_ref().set_read_timeout(timeout)?; 17 | Ok(()) 18 | } 19 | 20 | pub(crate) fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { 21 | let stream = self.0.lock().unwrap(); 22 | stream.get_ref().set_nodelay(nodelay)?; 23 | Ok(()) 24 | } 25 | 26 | pub(crate) fn into_inner(self) -> Result, Self> { 27 | match Arc::try_unwrap(self.0) { 28 | Ok(conn) => Ok(conn.into_inner().unwrap()), 29 | Err(err) => Err(Self(err)), 30 | } 31 | } 32 | } 33 | 34 | impl From> for RustlsConnection { 35 | fn from(tls: StreamOwned) -> Self { 36 | RustlsConnection(Arc::new(Mutex::new(tls))) 37 | } 38 | } 39 | 40 | impl RustlsConnection { 41 | pub fn peer_addr(&self) -> io::Result { 42 | self.0 43 | .lock() 44 | .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? 45 | .sock 46 | .peer_addr() 47 | } 48 | 49 | pub fn local_addr(&self) -> io::Result { 50 | self.0 51 | .lock() 52 | .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? 53 | .sock 54 | .local_addr() 55 | } 56 | } 57 | 58 | impl Read for RustlsConnection { 59 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 60 | self.0 61 | .lock() 62 | .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? 63 | .read(buf) 64 | } 65 | } 66 | 67 | impl Write for RustlsConnection { 68 | fn write(&mut self, buf: &[u8]) -> io::Result { 69 | self.0 70 | .lock() 71 | .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? 72 | .write(buf) 73 | } 74 | 75 | fn flush(&mut self) -> io::Result<()> { 76 | self.0 77 | .lock() 78 | .map_err(|_err| io::Error::new(io::ErrorKind::Other, "Failed to aquire lock"))? 79 | .flush() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: check 19 | args: --all-features --all-targets 20 | 21 | fmt: 22 | name: Formatting 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | profile: minimal 29 | toolchain: stable 30 | override: true 31 | - run: rustup component add rustfmt 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: --all -- --check 36 | 37 | clippy: 38 | name: Clippy 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | - run: rustup component add clippy 48 | - uses: actions-rs/cargo@v1 49 | with: 50 | command: clippy 51 | args: --all-features --all-targets -- -D warnings 52 | 53 | test: 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | job: 58 | - { os: ubuntu-latest, target: i686-unknown-linux-gnu, use-cross: true } 59 | - { os: ubuntu-latest, target: i686-unknown-linux-musl, use-cross: true } 60 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl, use-cross: true } 61 | - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu } 62 | - { os: macos-latest, target: x86_64-apple-darwin } 63 | - { os: macos-latest, target: aarch64-apple-darwin } 64 | - { os: windows-latest, target: i686-pc-windows-msvc } 65 | - { os: windows-latest, target: x86_64-pc-windows-gnu } 66 | - { os: windows-latest, target: x86_64-pc-windows-msvc } 67 | name: Test ${{ matrix.job.os }} (${{ matrix.job.target }}) 68 | runs-on: ${{ matrix.job.os }} 69 | steps: 70 | - uses: actions/checkout@v2 71 | - uses: actions-rs/toolchain@v1 72 | with: 73 | profile: minimal 74 | toolchain: stable 75 | override: true 76 | target: ${{ matrix.job.target }} 77 | - uses: actions-rs/cargo@v1 78 | with: 79 | use-cross: ${{ matrix.job.use-cross }} 80 | command: test 81 | args: --target=${{ matrix.job.target }} ${{ matrix.job.cmd }} 82 | -------------------------------------------------------------------------------- /examples/tls/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEAs6hprJLDn6gOfX+VGG1z0CK/ihmiD2fYsCmTRJ9DxBTBI1RL 3 | 9GrjO2yuLOHtJmXJ7dWqojTw1E9XckfZWUZ2PJkzgfMlSUR1haJ6H8eFUdXFcoFc 4 | lrRn/CnaXkiJHhGE31KOgO0L2bY1yH5/8jZEeLHSVxOQf39AsEYsX/37/fcnTWH0 5 | 3+Oj5S1kZd+49+Vim70AZOrTGHKmhsIoN2P5f1w5LqBw+4UerTDKzeDgAmIrAAgh 6 | g5ok8D/SrW1t2JSPPrv/GsHjsrdcVTaJjSf3fZsOszq3aHUGkK9Utsy7YwSCtbh4 7 | XfPisB40BOVXjctv0FjA4TP3yMvoT0PW68P3cTJ2YrscS2xFZz226+f9qodYBRhl 8 | 5qKEE67DAeG1hJYmr7GUgbatWo3tfznxgoIn0Vi4xRt17Mla1szSZCf2CnHMlrmC 9 | +Y2BQ6HOV01o+aU4JSwjAVQuCVDUiX/1zoH+mtJLSVpcjmMm/ewa5TugA9VG61d8 10 | rPXCBL32Kx6W+B/MalNfufdyks+bcooJez/d7GrXX60hiiMJ8bV8r6h1FFztWyoB 11 | 2G4o94O09GJmE5qWM1AdUxwHuu1LrLc88CN/0/1v/1XSrAI+wpAdyJNYvx7mShx9 12 | 0UYwXEmCznLH6DLRs+IOcqMzYUziLPSz8EiZ2nu4CQyPykOS232i0cGcKPsCAwEA 13 | AQKCAgA4sMPnuMgR8S3hXAjoFGGfI9fc9Am3YsV1kSjvVnJDxWjMhSUSk0fw0HKF 14 | pyT2INh/PBUD6kC0PfUSEqn7Ccj1hkJClz8Ex51t9b8jmAnxsMsFPtzAkVzerTZC 15 | 2hCqdEzBS4O8GxDKOjzubzxgac9hZntBDuNh1/3JodsOz05c7Uhxmp9yppppDtHH 16 | c7kiQDfFfPZK4e6KoGL70vhtwhZQogFJPUdplq//Zo4YOQV2uxNmhypMJK/TP9By 17 | +TzYQsiQuzHcd6IqGsIAAZ2V9S6ba/pPhA+r3oGb1jedFy1qKVo1jWgt/W9is6Um 18 | hlgCXleCHv9+EK/3pKBBot6Gtue1QRziwQgKNy+YaxqYMS7svcZzBogys4BJWhJ9 19 | XzA6mizZvA6NGCqZtuO5VbV6T9aH8UxLJoKKf9NoYJFYW7SMLKhdvHRm1P9FL97G 20 | yQUl9O9a7ezDNXOs0zWO3sO5yGI8FrWSjm1TpNf9pm+v+EBZeFCiXp1flIskpL4r 21 | 8JLbIVxCb9Iarpa2vzkD4T9ml3VHqp8P+7cn8izE8rNurKSRJRTimMnmvb//Grpy 22 | SMYs5FUqOR+gQtTCVKXsCGDJlVaZF1Bq/VS5tHal9pQGr0LKQZ4PJthxxexiM9sY 23 | iwV7CrwXZ30wbdICULgBmB78fgHnsmI1BnngYdmUyXwFMvJsAQKCAQEA26GOm4Pm 24 | AEPo/5nePSZ19pR6ZAUBwdWggBSKNZYRY5tUwn7pVgXfKZaObw2flBOuwuKUYwpX 25 | OhRBL7135b9BtIAu76zjc3568sr98OGDO0L4/4CwGv4euZPqCQXxX9UaEH6gmXEJ 26 | MrQz1nMlZ+7M579ynzk6LlIWZphtOzNIrF10G5E/V5sW4U6fAX7YqTInagbjZL5j 27 | 3SK3dPHk6GjJNTHDBrs+0kiEBOYkckumcWJfoR6LsbpNXAPVLVMXOQb9G3489E+2 28 | 4JKipwVRQzmwlFTkkoGfMTOjcugl4c4KsDHqYpY+NiGSNvohCS8Y6B1BYKtv5Wrd 29 | usjB0Ytlo3izuwKCAQEA0WhW+L1k77J4RGoPZzZNdq2+opyYej6U7GGGmwUVx5h6 30 | akcY8CgaOOVY272Pen8xPolJJZVDuq2TfBUcB21A81BvpsCONg+IOVAdvwSnHL7O 31 | IHTyOTN0D/Gq/3ifFoaJ2zr3VhChyGs7Zc5K+AiKTijDTQOYMJORcyjZiLCv5NVp 32 | 79o7oYINPNYMJvQbbDyqXVOr+3MMaLRV44uqkEYn+mZuE964aZVsVMx48nlthqn9 33 | sdgjlZ3gua7T5ULahgzpuYUw1JdeyO89KNkecJX5/KZh0GcLYV1HC+rSFZsECAg9 34 | hSgWlHRYT7aAYY8VaKCTTwSVevCMuTE129C6qeDrwQKCAQEA0oTezo31DlgJo8Z2 35 | 0SJme74COpDaCFOeDmahu49mh5VB/1PqkLv8Fj7rs+LZDPR41PCdzvkAdvknKn1v 36 | yVYzwrIDS1Tqh6+6ZbLRCez+/vTI+w78vkLKbJMtBw0dOgBnhVv60FkaufEkpu+j 37 | 8SViXtiRI7GU5QVl111Phik4BY9mVW45yt0rZzcXDjXpsvQR9302xUABNNusAXpD 38 | McoO4uAZ0f/0zVcYxCFc2flNRu1GQIeo2uh6cjJySYmVfuIGBi5QetVVs5iaM4HT 39 | TJ6ldOiSVyPL1Ttr9h4zAWefsxeFVI4yf2xaXDwRIl50fzhbjC+Hh92RPxJjRVla 40 | SQDPNQKCAQB3jcJZsId38cSICqVQn8gKAZfc7tp+sEwFyIg46OZOVddN5dlvAvY1 41 | lXrtuYO4w/UxZt/D/Tv10DNrnAGsm8uBHmMgKL8ITkXPstXIwLdHygeUsC+6SI3y 42 | rF6cDBmHmrgJ2dhr4oL7EX2P2f8jLH0JsmwOBAdXdGiN/rxzNay2UMFyln2E7+Lu 43 | 4KV/d85QNTJupd6CaGSlQo/moU53oBYi92fRIE7YuN5oa3SBuqG1SEf2yRdHzCMI 44 | 369KLKjl72lg6ZG/Tcxnm26na43daUKX48XkXXA53i85to7Klv3uOAYnh4Ocre/d 45 | UqxpBm+KlGKFsnBFCCDPejDtlnV7l1YBAoIBAAhCV5eHCygtYtDeroeKF8P9E7hA 46 | wzE9/VH2JNXhDQr524RdBLhZDlCjLOUMoznKZ5KRRxzDkyM7rR2UkGIiwXbbX0zt 47 | IFeUJBbmEegL+1rK3aiZPOZuDGJvXNKtm737QiichKe43r4BY1dWobRyR1xe4hX2 48 | zS1kYkupA197sWGIhx0twFMipCYQi56krvTfRx3eB3QGMC2vuj0HRrOv6QKBdJ5U 49 | GMVqSxm6mF9RUjTgIOzc1790tURSl3vspK2t3p1qwWCImyLbblcoBHHaW0u4EFZ2 50 | rfno4UgeKlTruVZ38TsDPV13OryoimI93PRKDb4I/tORT0NW/4BfjkrBPYk= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /src/read_queue.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, BufRead, Read}, 3 | mem, 4 | sync::mpsc::{self, Receiver, Sender}, 5 | }; 6 | 7 | // Thanks to tiny-http to come up with this "trampoline" idea, solved the 8 | // pipelining problem pretty well: 9 | // https://github.com/tiny-http/tiny-http/blob/master/src/util/sequential.rs 10 | pub enum ReadQueue { 11 | Head(R), 12 | Next(Receiver), 13 | } 14 | 15 | pub struct QueuedReader 16 | where 17 | R: Read + Send, 18 | { 19 | reader: Option>, 20 | next: Sender, 21 | } 22 | 23 | enum QueuedReaderInner { 24 | Current(R), 25 | Waiting(Receiver), 26 | } 27 | 28 | impl ReadQueue { 29 | pub fn new(reader: R) -> ReadQueue { 30 | ReadQueue::Head(reader) 31 | } 32 | 33 | pub fn enqueue(&mut self) -> QueuedReader { 34 | let (tx, rx) = mpsc::channel(); 35 | 36 | match mem::replace(self, ReadQueue::Next(rx)) { 37 | ReadQueue::Head(reader) => QueuedReader { 38 | reader: Some(QueuedReaderInner::Current(reader)), 39 | next: tx, 40 | }, 41 | ReadQueue::Next(previous) => QueuedReader { 42 | reader: Some(QueuedReaderInner::Waiting(previous)), 43 | next: tx, 44 | }, 45 | } 46 | } 47 | } 48 | 49 | impl Read for QueuedReader { 50 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 51 | match self.reader.as_mut().unwrap() { 52 | QueuedReaderInner::Current(ref mut reader) => reader.read(buf), 53 | QueuedReaderInner::Waiting(ref mut rx) => { 54 | let mut reader = rx.recv().unwrap(); 55 | let result = reader.read(buf); 56 | self.reader = Some(QueuedReaderInner::Current(reader)); 57 | result 58 | } 59 | } 60 | } 61 | } 62 | 63 | impl BufRead for QueuedReader { 64 | fn fill_buf(&mut self) -> io::Result<&[u8]> { 65 | match self.reader { 66 | Some(QueuedReaderInner::Current(ref mut reader)) => reader.fill_buf(), 67 | Some(QueuedReaderInner::Waiting(ref mut rx)) => { 68 | let reader = rx.recv().unwrap(); 69 | self.reader = Some(QueuedReaderInner::Current(reader)); 70 | self.fill_buf() 71 | } 72 | None => unreachable!(), 73 | } 74 | } 75 | 76 | fn consume(&mut self, amt: usize) { 77 | match self.reader { 78 | Some(QueuedReaderInner::Current(ref mut reader)) => reader.consume(amt), 79 | Some(QueuedReaderInner::Waiting(ref mut rx)) => { 80 | let reader = rx.recv().unwrap(); 81 | self.reader = Some(QueuedReaderInner::Current(reader)); 82 | self.consume(amt) 83 | } 84 | None => unreachable!(), 85 | } 86 | } 87 | } 88 | 89 | impl Drop for QueuedReader { 90 | #[allow(unused_must_use)] 91 | fn drop(&mut self) { 92 | match self.reader.take() { 93 | Some(QueuedReaderInner::Current(reader)) => { 94 | self.next.send(reader); 95 | } 96 | Some(QueuedReaderInner::Waiting(rx)) => { 97 | self.next.send(rx.recv().unwrap()); 98 | } 99 | None => {} 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/websocket-chat.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | error::Error, 4 | sync::{ 5 | atomic::{AtomicUsize, Ordering}, 6 | mpsc::{self, Sender}, 7 | Arc, Mutex, 8 | }, 9 | thread, 10 | }; 11 | 12 | use headers::HeaderMapExt; 13 | use serde::{Deserialize, Serialize}; 14 | use touche::{upgrade::Upgrade, Body, Connection, Request, Response, Server, StatusCode}; 15 | use tungstenite::{protocol::Role, WebSocket}; 16 | 17 | #[derive(Debug, Clone, Serialize, Deserialize)] 18 | #[serde(tag = "type")] 19 | enum Event { 20 | #[serde(rename = "user")] 21 | User { id: usize }, 22 | #[serde(rename = "message", rename_all = "camelCase")] 23 | Message { user_id: usize, text: String }, 24 | } 25 | 26 | type Users = Arc>>>; 27 | 28 | fn main() -> std::io::Result<()> { 29 | static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1); 30 | 31 | let users: Users = Arc::new(Mutex::new(HashMap::new())); 32 | 33 | Server::bind("0.0.0.0:4444").serve(move |req: Request| { 34 | let users = users.clone(); 35 | 36 | if req.headers().typed_get::().is_some() { 37 | let res = tungstenite::handshake::server::create_response(&req.map(|_| ()))? 38 | .map(|_| Body::empty()); 39 | 40 | Ok::<_, Box>(res.upgrade(move |stream: Connection| { 41 | let users = users.clone(); 42 | 43 | let user_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed); 44 | let (tx, rx) = mpsc::channel(); 45 | 46 | { 47 | let mut users = users.lock().unwrap(); 48 | 49 | users.insert(user_id, tx); 50 | 51 | let joined_msg = Event::User { id: user_id }; 52 | users.retain(|_id, tx| tx.send(joined_msg.clone()).is_ok()); 53 | }; 54 | 55 | let mut read_ws = WebSocket::from_raw_socket(stream.clone(), Role::Server, None); 56 | let mut write_ws = WebSocket::from_raw_socket(stream, Role::Server, None); 57 | 58 | let write_ws = thread::spawn(move || { 59 | for evt in rx { 60 | let msg = 61 | tungstenite::Message::Text(serde_json::to_string(&evt).unwrap().into()); 62 | if write_ws.send(msg).is_err() { 63 | break; 64 | } 65 | } 66 | }); 67 | 68 | let read_ws = thread::spawn(move || { 69 | while let Ok(msg) = read_ws.read() { 70 | match msg.to_text() { 71 | Ok(text) => { 72 | let text = text.to_owned(); 73 | let msg = Event::Message { user_id, text }; 74 | users 75 | .lock() 76 | .unwrap() 77 | .retain(|_id, tx| tx.send(msg.clone()).is_ok()); 78 | } 79 | Err(_) => break, 80 | } 81 | } 82 | }); 83 | 84 | write_ws.join().unwrap(); 85 | read_ws.join().unwrap(); 86 | })) 87 | } else { 88 | Ok(Response::builder() 89 | .status(StatusCode::OK) 90 | .header("content-type", "text/html") 91 | .body(include_str!("websocket-chat.html").into())?) 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /examples/sse-chat.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::{ 4 | atomic::{AtomicUsize, Ordering}, 5 | mpsc::{self, Sender}, 6 | Arc, Mutex, 7 | }, 8 | thread, 9 | }; 10 | 11 | use indoc::formatdoc; 12 | use touche::{body::HttpBody, Body, Method, Request, Response, Server, StatusCode}; 13 | 14 | #[derive(Debug)] 15 | enum Event { 16 | User(usize), 17 | Message(usize, String), 18 | } 19 | 20 | type Users = Arc>>>; 21 | 22 | fn main() -> std::io::Result<()> { 23 | static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1); 24 | 25 | let users: Users = Arc::new(Mutex::new(HashMap::new())); 26 | 27 | Server::bind("0.0.0.0:4444").serve(move |req: Request| { 28 | let users = users.clone(); 29 | 30 | match req.uri().path() { 31 | "/messages" if req.method() == Method::POST => { 32 | let user_id = req 33 | .headers() 34 | .get("x-user-id") 35 | .and_then(|h| h.to_str().ok()) 36 | .and_then(|user_id| user_id.parse::().ok()); 37 | 38 | match user_id { 39 | Some(user_id) => { 40 | let text = req.into_body().into_bytes().unwrap_or_default(); 41 | let text = std::str::from_utf8(&text).unwrap_or_default(); 42 | 43 | users.lock().unwrap().retain(|id, tx| { 44 | if user_id != *id { 45 | tx.send(Event::Message(user_id, text.to_string())).is_ok() 46 | } else { 47 | true 48 | } 49 | }); 50 | 51 | Response::builder() 52 | .status(StatusCode::CREATED) 53 | .body(Body::empty()) 54 | } 55 | 56 | None => Response::builder() 57 | .status(StatusCode::UNAUTHORIZED) 58 | .body("Missing x-user-id header".into()), 59 | } 60 | } 61 | 62 | "/messages" => { 63 | let user_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed); 64 | 65 | let (tx, rx) = mpsc::channel(); 66 | tx.send(Event::User(user_id)).unwrap(); 67 | users.lock().unwrap().insert(user_id, tx); 68 | 69 | let (sender, body) = Body::channel(); 70 | thread::spawn(move || { 71 | for event in rx { 72 | let message = match event { 73 | Event::User(id) => formatdoc! {r#" 74 | event: user 75 | data: {{"id": "{id}"}} 76 | 77 | "#}, 78 | Event::Message(user_id, text) => formatdoc! {r#" 79 | event: message 80 | data: {{"userId": {user_id}, "message": "{text}"}} 81 | 82 | "#}, 83 | }; 84 | 85 | if let Err(_err) = sender.send(message) { 86 | break; 87 | } 88 | } 89 | }); 90 | 91 | Response::builder() 92 | .status(StatusCode::OK) 93 | .header("content-type", "text/event-stream") 94 | .header("connection", "close") 95 | .body(body) 96 | } 97 | 98 | "/" => Response::builder() 99 | .status(StatusCode::OK) 100 | .header("content-type", "text/html") 101 | .body(include_str!("sse-chat.html").into()), 102 | 103 | _ => Response::builder() 104 | .status(StatusCode::NOT_FOUND) 105 | .body(Body::empty()), 106 | } 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /src/body/http_body.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Cursor, Read}, 3 | iter, 4 | }; 5 | 6 | #[cfg(feature = "bytes")] 7 | use bytes::{Buf, Bytes, BytesMut}; 8 | use headers::HeaderMap; 9 | 10 | /// Trait representing a streaming body 11 | pub trait HttpBody: Sized { 12 | type Reader: Read; 13 | type Chunks: Iterator>; 14 | 15 | /// The length of a body, when it is known. 16 | fn len(&self) -> Option; 17 | 18 | /// Returns if this body is empty. 19 | /// Note that unknown sized bodies (such as close delimited or chunked encoded) will never be 20 | /// considered to be empty. 21 | fn is_empty(&self) -> bool { 22 | matches!(self.len(), Some(0)) 23 | } 24 | 25 | /// Consumes this body and returns a [`Read`]. 26 | fn into_reader(self) -> Self::Reader; 27 | 28 | /// Consumes this body in chunks. 29 | fn into_chunks(self) -> Self::Chunks; 30 | 31 | /// Consumes this body and returns its bytes. 32 | fn into_bytes(self) -> io::Result> { 33 | let mut buf = Vec::with_capacity(self.len().unwrap_or(1024) as usize); 34 | self.into_reader().read_to_end(&mut buf)?; 35 | Ok(buf) 36 | } 37 | } 38 | 39 | impl HttpBody for () { 40 | type Reader = io::Empty; 41 | type Chunks = iter::Empty>; 42 | 43 | fn len(&self) -> Option { 44 | Some(0) 45 | } 46 | 47 | fn into_reader(self) -> Self::Reader { 48 | io::empty() 49 | } 50 | 51 | fn into_bytes(self) -> io::Result> { 52 | Ok(Vec::new()) 53 | } 54 | 55 | fn into_chunks(self) -> Self::Chunks { 56 | iter::empty() 57 | } 58 | } 59 | 60 | impl HttpBody for String { 61 | type Reader = Cursor>; 62 | type Chunks = iter::Once>; 63 | 64 | fn len(&self) -> Option { 65 | self.len().try_into().ok() 66 | } 67 | 68 | fn into_reader(self) -> Self::Reader { 69 | Cursor::new(self.into_bytes()) 70 | } 71 | 72 | fn into_bytes(self) -> io::Result> { 73 | Ok(self.into_bytes()) 74 | } 75 | 76 | fn into_chunks(self) -> Self::Chunks { 77 | iter::once(Ok(self.into_bytes().into())) 78 | } 79 | } 80 | 81 | impl HttpBody for &str { 82 | type Reader = Cursor>; 83 | type Chunks = iter::Once>; 84 | 85 | fn len(&self) -> Option { 86 | str::len(self).try_into().ok() 87 | } 88 | 89 | fn into_reader(self) -> Self::Reader { 90 | Cursor::new(self.bytes().collect()) 91 | } 92 | 93 | fn into_bytes(self) -> io::Result> { 94 | Ok(self.bytes().collect()) 95 | } 96 | 97 | fn into_chunks(self) -> Self::Chunks { 98 | iter::once(Ok(Chunk::Data(self.bytes().collect()))) 99 | } 100 | } 101 | 102 | impl HttpBody for &'static [u8] { 103 | type Reader = &'static [u8]; 104 | type Chunks = iter::Once>; 105 | 106 | fn len(&self) -> Option { 107 | (*self).len().try_into().ok() 108 | } 109 | 110 | fn into_reader(self) -> Self::Reader { 111 | self 112 | } 113 | 114 | fn into_bytes(self) -> io::Result> { 115 | Ok(self.to_vec()) 116 | } 117 | 118 | fn into_chunks(self) -> Self::Chunks { 119 | iter::once(Ok(self.to_vec().into())) 120 | } 121 | } 122 | 123 | impl HttpBody for Vec { 124 | type Reader = Cursor>; 125 | type Chunks = iter::Once>; 126 | 127 | fn len(&self) -> Option { 128 | self.len().try_into().ok() 129 | } 130 | 131 | fn into_reader(self) -> Self::Reader { 132 | Cursor::new(self) 133 | } 134 | 135 | fn into_bytes(self) -> io::Result> { 136 | Ok(self) 137 | } 138 | 139 | fn into_chunks(self) -> Self::Chunks { 140 | iter::once(Ok(self.into())) 141 | } 142 | } 143 | 144 | #[cfg(feature = "bytes")] 145 | impl HttpBody for Bytes { 146 | type Reader = bytes::buf::Reader; 147 | type Chunks = iter::Once>; 148 | 149 | fn len(&self) -> Option { 150 | self.len().try_into().ok() 151 | } 152 | 153 | fn into_reader(self) -> Self::Reader { 154 | self.reader() 155 | } 156 | 157 | fn into_bytes(self) -> io::Result> { 158 | Ok(self.to_vec()) 159 | } 160 | 161 | fn into_chunks(self) -> Self::Chunks { 162 | iter::once(Ok(self.into())) 163 | } 164 | } 165 | 166 | #[cfg(feature = "bytes")] 167 | impl HttpBody for BytesMut { 168 | type Reader = bytes::buf::Reader; 169 | type Chunks = iter::Once>; 170 | 171 | fn len(&self) -> Option { 172 | self.len().try_into().ok() 173 | } 174 | 175 | fn into_reader(self) -> Self::Reader { 176 | self.reader() 177 | } 178 | 179 | fn into_bytes(self) -> io::Result> { 180 | Ok(self.to_vec()) 181 | } 182 | 183 | fn into_chunks(self) -> Self::Chunks { 184 | iter::once(Ok(self.into())) 185 | } 186 | } 187 | 188 | /// A message of a chunked encoded body. 189 | #[derive(Debug)] 190 | pub enum Chunk { 191 | /// Data chunk. 192 | Data(Vec), 193 | /// [Trailers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer) header chunk. 194 | Trailers(HeaderMap), 195 | } 196 | 197 | impl>> From for Chunk { 198 | fn from(chunk: T) -> Self { 199 | Self::Data(chunk.into()) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::{Any, TypeId}, 3 | io::{self, Read, Write}, 4 | net::{SocketAddr, TcpStream}, 5 | time::Duration, 6 | }; 7 | 8 | #[cfg(feature = "unix-sockets")] 9 | use std::os::unix::net::UnixStream; 10 | 11 | #[cfg(feature = "rustls")] 12 | use crate::tls::RustlsConnection; 13 | 14 | /// Abstracts away the several types of streams where HTTP can be deployed. 15 | #[derive(Debug)] 16 | pub struct Connection(ConnectionInner); 17 | 18 | #[derive(Debug)] 19 | enum ConnectionInner { 20 | Tcp(TcpStream), 21 | #[cfg(feature = "unix-sockets")] 22 | Unix(UnixStream), 23 | #[cfg(feature = "rustls")] 24 | Rustls(RustlsConnection), 25 | } 26 | 27 | impl Connection { 28 | pub fn peer_addr(&self) -> Option { 29 | match self.0 { 30 | ConnectionInner::Tcp(ref tcp) => tcp.peer_addr().ok(), 31 | #[cfg(feature = "unix-sockets")] 32 | ConnectionInner::Unix(_) => None, 33 | #[cfg(feature = "rustls")] 34 | ConnectionInner::Rustls(ref tls) => tls.peer_addr().ok(), 35 | } 36 | } 37 | 38 | pub fn local_addr(&self) -> Option { 39 | match self.0 { 40 | ConnectionInner::Tcp(ref tcp) => tcp.local_addr().ok(), 41 | #[cfg(feature = "unix-sockets")] 42 | ConnectionInner::Unix(_) => None, 43 | #[cfg(feature = "rustls")] 44 | ConnectionInner::Rustls(ref tls) => tls.local_addr().ok(), 45 | } 46 | } 47 | 48 | pub fn set_read_timeout(&self, timeout: Option) -> Result<(), io::Error> { 49 | match self.0 { 50 | ConnectionInner::Tcp(ref tcp) => tcp.set_read_timeout(timeout), 51 | #[cfg(feature = "unix-sockets")] 52 | ConnectionInner::Unix(ref unix) => unix.set_read_timeout(timeout), 53 | #[cfg(feature = "rustls")] 54 | ConnectionInner::Rustls(ref tls) => tls.set_read_timeout(timeout), 55 | } 56 | } 57 | 58 | pub fn set_nodelay(&self, nodelay: bool) -> Result<(), io::Error> { 59 | match self.0 { 60 | ConnectionInner::Tcp(ref tcp) => tcp.set_nodelay(nodelay), 61 | #[cfg(feature = "unix-sockets")] 62 | ConnectionInner::Unix(_) => Ok(()), 63 | #[cfg(feature = "rustls")] 64 | ConnectionInner::Rustls(ref tls) => tls.set_nodelay(nodelay), 65 | } 66 | } 67 | 68 | /// Attempts to downcast the [`Connection`] into the underlying stream. 69 | /// On error returns the [`Connection`] back. 70 | /// 71 | /// # Example 72 | /// ```no_run 73 | /// # use std::net::{TcpListener, TcpStream}; 74 | /// # use touche::Connection; 75 | /// # fn main() -> std::io::Result<()> { 76 | /// # let listener = TcpListener::bind("0.0.0.0:4444")?; 77 | /// # let connection = Connection::from(listener.accept()?); 78 | /// if let Ok(tcp) = connection.downcast::() { 79 | /// println!("Connection is a TcpStream"); 80 | /// } else { 81 | /// println!("Connection is not a TcpStream"); 82 | /// } 83 | /// # Ok(()) 84 | /// # } 85 | /// ``` 86 | pub fn downcast(self) -> Result { 87 | match self.0 { 88 | ConnectionInner::Tcp(tcp) if Any::type_id(&tcp) == TypeId::of::() => { 89 | let tcp = Box::new(tcp) as Box; 90 | Ok(tcp.downcast().map(|tcp| *tcp).unwrap()) 91 | } 92 | 93 | #[cfg(feature = "unix-sockets")] 94 | ConnectionInner::Unix(unix) if Any::type_id(&unix) == TypeId::of::() => { 95 | let unix = Box::new(unix) as Box; 96 | Ok(unix.downcast().map(|unix| *unix).unwrap()) 97 | } 98 | 99 | #[cfg(feature = "rustls")] 100 | ConnectionInner::Rustls(tls) => match tls.into_inner() { 101 | Ok(tls) if Any::type_id(&tls) == TypeId::of::() => { 102 | let tls = Box::new(tls) as Box; 103 | Ok(tls.downcast().map(|tls| *tls).unwrap()) 104 | } 105 | Ok(tls) => Err(Self(ConnectionInner::Rustls(tls.into()))), 106 | Err(tls) => Err(Self(ConnectionInner::Rustls(tls))), 107 | }, 108 | 109 | conn => Err(Self(conn)), 110 | } 111 | } 112 | } 113 | 114 | impl Read for Connection { 115 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 116 | match self { 117 | Connection(ConnectionInner::Tcp(tcp)) => tcp.read(buf), 118 | #[cfg(feature = "unix-sockets")] 119 | Connection(ConnectionInner::Unix(unix)) => unix.read(buf), 120 | #[cfg(feature = "rustls")] 121 | Connection(ConnectionInner::Rustls(tls)) => tls.read(buf), 122 | } 123 | } 124 | } 125 | 126 | impl Write for Connection { 127 | fn write(&mut self, buf: &[u8]) -> io::Result { 128 | match self { 129 | Connection(ConnectionInner::Tcp(tcp)) => tcp.write(buf), 130 | #[cfg(feature = "unix-sockets")] 131 | Connection(ConnectionInner::Unix(unix)) => unix.write(buf), 132 | #[cfg(feature = "rustls")] 133 | Connection(ConnectionInner::Rustls(tls)) => tls.write(buf), 134 | } 135 | } 136 | 137 | fn flush(&mut self) -> io::Result<()> { 138 | match self { 139 | Connection(ConnectionInner::Tcp(tcp)) => tcp.flush(), 140 | #[cfg(feature = "unix-sockets")] 141 | Connection(ConnectionInner::Unix(unix)) => unix.flush(), 142 | #[cfg(feature = "rustls")] 143 | Connection(ConnectionInner::Rustls(tls)) => tls.flush(), 144 | } 145 | } 146 | } 147 | 148 | impl Clone for Connection { 149 | fn clone(&self) -> Self { 150 | match self { 151 | Connection(ConnectionInner::Tcp(tcp)) => { 152 | Connection(ConnectionInner::Tcp(tcp.try_clone().unwrap())) 153 | } 154 | #[cfg(feature = "unix-sockets")] 155 | Connection(ConnectionInner::Unix(unix)) => { 156 | Connection(ConnectionInner::Unix(unix.try_clone().unwrap())) 157 | } 158 | #[cfg(feature = "rustls")] 159 | Connection(ConnectionInner::Rustls(tls)) => { 160 | Connection(ConnectionInner::Rustls(tls.clone())) 161 | } 162 | } 163 | } 164 | } 165 | 166 | impl From for Connection { 167 | fn from(conn: TcpStream) -> Self { 168 | Connection(ConnectionInner::Tcp(conn)) 169 | } 170 | } 171 | 172 | impl From<(TcpStream, SocketAddr)> for Connection { 173 | fn from((conn, _addr): (TcpStream, SocketAddr)) -> Self { 174 | Connection(ConnectionInner::Tcp(conn)) 175 | } 176 | } 177 | 178 | #[cfg(feature = "unix-sockets")] 179 | impl From for Connection { 180 | fn from(unix: UnixStream) -> Self { 181 | Connection(ConnectionInner::Unix(unix)) 182 | } 183 | } 184 | 185 | #[cfg(feature = "rustls")] 186 | impl From> for Connection { 187 | fn from(tls: rustls::StreamOwned) -> Self { 188 | Connection(ConnectionInner::Rustls(tls.into())) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # touché 2 | 3 | Touché is a low level but fully featured HTTP 1.0/1.1 library. 4 | 5 | It tries to mimic [hyper](https://crates.io/crates/hyper), but with a synchronous API. 6 | 7 | For now only the server API is implemented. 8 | 9 | ## Hello world 10 | 11 | ```rust no_run 12 | use touche::{Response, Server, StatusCode}; 13 | 14 | fn main() -> std::io::Result<()> { 15 | Server::bind("0.0.0.0:4444").serve(|_req| { 16 | Response::builder() 17 | .status(StatusCode::OK) 18 | .body("Hello World") 19 | }) 20 | } 21 | ``` 22 | 23 | ## Features 24 | - HTTP Server (thread per connection model, backed by a thread pool) 25 | - Non buffered (streaming) requests and response bodies 26 | - HTTP/1.1 pipelining 27 | - TLS 28 | - Upgrade connections 29 | - Trailers headers 30 | - 100 continue expectation 31 | - Unix sockets servers 32 | 33 | ## Comparison with Hyper 34 | 35 | Touché shares a lot of similarities with Hyper: 36 | 37 | - "Low level" 38 | - Uses the [http crate](https://crates.io/crates/http) to represent HTTP related types 39 | - Allows fine-grained implementations of streaming HTTP bodies 40 | - A simple and easy to read implementation and examples 41 | 42 | But also has some key differences: 43 | 44 | - It is synchronous 45 | - Uses `Vec` to represent bytes instead of [Bytes](https://crates.io/crates/bytes) 46 | - Doesn't support HTTP 2 (and probably never will) 47 | 48 | ## Handling persistent connections with non blocking IO 49 | 50 | Connection-per-thread web servers are notorious bad with persistent connections like websockets or event streams. 51 | This is primarily because the thread gets locked to the connection until it is closed. 52 | 53 | One solution to this problem is to handle such connections with non-blocking IO. 54 | By doing so, the server thread becomes available for other connections. 55 | 56 | The following example demonstrates a single-threaded touché server that handles websockets upgrades to a Tokio runtime. 57 | 58 | ```rust no_run 59 | use std::error::Error; 60 | 61 | use futures::{stream::StreamExt, SinkExt}; 62 | use tokio::{net::TcpStream, runtime}; 63 | use tokio_tungstenite::{tungstenite::protocol::Role, WebSocketStream}; 64 | use touche::{upgrade::Upgrade, Body, Connection, Request, Server}; 65 | 66 | fn main() -> std::io::Result<()> { 67 | let tokio_runtime = runtime::Builder::new_multi_thread().enable_all().build()?; 68 | let tokio_handle = tokio_runtime.handle(); 69 | 70 | Server::builder() 71 | .bind("0.0.0.0:4444") 72 | // We can handle multiple websockets even with a single thread server, since the websocket 73 | // connections will be handled by Tokio and not by Touche. 74 | .serve_single_thread(move |req: Request| { 75 | let tokio_handle = tokio_handle.clone(); 76 | 77 | let res = tungstenite::handshake::server::create_response(&req.map(|_| ()))?; 78 | 79 | Ok::<_, Box>(res.upgrade(move |stream: Connection| { 80 | let stream = stream.downcast::().unwrap(); 81 | stream.set_nonblocking(true).unwrap(); 82 | 83 | tokio_handle.spawn(async move { 84 | let stream = TcpStream::from_std(stream).unwrap(); 85 | let mut ws = WebSocketStream::from_raw_socket(stream, Role::Server, None).await; 86 | 87 | while let Some(Ok(msg)) = ws.next().await { 88 | if msg.is_text() && ws.send(msg).await.is_err() { 89 | break; 90 | } 91 | } 92 | }); 93 | })) 94 | }) 95 | } 96 | ``` 97 | 98 | ## Other examples 99 | 100 | ### Chunked response 101 | 102 | ```rust no_run 103 | use std::{error::Error, thread}; 104 | 105 | use touche::{Body, Response, Server, StatusCode}; 106 | 107 | fn main() -> std::io::Result<()> { 108 | Server::bind("0.0.0.0:4444").serve(|_req| { 109 | let (channel, body) = Body::channel(); 110 | 111 | thread::spawn(move || { 112 | channel.send("chunk1").unwrap(); 113 | channel.send("chunk2").unwrap(); 114 | channel.send("chunk3").unwrap(); 115 | }); 116 | 117 | Response::builder() 118 | .status(StatusCode::OK) 119 | .body(body) 120 | }) 121 | } 122 | ``` 123 | 124 | ### Streaming files 125 | 126 | ```rust no_run 127 | use std::{fs, io}; 128 | 129 | use touche::{Body, Response, Server, StatusCode}; 130 | 131 | fn main() -> std::io::Result<()> { 132 | Server::bind("0.0.0.0:4444").serve(|_req| { 133 | let file = fs::File::open("./examples/file.rs")?; 134 | Ok::<_, io::Error>( 135 | Response::builder() 136 | .status(StatusCode::OK) 137 | .body(Body::try_from(file)?) 138 | .unwrap(), 139 | ) 140 | }) 141 | } 142 | ``` 143 | 144 | ### Naive routing with pattern matching 145 | 146 | ```rust no_run 147 | use touche::{body::HttpBody, Body, Method, Request, Response, Server, StatusCode}; 148 | 149 | fn main() -> std::io::Result<()> { 150 | Server::builder() 151 | .bind("0.0.0.0:4444") 152 | .serve(|req: Request| { 153 | match (req.method(), req.uri().path()) { 154 | (_, "/") => Response::builder() 155 | .status(StatusCode::OK) 156 | .body(Body::from("Usage: curl -d hello localhost:4444/echo\n")), 157 | 158 | // Responds with the same payload 159 | (&Method::POST, "/echo") => Response::builder() 160 | .status(StatusCode::OK) 161 | .body(req.into_body()), 162 | 163 | // Responds with the reversed payload 164 | (&Method::POST, "/reverse") => { 165 | let body = req.into_body().into_bytes().unwrap_or_default(); 166 | 167 | match std::str::from_utf8(&body) { 168 | Ok(message) => Response::builder() 169 | .status(StatusCode::OK) 170 | .body(message.chars().rev().collect::().into()), 171 | 172 | Err(err) => Response::builder() 173 | .status(StatusCode::BAD_REQUEST) 174 | .body(err.to_string().into()), 175 | } 176 | } 177 | 178 | _ => Response::builder() 179 | .status(StatusCode::NOT_FOUND) 180 | .body(Body::empty()), 181 | } 182 | }) 183 | } 184 | 185 | ``` 186 | 187 | ### Response upgrades 188 | ```rust no_run 189 | use std::io::{BufRead, BufReader, BufWriter, Write}; 190 | 191 | use touche::{header, upgrade::Upgrade, Body, Connection, Response, Server, StatusCode}; 192 | 193 | fn main() -> std::io::Result<()> { 194 | Server::bind("0.0.0.0:4444").serve(|_req| { 195 | Response::builder() 196 | .status(StatusCode::SWITCHING_PROTOCOLS) 197 | .header(header::UPGRADE, "line-protocol") 198 | .upgrade(|stream: Connection| { 199 | let reader = BufReader::new(stream.clone()); 200 | let mut writer = BufWriter::new(stream); 201 | 202 | // Just a simple protocol that will echo every line sent 203 | for line in reader.lines() { 204 | match line { 205 | Ok(line) if line.as_str() == "quit" => break, 206 | Ok(line) => { 207 | writer.write_all(format!("{line}\n").as_bytes()); 208 | writer.flush(); 209 | } 210 | Err(_err) => break, 211 | }; 212 | } 213 | }) 214 | .body(Body::empty()) 215 | }) 216 | } 217 | ``` 218 | 219 | You can find other examples in the [examples directory](https://github.com/reu/touche/tree/master/examples). 220 | 221 | ## Performance 222 | 223 | While the primary focus is having a simple and readable implementation, the library 224 | shows some decent performance. 225 | 226 | A simple benchmark of the hello-world.rs example gives the following result: 227 | 228 | ```sh 229 | $ cat /proc/cpuinfo | grep name | uniq 230 | model name : 13th Gen Intel(R) Core(TM) i7-13700K 231 | 232 | $ wrk --latency -t6 -c 200 -d 10s http://localhost:4444 233 | Running 10s test @ http://localhost:4444 234 | 6 threads and 200 connections 235 | Thread Stats Avg Stdev Max +/- Stdev 236 | Latency 51.73us 450.03us 29.59ms 99.81% 237 | Req/Sec 251.58k 52.42k 366.43k 70.15% 238 | Latency Distribution 239 | 50% 36.00us 240 | 75% 47.00us 241 | 90% 71.00us 242 | 99% 115.00us 243 | 15089728 requests in 10.10s, 1.25GB read 244 | Requests/sec: 1494130.41 245 | Transfer/sec: 126.82MB 246 | ``` 247 | 248 | The result is on par with Hyper's hello world running on the same machine. 249 | 250 | ## Disclaimer 251 | 252 | The main motivation I had to write this library was to be able to introduce Rust to my co-workers 253 | (which are mainly web developers). 254 | 255 | Most of HTTP server libraries in Rust are async, which makes sense for the problem domain, but with 256 | that some additional complexity comes together, which can be overwhelming when you are just getting 257 | start with the language. 258 | 259 | The ideia is to provide an API that ressembles the "canonical" HTTP Rust library, so people can 260 | learn its concepts in a easier way before adventuring through Hyper and async Rust. 261 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io::{self, BufReader, BufWriter, Write}, 4 | net::TcpStream, 5 | }; 6 | 7 | use headers::HeaderMapExt; 8 | use http::{header::HOST, uri::Authority, StatusCode}; 9 | use thiserror::Error; 10 | 11 | use crate::{request, response, Body, Connection, HttpBody}; 12 | 13 | #[derive(Debug, Error)] 14 | pub enum RequestError { 15 | #[error("invalid uri")] 16 | InvalidUri, 17 | #[error("unsupported scheme")] 18 | UnsupportedScheme, 19 | #[error("unsupported http version: {0}")] 20 | UnsupportedHttpVersion(u8), 21 | #[error("io error")] 22 | Io(#[from] io::Error), 23 | #[error("invalid request")] 24 | InvalidRequest(#[from] Box), 25 | } 26 | 27 | #[derive(Debug, Default)] 28 | pub struct Client { 29 | connections: HashMap, 30 | } 31 | 32 | impl Client { 33 | pub fn new() -> Self { 34 | Client { 35 | connections: Default::default(), 36 | } 37 | } 38 | 39 | pub fn request( 40 | &mut self, 41 | mut req: http::Request, 42 | ) -> Result, RequestError> { 43 | let authority = req 44 | .uri() 45 | .authority() 46 | .ok_or(RequestError::InvalidUri)? 47 | .clone(); 48 | 49 | let host = authority.host().to_string(); 50 | let port = authority.port_u16().unwrap_or(80); 51 | 52 | let connection = match self.connections.remove(&authority) { 53 | Some(conn) => conn, 54 | None => TcpStream::connect(format!("{host}:{port}"))?.into(), 55 | }; 56 | 57 | req.headers_mut() 58 | .insert(HOST, host.as_str().try_into().unwrap()); 59 | 60 | let (connection, mut res) = send_request(connection, req)?; 61 | 62 | match connection { 63 | ConnectionOutcome::Close => Ok(res), 64 | ConnectionOutcome::Upgrade(conn) => { 65 | res.extensions_mut().insert(conn); 66 | Ok(res) 67 | } 68 | ConnectionOutcome::KeepAlive(conn) => { 69 | self.connections.insert(authority, conn); 70 | Ok(res) 71 | } 72 | } 73 | } 74 | } 75 | 76 | #[derive(Debug)] 77 | pub enum ConnectionOutcome { 78 | Close, 79 | KeepAlive(Connection), 80 | Upgrade(Connection), 81 | } 82 | 83 | impl ConnectionOutcome { 84 | pub fn closed(&self) -> bool { 85 | matches!(self, ConnectionOutcome::Close) 86 | } 87 | 88 | pub fn unwrap(self) -> Connection { 89 | match self { 90 | ConnectionOutcome::Close => panic!("Connection closed"), 91 | ConnectionOutcome::KeepAlive(conn) => conn, 92 | ConnectionOutcome::Upgrade(conn) => conn, 93 | } 94 | } 95 | 96 | pub fn into_inner(self) -> Result { 97 | match self { 98 | ConnectionOutcome::KeepAlive(conn) => Ok(conn), 99 | ConnectionOutcome::Upgrade(conn) => Ok(conn), 100 | ConnectionOutcome::Close => Err(self), 101 | } 102 | } 103 | } 104 | 105 | pub fn send_request( 106 | connection: C, 107 | req: http::Request, 108 | ) -> io::Result<(ConnectionOutcome, http::Response)> 109 | where 110 | C: Into, 111 | B: HttpBody, 112 | { 113 | let conn = connection.into(); 114 | 115 | let reader = BufReader::new(conn.clone()); 116 | let mut writer = BufWriter::new(conn); 117 | 118 | request::write_request(req, &mut writer)?; 119 | writer.flush()?; 120 | 121 | let res = response::parse_response(reader) 122 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; 123 | 124 | let asks_for_close = res 125 | .headers() 126 | .typed_get::() 127 | .filter(|conn| conn.contains("close")) 128 | .is_some(); 129 | 130 | let outcome = if asks_for_close { 131 | ConnectionOutcome::Close 132 | } else if res.status() == StatusCode::SWITCHING_PROTOCOLS { 133 | ConnectionOutcome::Upgrade(writer.into_inner()?) 134 | } else { 135 | ConnectionOutcome::KeepAlive(writer.into_inner()?) 136 | }; 137 | 138 | Ok((outcome, res)) 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use std::{ 144 | io::Cursor, 145 | net::{TcpListener, TcpStream}, 146 | thread, 147 | }; 148 | 149 | use http::{Request, Version}; 150 | 151 | use crate::Server; 152 | 153 | use super::*; 154 | 155 | #[test] 156 | fn test_client() { 157 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 158 | let port = listener.local_addr().unwrap().port(); 159 | 160 | thread::spawn(move || { 161 | Server::from(listener) 162 | .serve(|req: Request<_>| http::Response::builder().body(req.into_body())) 163 | .ok() 164 | }); 165 | 166 | let mut client = Client::new(); 167 | let uri = format!("http://127.0.0.1:{port}"); 168 | 169 | let res = client 170 | .request( 171 | http::Request::builder() 172 | .uri(&uri) 173 | .method("POST") 174 | .body("Hello world") 175 | .unwrap(), 176 | ) 177 | .unwrap(); 178 | assert_eq!(res.into_body().into_bytes().unwrap(), b"Hello world"); 179 | 180 | let res = client 181 | .request( 182 | http::Request::builder() 183 | .uri(&uri) 184 | .method("POST") 185 | .body("Bye world") 186 | .unwrap(), 187 | ) 188 | .unwrap(); 189 | assert_eq!(res.into_body().into_bytes().unwrap(), b"Bye world"); 190 | } 191 | 192 | #[test] 193 | fn test_send_request() { 194 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 195 | let port = listener.local_addr().unwrap().port(); 196 | 197 | thread::spawn(move || { 198 | Server::from(listener) 199 | .serve(|req: Request<_>| http::Response::builder().body(req.into_body())) 200 | .ok() 201 | }); 202 | 203 | let conn = TcpStream::connect(("127.0.0.1", port)).unwrap(); 204 | 205 | let req = http::Request::builder().body("Hello world").unwrap(); 206 | let (conn, res) = send_request(conn, req).unwrap(); 207 | assert_eq!(res.into_body().into_bytes().unwrap(), b"Hello world"); 208 | 209 | let req = http::Request::builder().body("Bye world").unwrap(); 210 | let (conn, res) = send_request(conn.unwrap(), req).unwrap(); 211 | assert_eq!(res.into_body().into_bytes().unwrap(), b"Bye world"); 212 | 213 | let req = http::Request::builder().body(()).unwrap(); 214 | let (conn, res) = send_request(conn.unwrap(), req).unwrap(); 215 | assert_eq!(res.into_body().into_bytes().unwrap(), b""); 216 | 217 | let req = http::Request::builder() 218 | .header("transfer-encoding", "chunked") 219 | .body(Body::from_iter(vec![&b"lol"[..], &b"wut"[..]])) 220 | .unwrap(); 221 | let (_conn, res) = send_request(conn.unwrap(), req).unwrap(); 222 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 223 | } 224 | 225 | #[test] 226 | fn correctly_handles_closing_connections() { 227 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 228 | let port = listener.local_addr().unwrap().port(); 229 | 230 | thread::spawn(move || { 231 | Server::from(listener) 232 | .serve(|_req| { 233 | http::Response::builder() 234 | .header("connection", "close") 235 | .body(Body::from_reader(Cursor::new(b"lolwut"), None)) 236 | }) 237 | .ok(); 238 | }); 239 | 240 | let conn = TcpStream::connect(("127.0.0.1", port)).unwrap(); 241 | 242 | let req = http::Request::builder().body(()).unwrap(); 243 | let (conn, res) = send_request(conn, req).unwrap(); 244 | 245 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 246 | assert!(conn.closed()); 247 | } 248 | 249 | #[test] 250 | fn keep_http_10_connection_alive_when_asked_to() { 251 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 252 | let port = listener.local_addr().unwrap().port(); 253 | 254 | thread::spawn(move || { 255 | Server::from(listener) 256 | .serve(|_req| http::Response::builder().body("lolwut")) 257 | .ok(); 258 | }); 259 | 260 | let conn = TcpStream::connect(("127.0.0.1", port)).unwrap(); 261 | 262 | let req = http::Request::builder() 263 | .version(Version::HTTP_10) 264 | .header("connection", "keep-alive") 265 | .body(()) 266 | .unwrap(); 267 | 268 | let (conn, res) = send_request(conn, req).unwrap(); 269 | 270 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 271 | assert!(matches!(conn, ConnectionOutcome::KeepAlive(_))); 272 | 273 | let req = http::Request::builder() 274 | .version(Version::HTTP_10) 275 | .body(()) 276 | .unwrap(); 277 | 278 | let (conn, res) = send_request(conn.unwrap(), req).unwrap(); 279 | 280 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 281 | assert!(matches!(conn, ConnectionOutcome::Close)); 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "client")] 2 | use std::io::Write; 3 | use std::io::{self, BufRead, Read}; 4 | 5 | use http::Request; 6 | use thiserror::Error; 7 | 8 | use crate::body::Body; 9 | 10 | #[cfg(feature = "client")] 11 | use crate::HttpBody; 12 | 13 | #[derive(Error, Debug)] 14 | pub enum ParseError { 15 | #[error("connection closed")] 16 | ConnectionClosed, 17 | #[error("io error")] 18 | Io(#[from] io::Error), 19 | #[error("invalid request")] 20 | Invalid(#[from] httparse::Error), 21 | #[error("incomplete request")] 22 | IncompleteRequest, 23 | #[error("unsupported http version: {0}")] 24 | UnsupportedHttpVersion(u8), 25 | #[error("invalid Transfer-Encoding header")] 26 | InvalidTransferEncoding, 27 | #[error("invalid header")] 28 | InvalidHeader(#[from] headers::Error), 29 | #[error("failed to parse http request")] 30 | Unknown, 31 | } 32 | 33 | #[cfg(feature = "server")] 34 | pub(crate) fn parse_request( 35 | mut stream: impl BufRead + Send + 'static, 36 | ) -> Result, ParseError> { 37 | use headers::HeaderMapExt; 38 | use http::{Method, Version}; 39 | 40 | let mut buf = Vec::with_capacity(800); 41 | 42 | loop { 43 | if stream.read_until(b'\n', &mut buf)? == 0 { 44 | break; 45 | } 46 | 47 | match buf.as_slice() { 48 | [.., b'\r', b'\n', b'\r', b'\n'] => break, 49 | [.., b'\n', b'\n'] => break, 50 | _ => continue, 51 | } 52 | } 53 | 54 | if buf.is_empty() { 55 | return Err(ParseError::ConnectionClosed); 56 | } 57 | 58 | let mut headers = [httparse::EMPTY_HEADER; 64]; 59 | let mut req = httparse::Request::new(&mut headers); 60 | req.parse(&buf)?; 61 | 62 | let method = req 63 | .method 64 | .map(|method| method.as_bytes()) 65 | .ok_or(ParseError::IncompleteRequest)?; 66 | 67 | let path = req.path.ok_or(ParseError::IncompleteRequest)?; 68 | 69 | let version = match req.version.ok_or(ParseError::IncompleteRequest)? { 70 | 0 => Version::HTTP_10, 71 | 1 => Version::HTTP_11, 72 | version => return Err(ParseError::UnsupportedHttpVersion(version)), 73 | }; 74 | 75 | let request = Request::builder() 76 | .method(Method::from_bytes(method).map_err(|_| ParseError::IncompleteRequest)?) 77 | .uri(path) 78 | .version(version); 79 | 80 | let request = headers 81 | .into_iter() 82 | .take_while(|header| *header != httparse::EMPTY_HEADER) 83 | .map(|header| (header.name, header.value)) 84 | .fold(request, |req, (name, value)| req.header(name, value)); 85 | 86 | let headers = request.headers_ref().ok_or(ParseError::Unknown)?; 87 | 88 | let body = if let Some(encoding) = headers.typed_try_get::()? { 89 | if !encoding.is_chunked() { 90 | // https://datatracker.ietf.org/doc/html/rfc2616#section-3.6 91 | return Err(ParseError::InvalidTransferEncoding); 92 | } 93 | Body::from_iter(ChunkedReader(Box::new(stream))) 94 | } else if let Some(len) = headers.typed_try_get::()? { 95 | // Let's automatically buffer small bodies 96 | if len.0 < 1024 { 97 | let mut buf = vec![0_u8; len.0 as usize]; 98 | stream.read_exact(&mut buf)?; 99 | Body::from(buf) 100 | } else { 101 | Body::from_reader(stream, len.0 as usize) 102 | } 103 | } else { 104 | Body::empty() 105 | }; 106 | 107 | request.body(body).map_err(|_| ParseError::Unknown) 108 | } 109 | 110 | #[cfg(feature = "client")] 111 | pub(crate) fn write_request( 112 | req: http::Request, 113 | stream: &mut impl Write, 114 | ) -> io::Result<()> { 115 | use crate::{body::Chunk, response::Encoding}; 116 | use headers::{HeaderMap, HeaderMapExt}; 117 | use http::{request::Parts, Method, Version}; 118 | 119 | let ( 120 | Parts { 121 | method, 122 | uri, 123 | version, 124 | mut headers, 125 | .. 126 | }, 127 | body, 128 | ) = req.into_parts(); 129 | 130 | let has_chunked_encoding = headers 131 | .typed_get::() 132 | .filter(|te| te.is_chunked()) 133 | .is_some(); 134 | 135 | let content_length = headers.typed_get::(); 136 | 137 | let encoding = if has_chunked_encoding && version == Version::HTTP_11 { 138 | Encoding::Chunked 139 | } else if content_length.is_some() || body.len().is_some() { 140 | match (content_length, body.len()) { 141 | (Some(len), Some(body_len)) => { 142 | if len.0 != body_len { 143 | return Err(io::Error::new( 144 | io::ErrorKind::Other, 145 | "content-length doesn't match body length", 146 | )); 147 | } 148 | Encoding::FixedLength(len.0) 149 | } 150 | (Some(len), None) => Encoding::FixedLength(len.0), 151 | (None, Some(len)) => { 152 | headers.typed_insert::(headers::ContentLength(len)); 153 | Encoding::FixedLength(len) 154 | } 155 | (None, None) => unreachable!(), 156 | } 157 | } else if body.len().is_none() 158 | && method != Method::GET 159 | && method != Method::HEAD 160 | && version == Version::HTTP_11 161 | { 162 | headers.typed_insert::(headers::TransferEncoding::chunked()); 163 | Encoding::Chunked 164 | } else { 165 | return Err(io::Error::new( 166 | io::ErrorKind::Other, 167 | "could not determine the size of the body", 168 | )); 169 | }; 170 | 171 | let version = if version == Version::HTTP_11 { 172 | "HTTP/1.1" 173 | } else if version == Version::HTTP_10 { 174 | "HTTP/1.0" 175 | } else { 176 | return Err(io::Error::new( 177 | io::ErrorKind::Other, 178 | "unsupported http version", 179 | )); 180 | }; 181 | 182 | stream.write_all(format!("{method} {uri} {version}\r\n").as_bytes())?; 183 | 184 | for (name, val) in headers.iter() { 185 | stream.write_all(&[format!("{name}: ").as_bytes(), val.as_bytes(), b"\r\n"].concat())?; 186 | } 187 | 188 | stream.write_all(b"\r\n")?; 189 | 190 | match encoding { 191 | // Just buffer small bodies 192 | Encoding::FixedLength(len) if len < 1024 => { 193 | stream.write_all(&body.into_bytes()?)?; 194 | } 195 | Encoding::FixedLength(_) | Encoding::CloseDelimited => { 196 | io::copy(&mut body.into_reader(), stream)?; 197 | } 198 | Encoding::Chunked => { 199 | let mut trailers = HeaderMap::new(); 200 | 201 | for chunk in body.into_chunks() { 202 | match chunk? { 203 | Chunk::Data(chunk) => { 204 | stream.write_all(format!("{:x}\r\n", chunk.len()).as_bytes())?; 205 | stream.write_all(&chunk)?; 206 | stream.write_all(b"\r\n")?; 207 | stream.flush()?; 208 | } 209 | Chunk::Trailers(te) => { 210 | trailers.extend(te); 211 | } 212 | } 213 | } 214 | 215 | stream.write_all(b"0\r\n")?; 216 | for (name, val) in trailers.iter() { 217 | stream.write_all( 218 | &[format!("{name}: ").as_bytes(), val.as_bytes(), b"\r\n"].concat(), 219 | )?; 220 | } 221 | stream.write_all(b"\r\n")?; 222 | } 223 | }; 224 | 225 | Ok(()) 226 | } 227 | 228 | pub(crate) struct ChunkedReader(pub(crate) Box); 229 | 230 | impl Iterator for ChunkedReader { 231 | type Item = Vec; 232 | 233 | fn next(&mut self) -> Option { 234 | let mut buf = Vec::new(); 235 | 236 | loop { 237 | if self.0.read_until(b'\n', &mut buf).ok()? == 0 { 238 | return None; 239 | } 240 | 241 | match httparse::parse_chunk_size(&buf) { 242 | Ok(httparse::Status::Complete((_pos, 0))) => { 243 | return None; 244 | } 245 | Ok(httparse::Status::Complete((_pos, size))) => { 246 | let mut chunk = vec![0_u8; size as usize]; 247 | self.0.read_exact(&mut chunk).ok()?; 248 | self.0.read_until(b'\n', &mut buf).ok()?; 249 | return Some(chunk); 250 | } 251 | Ok(httparse::Status::Partial) => continue, 252 | Err(_) => return None, 253 | } 254 | } 255 | } 256 | } 257 | 258 | #[cfg(test)] 259 | mod test { 260 | use http::Version; 261 | 262 | use crate::body::HttpBody; 263 | 264 | use super::*; 265 | 266 | #[test] 267 | fn parse_request_without_body() { 268 | let req = "GET /lolwut HTTP/1.1\r\nHost: lol.com\r\n\r\n"; 269 | let req = std::io::Cursor::new(req); 270 | 271 | let req = parse_request(req).unwrap(); 272 | 273 | assert_eq!(Version::HTTP_11, req.version()); 274 | assert_eq!("/lolwut", req.uri().path()); 275 | assert_eq!( 276 | Some("lol.com"), 277 | req.headers() 278 | .get(http::header::HOST) 279 | .and_then(|v| v.to_str().ok()) 280 | ); 281 | } 282 | 283 | #[test] 284 | fn parse_request_with_content_length_body() { 285 | let req = "POST /lol HTTP/1.1\r\nHost: lol.com\r\nContent-Length: 6\r\n\r\nlolwut ignored"; 286 | let req = std::io::Cursor::new(req); 287 | 288 | let req = parse_request(req).unwrap(); 289 | 290 | assert_eq!(req.into_body().into_bytes().unwrap(), b"lolwut"); 291 | } 292 | 293 | #[test] 294 | fn parse_request_with_chunked_body() { 295 | let req = "POST /lol HTTP/1.1\r\nHost: lol.com\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nlol\r\n3\r\nwut\r\n0\r\n\r\n"; 296 | let req = std::io::Cursor::new(req); 297 | 298 | let req = parse_request(req).unwrap(); 299 | 300 | assert_eq!(req.into_body().into_bytes().unwrap(), b"lolwut"); 301 | } 302 | 303 | #[test] 304 | fn parse_request_with_chunked_body_and_extensions() { 305 | let req = "POST /lol HTTP/1.1\r\nHost: lol.com\r\nTransfer-Encoding: chunked\r\n\r\n3;extension\r\nlol\r\n3\r\nwut\r\n0\r\n\r\n"; 306 | let req = std::io::Cursor::new(req); 307 | 308 | let req = parse_request(req).unwrap(); 309 | 310 | assert_eq!(req.into_body().into_bytes().unwrap(), b"lolwut"); 311 | } 312 | 313 | #[test] 314 | fn parse_request_with_streaming_body() { 315 | let req = b"POST /lol HTTP/1.1\r\nHost: lol.com\r\nContent-Length: 2048\r\n\r\n"; 316 | let body = [65_u8; 2048]; 317 | let req = std::io::Cursor::new([req.as_ref(), body.as_ref()].concat()); 318 | 319 | let req = parse_request(req).unwrap(); 320 | 321 | assert_eq!(req.into_body().into_bytes().unwrap(), body); 322 | } 323 | 324 | #[test] 325 | fn fails_to_parse_incomplete_request() { 326 | let req = std::io::Cursor::new("POST /lol"); 327 | 328 | assert!(matches!( 329 | parse_request(req), 330 | Err(ParseError::IncompleteRequest) 331 | )); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/body.rs: -------------------------------------------------------------------------------- 1 | //! Streaming bodies for [`Requests`](http::Request) and [`Responses`](http::Response). 2 | //! 3 | //! Bodies are not buffered by default, so applications don't use memory they don't need. 4 | //! 5 | //! - The [`HttpBody`] trait, which describes all possible bodies. This allows custom 6 | //! implementation if you need fine-grained control on how to stream and chunk the data. 7 | //! - The [`Body`] concrete type, which is an implementation of [`HttpBody`] returned by touche 8 | //! as a "receive stream". It is also a decent default implementation for your send streams. 9 | use std::{ 10 | error::Error, 11 | fmt::Debug, 12 | fs::File, 13 | io::{self, Cursor, Read}, 14 | path::PathBuf, 15 | sync::mpsc::{self, Sender}, 16 | }; 17 | 18 | use headers::{HeaderMap, HeaderName, HeaderValue}; 19 | pub use http_body::*; 20 | 21 | mod http_body; 22 | 23 | /// The [`HttpBody`] used on receiving server requests. 24 | /// It is also a good default body to return as responses. 25 | #[derive(Default)] 26 | pub struct Body(Option); 27 | 28 | #[derive(Default)] 29 | enum BodyInner { 30 | #[default] 31 | Empty, 32 | Buffered(Vec), 33 | Iter(Box> + Send>), 34 | Reader(Box, Option), 35 | } 36 | 37 | impl Debug for Body { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | let mut out = f.debug_tuple("Body"); 40 | match self.0 { 41 | Some(BodyInner::Empty) | None => out.field(&"empty"), 42 | Some(BodyInner::Buffered(ref buffer)) => out.field(buffer), 43 | Some(BodyInner::Iter(_)) => out.field(&"chunked"), 44 | Some(BodyInner::Reader(..)) => out.field(&"streaming"), 45 | }; 46 | out.finish() 47 | } 48 | } 49 | 50 | /// The sender half of a channel, used to stream chunks from another thread. 51 | #[derive(Debug)] 52 | pub struct BodyChannel(Sender>); 53 | 54 | impl BodyChannel { 55 | /// Send a chunk of bytes to this body. 56 | pub fn send>>(&self, data: T) -> io::Result<()> { 57 | self.0 58 | .send(Ok(data.into().into())) 59 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "body closed")) 60 | } 61 | 62 | /// Send a trailer header. Note that trailers are buffered, and are only sent after the last 63 | /// chunk is sent 64 | pub fn send_trailer( 65 | &self, 66 | header: K, 67 | value: V, 68 | ) -> Result<(), Box> 69 | where 70 | K: TryInto, 71 | V: TryInto, 72 | >::Error: Error + Send + Sync + 'static, 73 | >::Error: Error + Send + Sync + 'static, 74 | { 75 | let mut trailers = HeaderMap::new(); 76 | trailers.insert(header.try_into()?, value.try_into()?); 77 | Ok(self.send_trailers(trailers)?) 78 | } 79 | 80 | /// Sends trailers to this body. Note that trailers are buffered, and are only sent after the 81 | /// last chunk is sent 82 | pub fn send_trailers(&self, trailers: HeaderMap) -> io::Result<()> { 83 | self.0 84 | .send(Ok(Chunk::Trailers(trailers))) 85 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "body closed")) 86 | } 87 | 88 | /// Aborts the body in an abnormal fashion. 89 | pub fn abort(self) { 90 | self.0 91 | .send(Err(io::Error::new(io::ErrorKind::Other, "aborted"))) 92 | .ok(); 93 | } 94 | } 95 | 96 | impl Body { 97 | /// Creates an empty [`Body`] stream. 98 | pub fn empty() -> Self { 99 | Body(Some(BodyInner::Empty)) 100 | } 101 | 102 | /// Creates a [`Body`] stream with an associated sender half. 103 | /// Useful when wanting to stream chunks from another thread. 104 | pub fn channel() -> (BodyChannel, Self) { 105 | let (tx, rx) = mpsc::channel(); 106 | let body = Body(Some(BodyInner::Iter(Box::new(rx.into_iter())))); 107 | (BodyChannel(tx), body) 108 | } 109 | 110 | /// Creates a [`Body`] stream from an Iterator of chunks. 111 | /// Each item emitted will be written as a separated chunk on chunked encoded requests or 112 | /// responses. 113 | #[allow(clippy::should_implement_trait)] 114 | pub fn from_iter(chunks: I) -> Self 115 | where 116 | T: Into, 117 | I: IntoIterator + Send + 'static, 118 | ::IntoIter: Send, 119 | { 120 | Body(Some(BodyInner::Iter(Box::new( 121 | chunks.into_iter().map(|chunk| Ok(chunk.into())), 122 | )))) 123 | } 124 | 125 | /// Creates a [`Body`] stream from an [`Read`], with an optional length. 126 | pub fn from_reader>>( 127 | reader: impl Read + Send + 'static, 128 | length: T, 129 | ) -> Self { 130 | Body(Some(BodyInner::Reader(Box::new(reader), length.into()))) 131 | } 132 | } 133 | 134 | impl HttpBody for Body { 135 | type Reader = BodyReader; 136 | type Chunks = ChunkIterator; 137 | 138 | fn len(&self) -> Option { 139 | match &self.0 { 140 | Some(BodyInner::Empty) => Some(0), 141 | Some(BodyInner::Buffered(bytes)) => Some(bytes.len() as u64), 142 | Some(BodyInner::Iter(_)) => None, 143 | Some(BodyInner::Reader(_, Some(len))) => Some(*len as u64), 144 | Some(BodyInner::Reader(_, None)) => None, 145 | None => None, 146 | } 147 | } 148 | 149 | fn into_reader(mut self) -> Self::Reader { 150 | match self.0.take().unwrap() { 151 | BodyInner::Empty => BodyReader(BodyReaderInner::Buffered(Cursor::new(Vec::new()))), 152 | BodyInner::Buffered(bytes) => BodyReader(BodyReaderInner::Buffered(Cursor::new(bytes))), 153 | BodyInner::Iter(chunks) => { 154 | let mut chunks = chunks.filter_map(|chunk| match chunk { 155 | Ok(Chunk::Data(data)) => Some(Ok(data)), 156 | Ok(Chunk::Trailers(_)) => None, 157 | Err(err) => Some(Err(err)), 158 | }); 159 | let cursor = chunks 160 | .next() 161 | .map(|chunk| chunk.unwrap_or_default()) 162 | .map(Cursor::new); 163 | BodyReader(BodyReaderInner::Iter(Box::new(chunks), cursor)) 164 | } 165 | BodyInner::Reader(stream, Some(len)) => { 166 | BodyReader(BodyReaderInner::Reader(Box::new(stream.take(len as u64)))) 167 | } 168 | BodyInner::Reader(stream, None) => BodyReader(BodyReaderInner::Reader(stream)), 169 | } 170 | } 171 | 172 | fn into_bytes(mut self) -> io::Result> { 173 | match self.0.take().unwrap() { 174 | BodyInner::Empty => Ok(Vec::new()), 175 | BodyInner::Buffered(bytes) => Ok(bytes), 176 | BodyInner::Iter(chunks) => Ok(chunks 177 | .filter_map(|chunk| match chunk { 178 | Ok(Chunk::Data(data)) => Some(Ok(data)), 179 | Ok(Chunk::Trailers(_)) => None, 180 | Err(err) => Some(Err(err)), 181 | }) 182 | .collect::>>()? 183 | .into_iter() 184 | .flatten() 185 | .collect()), 186 | BodyInner::Reader(stream, Some(len)) => { 187 | let mut buf = Vec::with_capacity(len); 188 | stream.take(len as u64).read_to_end(&mut buf)?; 189 | Ok(buf) 190 | } 191 | BodyInner::Reader(mut stream, None) => { 192 | let mut buf = Vec::with_capacity(8 * 1024); 193 | stream.read_to_end(&mut buf)?; 194 | Ok(buf) 195 | } 196 | } 197 | } 198 | 199 | fn into_chunks(mut self) -> Self::Chunks { 200 | match self.0.take().unwrap() { 201 | BodyInner::Empty => ChunkIterator(None), 202 | BodyInner::Buffered(bytes) => ChunkIterator(Some(ChunkIteratorInner::Single(bytes))), 203 | BodyInner::Iter(chunks) => ChunkIterator(Some(ChunkIteratorInner::Iter(chunks))), 204 | BodyInner::Reader(reader, len) => { 205 | ChunkIterator(Some(ChunkIteratorInner::Reader(reader, len))) 206 | } 207 | } 208 | } 209 | } 210 | 211 | impl Drop for Body { 212 | fn drop(&mut self) { 213 | #[allow(unused_must_use)] 214 | match self.0.take() { 215 | Some(BodyInner::Reader(ref mut stream, Some(len))) => { 216 | io::copy(&mut stream.take(len as u64), &mut io::sink()); 217 | } 218 | Some(BodyInner::Reader(ref mut stream, None)) => { 219 | io::copy(stream, &mut io::sink()); 220 | } 221 | _ => {} 222 | } 223 | } 224 | } 225 | 226 | impl From> for Body { 227 | fn from(body: Vec) -> Self { 228 | Body(Some(BodyInner::Buffered(body))) 229 | } 230 | } 231 | 232 | impl From<&[u8]> for Body { 233 | fn from(body: &[u8]) -> Self { 234 | body.to_vec().into() 235 | } 236 | } 237 | 238 | impl From<&str> for Body { 239 | fn from(body: &str) -> Self { 240 | body.as_bytes().to_vec().into() 241 | } 242 | } 243 | 244 | impl From for Body { 245 | fn from(body: String) -> Self { 246 | body.into_bytes().into() 247 | } 248 | } 249 | 250 | impl TryFrom for Body { 251 | type Error = io::Error; 252 | 253 | fn try_from(file: File) -> Result { 254 | match file.metadata() { 255 | Ok(meta) if meta.is_file() => Ok(Body::from_reader(file, meta.len() as usize)), 256 | Ok(_) => Err(io::Error::new(io::ErrorKind::Other, "not a file")), 257 | Err(err) => Err(err), 258 | } 259 | } 260 | } 261 | 262 | impl TryFrom for Body { 263 | type Error = io::Error; 264 | 265 | fn try_from(path: PathBuf) -> Result { 266 | File::open(path)?.try_into() 267 | } 268 | } 269 | 270 | /// Wraps a body and turns into a [`Read`]. 271 | pub struct BodyReader(BodyReaderInner); 272 | 273 | impl BodyReader { 274 | /// Creates a [`BodyReader`] from an [`Read`] 275 | pub fn from_reader(reader: impl Read + 'static) -> Self { 276 | BodyReader(BodyReaderInner::Reader(Box::new(reader))) 277 | } 278 | 279 | /// Creates a [`BodyReader`] from an [`Iterator`] 280 | #[allow(clippy::should_implement_trait)] 281 | pub fn from_iter(iter: impl IntoIterator> + 'static) -> Self { 282 | let mut iter = iter.into_iter(); 283 | let cursor = iter.next().map(Cursor::new); 284 | BodyReader(BodyReaderInner::Iter(Box::new(iter.map(Ok)), cursor)) 285 | } 286 | } 287 | 288 | enum BodyReaderInner { 289 | Buffered(Cursor>), 290 | Iter( 291 | Box>>>, 292 | Option>>, 293 | ), 294 | Reader(Box), 295 | } 296 | 297 | impl Read for BodyReader { 298 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 299 | match self.0 { 300 | BodyReaderInner::Buffered(ref mut cursor) => cursor.read(buf), 301 | BodyReaderInner::Reader(ref mut reader) => reader.read(buf), 302 | 303 | // TODO: support for non partial reads here 304 | BodyReaderInner::Iter(ref mut iter, ref mut leftover) => { 305 | while let Some(ref mut cursor) = leftover { 306 | let read = cursor.read(buf)?; 307 | if read > 0 { 308 | return Ok(read); 309 | } 310 | let next = iter.next().and_then(|next| next.ok()).map(Cursor::new); 311 | *leftover = next; 312 | } 313 | Ok(0) 314 | } 315 | } 316 | } 317 | } 318 | 319 | impl From> for BodyReader { 320 | fn from(buf: Vec) -> Self { 321 | Self(BodyReaderInner::Buffered(Cursor::new(buf))) 322 | } 323 | } 324 | 325 | impl From for BodyReader { 326 | fn from(mut body: Body) -> Self { 327 | match body.0.take().unwrap() { 328 | BodyInner::Empty => Vec::new().into(), 329 | BodyInner::Buffered(bytes) => bytes.into(), 330 | BodyInner::Iter(chunks) => { 331 | let mut chunks = chunks.filter_map(|chunk| match chunk { 332 | Ok(Chunk::Data(data)) => Some(Ok(data)), 333 | Ok(Chunk::Trailers(_)) => None, 334 | Err(err) => Some(Err(err)), 335 | }); 336 | let cursor = chunks 337 | .next() 338 | .map(|chunk| chunk.unwrap_or_default()) 339 | .map(Cursor::new); 340 | BodyReader(BodyReaderInner::Iter(Box::new(chunks), cursor)) 341 | } 342 | BodyInner::Reader(stream, Some(len)) => { 343 | BodyReader(BodyReaderInner::Reader(Box::new(stream.take(len as u64)))) 344 | } 345 | BodyInner::Reader(stream, None) => BodyReader(BodyReaderInner::Reader(stream)), 346 | } 347 | } 348 | } 349 | 350 | /// Iterate bodies in chunks 351 | pub struct ChunkIterator(Option); 352 | 353 | impl ChunkIterator { 354 | pub fn from_reader>>(reader: impl Read + 'static, length: T) -> Self { 355 | Self(Some(ChunkIteratorInner::Reader( 356 | Box::new(reader), 357 | length.into(), 358 | ))) 359 | } 360 | } 361 | 362 | enum ChunkIteratorInner { 363 | Single(Vec), 364 | Iter(Box>>), 365 | Reader(Box, Option), 366 | } 367 | 368 | impl Iterator for ChunkIterator { 369 | type Item = io::Result; 370 | 371 | fn next(&mut self) -> Option { 372 | match self.0.take()? { 373 | ChunkIteratorInner::Single(bytes) => Some(Ok(bytes.into())), 374 | ChunkIteratorInner::Iter(mut iter) => { 375 | let item = iter.next()?.ok()?; 376 | self.0 = Some(ChunkIteratorInner::Iter(iter)); 377 | Some(Ok(item)) 378 | } 379 | ChunkIteratorInner::Reader(mut reader, Some(len)) => { 380 | let mut buf = [0_u8; 8 * 1024]; 381 | match reader.read(&mut buf) { 382 | Ok(0) => None, 383 | Ok(bytes) => { 384 | self.0 = match len.checked_sub(bytes) { 385 | r @ Some(rem) if rem > 0 => Some(ChunkIteratorInner::Reader(reader, r)), 386 | _ => None, 387 | }; 388 | Some(Ok(buf[0..bytes].to_vec().into())) 389 | } 390 | Err(err) => Some(Err(err)), 391 | } 392 | } 393 | ChunkIteratorInner::Reader(mut reader, None) => { 394 | let mut buf = [0_u8; 8 * 1024]; 395 | match reader.read(&mut buf) { 396 | Ok(0) => None, 397 | Ok(bytes) => { 398 | self.0 = Some(ChunkIteratorInner::Reader(reader, None)); 399 | Some(Ok(buf[0..bytes].to_vec().into())) 400 | } 401 | Err(err) => Some(Err(err)), 402 | } 403 | } 404 | } 405 | } 406 | } 407 | 408 | #[cfg(test)] 409 | mod tests { 410 | use std::io::{Cursor, Read}; 411 | 412 | use crate::{body::HttpBody, Body}; 413 | 414 | #[test] 415 | fn test_body_reader_buffered() { 416 | let body = Body::from(vec![1_u8, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 417 | let mut reader = body.into_reader(); 418 | 419 | let mut buf = [0_u8; 4]; 420 | reader.read_exact(&mut buf).unwrap(); 421 | assert_eq!(buf, [1, 2, 3, 4]); 422 | 423 | let mut buf = [0_u8; 1]; 424 | reader.read_exact(&mut buf).unwrap(); 425 | assert_eq!(buf, [5]); 426 | 427 | let mut buf = [0_u8; 5]; 428 | reader.read_exact(&mut buf).unwrap(); 429 | assert_eq!(buf, [6, 7, 8, 9, 10]); 430 | } 431 | 432 | #[test] 433 | fn test_body_reader_chunked() { 434 | let body = Body::from_iter([vec![1, 2, 3], vec![4, 5, 6], vec![7], vec![8, 9], vec![10]]); 435 | let mut reader = body.into_reader(); 436 | 437 | let mut buf = [0_u8; 4]; 438 | reader.read_exact(&mut buf).unwrap(); 439 | assert_eq!(buf, [1, 2, 3, 4]); 440 | 441 | let mut buf = [0_u8; 1]; 442 | reader.read_exact(&mut buf).unwrap(); 443 | assert_eq!(buf, [5]); 444 | 445 | let mut buf = [0_u8; 5]; 446 | reader.read_exact(&mut buf).unwrap(); 447 | assert_eq!(buf, [6, 7, 8, 9, 10]); 448 | } 449 | 450 | #[test] 451 | fn test_body_reader_with_unknown_size() { 452 | let reader = Cursor::new(vec![1_u8, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 453 | let body = Body::from_reader(reader, None); 454 | let mut reader = body.into_reader(); 455 | 456 | let mut buf = [0_u8; 4]; 457 | reader.read_exact(&mut buf).unwrap(); 458 | assert_eq!(buf, [1, 2, 3, 4]); 459 | 460 | let mut buf = [0_u8; 1]; 461 | reader.read_exact(&mut buf).unwrap(); 462 | assert_eq!(buf, [5]); 463 | 464 | let mut buf = [0_u8; 5]; 465 | reader.read_exact(&mut buf).unwrap(); 466 | assert_eq!(buf, [6, 7, 8, 9, 10]); 467 | } 468 | 469 | #[test] 470 | fn test_body_reader_with_known_size() { 471 | let reader = Cursor::new(vec![1_u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); 472 | let body = Body::from_reader(reader, 10); 473 | let mut reader = body.into_reader(); 474 | 475 | let mut buf = [0_u8; 4]; 476 | reader.read_exact(&mut buf).unwrap(); 477 | assert_eq!(buf, [1, 2, 3, 4]); 478 | 479 | let mut buf = [0_u8; 1]; 480 | reader.read_exact(&mut buf).unwrap(); 481 | assert_eq!(buf, [5]); 482 | 483 | let mut buf = [0_u8; 5]; 484 | reader.read_exact(&mut buf).unwrap(); 485 | assert_eq!(buf, [6, 7, 8, 9, 10]); 486 | 487 | let mut buf = Vec::new(); 488 | reader.read_to_end(&mut buf).unwrap(); 489 | assert!(buf.is_empty()); 490 | } 491 | 492 | #[test] 493 | fn test_chunk_with_errors() { 494 | let (channel, body) = Body::channel(); 495 | channel.send("123").unwrap(); 496 | channel.send("456").unwrap(); 497 | drop(channel); 498 | assert_eq!(body.into_bytes().unwrap(), b"123456"); 499 | 500 | let (channel, body) = Body::channel(); 501 | channel.send("123").unwrap(); 502 | channel.send("456").unwrap(); 503 | channel.abort(); 504 | assert!(body.into_bytes().is_err()); 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! HTTP Server 2 | //! 3 | //! The [`Server`] is responsible to read and parse a [`http::Request`], and then execute a [`Service`] 4 | //! to generate a [`http::Response`]. 5 | //! 6 | //! The implementation follows a simple thread per connection model, backed by a thread pool. 7 | //! 8 | //! # Example 9 | //! ```no_run 10 | //! use touche::{Response, Server, StatusCode}; 11 | //! 12 | //! fn main() -> std::io::Result<()> { 13 | //! Server::builder() 14 | //! .max_threads(256) 15 | //! .bind("0.0.0.0:4444") 16 | //! .serve(|_req| { 17 | //! Response::builder() 18 | //! .status(StatusCode::OK) 19 | //! .body(()) 20 | //! }) 21 | //! } 22 | //! ``` 23 | use std::{ 24 | error::Error, 25 | io::{self, BufReader, BufWriter, Write}, 26 | net::{TcpListener, ToSocketAddrs}, 27 | time::{Duration, SystemTime}, 28 | }; 29 | 30 | use headers::{HeaderMapExt, HeaderValue}; 31 | use http::{Method, Request, Response, StatusCode, Version}; 32 | #[cfg(feature = "threadpool")] 33 | use threadpool::ThreadPool; 34 | 35 | use crate::{ 36 | body::HttpBody, 37 | read_queue::ReadQueue, 38 | request::{self, ParseError}, 39 | response::{self, Outcome}, 40 | Body, Connection, 41 | }; 42 | 43 | type IncomingRequest = Request; 44 | 45 | /// Maps [`Requests`](http::Request) to [`Responses`](http::Response). 46 | /// 47 | /// Usually you don't need to manually implement this trait, as its `Fn` implementation might suffice 48 | /// most of the needs. 49 | /// 50 | /// ```no_run 51 | /// # use std::convert::Infallible; 52 | /// # use touche::{Body, Request, Response, Server, StatusCode}; 53 | /// fn app(req: Request) -> Result, Infallible> { 54 | /// Ok(Response::builder().status(StatusCode::OK).body(()).unwrap()) 55 | /// } 56 | /// 57 | /// fn main() -> std::io::Result<()> { 58 | /// Server::bind("0.0.0.0:4444").serve(app) 59 | /// } 60 | /// ``` 61 | /// 62 | /// You might want to implement this trait if you wish to handle Expect 100-continue. 63 | /// ```no_run 64 | /// # use std::convert::Infallible; 65 | /// # use headers::HeaderMapExt; 66 | /// # use touche::{server::Service, Body, Request, Response, Server, StatusCode}; 67 | /// #[derive(Clone)] 68 | /// struct UploadService { 69 | /// max_length: u64, 70 | /// } 71 | /// 72 | /// impl Service for UploadService { 73 | /// type Body = &'static str; 74 | /// type Error = Infallible; 75 | /// 76 | /// fn call(&mut self, _req: Request) -> Result, Self::Error> { 77 | /// Ok(Response::builder() 78 | /// .status(StatusCode::OK) 79 | /// .body("Thanks for the info!") 80 | /// .unwrap()) 81 | /// } 82 | /// 83 | /// fn should_continue(&mut self, req: &Request) -> StatusCode { 84 | /// match req.headers().typed_get::() { 85 | /// Some(len) if len.0 <= self.max_length => StatusCode::CONTINUE, 86 | /// _ => StatusCode::EXPECTATION_FAILED, 87 | /// } 88 | /// } 89 | /// } 90 | /// 91 | /// fn main() -> std::io::Result<()> { 92 | /// Server::bind("0.0.0.0:4444").serve(UploadService { max_length: 1024 }) 93 | /// } 94 | /// ``` 95 | pub trait Service { 96 | type Body: HttpBody; 97 | type Error: Into>; 98 | 99 | fn call(&mut self, request: IncomingRequest) -> Result, Self::Error>; 100 | 101 | fn should_continue(&mut self, _: &IncomingRequest) -> StatusCode { 102 | StatusCode::CONTINUE 103 | } 104 | } 105 | 106 | impl Service for F 107 | where 108 | F: FnMut(IncomingRequest) -> Result, Err>, 109 | Body: HttpBody, 110 | Err: Into>, 111 | { 112 | type Body = Body; 113 | type Error = Err; 114 | 115 | fn call(&mut self, request: IncomingRequest) -> Result, Self::Error> { 116 | self(request) 117 | } 118 | } 119 | 120 | /// A listening HTTP server that accepts HTTP 1 connections. 121 | pub struct Server<'a> { 122 | #[cfg(feature = "threadpool")] 123 | thread_pool: ThreadPool, 124 | incoming: Box + 'a>, 125 | } 126 | 127 | impl From for Server<'static> { 128 | fn from(listener: TcpListener) -> Self { 129 | Self::builder().from_connections(TcpAcceptor { listener }) 130 | } 131 | } 132 | 133 | impl Server<'_> { 134 | /// Starts the [`ServerBuilder`]. 135 | pub fn builder() -> ServerBuilder { 136 | Default::default() 137 | } 138 | 139 | /// Binds the [`Server`] to the given `addr`. 140 | /// 141 | /// # Panics 142 | /// 143 | /// This method will panic if binding to the address fails. For a non panic method to bind the 144 | /// server, see [`ServerBuilder::try_bind`]. 145 | pub fn bind(addr: A) -> Server<'static> { 146 | Self::builder().bind(addr) 147 | } 148 | 149 | /// Serves an [`Service`] on a thread per connection model, backed by a thread pool. 150 | /// 151 | /// # Example 152 | /// ```no_run 153 | /// # use touche::{Request, Response, Server, StatusCode}; 154 | /// # fn main() -> std::io::Result<()> { 155 | /// Server::bind("0.0.0.0:4444").serve(|req: Request<_>| { 156 | /// Response::builder() 157 | /// .status(StatusCode::OK) 158 | /// .body(req.into_body()) 159 | /// }) 160 | /// # } 161 | /// ``` 162 | #[cfg(feature = "threadpool")] 163 | pub fn serve(self, service: S) -> io::Result<()> 164 | where 165 | S: Service, 166 | S: Send + Clone + 'static, 167 | { 168 | for conn in self.incoming { 169 | let mut app = service.clone(); 170 | self.thread_pool.execute(move || { 171 | serve(conn, &mut app).ok(); 172 | }); 173 | } 174 | 175 | Ok(()) 176 | } 177 | 178 | /// Serves an [`Service`] on a single thread. This is useful when your [`Service`] is not 179 | /// [`Send`]. Note that if a connection is kept alive on this mode, no other request may be 180 | /// served before the said connection is closed. 181 | /// 182 | /// # Example 183 | /// ```no_run 184 | /// # use touche::{Request, Response, Server, StatusCode}; 185 | /// # fn main() -> std::io::Result<()> { 186 | /// let mut counter = 0; 187 | /// Server::bind("0.0.0.0:4444").serve_single_thread(|_req| { 188 | /// counter += 1; 189 | /// Response::builder() 190 | /// .status(StatusCode::OK) 191 | /// .body(format!("Request count: {}", counter)) 192 | /// }) 193 | /// # } 194 | /// ``` 195 | pub fn serve_single_thread(self, mut service: S) -> io::Result<()> 196 | where 197 | S: Service, 198 | { 199 | for conn in self.incoming { 200 | serve(conn, &mut service).ok(); 201 | } 202 | Ok(()) 203 | } 204 | 205 | /// Hook into how a [`Connection`] handles its requests. This should be used when you need to 206 | /// execute some logic on every connection. 207 | /// 208 | /// # Example 209 | /// ```no_run 210 | /// # use std::convert::Infallible; 211 | /// # use touche::{Connection, Response, Server, StatusCode}; 212 | /// # fn main() -> std::io::Result<()> { 213 | /// Server::builder() 214 | /// .bind("0.0.0.0:4444") 215 | /// .make_service(|conn: &Connection| { 216 | /// println!("New connection arrived: {:?}", conn.peer_addr()); 217 | /// 218 | /// Ok::<_, Infallible>(|_req| { 219 | /// Response::builder() 220 | /// .status(StatusCode::OK) 221 | /// .body(()) 222 | /// }) 223 | /// }) 224 | /// # } 225 | /// ``` 226 | /// 227 | /// # Per connection shared mutable state 228 | /// 229 | /// You share any state for a given Connection without having to worry about any 230 | /// synchronization on it. 231 | /// ```no_run 232 | /// # use std::convert::Infallible; 233 | /// # use touche::{Connection, Response, Server, StatusCode}; 234 | /// # fn main() -> std::io::Result<()> { 235 | /// Server::builder() 236 | /// .bind("0.0.0.0:4444") 237 | /// .make_service(|_conn: &Connection| { 238 | /// let mut counter = 0; 239 | /// 240 | /// Ok::<_, Infallible>(move |_req| { 241 | /// counter += 1; 242 | /// 243 | /// Response::builder() 244 | /// .status(StatusCode::OK) 245 | /// .body(format!("Requests on this connection: {counter}")) 246 | /// }) 247 | /// }) 248 | /// # } 249 | /// ``` 250 | #[cfg(feature = "threadpool")] 251 | pub fn make_service(self, make_service: M) -> io::Result<()> 252 | where 253 | M: MakeService + 'static, 254 | ::Service: Send, 255 | { 256 | for conn in self.incoming { 257 | if let Ok(mut handler) = make_service.call(&conn) { 258 | self.thread_pool.execute(move || { 259 | serve(conn, &mut handler).ok(); 260 | }); 261 | } 262 | } 263 | 264 | Ok(()) 265 | } 266 | } 267 | 268 | pub struct ServerBuilder { 269 | #[cfg(feature = "threadpool")] 270 | max_threads: usize, 271 | read_timeout: Option, 272 | nodelay: bool, 273 | } 274 | 275 | impl Default for ServerBuilder { 276 | fn default() -> Self { 277 | Self { 278 | #[cfg(feature = "threadpool")] 279 | max_threads: 512, 280 | read_timeout: None, 281 | nodelay: false, 282 | } 283 | } 284 | } 285 | 286 | impl ServerBuilder { 287 | /// Define the max number of threads this server may create. Defaults to `512`. 288 | /// 289 | /// # Example 290 | /// ```no_run 291 | /// # use touche::{Response, Server, StatusCode}; 292 | /// # fn main() -> std::io::Result<()> { 293 | /// Server::builder() 294 | /// .max_threads(12) 295 | /// .bind("0.0.0.0:4444") 296 | /// .serve(|_req| { 297 | /// Response::builder() 298 | /// .status(StatusCode::OK) 299 | /// .body(()) 300 | /// }) 301 | /// # } 302 | /// ``` 303 | #[cfg(feature = "threadpool")] 304 | pub fn max_threads(self, max_threads: usize) -> Self { 305 | Self { 306 | max_threads, 307 | ..self 308 | } 309 | } 310 | 311 | /// Sets the time limit that connections will be kept alive when no data is received. 312 | /// Defaults to no time limit at all. 313 | /// 314 | /// # Example 315 | /// ```no_run 316 | /// # use std::time::Duration; 317 | /// # use touche::{Response, Server, StatusCode}; 318 | /// # fn main() -> std::io::Result<()> { 319 | /// Server::builder() 320 | /// // Close the connection if no data arrives in 10 seconds 321 | /// .read_timeout(Duration::from_secs(10)) 322 | /// .bind("0.0.0.0:4444") 323 | /// .serve(|_req| { 324 | /// Response::builder() 325 | /// .status(StatusCode::OK) 326 | /// .body(()) 327 | /// }) 328 | /// # } 329 | /// ``` 330 | /// 331 | /// # Example with upgraded connection 332 | /// 333 | /// Be careful when using this option with upgraded connections, as the underlying protocol may 334 | /// need some different timeout configurations. In that case, you can use the 335 | /// [`Connection::set_read_timeout`] to set per connection configuration. 336 | /// 337 | /// ```no_run 338 | /// # use std::{ 339 | /// # io::{Read, Write}, 340 | /// # time::Duration, 341 | /// # }; 342 | /// # use touche::{header, upgrade::Upgrade, Connection, Response, Server, StatusCode}; 343 | /// # fn main() -> std::io::Result<()> { 344 | /// Server::builder() 345 | /// // Sets the server read timeout to 10 seconds 346 | /// .read_timeout(Duration::from_secs(10)) 347 | /// .bind("0.0.0.0:4444") 348 | /// .serve(|_req| { 349 | /// Response::builder() 350 | /// .status(StatusCode::SWITCHING_PROTOCOLS) 351 | /// .header(header::UPGRADE, "echo") 352 | /// .upgrade(|mut conn: Connection| { 353 | /// // Don't timeout on the upgraded connection 354 | /// conn.set_read_timeout(None).unwrap(); 355 | /// 356 | /// loop { 357 | /// let mut buf = [0; 1024]; 358 | /// match conn.read(&mut buf) { 359 | /// Ok(n) if n > 0 => conn.write(&buf[0..n]).unwrap(), 360 | /// _ => break, 361 | /// }; 362 | /// } 363 | /// }) 364 | /// .body(()) 365 | /// }) 366 | /// # } 367 | /// ``` 368 | pub fn read_timeout>>(self, timeout: T) -> Self { 369 | Self { 370 | read_timeout: timeout.into(), 371 | ..self 372 | } 373 | } 374 | 375 | /// Sets the value of the `TCP_NODELAY` option on every server connection by default. 376 | /// 377 | /// If set, this option disables the Nagle algorithm. This means that segments are always sent 378 | /// as soon as possible, even if there is only a small amount of data. When not set, data is 379 | /// buffered until there is a sufficient amount to send out, thereby avoiding the frequent 380 | /// sending of small packets. 381 | /// 382 | /// Note that you can also set this option per [`Connection`]. 383 | pub fn nodelay(self, nodelay: bool) -> Self { 384 | Self { nodelay, ..self } 385 | } 386 | 387 | /// Binds the [`Server`] to the given `addr`. 388 | /// 389 | /// # Panics 390 | /// 391 | /// This method will panic if binding to the address fails. For a non panic way to bind a 392 | /// server, see [`ServerBuilder::try_bind`]. 393 | pub fn bind(self, addr: A) -> Server<'static> { 394 | self.try_bind(addr).unwrap() 395 | } 396 | 397 | /// Tries to bind the server to the informed `addr`. 398 | pub fn try_bind(self, addr: A) -> io::Result> { 399 | let listener = TcpListener::bind(addr)?; 400 | Ok(self.from_connections(TcpAcceptor { listener })) 401 | } 402 | 403 | /// Accepts connections from some [`Iterator`]. 404 | pub fn from_connections<'a, C: Into>( 405 | self, 406 | conns: impl IntoIterator + 'a, 407 | ) -> Server<'a> { 408 | Server { 409 | #[cfg(feature = "threadpool")] 410 | thread_pool: ThreadPool::new(self.max_threads), 411 | incoming: Box::new(conns.into_iter().filter_map(move |conn| { 412 | let conn = conn.into(); 413 | conn.set_read_timeout(self.read_timeout).ok()?; 414 | conn.set_nodelay(self.nodelay).ok()?; 415 | Some(conn) 416 | })), 417 | } 418 | } 419 | } 420 | 421 | struct TcpAcceptor { 422 | listener: TcpListener, 423 | } 424 | 425 | impl Iterator for TcpAcceptor { 426 | type Item = Connection; 427 | 428 | fn next(&mut self) -> Option { 429 | Some(self.listener.accept().ok()?.into()) 430 | } 431 | } 432 | 433 | pub trait MakeService { 434 | type Service: Service; 435 | type Error: Into>; 436 | 437 | fn call(&self, conn: &Connection) -> Result; 438 | } 439 | 440 | impl MakeService for F 441 | where 442 | F: Fn(&Connection) -> Result, 443 | Err: Into>, 444 | S: Service + Send, 445 | { 446 | type Service = S; 447 | type Error = Err; 448 | 449 | fn call(&self, conn: &Connection) -> Result { 450 | self(conn) 451 | } 452 | } 453 | 454 | fn serve(conn: Connection, app: &mut A) -> io::Result<()> { 455 | let mut read_queue = ReadQueue::new(BufReader::new(conn.clone())); 456 | 457 | let mut reader = read_queue.enqueue(); 458 | let mut writer = BufWriter::new(conn); 459 | 460 | loop { 461 | match request::parse_request(reader) { 462 | Ok(req) => { 463 | reader = read_queue.enqueue(); 464 | 465 | let asks_for_close = req 466 | .headers() 467 | .typed_get::() 468 | .filter(|conn| conn.contains("close")) 469 | .is_some(); 470 | 471 | let asks_for_keep_alive = req 472 | .headers() 473 | .typed_get::() 474 | .filter(|conn| conn.contains("keep-alive")) 475 | .is_some(); 476 | 477 | let version = req.version(); 478 | let method = req.method().clone(); 479 | 480 | let demands_close = match version { 481 | Version::HTTP_09 => true, 482 | Version::HTTP_10 => !asks_for_keep_alive, 483 | _ => asks_for_close, 484 | }; 485 | 486 | let expects_continue = req 487 | .headers() 488 | .typed_get::() 489 | .filter(|expect| expect == &headers::Expect::CONTINUE) 490 | .is_some(); 491 | 492 | if expects_continue { 493 | match app.should_continue(&req) { 494 | status @ StatusCode::CONTINUE => { 495 | let res = Response::builder().status(status).body(()).unwrap(); 496 | response::write_response(res, &mut writer, true)?; 497 | writer.flush()?; 498 | } 499 | status => { 500 | let res = Response::builder().status(status).body(()).unwrap(); 501 | response::write_response(res, &mut writer, true)?; 502 | writer.flush()?; 503 | continue; 504 | } 505 | }; 506 | } 507 | 508 | let mut res = app 509 | .call(req) 510 | .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; 511 | 512 | *res.version_mut() = version; 513 | 514 | if version == Version::HTTP_10 && !asks_for_keep_alive { 515 | res.headers_mut() 516 | .insert("connection", HeaderValue::from_static("close")); 517 | } 518 | 519 | if res.headers().typed_get::().is_none() { 520 | res.headers_mut() 521 | .typed_insert(headers::Date::from(SystemTime::now())); 522 | } 523 | 524 | let should_write_body = match method { 525 | Method::HEAD => false, 526 | Method::CONNECT => res.status().is_success(), 527 | _ => true, 528 | }; 529 | 530 | match response::write_response(res, &mut writer, should_write_body)? { 531 | Outcome::KeepAlive if demands_close => break, 532 | Outcome::KeepAlive => writer.flush()?, 533 | Outcome::Close => break, 534 | Outcome::Upgrade(upgrade) => { 535 | drop(reader); 536 | drop(read_queue); 537 | upgrade.handler.handle(writer.into_inner()?); 538 | break; 539 | } 540 | } 541 | } 542 | Err(ParseError::ConnectionClosed) => break, 543 | Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err)), 544 | } 545 | } 546 | 547 | Ok(()) 548 | } 549 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "client", test))] 2 | use std::io::BufRead; 3 | use std::io::{self, Write}; 4 | 5 | use headers::{HeaderMap, HeaderMapExt}; 6 | #[cfg(any(feature = "client", test))] 7 | use http::StatusCode; 8 | use http::{response::Parts, Version}; 9 | 10 | use crate::{body::Chunk, upgrade::UpgradeExtension, HttpBody}; 11 | #[cfg(any(feature = "client", test))] 12 | use crate::{ 13 | request::{ChunkedReader, ParseError}, 14 | Body, 15 | }; 16 | 17 | #[derive(PartialEq, Eq)] 18 | pub(crate) enum Encoding { 19 | FixedLength(u64), 20 | Chunked, 21 | CloseDelimited, 22 | } 23 | 24 | pub(crate) enum Outcome { 25 | Close, 26 | KeepAlive, 27 | Upgrade(UpgradeExtension), 28 | } 29 | 30 | #[cfg(any(feature = "client", test))] 31 | pub(crate) fn parse_response( 32 | mut stream: impl BufRead + Send + 'static, 33 | ) -> Result, ParseError> { 34 | let mut buf = Vec::with_capacity(800); 35 | 36 | loop { 37 | if stream.read_until(b'\n', &mut buf)? == 0 { 38 | break; 39 | } 40 | 41 | match buf.as_slice() { 42 | [.., b'\r', b'\n', b'\r', b'\n'] => break, 43 | [.., b'\n', b'\n'] => break, 44 | _ => continue, 45 | } 46 | } 47 | 48 | if buf.is_empty() { 49 | return Err(ParseError::IncompleteRequest); 50 | } 51 | 52 | let mut headers = [httparse::EMPTY_HEADER; 64]; 53 | let mut res = httparse::Response::new(&mut headers); 54 | res.parse(&buf)?; 55 | 56 | let status = res 57 | .code 58 | .and_then(|code| StatusCode::from_u16(code).ok()) 59 | .ok_or(ParseError::IncompleteRequest)?; 60 | 61 | let version = match res.version.ok_or(ParseError::IncompleteRequest)? { 62 | 0 => Version::HTTP_10, 63 | 1 => Version::HTTP_11, 64 | version => return Err(ParseError::UnsupportedHttpVersion(version)), 65 | }; 66 | 67 | let res = http::Response::builder().version(version).status(status); 68 | 69 | let res = headers 70 | .into_iter() 71 | .take_while(|header| *header != httparse::EMPTY_HEADER) 72 | .map(|header| (header.name, header.value)) 73 | .fold(res, |res, (name, value)| res.header(name, value)); 74 | 75 | let headers = res.headers_ref().ok_or(ParseError::Unknown)?; 76 | 77 | let body = if let Some(encoding) = headers.typed_try_get::()? { 78 | if !encoding.is_chunked() { 79 | // https://datatracker.ietf.org/doc/html/rfc2616#section-3.6 80 | return Err(ParseError::InvalidTransferEncoding); 81 | } 82 | Body::from_iter(ChunkedReader(Box::new(stream))) 83 | } else if let Some(len) = headers.typed_try_get::()? { 84 | // Let's automatically buffer small bodies 85 | if len.0 < 1024 { 86 | let mut buf = vec![0_u8; len.0 as usize]; 87 | stream.read_exact(&mut buf)?; 88 | Body::from(buf) 89 | } else { 90 | Body::from_reader(stream, len.0 as usize) 91 | } 92 | } else if headers 93 | .typed_get::() 94 | .filter(|conn| conn.contains("close")) 95 | .is_some() 96 | { 97 | Body::from_reader(stream, None) 98 | } else { 99 | Body::empty() 100 | }; 101 | 102 | res.body(body).map_err(|_| ParseError::Unknown) 103 | } 104 | 105 | #[cfg(feature = "server")] 106 | pub(crate) fn write_response( 107 | res: http::Response, 108 | stream: &mut impl Write, 109 | write_body: bool, 110 | ) -> io::Result { 111 | let ( 112 | Parts { 113 | status, 114 | version, 115 | mut headers, 116 | mut extensions, 117 | .. 118 | }, 119 | body, 120 | ) = res.into_parts(); 121 | 122 | let has_chunked_encoding = headers 123 | .typed_get::() 124 | .filter(|te| te.is_chunked()) 125 | .is_some(); 126 | 127 | let has_connection_close = headers 128 | .typed_get::() 129 | .filter(|conn| conn.contains("close")) 130 | .is_some(); 131 | 132 | let content_length = headers.typed_get::(); 133 | 134 | let encoding = if has_chunked_encoding && version == Version::HTTP_11 { 135 | Encoding::Chunked 136 | } else if content_length.is_some() || body.len().is_some() { 137 | match (content_length, body.len()) { 138 | (Some(len), Some(body_len)) => { 139 | if len.0 != body_len { 140 | return Err(io::Error::new( 141 | io::ErrorKind::Other, 142 | "content-length doesn't match body length", 143 | )); 144 | } 145 | Encoding::FixedLength(len.0) 146 | } 147 | (Some(len), None) => Encoding::FixedLength(len.0), 148 | (None, Some(len)) => { 149 | headers.typed_insert::(headers::ContentLength(len)); 150 | Encoding::FixedLength(len) 151 | } 152 | (None, None) => unreachable!(), 153 | } 154 | } else if body.len().is_none() && !has_connection_close && version == Version::HTTP_11 { 155 | headers.typed_insert::(headers::TransferEncoding::chunked()); 156 | Encoding::Chunked 157 | } else { 158 | if !has_connection_close { 159 | headers.typed_insert::(headers::Connection::close()); 160 | } 161 | Encoding::CloseDelimited 162 | }; 163 | 164 | if version == Version::HTTP_10 && has_chunked_encoding { 165 | headers.remove(http::header::TRANSFER_ENCODING); 166 | }; 167 | 168 | stream.write_all(format!("{version:?} {status}\r\n").as_bytes())?; 169 | 170 | for (name, val) in headers.iter() { 171 | stream.write_all(&[format!("{name}: ").as_bytes(), val.as_bytes(), b"\r\n"].concat())?; 172 | } 173 | 174 | stream.write_all(b"\r\n")?; 175 | 176 | if write_body { 177 | match encoding { 178 | // Just buffer small bodies 179 | Encoding::FixedLength(len) if len < 1024 => { 180 | stream.write_all(&body.into_bytes()?)?; 181 | } 182 | Encoding::FixedLength(_) | Encoding::CloseDelimited => { 183 | io::copy(&mut body.into_reader(), stream)?; 184 | } 185 | Encoding::Chunked => { 186 | let mut trailers = HeaderMap::new(); 187 | 188 | for chunk in body.into_chunks() { 189 | match chunk? { 190 | Chunk::Data(chunk) => { 191 | stream.write_all(format!("{:x}\r\n", chunk.len()).as_bytes())?; 192 | stream.write_all(&chunk)?; 193 | stream.write_all(b"\r\n")?; 194 | stream.flush()?; 195 | } 196 | Chunk::Trailers(te) => { 197 | trailers.extend(te); 198 | } 199 | } 200 | } 201 | 202 | stream.write_all(b"0\r\n")?; 203 | for (name, val) in trailers.iter() { 204 | stream.write_all( 205 | &[format!("{name}: ").as_bytes(), val.as_bytes(), b"\r\n"].concat(), 206 | )?; 207 | } 208 | stream.write_all(b"\r\n")?; 209 | } 210 | }; 211 | } 212 | 213 | let connection = headers.typed_get::(); 214 | 215 | let outcome = if let Some(upgrade) = extensions.remove::() { 216 | Outcome::Upgrade(upgrade) 217 | } else if encoding == Encoding::CloseDelimited 218 | || connection.filter(|conn| conn.contains("close")).is_some() 219 | { 220 | Outcome::Close 221 | } else { 222 | Outcome::KeepAlive 223 | }; 224 | 225 | Ok(outcome) 226 | } 227 | 228 | #[cfg(test)] 229 | mod tests { 230 | use std::{io::Cursor, thread}; 231 | 232 | use crate::{upgrade::Upgrade, Body}; 233 | 234 | use super::*; 235 | use http::{Response, StatusCode}; 236 | 237 | #[test] 238 | fn writes_responses_without_bodies() { 239 | let res = Response::builder() 240 | .status(StatusCode::OK) 241 | .body(Body::empty()) 242 | .unwrap(); 243 | 244 | let mut output: Cursor> = Cursor::new(Vec::new()); 245 | let outcome = write_response(res, &mut output, true).unwrap(); 246 | 247 | assert_eq!( 248 | output.get_ref(), 249 | b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n" 250 | ); 251 | assert!(matches!(outcome, Outcome::KeepAlive)); 252 | } 253 | 254 | #[test] 255 | fn writes_responses_with_bodies() { 256 | let res = Response::builder() 257 | .status(StatusCode::OK) 258 | .body("lol") 259 | .unwrap(); 260 | 261 | let mut output: Cursor> = Cursor::new(Vec::new()); 262 | let outcome = write_response(res, &mut output, true).unwrap(); 263 | 264 | assert_eq!( 265 | output.get_ref(), 266 | b"HTTP/1.1 200 OK\r\ncontent-length: 3\r\n\r\nlol" 267 | ); 268 | assert!(matches!(outcome, Outcome::KeepAlive)); 269 | } 270 | 271 | #[test] 272 | fn allows_to_skip_body_writing() { 273 | let res = Response::builder() 274 | .status(StatusCode::OK) 275 | .body("lol") 276 | .unwrap(); 277 | 278 | let mut output: Cursor> = Cursor::new(Vec::new()); 279 | let outcome = write_response(res, &mut output, false).unwrap(); 280 | 281 | assert_eq!( 282 | output.get_ref(), 283 | b"HTTP/1.1 200 OK\r\ncontent-length: 3\r\n\r\n" 284 | ); 285 | assert!(matches!(outcome, Outcome::KeepAlive)); 286 | } 287 | 288 | #[test] 289 | fn fails_when_the_informed_content_length_does_not_match_the_body_length() { 290 | let res = Response::builder() 291 | .status(StatusCode::OK) 292 | .header("content-length", "5") 293 | .body("lol") 294 | .unwrap(); 295 | 296 | let mut output: Cursor> = Cursor::new(Vec::new()); 297 | assert!(write_response(res, &mut output, true).is_err()); 298 | } 299 | 300 | #[test] 301 | fn writes_chunked_responses() { 302 | let res = Response::builder() 303 | .status(StatusCode::OK) 304 | .header("transfer-encoding", "chunked") 305 | .body(Body::from_iter(vec![ 306 | b"chunk1".to_vec(), 307 | b"chunk2".to_vec(), 308 | ])) 309 | .unwrap(); 310 | 311 | let mut output: Cursor> = Cursor::new(Vec::new()); 312 | let outcome = write_response(res, &mut output, true).unwrap(); 313 | 314 | assert_eq!( 315 | output.get_ref(), 316 | b"HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n6\r\nchunk1\r\n6\r\nchunk2\r\n0\r\n\r\n" 317 | ); 318 | assert!(matches!(outcome, Outcome::KeepAlive)); 319 | } 320 | 321 | #[test] 322 | fn writes_chunked_responses_with_trailers() { 323 | let (sender, body) = Body::channel(); 324 | 325 | let send_thread = thread::spawn(move || { 326 | sender.send("lol").unwrap(); 327 | sender.send("wut").unwrap(); 328 | sender.send_trailer("content-length", "6").unwrap(); 329 | }); 330 | 331 | let res = Response::builder() 332 | .status(StatusCode::OK) 333 | .header("trailers", "content-length") 334 | .body(body) 335 | .unwrap(); 336 | 337 | let mut output: Cursor> = Cursor::new(Vec::new()); 338 | let outcome = write_response(res, &mut output, true).unwrap(); 339 | 340 | send_thread.join().unwrap(); 341 | 342 | assert_eq!( 343 | output.get_ref(), 344 | b"HTTP/1.1 200 OK\r\ntrailers: content-length\r\ntransfer-encoding: chunked\r\n\r\n3\r\nlol\r\n3\r\nwut\r\n0\r\ncontent-length: 6\r\n\r\n" 345 | ); 346 | assert!(matches!(outcome, Outcome::KeepAlive)); 347 | } 348 | 349 | #[test] 350 | fn writes_responses_from_reader_with_known_size() { 351 | let res = Response::builder() 352 | .status(StatusCode::OK) 353 | .body(Body::from_reader(Cursor::new(b"lol"), Some(3))) 354 | .unwrap(); 355 | 356 | let mut output: Cursor> = Cursor::new(Vec::new()); 357 | let outcome = write_response(res, &mut output, true).unwrap(); 358 | 359 | assert_eq!( 360 | output.get_ref(), 361 | b"HTTP/1.1 200 OK\r\ncontent-length: 3\r\n\r\nlol" 362 | ); 363 | assert!(matches!(outcome, Outcome::KeepAlive)); 364 | } 365 | 366 | #[test] 367 | fn limits_the_from_reader_response_body_size() { 368 | let res = Response::builder() 369 | .status(StatusCode::OK) 370 | .body(Body::from_reader(Cursor::new(b"lolwut"), Some(3))) 371 | .unwrap(); 372 | 373 | let mut output: Cursor> = Cursor::new(Vec::new()); 374 | let outcome = write_response(res, &mut output, true).unwrap(); 375 | 376 | assert_eq!( 377 | output.get_ref(), 378 | b"HTTP/1.1 200 OK\r\ncontent-length: 3\r\n\r\nlol" 379 | ); 380 | assert!(matches!(outcome, Outcome::KeepAlive)); 381 | } 382 | 383 | #[test] 384 | fn uses_chunked_transfer_when_the_reader_size_is_undefined() { 385 | let res = Response::builder() 386 | .status(StatusCode::OK) 387 | .body(Body::from_reader(Cursor::new(b"lolwut"), None)) 388 | .unwrap(); 389 | 390 | let mut output: Cursor> = Cursor::new(Vec::new()); 391 | let outcome = write_response(res, &mut output, true).unwrap(); 392 | 393 | assert_eq!( 394 | output.get_ref(), 395 | b"HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n6\r\nlolwut\r\n0\r\n\r\n" 396 | ); 397 | assert!(matches!(outcome, Outcome::KeepAlive)); 398 | } 399 | 400 | #[test] 401 | fn does_not_use_chunked_encoding_when_the_reader_size_is_undefined_and_connection_is_close() { 402 | let res = Response::builder() 403 | .status(StatusCode::OK) 404 | .header("connection", "close") 405 | .body(Body::from_reader(Cursor::new(b"lolwut"), None)) 406 | .unwrap(); 407 | 408 | let mut output: Cursor> = Cursor::new(Vec::new()); 409 | let outcome = write_response(res, &mut output, true).unwrap(); 410 | 411 | assert_eq!( 412 | output.get_ref(), 413 | b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nlolwut" 414 | ); 415 | assert!(matches!(outcome, Outcome::Close)); 416 | } 417 | 418 | #[test] 419 | fn supports_channel_response_bodies() { 420 | let (sender, body) = Body::channel(); 421 | 422 | let send_thread = thread::spawn(move || { 423 | sender.send("lol").unwrap(); 424 | sender.send("wut").unwrap(); 425 | }); 426 | 427 | let res = Response::builder() 428 | .status(StatusCode::OK) 429 | .header("connection", "close") 430 | .body(body) 431 | .unwrap(); 432 | 433 | let mut output: Cursor> = Cursor::new(Vec::new()); 434 | let outcome = write_response(res, &mut output, true).unwrap(); 435 | 436 | send_thread.join().unwrap(); 437 | 438 | assert_eq!( 439 | std::str::from_utf8(output.get_ref()).unwrap(), 440 | "HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nlolwut" 441 | ); 442 | assert!(matches!(outcome, Outcome::Close)); 443 | } 444 | 445 | #[test] 446 | fn returns_a_close_connection_outcome_when_informed_an_explicit_close_connection_header() { 447 | let res = Response::builder() 448 | .status(StatusCode::OK) 449 | .header("connection", "close") 450 | .body(Body::empty()) 451 | .unwrap(); 452 | 453 | let mut output: Cursor> = Cursor::new(Vec::new()); 454 | let outcome = write_response(res, &mut output, true).unwrap(); 455 | 456 | assert!(matches!(outcome, Outcome::Close)); 457 | } 458 | 459 | #[test] 460 | fn returns_a_close_keep_alive_outcome_when_no_close_connection_is_informed() { 461 | let res = Response::builder() 462 | .status(StatusCode::OK) 463 | .body(Body::empty()) 464 | .unwrap(); 465 | 466 | let mut output: Cursor> = Cursor::new(Vec::new()); 467 | let outcome = write_response(res, &mut output, true).unwrap(); 468 | 469 | assert!(matches!(outcome, Outcome::KeepAlive)); 470 | } 471 | 472 | #[test] 473 | fn returns_upgrade_outcome() { 474 | let res = Response::builder() 475 | .status(StatusCode::SWITCHING_PROTOCOLS) 476 | .upgrade(|_| {}) 477 | .body(Body::empty()) 478 | .unwrap(); 479 | 480 | let mut output: Cursor> = Cursor::new(Vec::new()); 481 | let outcome = write_response(res, &mut output, true).unwrap(); 482 | 483 | assert!(matches!(outcome, Outcome::Upgrade(_))); 484 | } 485 | 486 | #[test] 487 | fn writes_http_10_responses() { 488 | let res = Response::builder() 489 | .status(StatusCode::OK) 490 | .version(Version::HTTP_10) 491 | .body("lol") 492 | .unwrap(); 493 | 494 | let mut output: Cursor> = Cursor::new(Vec::new()); 495 | let outcome = write_response(res, &mut output, true).unwrap(); 496 | 497 | assert_eq!( 498 | output.get_ref(), 499 | b"HTTP/1.0 200 OK\r\ncontent-length: 3\r\n\r\nlol" 500 | ); 501 | assert!(matches!(outcome, Outcome::KeepAlive)); 502 | } 503 | 504 | #[test] 505 | fn removes_chunked_transfer_encoding_from_http_10_responses() { 506 | let res = Response::builder() 507 | .status(StatusCode::OK) 508 | .version(Version::HTTP_10) 509 | .header("transfer-encoding", "chunked") 510 | .body(Body::from_iter(std::iter::once("lol"))) 511 | .unwrap(); 512 | 513 | let mut output: Cursor> = Cursor::new(Vec::new()); 514 | let outcome = write_response(res, &mut output, true).unwrap(); 515 | 516 | assert_eq!( 517 | output.get_ref(), 518 | b"HTTP/1.0 200 OK\r\nconnection: close\r\n\r\nlol" 519 | ); 520 | assert!(matches!(outcome, Outcome::Close)); 521 | } 522 | 523 | #[test] 524 | fn parse_response_without_body() { 525 | let res = "HTTP/1.1 200 OK\r\ndate: Mon, 25 Jul 2022 21:34:35 GMT\r\n\r\n"; 526 | let res = Cursor::new(res); 527 | 528 | let res = parse_response(res).unwrap(); 529 | 530 | assert_eq!(Version::HTTP_11, res.version()); 531 | assert_eq!(StatusCode::OK, res.status()); 532 | assert_eq!( 533 | Some("Mon, 25 Jul 2022 21:34:35 GMT"), 534 | res.headers() 535 | .get(http::header::DATE) 536 | .and_then(|v| v.to_str().ok()) 537 | ); 538 | } 539 | 540 | #[test] 541 | fn parse_response_with_content_length_body() { 542 | let res = "HTTP/1.1 200 OK\r\ncontent-length: 6\r\n\r\nlolwut ignored"; 543 | let res = Cursor::new(res); 544 | 545 | let res = parse_response(res).unwrap(); 546 | 547 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 548 | } 549 | 550 | #[test] 551 | fn parse_response_with_chunked_body() { 552 | let res = "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n3\r\nlol\r\n3\r\nwut\r\n0\r\n\r\n"; 553 | let res = Cursor::new(res); 554 | 555 | let res = parse_response(res).unwrap(); 556 | 557 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 558 | } 559 | 560 | #[test] 561 | fn parse_response_with_chunked_body_and_extensions() { 562 | let res = "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n3;extension\r\nlol\r\n3\r\nwut\r\n0\r\n\r\n"; 563 | let res = Cursor::new(res); 564 | 565 | let res = parse_response(res).unwrap(); 566 | 567 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 568 | } 569 | 570 | #[test] 571 | fn parse_response_with_streaming_body() { 572 | let res = b"HTTP/1.1 200 OK\r\ncontent-length: 2048\r\n\r\n"; 573 | let body = [65_u8; 2048]; 574 | let res = Cursor::new([res.as_ref(), body.as_ref()].concat()); 575 | 576 | let res = parse_response(res).unwrap(); 577 | 578 | assert_eq!(res.into_body().into_bytes().unwrap(), body); 579 | } 580 | 581 | #[test] 582 | fn parse_response_with_close_delimited_body() { 583 | let res = "HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nlolwut"; 584 | let res = Cursor::new(res); 585 | 586 | let res = parse_response(res).unwrap(); 587 | 588 | assert_eq!(res.into_body().into_bytes().unwrap(), b"lolwut"); 589 | } 590 | } 591 | --------------------------------------------------------------------------------