diff --git a/examples/parallel_sites.rs b/examples/parallel_sites.rs index cb2b627..794e9fc 100644 --- a/examples/parallel_sites.rs +++ b/examples/parallel_sites.rs @@ -21,9 +21,9 @@ impl HttpServer for EzSite { // println!("{} > {} {}", req.addr, req.method, req.page); if req.page == "/" { - Some(HttpResponse::from_str( + Some(HttpResponse::from_string( Headers::from(vec![("Content-Type", "text/html")]), // response headers - "200 OK".to_string(), // response status code + "200 OK", // response status code &self.index_page, // response body )) } else { @@ -31,7 +31,7 @@ impl HttpServer for EzSite { } } - async fn on_start(&mut self, host: &str) { + async fn on_start(&mut self, _: &str) { // println!("Http server started on {}", host); } diff --git a/src/ezhttp/error.rs b/src/ezhttp/error.rs new file mode 100644 index 0000000..e2086c6 --- /dev/null +++ b/src/ezhttp/error.rs @@ -0,0 +1,24 @@ +use std::error::Error; + +/// Http error +#[derive(Debug)] +pub enum HttpError { + ReadLineEof, + ReadLineUnknown, + InvalidHeaders, + InvalidQuery, + InvalidContentSize, + InvalidContent, + JsonParseError, + WriteHeadError, + WriteBodyError, + InvalidStatus, +} + +impl std::fmt::Display for HttpError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} + +impl Error for HttpError {} diff --git a/src/ezhttp/headers.rs b/src/ezhttp/headers.rs new file mode 100644 index 0000000..63cf0e0 --- /dev/null +++ b/src/ezhttp/headers.rs @@ -0,0 +1,102 @@ +use std::{ + collections::HashMap, + fmt::{Debug, Display}, +}; + +/// Http headers +#[derive(Clone, Debug)] +pub struct Headers { + entries: Vec<(String, String)>, +} + +impl Into> for Headers { + fn into(self) -> HashMap { + HashMap::from_iter(self.entries().into_iter()) + } +} + +impl From> for Headers +where + T: ToString, + U: ToString, +{ + fn from(value: Vec<(T, U)>) -> Self { + Headers { + entries: value + .into_iter() + .map(|v| (v.0.to_string(), v.1.to_string())) + .collect(), + } + } +} + +impl Headers { + pub fn new() -> Self { + Headers { + entries: Vec::new(), + } + } + + pub fn contains(self, header: impl ToString) -> bool { + for (k, _) in self.entries { + if k.to_lowercase() == header.to_string().to_lowercase() { + return true; + } + } + return false; + } + + pub fn get(self, key: impl ToString) -> Option { + for (k, v) in self.entries { + if k.to_lowercase() == key.to_string().to_lowercase() { + return Some(v); + } + } + return None; + } + + pub fn put(&mut self, key: impl ToString, value: String) { + for t in self.entries.iter_mut() { + if t.0.to_lowercase() == key.to_string().to_lowercase() { + t.1 = value; + return; + } + } + self.entries.push((key.to_string(), value)); + } + + pub fn remove(&mut self, key: impl ToString) { + for (i, t) in self.entries.iter_mut().enumerate() { + if t.0.to_lowercase() == key.to_string().to_lowercase() { + self.entries.remove(i); + return; + } + } + } + + pub fn keys(self) -> Vec { + self.entries.iter().map(|e| e.0.clone()).collect() + } + + pub fn values(self) -> Vec { + self.entries.iter().map(|e| e.1.clone()).collect() + } + + pub fn entries(self) -> Vec<(String, String)> { + return self.entries; + } + + pub fn len(self) -> usize { + return self.entries.len(); + } + + pub fn clear(&mut self) { + self.entries.clear(); + } +} + +impl Display for Headers { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} diff --git a/src/ezhttp/mod.rs b/src/ezhttp/mod.rs new file mode 100644 index 0000000..f9856f5 --- /dev/null +++ b/src/ezhttp/mod.rs @@ -0,0 +1,275 @@ +use futures::executor::block_on; +use std::{ + boxed::Box, + error::Error, + future::Future, + io::Read, + net::{TcpListener, TcpStream}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + thread, + time::Duration, +}; +use threadpool::ThreadPool; + +pub mod error; +pub mod headers; +pub mod request; +pub mod response; +pub mod starter; + +pub use error::*; +pub use headers::*; +pub use request::*; +pub use response::*; +pub use starter::*; + +fn read_line(data: &mut impl Read) -> Result { + let mut bytes = Vec::new(); + + for byte in data.bytes() { + let byte = match byte { + Ok(i) => i, + Err(_) => return Err(HttpError::ReadLineEof), + }; + + bytes.push(byte); + + if byte == 0x0A { + break; + } + } + + match String::from_utf8(bytes) { + Ok(i) => Ok(i), + Err(_) => Err(HttpError::ReadLineUnknown), + } +} + +fn read_line_crlf(data: &mut impl Read) -> Result { + match read_line(data) { + Ok(i) => Ok(i[..i.len() - 2].to_string()), + Err(e) => Err(e), + } +} + +fn read_line_lf(data: &mut impl Read) -> Result { + match read_line(data) { + Ok(i) => Ok(i[..i.len() - 1].to_string()), + Err(e) => Err(e), + } +} + +fn rem_first(value: &str) -> &str { + let mut chars = value.chars(); + chars.next(); + chars.as_str() +} + +fn split(text: String, delimiter: &str, times: usize) -> Vec { + match times { + 0 => text.split(delimiter).map(|v| v.to_string()).collect(), + 1 => { + let mut v: Vec = Vec::new(); + match text.split_once(delimiter) { + Some(i) => { + v.push(i.0.to_string()); + v.push(i.1.to_string()); + } + None => { + v.push(text); + } + } + v + } + _ => text + .splitn(times, delimiter) + .map(|v| v.to_string()) + .collect(), + } +} + +/// Async http server trait +pub trait HttpServer { + fn on_start(&mut self, host: &str) -> impl Future + Send; + fn on_close(&mut self) -> impl Future + Send; + fn on_request( + &mut self, + req: &HttpRequest, + ) -> impl Future> + Send; +} + +fn start_server_with_threadpool( + server: S, + host: &str, + timeout: Option, + threads: usize, + handler: F, + running: Arc, +) -> Result<(), Box> +where + F: (Fn(Arc>, TcpStream) -> ()) + Send + 'static + Copy, + S: HttpServer + Send + 'static, +{ + let threadpool = ThreadPool::new(threads); + let server = Arc::new(Mutex::new(server)); + let listener = TcpListener::bind(host)?; + + let host_clone = String::from(host).clone(); + let server_clone = server.clone(); + block_on(server_clone.lock().unwrap().on_start(&host_clone)); + + while running.load(Ordering::Acquire) { + let (sock, _) = match listener.accept() { + Ok(i) => i, + Err(_) => { + continue; + } + }; + + sock.set_read_timeout(timeout).unwrap(); + sock.set_write_timeout(timeout).unwrap(); + + let now_server = Arc::clone(&server); + threadpool.execute(move || { + handler(now_server, sock); + }); + } + + threadpool.join(); + + block_on(server.lock().unwrap().on_close()); + + Ok(()) +} + +fn start_server_new_thread( + server: S, + host: &str, + timeout: Option, + handler: F, + running: Arc, +) -> Result<(), Box> +where + F: (Fn(Arc>, TcpStream) -> ()) + Send + 'static + Copy, + S: HttpServer + Send + 'static, +{ + let server = Arc::new(Mutex::new(server)); + let listener = TcpListener::bind(host)?; + + let host_clone = String::from(host).clone(); + let server_clone = server.clone(); + block_on(server_clone.lock().unwrap().on_start(&host_clone)); + + while running.load(Ordering::Acquire) { + let (sock, _) = match listener.accept() { + Ok(i) => i, + Err(_) => { + continue; + } + }; + + sock.set_read_timeout(timeout).unwrap(); + sock.set_write_timeout(timeout).unwrap(); + + let now_server = Arc::clone(&server); + thread::spawn(move || { + handler(now_server, sock); + }); + } + + block_on(server.lock().unwrap().on_close()); + + Ok(()) +} + +fn start_server_sync( + server: S, + host: &str, + timeout: Option, + handler: F, + running: Arc, +) -> Result<(), Box> +where + F: (Fn(Arc>, TcpStream) -> ()) + Send + 'static + Copy, + S: HttpServer + Send + 'static, +{ + let server = Arc::new(Mutex::new(server)); + let listener = TcpListener::bind(host)?; + + let host_clone = String::from(host).clone(); + let server_clone = server.clone(); + block_on(server_clone.lock().unwrap().on_start(&host_clone)); + + while running.load(Ordering::Acquire) { + let (sock, _) = match listener.accept() { + Ok(i) => i, + Err(_) => { + continue; + } + }; + + sock.set_read_timeout(timeout).unwrap(); + sock.set_write_timeout(timeout).unwrap(); + + let now_server = Arc::clone(&server); + handler(now_server, sock); + } + + block_on(server.lock().unwrap().on_close()); + + Ok(()) +} + +fn handle_connection(server: Arc>, mut sock: TcpStream) { + let addr = sock.peer_addr().unwrap(); + + let req = match HttpRequest::read(&mut sock, &addr) { + Ok(i) => i, + Err(_) => { + return; + } + }; + let resp = match block_on(server.lock().unwrap().on_request(&req)) { + Some(i) => i, + None => { + return; + } + }; + resp.write(&mut sock).unwrap(); +} + +fn handle_connection_rrs( + server: Arc>, + mut sock: TcpStream, +) { + let req = match HttpRequest::read_with_rrs(&mut sock) { + Ok(i) => i, + Err(_) => { + return; + } + }; + let resp = match block_on(server.lock().unwrap().on_request(&req)) { + Some(i) => i, + None => { + return; + } + }; + resp.write(&mut sock).unwrap(); +} + +/// Start [`HttpServer`](HttpServer) on some host +/// +/// Use [`HttpServerStarter`](HttpServerStarter) to set more options +pub fn start_server(server: S, host: &str) { + start_server_new_thread( + server, + host, + None, + handle_connection, + Arc::new(AtomicBool::new(true)), + ) + .unwrap(); +} diff --git a/src/ezhttp/request.rs b/src/ezhttp/request.rs new file mode 100644 index 0000000..5590905 --- /dev/null +++ b/src/ezhttp/request.rs @@ -0,0 +1,280 @@ +use super::{read_line_crlf, read_line_lf, rem_first, split, Headers, HttpError}; + +use serde_json::Value; +use std::{ + fmt::{Debug, Display}, + io::{Read, Write}, + net::{IpAddr, SocketAddr, ToSocketAddrs}, +}; + +/// Http request +#[derive(Debug, Clone)] +pub struct HttpRequest { + pub page: String, + pub method: String, + pub addr: String, + pub headers: Headers, + pub params: Value, + pub data: Vec, +} + +impl Display for HttpRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +impl HttpRequest { + /// Create new http request + pub fn new(page: &str, method: &str, params: Value, headers: Headers, data: Vec) -> Self { + HttpRequest { + page: page.to_string(), + method: method.to_string(), + addr: String::new(), + params, + headers, + data, + } + } + + /// Read http request from stream + pub fn read(data: &mut impl Read, addr: &SocketAddr) -> Result { + let octets = match addr.ip() { + IpAddr::V4(ip) => ip.octets(), + _ => [127, 0, 0, 1], + }; + + let ip_str = octets[0].to_string() + + "." + + &octets[1].to_string() + + "." + + &octets[2].to_string() + + "." + + &octets[3].to_string(); + + let status = split( + match read_line_crlf(data) { + Ok(i) => i, + Err(e) => return Err(e), + }, + " ", + 3, + ); + + let method = status[0].clone(); + let (page, query) = match status[1].split_once("?") { + Some(i) => (i.0.to_string(), Some(i.1)), + None => (status[1].clone(), None), + }; + + let mut headers = Headers::new(); + + loop { + let text = match read_line_crlf(data) { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidHeaders), + }; + + if text.len() == 0 { + break; + } + + let (key, value) = match text.split_once(": ") { + Some(i) => i, + None => return Err(HttpError::InvalidHeaders), + }; + + headers.put(key.to_lowercase(), value.to_string()); + } + + let mut params = serde_json::Map::new(); + + if let Some(i) = query { + for ele in i.split("&") { + let (k, v) = match ele.split_once("=") { + Some(i) => i, + None => return Err(HttpError::InvalidQuery), + }; + + params.insert( + match urlencoding::decode(k) { + Ok(i) => i.to_string(), + Err(_) => return Err(HttpError::InvalidQuery), + }, + match urlencoding::decode(v) { + Ok(i) => Value::String(i.to_string()), + Err(_) => return Err(HttpError::InvalidQuery), + }, + ); + } + } + + let mut reqdata: Vec = Vec::new(); + + if let Some(content_size) = headers.clone().get("content-length".to_string()) { + let content_size: usize = match content_size.parse() { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidContentSize), + }; + + if content_size > reqdata.len() { + let mut buf: Vec = Vec::new(); + buf.resize(content_size - reqdata.len(), 0); + + match data.read_exact(&mut buf) { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidContent), + }; + + reqdata.append(&mut buf); + } + } + + if let Some(content_type) = headers.clone().get("content-type".to_string()) { + let mut body = match String::from_utf8(reqdata.clone()) { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidContent), + }; + + match content_type.as_str() { + "application/json" => { + let val: Value = match serde_json::from_str(&body) { + Ok(i) => i, + Err(_) => return Err(HttpError::JsonParseError), + }; + + if let Value::Object(mut dict) = val { + params.append(&mut dict); + } + } + "multipart/form-data" => { + let boundary = "--".to_string() + + &content_type[(content_type.find("boundary=").unwrap() + 9)..] + + "\r\n"; + for part in body.split(boundary.as_str()) { + let lines: Vec<&str> = part.split("\r\n").collect(); + if lines.len() >= 3 { + if lines[0].starts_with("Content-Disposition: form-data; name=\"") { + let name: &str = + &lines[0]["Content-Disposition: form-data; name=\"".len()..]; + let name: &str = &name[..name.len() - 1]; + params + .insert(name.to_string(), Value::String(lines[2].to_string())); + } + } + } + } + "application/x-www-form-urlencoded" => { + if body.starts_with("?") { + body = rem_first(body.as_str()).to_string() + } + + for ele in body.split("&") { + let (k, v) = match ele.split_once("=") { + Some(i) => i, + None => return Err(HttpError::InvalidQuery), + }; + + params.insert( + match urlencoding::decode(k) { + Ok(i) => i.to_string(), + Err(_) => return Err(HttpError::InvalidQuery), + }, + match urlencoding::decode(v) { + Ok(i) => Value::String(i.to_string()), + Err(_) => return Err(HttpError::InvalidQuery), + }, + ); + } + } + _ => {} + } + } + + Ok(HttpRequest { + page, + method, + addr: ip_str.to_string(), + params: Value::Object(params), + headers, + data: reqdata.clone(), + }) + } + + /// Read http request with http_rrs support + pub fn read_with_rrs(data: &mut impl Read) -> Result { + let addr = match read_line_lf(data) { + Ok(i) => i, + Err(e) => { + return Err(e); + } + } + .to_socket_addrs() + .unwrap() + .collect::>()[0]; + HttpRequest::read(data, &addr) + } + + /// Set params to query in url + pub fn params_to_page(&mut self) { + let mut query = String::new(); + + let mut i: bool = !self.page.contains("?"); + + if let Value::Object(obj) = self.params.clone() { + for (k, v) in obj { + query.push_str(if i { "?" } else { "&" }); + query.push_str(urlencoding::encode(k.as_str()).to_string().as_str()); + query.push_str("="); + query.push_str( + urlencoding::encode(v.as_str().unwrap()) + .to_string() + .as_str(), + ); + i = false; + } + } + + self.page += query.as_str(); + } + + /// Set params to json data + pub fn params_to_json(&mut self) { + self.data = Vec::from(self.params.to_string().as_bytes()); + } + + /// Write http request to stream + /// + /// [`params`](Self::params) is not written to the stream, you need to use [`params_to_json`](Self::params_to_json) or [`params_to_page`](Self::params_to_page) + pub fn write(self, data: &mut impl Write) -> Result<(), HttpError> { + let mut head: String = String::new(); + head.push_str(&self.method); + head.push_str(" "); + head.push_str(&self.page); + head.push_str(" HTTP/1.1"); + head.push_str("\r\n"); + + for (k, v) in self.headers.entries() { + head.push_str(&k); + head.push_str(": "); + head.push_str(&v); + head.push_str("\r\n"); + } + + head.push_str("\r\n"); + + match data.write_all(head.as_bytes()) { + Ok(i) => i, + Err(_) => return Err(HttpError::WriteHeadError), + }; + + if !self.data.is_empty() { + match data.write_all(&self.data) { + Ok(i) => i, + Err(_) => return Err(HttpError::WriteBodyError), + }; + } + + Ok(()) + } +} diff --git a/src/ezhttp/response.rs b/src/ezhttp/response.rs new file mode 100644 index 0000000..8dcb0c6 --- /dev/null +++ b/src/ezhttp/response.rs @@ -0,0 +1,168 @@ +use super::{read_line_crlf, Headers, HttpError}; + +use serde_json::Value; +use std::{ + fmt::{Debug, Display}, + io::{Read, Write}, +}; + +/// Http response +#[derive(Debug, Clone)] +pub struct HttpResponse { + pub headers: Headers, + pub status_code: String, + pub data: Vec, +} + +impl Display for HttpResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +impl HttpResponse { + /// Create new http response with empty headers and data and a 200 OK status code + pub fn new() -> Self { + Self::from_bytes(Headers::new(), "200 OK", Vec::new()) + } + + /// Create new http response from headers, bytes data, and status code + pub fn from_bytes(headers: Headers, status_code: impl ToString, data: Vec) -> Self { + HttpResponse { + headers, + data, + status_code: status_code.to_string(), + } + } + + /// Create new http response from headers, string data, and status code + pub fn from_string(headers: Headers, status_code: impl ToString, data: impl ToString) -> Self { + HttpResponse { + headers, + data: data.to_string().into_bytes(), + status_code: status_code.to_string(), + } + } + + /// Get data in UTF-8 + pub fn get_text(self) -> String { + match String::from_utf8(self.data) { + Ok(i) => i, + Err(_) => String::new(), + } + } + + /// Get json [`Value`](Value) from data + pub fn get_json(self) -> Value { + match serde_json::from_str(self.get_text().as_str()) { + Ok(i) => i, + Err(_) => Value::Null, + } + } + + /// Read http response from stream + pub fn read(data: &mut impl Read) -> Result { + let status = match read_line_crlf(data) { + Ok(i) => i, + Err(e) => { + return Err(e); + } + }; + + let (_, status_code) = match status.split_once(" ") { + Some(i) => i, + None => return Err(HttpError::InvalidStatus), + }; + + let mut headers = Headers::new(); + + loop { + let text = match read_line_crlf(data) { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidHeaders), + }; + + if text.len() == 0 { + break; + } + + let (key, value) = match text.split_once(": ") { + Some(i) => i, + None => return Err(HttpError::InvalidHeaders), + }; + + headers.put(key.to_lowercase(), value.to_string()); + } + + let mut reqdata: Vec = Vec::new(); + + if let Some(content_size) = headers.clone().get("content-length".to_string()) { + let content_size: usize = match content_size.parse() { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidContentSize), + }; + + if content_size > reqdata.len() { + let mut buf: Vec = Vec::new(); + buf.resize(content_size - reqdata.len(), 0); + + match data.read_exact(&mut buf) { + Ok(i) => i, + Err(_) => return Err(HttpError::InvalidContent), + }; + + reqdata.append(&mut buf); + } + } else { + loop { + let mut buf: Vec = vec![0; 1024 * 32]; + + let buf_len = match data.read(&mut buf) { + Ok(i) => i, + Err(_) => { + break; + } + }; + + if buf_len == 0 { + break; + } + + buf.truncate(buf_len); + + reqdata.append(&mut buf); + } + } + + Ok(HttpResponse::from_bytes(headers, status_code, reqdata)) + } + + /// Write http response to stream + pub fn write(self, data: &mut impl Write) -> Result<(), &str> { + let mut head: String = String::new(); + head.push_str("HTTP/1.1 "); + head.push_str(&self.status_code); + head.push_str("\r\n"); + + for (k, v) in self.headers.entries() { + head.push_str(&k); + head.push_str(": "); + head.push_str(&v); + head.push_str("\r\n"); + } + + head.push_str("\r\n"); + + match data.write_all(head.as_bytes()) { + Ok(i) => i, + Err(_) => return Err("write head error"), + }; + + match data.write_all(&self.data) { + Ok(i) => i, + Err(_) => return Err("write body error"), + }; + + Ok(()) + } +} diff --git a/src/ezhttp/starter.rs b/src/ezhttp/starter.rs new file mode 100644 index 0000000..795c958 --- /dev/null +++ b/src/ezhttp/starter.rs @@ -0,0 +1,199 @@ +use super::{ + handle_connection, handle_connection_rrs, start_server_new_thread, start_server_sync, + start_server_with_threadpool, HttpServer, +}; + +use std::{ + error::Error, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, +}; + +/// Http server start builder +pub struct HttpServerStarter { + http_server: T, + support_http_rrs: bool, + timeout: Option, + host: String, + threads: usize, +} + +/// Running http server +pub struct RunningHttpServer { + thread: thread::JoinHandle<()>, + running: Arc, +} + +impl RunningHttpServer { + fn new(thread: thread::JoinHandle<()>, running: Arc) -> Self { + RunningHttpServer { thread, running } + } + + /// Stop http server + pub fn close(self) { + self.running.store(false, Ordering::Release); + self.thread.join().unwrap(); + } +} + +impl HttpServerStarter { + /// Create new HttpServerStarter + pub fn new(http_server: T, host: &str) -> Self { + HttpServerStarter { + http_server, + support_http_rrs: false, + timeout: None, + host: host.to_string(), + threads: 0, + } + } + + /// Set http server + pub fn http_server(mut self, http_server: T) -> Self { + self.http_server = http_server; + return self; + } + + /// Set if http_rrs is supported + pub fn support_http_rrs(mut self, support_http_rrs: bool) -> Self { + self.support_http_rrs = support_http_rrs; + return self; + } + + /// Set timeout for read & write + pub fn timeout(mut self, timeout: Option) -> Self { + self.timeout = timeout; + return self; + } + + /// Set host + pub fn host(mut self, host: String) -> Self { + self.host = host; + return self; + } + + /// Set threads in threadpool and return builder + /// + /// 0 threads means that a new thread is created for each connection \ + /// 1 thread means that all connections are processed in the main thread + pub fn threads(mut self, threads: usize) -> Self { + self.threads = threads; + return self; + } + + /// Get http server + pub fn get_http_server(self) -> T { + self.http_server + } + + /// Get if http_rrs is supported + pub fn get_support_http_rrs(&self) -> bool { + self.support_http_rrs + } + + /// Get timeout for read & write + pub fn get_timeout(&self) -> Option { + self.timeout + } + + /// Get host + pub fn get_host(&self) -> &str { + &self.host + } + + /// Get threads in threadpool + /// + /// 0 threads means that a new thread is created for each connection \ + /// 1 thread means that all connections are processed in the main thread + pub fn get_threads(&self) -> usize { + self.threads + } + + /// Start http server forever with options + pub fn start_forever(self) -> Result<(), Box> { + let handler = if self.support_http_rrs { + move |server, sock| { + handle_connection_rrs(server, sock); + } + } else { + move |server, sock| { + handle_connection(server, sock); + } + }; + + let running = Arc::new(AtomicBool::new(true)); + + if self.threads == 0 { + start_server_new_thread(self.http_server, &self.host, self.timeout, handler, running) + } else if self.threads == 1 { + start_server_sync(self.http_server, &self.host, self.timeout, handler, running) + } else { + start_server_with_threadpool( + self.http_server, + &self.host, + self.timeout, + self.threads, + handler, + running, + ) + } + } + + /// Start http server with options in new thread + pub fn start(self) -> RunningHttpServer { + let handler = if self.support_http_rrs { + move |server, sock| { + handle_connection_rrs(server, sock); + } + } else { + move |server, sock| { + handle_connection(server, sock); + } + }; + + let running = Arc::new(AtomicBool::new(true)); + let running_clone = running.clone(); + + let thread = if self.threads == 0 { + thread::spawn(move || { + start_server_new_thread( + self.http_server, + &self.host, + self.timeout, + handler, + running_clone, + ) + .expect("http server error"); + }) + } else if self.threads == 1 { + thread::spawn(move || { + start_server_sync( + self.http_server, + &self.host, + self.timeout, + handler, + running_clone, + ) + .expect("http server error"); + }) + } else { + thread::spawn(move || { + start_server_with_threadpool( + self.http_server, + &self.host, + self.timeout, + self.threads, + handler, + running_clone, + ) + .expect("http server error") + }) + }; + + RunningHttpServer::new(thread, running.clone()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 7ddab65..6ac547c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,1001 +1,3 @@ -use futures::executor::block_on; -use serde_json::Value; -use std::{ - boxed::Box, - error::Error, - fmt::{Debug, Display}, - future::Future, - net::{IpAddr, SocketAddr, ToSocketAddrs, TcpListener, TcpStream}, - sync::{Arc, atomic::{AtomicBool, Ordering}, Mutex}, - thread, - io::{Read, Write}, - time::Duration, - collections::HashMap -}; -use threadpool::ThreadPool; +pub mod ezhttp; -/// Http headers -#[derive(Clone, Debug)] -pub struct Headers { - entries: Vec<(String, String)>, -} - -impl Into> for Headers { - fn into(self) -> HashMap { - HashMap::from_iter(self.entries().into_iter()) - } -} - -impl From> for Headers -where - T: ToString, - U: ToString, -{ - fn from(value: Vec<(T, U)>) -> Self { - Headers { - entries: value - .into_iter() - .map(|v| (v.0.to_string(), v.1.to_string())) - .collect(), - } - } -} - -impl Headers { - pub fn new() -> Self { - Headers { - entries: Vec::new(), - } - } - - pub fn contains(self, header: impl ToString) -> bool { - for (k, _) in self.entries { - if k.to_lowercase() == header.to_string().to_lowercase() { - return true; - } - } - return false; - } - - pub fn get(self, key: impl ToString) -> Option { - for (k, v) in self.entries { - if k.to_lowercase() == key.to_string().to_lowercase() { - return Some(v); - } - } - return None; - } - - pub fn put(&mut self, key: impl ToString, value: String) { - for t in self.entries.iter_mut() { - if t.0.to_lowercase() == key.to_string().to_lowercase() { - t.1 = value; - return; - } - } - self.entries.push((key.to_string(), value)); - } - - pub fn remove(&mut self, key: impl ToString) { - for (i, t) in self.entries.iter_mut().enumerate() { - if t.0.to_lowercase() == key.to_string().to_lowercase() { - self.entries.remove(i); - return; - } - } - } - - pub fn keys(self) -> Vec { - self.entries.iter().map(|e| e.0.clone()).collect() - } - - pub fn values(self) -> Vec { - self.entries.iter().map(|e| e.1.clone()).collect() - } - - pub fn entries(self) -> Vec<(String, String)> { - return self.entries; - } - - pub fn len(self) -> usize { - return self.entries.len(); - } - - pub fn clear(&mut self) { - self.entries.clear(); - } -} - -impl Display for Headers { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -/// Http request -#[derive(Debug, Clone)] -pub struct HttpRequest { - pub page: String, - pub method: String, - pub addr: String, - pub headers: Headers, - pub params: Value, - pub data: Vec, -} - -impl Display for HttpRequest { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -/// Http response -#[derive(Debug, Clone)] -pub struct HttpResponse { - pub headers: Headers, - pub status_code: String, - pub data: Vec, -} - -impl Display for HttpResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(self, f) - } -} - -#[derive(Debug)] -pub enum HttpError { - ReadLineEof, - ReadLineUnknown, - InvalidHeaders, - InvalidQuery, - InvalidContentSize, - InvalidContent, - JsonParseError, - WriteHeadError, - WriteBodyError, - InvalidStatus, -} - -impl std::fmt::Display for HttpError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(self, f) - } -} - -impl Error for HttpError {} - -fn read_line(data: &mut impl Read) -> Result { - let mut bytes = Vec::new(); - - for byte in data.bytes() { - let byte = match byte { - Ok(i) => i, - Err(_) => return Err(HttpError::ReadLineEof), - }; - - bytes.push(byte); - - if byte == 0x0A { - break; - } - } - - match String::from_utf8(bytes) { - Ok(i) => Ok(i), - Err(_) => Err(HttpError::ReadLineUnknown), - } -} - -fn read_line_crlf(data: &mut impl Read) -> Result { - match read_line(data) { - Ok(i) => Ok(i[..i.len() - 2].to_string()), - Err(e) => Err(e), - } -} - -fn read_line_lf(data: &mut impl Read) -> Result { - match read_line(data) { - Ok(i) => Ok(i[..i.len() - 1].to_string()), - Err(e) => Err(e), - } -} - -fn rem_first(value: &str) -> &str { - let mut chars = value.chars(); - chars.next(); - chars.as_str() -} - -fn split(text: String, delimiter: &str, times: usize) -> Vec { - match times { - 0 => text.split(delimiter).map(|v| v.to_string()).collect(), - 1 => { - let mut v: Vec = Vec::new(); - match text.split_once(delimiter) { - Some(i) => { - v.push(i.0.to_string()); - v.push(i.1.to_string()); - } - None => { - v.push(text); - } - } - v - } - _ => text - .splitn(times, delimiter) - .map(|v| v.to_string()) - .collect(), - } -} - -impl HttpRequest { - /// Create new http request - pub fn new(page: &str, method: &str, params: Value, headers: Headers, data: Vec) -> Self { - HttpRequest { - page: page.to_string(), - method: method.to_string(), - addr: String::new(), - params, - headers, - data, - } - } - - /// Read http request from stream - pub fn read(data: &mut impl Read, addr: &SocketAddr) -> Result { - let octets = match addr.ip() { - IpAddr::V4(ip) => ip.octets(), - _ => [127, 0, 0, 1], - }; - - let ip_str = octets[0].to_string() - + "." - + &octets[1].to_string() - + "." - + &octets[2].to_string() - + "." - + &octets[3].to_string(); - - let status = split( - match read_line_crlf(data) { - Ok(i) => i, - Err(e) => return Err(e), - }, - " ", - 3, - ); - - let method = status[0].clone(); - let (page, query) = match status[1].split_once("?") { - Some(i) => (i.0.to_string(), Some(i.1)), - None => (status[1].clone(), None), - }; - - let mut headers = Headers::new(); - - loop { - let text = match read_line_crlf(data) { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidHeaders), - }; - - if text.len() == 0 { - break; - } - - let (key, value) = match text.split_once(": ") { - Some(i) => i, - None => return Err(HttpError::InvalidHeaders), - }; - - headers.put(key.to_lowercase(), value.to_string()); - } - - let mut params = serde_json::Map::new(); - - if let Some(i) = query { - for ele in i.split("&") { - let (k, v) = match ele.split_once("=") { - Some(i) => i, - None => return Err(HttpError::InvalidQuery), - }; - - params.insert( - match urlencoding::decode(k) { - Ok(i) => i.to_string(), - Err(_) => return Err(HttpError::InvalidQuery), - }, - match urlencoding::decode(v) { - Ok(i) => Value::String(i.to_string()), - Err(_) => return Err(HttpError::InvalidQuery), - }, - ); - } - } - - let mut reqdata: Vec = Vec::new(); - - if let Some(content_size) = headers.clone().get("content-length".to_string()) { - let content_size: usize = match content_size.parse() { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidContentSize), - }; - - if content_size > reqdata.len() { - let mut buf: Vec = Vec::new(); - buf.resize(content_size - reqdata.len(), 0); - - match data.read_exact(&mut buf) { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidContent), - }; - - reqdata.append(&mut buf); - } - } - - if let Some(content_type) = headers.clone().get("content-type".to_string()) { - let mut body = match String::from_utf8(reqdata.clone()) { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidContent), - }; - - match content_type.as_str() { - "application/json" => { - let val: Value = match serde_json::from_str(&body) { - Ok(i) => i, - Err(_) => return Err(HttpError::JsonParseError), - }; - - if let Value::Object(mut dict) = val { - params.append(&mut dict); - } - } - "multipart/form-data" => { - let boundary = "--".to_string() - + &content_type[(content_type.find("boundary=").unwrap() + 9)..] - + "\r\n"; - for part in body.split(boundary.as_str()) { - let lines: Vec<&str> = part.split("\r\n").collect(); - if lines.len() >= 3 { - if lines[0].starts_with("Content-Disposition: form-data; name=\"") { - let name: &str = - &lines[0]["Content-Disposition: form-data; name=\"".len()..]; - let name: &str = &name[..name.len() - 1]; - params - .insert(name.to_string(), Value::String(lines[2].to_string())); - } - } - } - } - "application/x-www-form-urlencoded" => { - if body.starts_with("?") { - body = rem_first(body.as_str()).to_string() - } - - for ele in body.split("&") { - let (k, v) = match ele.split_once("=") { - Some(i) => i, - None => return Err(HttpError::InvalidQuery), - }; - - params.insert( - match urlencoding::decode(k) { - Ok(i) => i.to_string(), - Err(_) => return Err(HttpError::InvalidQuery), - }, - match urlencoding::decode(v) { - Ok(i) => Value::String(i.to_string()), - Err(_) => return Err(HttpError::InvalidQuery), - }, - ); - } - } - _ => {} - } - } - - Ok(HttpRequest { - page, - method, - addr: ip_str.to_string(), - params: Value::Object(params), - headers, - data: reqdata.clone(), - }) - } - - /// Read http request with http_rrs support - pub fn read_with_rrs(data: &mut impl Read) -> Result { - let addr = match read_line_lf(data) { - Ok(i) => i, - Err(e) => { - return Err(e); - } - } - .to_socket_addrs() - .unwrap() - .collect::>()[0]; - HttpRequest::read(data, &addr) - } - - /// Set params to query in url - pub fn params_to_page(&mut self) { - let mut query = String::new(); - - let mut i: bool = !self.page.contains("?"); - - if let Value::Object(obj) = self.params.clone() { - for (k, v) in obj { - query.push_str(if i { "?" } else { "&" }); - query.push_str(urlencoding::encode(k.as_str()).to_string().as_str()); - query.push_str("="); - query.push_str( - urlencoding::encode(v.as_str().unwrap()) - .to_string() - .as_str(), - ); - i = false; - } - } - - self.page += query.as_str(); - } - - /// Set params to json data - pub fn params_to_json(&mut self) { - self.data = Vec::from(self.params.to_string().as_bytes()); - } - - /// Write http request to stream - /// - /// [`params`](Self::params) is not written to the stream, you need to use [`params_to_json`](Self::params_to_json) or [`params_to_page`](Self::params_to_page) - pub fn write(self, data: &mut impl Write) -> Result<(), HttpError> { - let mut head: String = String::new(); - head.push_str(&self.method); - head.push_str(" "); - head.push_str(&self.page); - head.push_str(" HTTP/1.1"); - head.push_str("\r\n"); - - for (k, v) in self.headers.entries { - head.push_str(&k); - head.push_str(": "); - head.push_str(&v); - head.push_str("\r\n"); - } - - head.push_str("\r\n"); - - match data.write_all(head.as_bytes()) { - Ok(i) => i, - Err(_) => return Err(HttpError::WriteHeadError), - }; - - if !self.data.is_empty() { - match data.write_all(&self.data) { - Ok(i) => i, - Err(_) => return Err(HttpError::WriteBodyError), - }; - } - - Ok(()) - } -} - -impl HttpResponse { - /// Create new http response with empty headers and data and a 200 OK status code - pub fn new() -> Self { - Self::from_bytes(Headers::new(), "200 OK", Vec::new()) - } - - /// Create new http response from headers, bytes data, and status code - pub fn from_bytes(headers: Headers, status_code: impl ToString, data: Vec) -> Self { - HttpResponse { - headers, - data, - status_code: status_code.to_string(), - } - } - - /// Create new http response from headers, string data, and status code - pub fn from_string(headers: Headers, status_code: impl ToString, data: impl ToString) -> Self { - HttpResponse { - headers, - data: data.to_string().into_bytes(), - status_code: status_code.to_string(), - } - } - - /// Get data in UTF-8 - pub fn get_text(self) -> String { - match String::from_utf8(self.data) { - Ok(i) => i, - Err(_) => String::new(), - } - } - - /// Get json [`Value`](Value) from data - pub fn get_json(self) -> Value { - match serde_json::from_str(self.get_text().as_str()) { - Ok(i) => i, - Err(_) => Value::Null, - } - } - - /// Read http response from stream - pub fn read(data: &mut impl Read) -> Result { - let status = match read_line_crlf(data) { - Ok(i) => i, - Err(e) => { - return Err(e); - } - }; - - let (_, status_code) = match status.split_once(" ") { - Some(i) => i, - None => return Err(HttpError::InvalidStatus), - }; - - let mut headers = Headers::new(); - - loop { - let text = match read_line_crlf(data) { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidHeaders), - }; - - if text.len() == 0 { - break; - } - - let (key, value) = match text.split_once(": ") { - Some(i) => i, - None => return Err(HttpError::InvalidHeaders), - }; - - headers.put(key.to_lowercase(), value.to_string()); - } - - let mut reqdata: Vec = Vec::new(); - - if let Some(content_size) = headers.clone().get("content-length".to_string()) { - let content_size: usize = match content_size.parse() { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidContentSize), - }; - - if content_size > reqdata.len() { - let mut buf: Vec = Vec::new(); - buf.resize(content_size - reqdata.len(), 0); - - match data.read_exact(&mut buf) { - Ok(i) => i, - Err(_) => return Err(HttpError::InvalidContent), - }; - - reqdata.append(&mut buf); - } - } else { - loop { - let mut buf: Vec = vec![0; 1024 * 32]; - - let buf_len = match data.read(&mut buf) { - Ok(i) => i, - Err(_) => { - break; - } - }; - - if buf_len == 0 { - break; - } - - buf.truncate(buf_len); - - reqdata.append(&mut buf); - } - } - - Ok(HttpResponse::from_bytes(headers, status_code, reqdata)) - } - - /// Write http response to stream - pub fn write(self, data: &mut impl Write) -> Result<(), &str> { - let mut head: String = String::new(); - head.push_str("HTTP/1.1 "); - head.push_str(&self.status_code); - head.push_str("\r\n"); - - for (k, v) in self.headers.entries { - head.push_str(&k); - head.push_str(": "); - head.push_str(&v); - head.push_str("\r\n"); - } - - head.push_str("\r\n"); - - match data.write_all(head.as_bytes()) { - Ok(i) => i, - Err(_) => return Err("write head error"), - }; - - match data.write_all(&self.data) { - Ok(i) => i, - Err(_) => return Err("write body error"), - }; - - Ok(()) - } -} - -/// Async http server trait -pub trait HttpServer { - fn on_start(&mut self, host: &str) -> impl Future + Send; - fn on_close(&mut self) -> impl Future + Send; - fn on_request( - &mut self, - req: &HttpRequest, - ) -> impl Future> + Send; -} - -/// Http server start builder -pub struct HttpServerStarter { - http_server: T, - support_http_rrs: bool, - timeout: Option, - host: String, - threads: usize, -} - -/// Running http server -pub struct RunningHttpServer { - thread: thread::JoinHandle<()>, - running: Arc, -} - -impl RunningHttpServer { - fn new(thread: thread::JoinHandle<()>, running: Arc) -> Self { - RunningHttpServer { thread, running } - } - - /// Stop http server - pub fn close(self) { - self.running.store(false, Ordering::Release); - self.thread.join().unwrap(); - } -} - -impl HttpServerStarter { - /// Create new HttpServerStarter - pub fn new(http_server: T, host: &str) -> Self { - HttpServerStarter { - http_server, - support_http_rrs: false, - timeout: None, - host: host.to_string(), - threads: 0, - } - } - - /// Set http server - pub fn http_server(mut self, http_server: T) -> Self { - self.http_server = http_server; - return self; - } - - /// Set if http_rrs is supported - pub fn support_http_rrs(mut self, support_http_rrs: bool) -> Self { - self.support_http_rrs = support_http_rrs; - return self; - } - - /// Set timeout for read & write - pub fn timeout(mut self, timeout: Option) -> Self { - self.timeout = timeout; - return self; - } - - /// Set host - pub fn host(mut self, host: String) -> Self { - self.host = host; - return self; - } - - /// Set threads in threadpool and return builder - /// - /// 0 threads means that a new thread is created for each connection \ - /// 1 thread means that all connections are processed in the main thread - pub fn threads(mut self, threads: usize) -> Self { - self.threads = threads; - return self; - } - - /// Get http server - pub fn get_http_server(self) -> T { - self.http_server - } - - /// Get if http_rrs is supported - pub fn get_support_http_rrs(&self) -> bool { - self.support_http_rrs - } - - /// Get timeout for read & write - pub fn get_timeout(&self) -> Option { - self.timeout - } - - /// Get host - pub fn get_host(&self) -> &str { - &self.host - } - - /// Get threads in threadpool - /// - /// 0 threads means that a new thread is created for each connection \ - /// 1 thread means that all connections are processed in the main thread - pub fn get_threads(&self) -> usize { - self.threads - } - - /// Start http server forever with options - pub fn start_forever(self) -> Result<(), Box> { - let handler = if self.support_http_rrs { - move |server, sock| { - handle_connection_rrs(server, sock); - } - } else { - move |server, sock| { - handle_connection(server, sock); - } - }; - - let running = Arc::new(AtomicBool::new(true)); - - if self.threads == 0 { - start_server_new_thread(self.http_server, &self.host, self.timeout, handler, running) - } else if self.threads == 1 { - start_server_sync(self.http_server, &self.host, self.timeout, handler, running) - } else { - start_server_with_threadpool( - self.http_server, - &self.host, - self.timeout, - self.threads, - handler, - running, - ) - } - } - - /// Start http server with options in new thread - pub fn start(self) -> RunningHttpServer { - let handler = if self.support_http_rrs { - move |server, sock| { - handle_connection_rrs(server, sock); - } - } else { - move |server, sock| { - handle_connection(server, sock); - } - }; - - let running = Arc::new(AtomicBool::new(true)); - let running_clone = running.clone(); - - let thread = if self.threads == 0 { - thread::spawn(move || { - start_server_new_thread( - self.http_server, - &self.host, - self.timeout, - handler, - running_clone, - ) - .expect("http server error"); - }) - } else if self.threads == 1 { - thread::spawn(move || { - start_server_sync( - self.http_server, - &self.host, - self.timeout, - handler, - running_clone, - ) - .expect("http server error"); - }) - } else { - thread::spawn(move || { - start_server_with_threadpool( - self.http_server, - &self.host, - self.timeout, - self.threads, - handler, - running_clone, - ) - .expect("http server error") - }) - }; - - RunningHttpServer::new(thread, running.clone()) - } -} - -fn start_server_with_threadpool( - server: S, - host: &str, - timeout: Option, - threads: usize, - handler: F, - running: Arc, -) -> Result<(), Box> -where - F: (Fn(Arc>, TcpStream) -> ()) + Send + 'static + Copy, - S: HttpServer + Send + 'static, -{ - let threadpool = ThreadPool::new(threads); - let server = Arc::new(Mutex::new(server)); - let listener = TcpListener::bind(host)?; - - let host_clone = String::from(host).clone(); - let server_clone = server.clone(); - block_on(server_clone.lock().unwrap().on_start(&host_clone)); - - while running.load(Ordering::Acquire) { - let (sock, _) = match listener.accept() { - Ok(i) => i, - Err(_) => { - continue; - } - }; - - sock.set_read_timeout(timeout).unwrap(); - sock.set_write_timeout(timeout).unwrap(); - - let now_server = Arc::clone(&server); - threadpool.execute(move || { - handler(now_server, sock); - }); - } - - threadpool.join(); - - block_on(server.lock().unwrap().on_close()); - - Ok(()) -} - -fn start_server_new_thread( - server: S, - host: &str, - timeout: Option, - handler: F, - running: Arc, -) -> Result<(), Box> -where - F: (Fn(Arc>, TcpStream) -> ()) + Send + 'static + Copy, - S: HttpServer + Send + 'static, -{ - let server = Arc::new(Mutex::new(server)); - let listener = TcpListener::bind(host)?; - - let host_clone = String::from(host).clone(); - let server_clone = server.clone(); - block_on(server_clone.lock().unwrap().on_start(&host_clone)); - - while running.load(Ordering::Acquire) { - let (sock, _) = match listener.accept() { - Ok(i) => i, - Err(_) => { - continue; - } - }; - - sock.set_read_timeout(timeout).unwrap(); - sock.set_write_timeout(timeout).unwrap(); - - let now_server = Arc::clone(&server); - thread::spawn(move || { - handler(now_server, sock); - }); - } - - block_on(server.lock().unwrap().on_close()); - - Ok(()) -} - -fn start_server_sync( - server: S, - host: &str, - timeout: Option, - handler: F, - running: Arc, -) -> Result<(), Box> -where - F: (Fn(Arc>, TcpStream) -> ()) + Send + 'static + Copy, - S: HttpServer + Send + 'static, -{ - let server = Arc::new(Mutex::new(server)); - let listener = TcpListener::bind(host)?; - - let host_clone = String::from(host).clone(); - let server_clone = server.clone(); - block_on(server_clone.lock().unwrap().on_start(&host_clone)); - - while running.load(Ordering::Acquire) { - let (sock, _) = match listener.accept() { - Ok(i) => i, - Err(_) => { - continue; - } - }; - - sock.set_read_timeout(timeout).unwrap(); - sock.set_write_timeout(timeout).unwrap(); - - let now_server = Arc::clone(&server); - handler(now_server, sock); - } - - block_on(server.lock().unwrap().on_close()); - - Ok(()) -} - -fn handle_connection(server: Arc>, mut sock: TcpStream) { - let addr = sock.peer_addr().unwrap(); - - let req = match HttpRequest::read(&mut sock, &addr) { - Ok(i) => i, - Err(_) => { - return; - } - }; - let resp = match block_on(server.lock().unwrap().on_request(&req)) { - Some(i) => i, - None => { - return; - } - }; - resp.write(&mut sock).unwrap(); -} - -fn handle_connection_rrs( - server: Arc>, - mut sock: TcpStream, -) { - let req = match HttpRequest::read_with_rrs(&mut sock) { - Ok(i) => i, - Err(_) => { - return; - } - }; - let resp = match block_on(server.lock().unwrap().on_request(&req)) { - Some(i) => i, - None => { - return; - } - }; - resp.write(&mut sock).unwrap(); -} - -/// Start [`HttpServer`](HttpServer) on some host -/// -/// Use [`HttpServerStarter`](HttpServerStarter) to set more options -pub fn start_server(server: S, host: &str) { - start_server_new_thread( - server, - host, - None, - handle_connection, - Arc::new(AtomicBool::new(true)), - ) - .unwrap(); -} +pub use ezhttp::*; diff --git a/src/main.rs b/src/main.rs index d4564e8..7283727 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,8 @@ impl HttpServer for EzSite { if req.page == "/" { Some(HttpResponse::from_string( Headers::from(vec![("Content-Type", "text/html")]), // response headers - "200 OK", // response status code - self.index_page.clone(), // response body + "200 OK", // response status code + self.index_page.clone(), // response body )) } else { None // close connection