Async web server
- Rust course
- Advanced
- Lesson 78/81
- 7 min
Build a mini server that handles clients in parallel with tokio.
Final task: web server
The advanced level's first final task is a small HTTP server. A real project uses tokio::net::TcpListener and a tokio::spawn per connection; in the exercise we build the same structure with std threads. HTTP/1.1 is text-based: a request line, headers, an empty line; the response has the same form:
GET / HTTP/1.1\r\n
Host: localhost\r\n
\r\n
HTTP/1.1 200 OK\r\n
Content-Length: 7\r\n
\r\n
merhabaThe server reads the path from the request line, consumes the headers up to the empty line, and responds based on the path.
Request handler
The handler reads line by line and writes the response in one go:
fn istek_isle(akis: TcpStream) -> io::Result<()> {
let mut okuyucu = BufReader::new(akis.try_clone()?);
let mut ilk = String::new();
okuyucu.read_line(&mut ilk)?;
loop {
let mut s = String::new();
okuyucu.read_line(&mut s)?;
if s == "\r\n" || s.is_empty() { break; }
}
let (durum, govde) = match ilk.split_whitespace().nth(1) {
Some("/") => ("200 OK", "merhaba"),
_ => ("404 NOT FOUND", "bulunamadı"),
};
let mut akis = akis;
write!(akis, "HTTP/1.1 {durum}\r\nContent-Length: {}\r\n\r\n{govde}", govde.len())
}Content-Length is in bytes: Turkish characters count as two bytes, so len() is used.
Practice HTTP server
Write the istek_isle function above. Run the server on a thread with incoming().take(2). Let the main thread send requests for / and /yok in turn and read each response with read_to_string; print the first response's status line and body, and the second's status line, on separate lines.
Starter code
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::thread;
fn main() {
// buraya yaz
}Sample solution
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::thread;
fn istek_isle(akis: TcpStream) -> io::Result<()> {
let mut okuyucu = BufReader::new(akis.try_clone()?);
let mut ilk = String::new();
okuyucu.read_line(&mut ilk)?;
loop {
let mut s = String::new();
okuyucu.read_line(&mut s)?;
if s == "\r\n" || s.is_empty() {
break;
}
}
let (durum, govde) = match ilk.split_whitespace().nth(1) {
Some("/") => ("200 OK", "merhaba"),
_ => ("404 NOT FOUND", "bulunamadı"),
};
let mut akis = akis;
write!(akis, "HTTP/1.1 {durum}\r\nContent-Length: {}\r\n\r\n{govde}", govde.len())
}
fn iste(adres: SocketAddr, yol: &str) -> io::Result<(String, String)> {
let mut akis = TcpStream::connect(adres)?;
write!(akis, "GET {yol} HTTP/1.1\r\nHost: localhost\r\n\r\n")?;
let mut yanit = String::new();
akis.read_to_string(&mut yanit)?;
let (bas, govde) = yanit.split_once("\r\n\r\n").unwrap_or((&yanit, ""));
let durum = bas.lines().next().unwrap_or("").to_string();
Ok((durum, govde.to_string()))
}
fn main() -> io::Result<()> {
let dinleyici = TcpListener::bind("127.0.0.1:0")?;
let adres = dinleyici.local_addr()?;
thread::spawn(move || {
for akis in dinleyici.incoming().take(2) {
let akis = akis.unwrap();
thread::spawn(move || istek_isle(akis));
}
});
let (durum, govde) = iste(adres, "/")?;
println!("{}", durum);
println!("{}", govde);
let (durum2, _) = iste(adres, "/yok")?;
println!("{}", durum2);
Ok(())
}Expected output
HTTP/1.1 200 OK merhaba HTTP/1.1 404 NOT FOUND
Questions and answers
Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.
1 What can happen if the server closes the connection without reading the headers?
- The response is sent twice
- Because of unread data, the OS may reset the connection (RST) and the client may not get the response Correct answer
- Nothing
- Compile error
Explanation: Consuming the request fully lets the connection close cleanly (with FIN).
2 Read the path
let yol = ilk.split_whitespace().____(1); // "GET / HTTP/1.1" → Some("/")
let yol = ilk.split_whitespace().nth(1); // "GET / HTTP/1.1" → Some("/")
Explanation: nth(1) gives the second part of the request line, the path.
3 Request and response
| Term | Meaning |
|---|---|
GET / |
200 OK |
GET /yok |
404 NOT FOUND |
Content-Length |
Byte count |
\r\n\r\n |
End of headers |