Skip to main content
  1. Posts/

Simple Web Server using Rust

·458 words

Introduction #

This course is to implement simple static web server using Rust

Modules #

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::fs::File;

preludehttps://doc.rust-lang.org/std/prelude/index.html
TcpListenerhttps://doc.rust-lang.org/std/net/struct.TcpListener.html

After creating a TcpListener by binding it to a socket address, it listens for incoming TCP connections. These can be accepted by calling accept or by iterating over the Incoming iterator returned by incoming.

TcpStreamhttps://doc.rust-lang.org/std/net/struct.TcpStream.html

Stream between a local and a remote socket. After creating a TcpStream by either connecting to a remote host or accepting a connection on a TcpListener, data can be transmitted by reading and writing to it.

Filehttps://doc.rust-lang.org/std/fs/struct.File.html

Opening files like index.html

main #

fn main() {
	let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
	for stream in listener.incoming() {
		let stream = stream.unwrap();
		handle_connection(stream).unwrap();
	}
}

listener.incoming() returns an iterator over the connections being received on this listener. Each stream in iterator becomes Result<TcpStream>.

handle_connection #

fn handle_connection(mut stream: TcpStream) {
	let mut buffer: [u8; 512] = [0; 512];
	stream.read(&mut buffer).unwrap();

	const GET: &[u8; 16] = b"GET / HTTP/1.1\r\n";
	
	// If client sends GET request
	if buffer.starts_with(GET) {
		let mut file = File::open("index.html").unwrap();
		let mut contents = String::new();
		// open index.html and put its content to string
		file.read_to_string(&mut contents).unwrap();
		
		// create response as HTTP protocol
		let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
			contents.len(),
			contents
		);

		// write response as bytes
		stream.write(response.as_bytes()).unwrap();
		// To ensure all buffered content reached its destination, flush this stream
		stream.flush().unwrap();
	} else {
		const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND";
		let contents = "Cannot Found its Method or Resources";
		let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}",
			NOT_FOUND,
			contents.len(),
			contents.to_string()
		);
		stream.write(response.as_bytes()).unwrap();
		stream.flush().unwrap();
	}
}

Multi threading Web Server #

Simple Web server using rust which mentioned above can’t be used in production. Because, it can’t handle multiple request concurrently. The server above could only handle one request at a time. For example, when one request takes more than 5 seconds, the next request must wait until the previous request is processed.

fn handle_slow_connection(mut stream: TcpStream) {
	// --snip--

	thread::sleep(Duration::from_millis(5000));
	stream.write(response.as_bytes()).unwrap();

	// --snip--
}

To prevent this, there are several ways to handle requests simultaneously. For this section, we’ll implement by using multi-threading method.

Thread for reqeust #

use std::thread;

fn main() {
	let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
	for stream in listener.incoming() {
		let stream = stream.unwrap();

		thread::spawn(|| {
			handle_connection(stream).unwrap();
		});
	}
}

Every time when listener gets a new request, thread::spawn() creates a new thread. This makes server possible to accept multiple requests in same time. But, the problem with this version is that it would create new thread every single time when there is a new request. When there are 1000 requests, thousands of new thread will be created. This, can be prevented using a term ThreadPool. I’ll deal with ThreadPool when I have a chance later.