CodeBlanks Logo Icon CodeBlanks

Async web sunucusu

tokio ile paralel istemci işleyen mini bir sunucu yazarsın.

Bitirme görevi: web sunucusu

İleri seviyenin ilk bitirme görevi küçük bir HTTP sunucusu. Gerçek projede tokio::net::TcpListener ve her bağlantı için tokio::spawn kullanılır; alıştırmada aynı yapıyı std thread'leriyle kuruyoruz. HTTP/1.1 metin tabanlıdır: istek satırı, başlıklar, boş satır; yanıt da aynı biçimdedir:

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

Sunucu istek satırından yolu okur, başlıkları boş satıra kadar tüketir ve yola göre yanıt verir.

İstek işleyici

İşleyici satır satır okur ve yanıtı tek seferde yazar:

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 bayt cinsindendir: Türkçe karakterler iki bayt sayılır, bu yüzden len() kullanılır.

Uygulama HTTP sunucusu

Yukarıdaki istek_isle fonksiyonunu yaz. Sunucuyu bir thread'de incoming().take(2) ile çalıştır. Ana thread / ve /yok için sırayla istek göndersin, yanıtı read_to_string ile okusun; ilk yanıtın durum satırını ve gövdesini, ikincinin durum satırını ayrı satırlara yazdırsın.

Başlangıç kodu

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

fn main() {
    // buraya yaz
}

Örnek çözüm

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(())
}

Beklenen çıktı

HTTP/1.1 200 OK
merhaba
HTTP/1.1 404 NOT FOUND

Soru ve cevaplar

Dersin sorularını cevaplarıyla birlikte inceleyebilirsin. Kendini denemek istersen dersi interaktif olarak çöz.

1 Sunucu başlıkları okumadan bağlantıyı kapatırsa ne olabilir?

  • Yanıt iki kez gider
  • Okunmamış veri yüzünden işletim sistemi bağlantıyı sıfırlayabilir (RST) ve istemci yanıtı alamayabilir Doğru cevap
  • Hiçbir şey
  • Derleme hatası

Açıklama: İsteği sonuna kadar tüketmek, bağlantının düzgün (FIN ile) kapanmasını sağlar.

2 Yolu oku

Soru
let yol = ilk.split_whitespace().____(1);   // "GET / HTTP/1.1" → Some("/")
Cevap
let yol = ilk.split_whitespace().nth(1);   // "GET / HTTP/1.1" → Some("/")

Açıklama: nth(1) istek satırının ikinci parçasını, yani yolu verir.

3 İstek ve yanıt

Kavram Anlamı
GET / 200 OK
GET /yok 404 NOT FOUND
Content-Length Bayt sayısı
\r\n\r\n Başlık sonu

İleri seviye bölümündeki tüm dersler