# Building a Single-Threaded Web Server 1st - Summary of network protocols (TCP + HTTP) - Request - Response : Cient asks server listens and delivers - TCP is how the messages are delivered and handled - HTTP defines the contents of the messages. ## Listening to TCP Connection `std::net` is the module provided by the stl. ```rust use std::net::TcpListener; fn main() { let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); // 7878 is not a usual port for HTTP, just for show as Rust would be (777 88 7777 8) on a 9key old ass telephone so 7878 // Use ports 1023< because smaller require privileges // Dont start instances of same or different programs to the same port, each instance must listen to a free port for stream in listener.incoming() { // incoming() returns iterator of the streams of type `TcpStream` // each stream is an open connection // If no connections, it will wait for successful attempt let stream = stream.unwrap(); println!("Connection established!"); } } ``` Run and open a page on your browser at `127.0.0.1:7878`, this will make the browser try to connect to the server but server gives no response. Should see in cl `Connection established!` repeatedly. ## Reading Requests ```rust use std::io::prelude::*; // Adds traits to read and write from data use std::net::TcpStream; // Type to handle the TcpStream fn handle_connection(mut stream: TcpStream) { let mut buffer = [0;1024]; // Create a ubyte array initialized to 0, of 1024 bytes stream.read(&mut buffer).unwrap(); // Read is a mutable operation, we are passing bytes from the stream into the buffer, is more like a copy println!("Request: {}", String::from_utf8_lossy(&buffer[..])); // Convert buffer to a String, being parsed as utf8 type // Lossy = invalid characters are turned into } /// Some Issues /// - Buffer is of fixed size, should be worked on ``` Add this function to the connection iteration, and it should now print the information of the request! - Type `Request: GET` ### Understanding HTTP Requests ```rust Method Request-URI HTTP-Version CRLF headers CRLF message-body // Ours... GET / HTTP/1.1 ``` - Method = GET - Request-URI = / = Relative Page requested - HTTP = 1.1 - headers = Information about the request, more part of the message... This is the form HTTP creates requests ## Writing a Response ```rust HTTP-Version Status-Code Reason-Phares CRLF headers CRLF message-body ``` - HTTP-Version = The one oyu use - Status-Code = Number to signify a fast code to parse - Reason-Phrase = Message that describes teh status code - Headers and body will come latesr ```rust HTTP/1.1 200 OK\r\n\r\n ``` Add to `handle_connection` ```rust let response = "HTTP/1.1 200 OK\r\n\r\n"; // Response String stream.write(response.as_bytes()).unwrap(); // Write the response to the conection, transform to bytes stream.flush().unwrap(); // Wait until the program can send message and write all bytes and send to connection ``` Now the page should be blank instead of giving an error! ## Returning Real HTML Create a file with: ```html
Hi from Rust
``` Then add `create_response()`: ```rust use std::fs; // Use fs to read from disk fn create_response() -> String { let contents = fs::read_to_string("data/hello.html").unwrap(); // Get HTML from local page file format!( "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}", contents.len(), contents ) // Format the string to add a header that says the content length in bytes // Then the entire contents of the page } ``` And swap string for the result of create response. Now we should see the html sent. - HTML Responses are very sensitive to formatting, VERY ## Validating Requests and Selective Responses Right now, single response for all requests, we should check what is being requestes and send appropiately: ```rust let get = b"GET / HTTP/1.1\r\n"; // b transform strings to bytes if buffer.starts_with(get) {...} ``` Just answer if the method and request match what you should! Now if you search for another thing, we get an error! If does not match any option provided, then we should reutrn a proper error description (create `404.html` file): ```rust else { let status_line = "HTTP/1.1 404 NOT FOUND"; let contents = fs::read_to_string("404.html").unwrap(); let response = format!( "{}\r\nContent-Length: {}\r\n\r\n{}", status_line, contents.len(), contents } ``` ## Refactoring! Change the function `if else` flow to be handled easier: ```rust // Pattern to deconstruct based on the get message! let (status_line, filename) = if buffer.starts_with(get) { ("HTTP/1.1 200 OK", "hello.html") } else { ("HTTP/1.1 404 NOT FOUND", "404.html") }; let contents = fs::read_to_string(filename).unwrap(); let response = format!( "{}\r\nContent-Length: {}\r\n\r\n{}", status_line, contents.len(), contents ) ```