Skip to content
Permalink
Browse files

Part 2

  • Loading branch information...
levidyrek committed May 27, 2019
1 parent 8de6e22 commit a91fa431345c207a5cf8cdbd0ff29877e2cc670d
Showing with 218 additions and 11 deletions.
  1. +56 −0 Cargo.lock
  2. +1 −0 Cargo.toml
  3. +2 −0 README.md
  4. +157 −10 src/lib.rs
  5. +2 −1 src/main.rs

Some generated files are not rendered by default. Learn more.

@@ -7,3 +7,4 @@ edition = "2018"
[dependencies]
bufstream = "0.1.4"
chrono = "0.4"
http = "0.1.17"
@@ -3,3 +3,5 @@
This is example code for a blog post about Creating a Static HTTP Server with Rust.

Read Part 1 here: [Creating a Static HTTP Server with Rust - Part 1](http://concisecoder.io/2019/05/11/creating-a-static-http-server-with-rust-part-1/)

Read Part 2 here: [Creating a Static HTTP Server with Rust - Part 2](http://concisecoder.io/2019/05/27/creating-a-static-http-server-with-rust-part-2/)
@@ -1,9 +1,11 @@
use std::io;
use std::{io, fs};
use std::io::prelude::*;
use std::io::ErrorKind;
use std::net::TcpStream;

use bufstream::BufStream;
use chrono::prelude::*;
use http::StatusCode;


struct Request {
@@ -13,21 +15,94 @@ struct Request {
time: DateTime<Local>,
}

pub fn handle_client(stream: TcpStream) -> io::Result<()> {
enum ContentType {
CSS,
GIF,
HTML,
JPEG,
PNG,
SVG,
TEXT,
XML,
}

impl ContentType {
fn from_file_ext(ext: &str) -> ContentType {
match ext {
"css" => ContentType::CSS,
"gif" => ContentType::GIF,
"htm" => ContentType::HTML,
"html" => ContentType::HTML,
"jpeg" => ContentType::JPEG,
"jpg" => ContentType::JPEG,
"png" => ContentType::PNG,
"svg" => ContentType::SVG,
"txt" => ContentType::TEXT,
"xml" => ContentType::XML,
_ => ContentType::TEXT,
}
}

fn value(&self) -> &str {
match *self {
ContentType::CSS => "text/css",
ContentType::GIF => "image/gif",
ContentType::HTML => "text/html",
ContentType::JPEG => "image/jpeg",
ContentType::PNG => "image/png",
ContentType::SVG => "image/svg+xml",
ContentType::TEXT => "text/plain",
ContentType::XML => "application/xml",
}
}
}

struct ResponseHeaders {
content_type: Option<ContentType>,
}

impl ResponseHeaders {
fn new() -> ResponseHeaders {
ResponseHeaders {
content_type: None,
}
}
}

struct Response {
body: Option<Vec<u8>>,
headers: ResponseHeaders,
status: StatusCode,
}

impl Response {
fn new() -> Response {
Response {
body: None,
headers: ResponseHeaders::new(),
status: StatusCode::OK,
}
}
}

pub fn handle_client(stream: TcpStream, static_root: &str) -> io::Result<()> {
let mut buf = BufStream::new(stream);
let mut request_line = String::new();

// Get only the first line of the request, since this
// is a static HTTP 1.0 server.
buf.read_line(&mut request_line)?;
match parse_request(&mut request_line) {
let response = match parse_request(&mut request_line) {
Ok(request) => {
log_request(&request);
let response = build_response(&request, static_root);
log_request(&request, &response);
response
},
Err(()) => {
println!("Bad request: {}", &request_line);
},
}
Err(()) => create_bad_request_response(),
};

let formatted = format_response(response);
buf.write_all(&formatted)?;

Ok(())
}
@@ -56,12 +131,84 @@ fn parse_request(request: &mut String) -> Result<Request, ()> {
} )
}

fn log_request(request: &Request) {
fn build_response(request: &Request, static_root: &str) -> Response {
let mut response = Response::new();
if request.method != "GET" {
response.status = StatusCode::METHOD_NOT_ALLOWED;
} else {
add_file_to_response(&request.path, &mut response, static_root);
}

response
}

fn add_file_to_response(path: &String, response: &mut Response, static_root: &str) {
let path = format!("{}{}", static_root, path);
let contents = fs::read(&path);
match contents {
Ok(contents) => {
response.body = Some(contents);
let ext = path.split(".").last().unwrap_or("");
response.headers.content_type = Some(ContentType::from_file_ext(ext));
},
Err(e) => {
response.status = match e.kind() {
ErrorKind::NotFound => StatusCode::NOT_FOUND,
ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
}

fn log_request(request: &Request, response: &Response) {
println!(
"[{}] \"{} {} {}\"",
"[{}] \"{} {} {}\" {}",
request.time,
request.method,
request.path,
request.http_version,
response.status.as_u16(),
);
}

fn create_bad_request_response() -> Response {
let mut response = Response::new();
response.status = StatusCode::BAD_REQUEST;

response
}

fn format_response(response: Response) -> Vec<u8> {
let mut result;
let status_reason = match response.status.canonical_reason() {
Some(reason) => reason,
None => "",
};
result = format!(
"HTTP/1.0 {} {}\n",
response.status.as_str(),
status_reason,
);
result = format!("{}Allow: GET\n", result);

match response.headers.content_type {
Some(content_type) => {
result = format!(
"{}Content-type: {}\n", result, content_type.value());
},
_ => (),
}

let mut bytes = result.as_bytes().to_vec();

match response.body {
Some(mut body) => {
bytes.append(&mut "\n".as_bytes().to_vec());
bytes.append(&mut body);
},
_ => (),
}

bytes
}
@@ -10,13 +10,14 @@ fn main() -> io::Result<()> {
println!("Starting server...");

let listener = TcpListener::bind("127.0.0.1:8001")?;
const STATIC_ROOT: &str = "/static_root";

println!("Server started!");

for stream in listener.incoming() {
match stream {
Ok(stream) => {
match rust_http_server::handle_client(stream) {
match rust_http_server::handle_client(stream, &STATIC_ROOT) {
Err(e) => eprintln!("Error handling client: {}", e),
_ => (),
}

0 comments on commit a91fa43

Please sign in to comment.
You can’t perform that action at this time.