CodeBlanks Logo Icon CodeBlanks

Async web server

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
merhaba

The 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

main.rs
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::thread;

fn main() {
    // buraya yaz
}

Sample solution

main.rs
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

Question
let yol = ilk.split_whitespace().____(1);   // "GET / HTTP/1.1" → Some("/")
Answer
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

All lessons in Advanced