Async web sunucusu
- Rust kursu
- İleri seviye
- Ders 78/81
- 7 dk
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
merhabaSunucu 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
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::thread;
fn main() {
// buraya yaz
}Örnek çözüm
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
let yol = ilk.split_whitespace().____(1); // "GET / HTTP/1.1" → Some("/")
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 |