Rust
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.
Örnek çözümü göster
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(())
}
Kendini test et
-
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
- Hiçbir şey
- Derleme hatası
Cevabı göster
Okunmamış veri yüzünden işletim sistemi bağlantıyı sıfırlayabilir (RST) ve istemci yanıtı alamayabilir — İsteği sonuna kadar tüketmek, bağlantının düzgün (FIN ile) kapanmasını sağlar.
-
Yolu oku
let yol = ilk.split_whitespace().____(1); // "GET / HTTP/1.1" → Some("/")
Cevabı göster
let yol = ilk.split_whitespace().nth(1); // "GET / HTTP/1.1" → Some("/")
-
İstek ve yanıt
- GET /
- 200 OK
- GET /yok
- 404 NOT FOUND
- Content-Length
- Bayt sayısı
- \r\n\r\n
- Başlık sonu
Sıradaki ders: ThreadPool