This commit is contained in:
MeexReay 2024-06-03 17:57:01 +03:00
commit 29b0ecfc17
6 changed files with 1313 additions and 0 deletions

479
Cargo.lock generated Normal file
View File

@ -0,0 +1,479 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "bytes"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ezhttp"
version = "0.1.0"
dependencies = [
"serde_json",
"tokio",
"urlencoding",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "miniz_oxide"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
dependencies = [
"memchr",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.5",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "proc-macro2"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.5",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

14
Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "ezhttp"
version = "0.1.0"
edition = "2021"
description = "lightweight http server"
license-file = "LICENSE"
readme = "README.md"
keywords = ["http", "server", "site"]
[dependencies]
urlencoding = "2.1.3"
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

59
README.md Normal file
View File

@ -0,0 +1,59 @@
# EzHttp
Easy http server on rust
Example:
```rust
use ezhttp::{Headers, HttpResponse, HttpRequest, HttpServer};
use tokio::{runtime::Runtime, net::TcpListener};
struct EzSite {
index_page: String
}
impl HttpServer for EzSite {
async fn on_request(&mut self, req: &HttpRequest) -> Option<HttpResponse> {
println!("{} > {} {}", req.addr, req.method, req.page);
if req.page == "/" {
Some(
HttpResponse::from_str(
Headers::from(vec![
("Content-Type", "text/html")
]),
"200 OK".to_string(),
&self.index_page
)
)
} else {
None // just shutdown socket
}
}
async fn on_start(&mut self, host: &str, listener: &TcpListener) {
println!("Http server started on {}", host);
}
async fn on_close(&mut self) {
println!("Http server closed");
}
}
impl EzSite {
fn new(index_page: &str) -> Self {
EzSite {
index_page: index_page.to_string()
}
}
}
fn main() {
let site = EzSite::new("Hello World!");
let host = "localhost:8080";
Runtime::new().unwrap().block_on(async move {
ezhttp::start_server(site, host).await.unwrap();
});
}
```

697
src/lib.rs Normal file
View File

@ -0,0 +1,697 @@
use serde_json::Value;
use std::{
boxed::Box, error::Error, net::{IpAddr, SocketAddr, ToSocketAddrs}, ptr::read, sync::Arc
};
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::{
io::AsyncBufReadExt,
net::{TcpListener, TcpStream},
sync::Mutex,
};
use urlencoding::{decode, encode};
#[derive(Clone, Debug)]
pub struct Headers {
entries: Vec<(String, String)>,
}
impl Headers {
pub fn from_entries(entries: Vec<(String, String)>) -> Self {
Headers { entries: entries }
}
pub fn from(entries: Vec<(&str, &str)>) -> Self {
Headers {
entries: entries
.iter()
.map(|v| (v.0.to_string(), v.1.to_string()))
.collect(),
}
}
pub fn new() -> Self {
Headers {
entries: Vec::new(),
}
}
pub fn contains_value(self, value: String) -> bool {
for (k, v) in self.entries {
if v == value {
return true;
}
}
return false;
}
pub fn contains_key(self, key: String) -> bool {
for (k, v) in self.entries {
if k == key.to_lowercase() {
return true;
}
}
return false;
}
pub fn get(self, key: String) -> Option<String> {
for (k, v) in self.entries {
if k == key.to_lowercase() {
return Some(v);
}
}
return None;
}
pub fn put(&mut self, key: String, value: String) {
for t in self.entries.iter_mut() {
if t.0 == key.to_lowercase() {
t.1 = value;
return;
}
}
self.entries.push((key.to_lowercase(), value));
}
pub fn remove(&mut self, key: String) {
for (i, t) in self.entries.iter_mut().enumerate() {
if t.0 == key.to_lowercase() {
self.entries.remove(i);
return;
}
}
}
pub fn keys(self) -> Vec<String> {
let mut keys = Vec::new();
for (k, v) in self.entries {
keys.push(k.to_lowercase());
}
keys
}
pub fn values(self) -> Vec<String> {
let mut values = Vec::new();
for (k, v) in self.entries {
values.push(v);
}
values
}
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 std::fmt::Display for Headers {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
#[derive(Debug, Clone)]
pub struct HttpRequest {
pub page: String,
pub method: String,
pub addr: String,
pub headers: Headers,
pub params: Value,
pub data: Vec<u8>,
}
impl std::fmt::Display for HttpRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub headers: Headers,
pub status_code: String,
pub data: Vec<u8>,
}
impl std::fmt::Display for HttpResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::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 {}
async fn read_line(data: &mut BufReader<&mut TcpStream>) -> Result<String, HttpError> {
let mut buf = String::new();
let mut buf = match data.read_line(&mut buf).await {
Ok(i) => {
if i == 0 {
return Err(HttpError::ReadLineEof);
}
buf
}
Err(_) => return Err(HttpError::ReadLineUnknown),
};
Ok(buf.to_string())
}
async fn read_line_crlf(data: &mut BufReader<&mut TcpStream>) -> Result<String, HttpError> {
match read_line(data).await {
Ok(i) => Ok(i[..i.len() - 2].to_string()),
Err(e) => Err(e)
}
}
async fn read_line_lf(data: &mut BufReader<&mut TcpStream>) -> Result<String, HttpError> {
match read_line(data).await {
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<String> {
match times {
0 => text.split(delimiter).map(|v| v.to_string()).collect(),
1 => {
let mut v: Vec<String> = 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 {
pub fn new(page: &str, method: &str, params: Value, headers: Headers, data: Vec<u8>) -> Self {
HttpRequest {
page: page.to_string(),
method: method.to_string(),
addr: String::new(),
params: params,
headers: headers,
data: data,
}
}
pub async fn read(mut data: BufReader<&mut TcpStream>, addr: &SocketAddr) -> Result<HttpRequest, HttpError> {
let octets = match addr.ip() {
IpAddr::V4(ip) => ip.octets(),
_ => [127, 0, 0, 1],
};
let ip_str = octets[0].to_string().as_str().to_owned()
+ "."
+ octets[1].to_string().as_str()
+ "."
+ octets[2].to_string().as_str()
+ "."
+ octets[3].to_string().as_str();
let mut status = split(
match read_line_crlf(&mut data).await {
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(&mut data).await {
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 decode(k) {
Ok(i) => i.to_string(),
Err(_) => return Err(HttpError::InvalidQuery),
},
match decode(v) {
Ok(i) => Value::String(i.to_string()),
Err(_) => return Err(HttpError::InvalidQuery),
},
);
}
}
let mut reqdata: Vec<u8> = 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<u8> = Vec::new();
buf.resize(content_size - reqdata.len(), 0);
match data.read_exact(&mut buf).await {
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 decode(k) {
Ok(i) => i.to_string(),
Err(_) => return Err(HttpError::InvalidQuery),
},
match decode(v) {
Ok(i) => Value::String(i.to_string()),
Err(_) => return Err(HttpError::InvalidQuery),
},
);
}
}
_ => {}
}
}
Ok(HttpRequest {
page: page,
method: method,
addr: ip_str.to_string(),
params: Value::Object(params),
headers: headers,
data: reqdata.clone(),
})
}
pub async fn read_with_rrs(mut data: BufReader<&mut TcpStream>, addr: &SocketAddr) -> Result<HttpRequest, HttpError> {
let addr = match read_line_lf(&mut data).await {
Ok(i) => i,
Err(e) => { return Err(e); }
}.to_socket_addrs().unwrap().collect::<Vec<SocketAddr>>()[0];
HttpRequest::read(data, &addr).await
}
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(encode(k.as_str()).to_string().as_str());
query.push_str("=");
query.push_str(encode(v.as_str().unwrap()).to_string().as_str());
i = false;
}
}
self.page += query.as_str();
}
pub async fn write(self, data: &mut TcpStream) -> 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()).await {
Ok(i) => i,
Err(_) => return Err(HttpError::WriteHeadError),
};
if !self.data.is_empty() {
match data.write_all(&self.data).await {
Ok(i) => i,
Err(_) => return Err(HttpError::WriteBodyError),
};
}
Ok(())
}
}
impl HttpResponse {
pub fn new(headers: Headers, status_code: String, data: Vec<u8>) -> Self {
HttpResponse {
headers: headers,
data: data,
status_code: status_code,
}
}
pub fn from_str(headers: Headers, status_code: String, data: &str) -> Self {
HttpResponse {
headers: headers,
data: data.to_string().into_bytes(),
status_code: status_code,
}
}
pub fn get_text(self) -> String {
match String::from_utf8(self.data) {
Ok(i) => i,
Err(_) => String::new(),
}
}
pub fn get_json(self) -> Value {
match serde_json::from_str(self.get_text().as_str()) {
Ok(i) => i,
Err(_) => Value::Null,
}
}
pub async fn read(dread: &mut TcpStream) -> Result<HttpResponse, HttpError> {
let mut data = BufReader::new(dread);
let status = match read_line_crlf(&mut data).await {
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(&mut data).await {
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<u8> = 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<u8> = Vec::new();
buf.resize(content_size - reqdata.len(), 0);
match data.read_exact(&mut buf).await {
Ok(i) => i,
Err(_) => return Err(HttpError::InvalidContent),
};
reqdata.append(&mut buf);
}
} else {
loop {
let mut buf: Vec<u8> = vec![0; 1024 * 32];
let mut buf_len: usize = match data.read(&mut buf).await {
Ok(i) => i,
Err(e) => {
break;
}
};
if buf_len == 0 {
break;
}
buf.truncate(buf_len);
reqdata.append(&mut buf);
}
}
Ok(HttpResponse {
headers: headers,
status_code: status_code.to_string(),
data: reqdata,
})
}
pub async fn write(self, data: &mut TcpStream) -> 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()).await {
Ok(i) => i,
Err(_) => return Err("write head error"),
};
match data.write_all(&self.data).await {
Ok(i) => i,
Err(_) => return Err("write body error"),
};
Ok(())
}
}
pub trait HttpServer: Sync {
async fn on_start(&mut self, host: &str, listener: &TcpListener);
async fn on_close(&mut self);
fn on_request(&mut self, req: &HttpRequest) -> impl std::future::Future<Output = Option<HttpResponse>> + std::marker::Send;
}
pub async fn handle_connection<S: HttpServer + Send + 'static>(
server: Arc<Mutex<S>>,
mut sock: TcpStream,
addr: std::net::SocketAddr,
) where
S: HttpServer,
{
let addr = sock.peer_addr().unwrap();
let req = match HttpRequest::read(BufReader::new(&mut sock), &addr).await {
Ok(i) => i,
Err(_) => {
return;
}
};
let resp = match server.lock().await.on_request(&req).await {
Some(i) => i,
None => {
return;
}
};
resp.write(&mut sock).await.unwrap();
}
pub async fn start_server(mut server: impl HttpServer + Send + 'static, host: &str) -> Result<(), Box<dyn Error>> {
let server = Arc::new(Mutex::new(server));
let listener = TcpListener::bind(host).await?;
server.lock().await.on_start(host, &listener).await;
loop {
let (sock, addr) = match listener.accept().await {
Ok(i) => i,
Err(_) => { break; }
};
let now_server = Arc::clone(&server);
tokio::spawn(handle_connection(now_server, sock, addr));
}
server.lock().await.on_close().await;
Ok(())
}
// http rrs
pub async fn start_server_rrs(mut server: impl HttpServer + Send + 'static, host: &str) -> Result<(), Box<dyn Error>> {
let server = Arc::new(Mutex::new(server));
let listener = TcpListener::bind(host).await?;
server.lock().await.on_start(host, &listener).await;
loop {
let (sock, addr) = match listener.accept().await {
Ok(i) => i,
Err(_) => { break; }
};
let now_server = Arc::clone(&server);
tokio::spawn(handle_connection_rrs(now_server, sock, addr));
}
server.lock().await.on_close().await;
Ok(())
}
// http rrs
async fn handle_connection_rrs<S: HttpServer + Send + 'static>(
server: Arc<Mutex<S>>,
mut sock: TcpStream,
addr: std::net::SocketAddr,
) {
let req = match HttpRequest::read_with_rrs(BufReader::new(&mut sock), &addr).await {
Ok(i) => i,
Err(_) => {
return;
}
};
let resp = match server.lock().await.on_request(&req).await {
Some(i) => i,
None => {
return;
}
};
resp.write(&mut sock).await.unwrap();
}

51
src/main.rs Normal file
View File

@ -0,0 +1,51 @@
use ezhttp::{Headers, HttpResponse, HttpRequest, HttpServer};
use tokio::{runtime::Runtime, net::TcpListener};
struct EzSite {
index_page: String
}
impl HttpServer for EzSite {
async fn on_request(&mut self, req: &HttpRequest) -> Option<HttpResponse> {
println!("{} > {} {}", req.addr, req.method, req.page);
if req.page == "/" {
Some(
HttpResponse::from_str(
Headers::from(vec![
("Content-Type", "text/html")
]),
"200 OK".to_string(),
&self.index_page
)
)
} else {
None // just shutdown socket
}
}
async fn on_start(&mut self, host: &str, listener: &TcpListener) {
println!("Http server started on {}", host);
}
async fn on_close(&mut self) {
println!("Http server closed");
}
}
impl EzSite {
fn new(index_page: &str) -> Self {
EzSite {
index_page: index_page.to_string()
}
}
}
fn main() {
let site = EzSite::new("Hello World!");
let host = "localhost:8080";
Runtime::new().unwrap().block_on(async move {
ezhttp::start_server(site, host).await.unwrap();
});
}