HTTP服务器基础的路由功能

This commit is contained in:
lsy 2024-10-21 18:09:47 +08:00
parent 0597ace088
commit aae863abe8
6 changed files with 360 additions and 0 deletions

View File

@ -0,0 +1,8 @@
[package]
name = "temp"
version = "0.1.0"
edition = "2021"
[dependencies]
log = "0.4.22"
tokio = { version = "1", features = ["full"] }

View File

@ -0,0 +1,94 @@
use std::cmp::PartialEq;
use std::sync::{Arc, RwLock};
use tokio::io::{AsyncReadExt};
use tokio::{net, runtime};
use crate::request::{Method,Resource,Request};
use crate::route::{Route};
use crate::respond::{Respond};
pub struct Server {
listener_addr: &'static str,
listener_port: i32,
status: bool,
routes:RwLock<Vec<Route>>,
}
impl Server {
pub fn new(listener_port:i32) -> Arc<Self> {
if listener_port < 1 || listener_port > 65535 {
panic!("listener port must be between 1 and 65535");
}
Arc::new(
Self {
listener_addr: "127.0.0.1",
listener_port,
status: false,
routes: RwLock::new(Vec::new()),
}
)
}
pub fn start(self: Arc<Self>) {
let address=format!("{}:{}", self.listener_addr, self.listener_port);
let rt = runtime::Runtime::new().unwrap();
println!("Listening on {}", address);
let listener =rt.block_on(async { net::TcpListener::bind(address).await.expect("Failed to bind listener") });
rt.block_on(self.receive(listener));
}
}
impl Server {
async fn receive(self:Arc<Self>,listener:net::TcpListener) {
loop {
let (mut socket, _) = listener.accept().await.expect("Failed to accept connection");
let server = self.clone();
tokio::spawn(async move {
let mut buf = [0; 1024];
if let Err(e) = socket.read(&mut buf).await {
println!("Failed to read from socket: {}", e);
return;
}
let content = match String::from_utf8(buf.to_vec()) {
Ok(s) => s,
Err(e1) => {
println!("Failed to convert buffer to string: {}", e1);
return;
}
};
let request = match Request::build(content) {
Some(req) => req,
None => {
println!("Request parsing failed");
return;
}
};
let respond=Respond::build(socket);
for route in server.routes.read().expect("Unable to read route").iter() {
if request.method() == &route.method && request.path() == &route.path {
(route.function)(request, respond);
return;
}
}
respond.body("Not Found".to_string());
respond.status_message("Not Found".to_string());
respond.status_code("403".to_string());
respond.send();
return;
});
}
}
}
impl Server {
pub fn route(self:&Arc<Self>, path: &'static str, method: &'static str, function: impl Fn(Request, Arc<Respond>) + Send + Sync + 'static) {
let tmp_method=method.to_uppercase().as_str().into();
let tmp_path;
if path.ends_with("/") && path.len() > 1 {
tmp_path=path.trim_end_matches("/").into();
}else {
tmp_path=path.into();
}
if tmp_method == Method::Uninitialized {panic!("该方法未被定义")}
let route = Route { path:tmp_path, method:tmp_method, function: Box::new(function) };
self.routes.write().expect("Route write failed").push(route);
}
}

View File

@ -0,0 +1,17 @@
use core::Server;
mod core;
mod request;
mod respond;
mod route;
fn main() {
let server = Server::new( 8000);
server.route("/","get",|request, respond| {
respond.body(String::from("lsy"));
respond.send();
});
server.route("/admin","get",|request, respond| {
respond.send();
});
server.start();
}

View File

@ -0,0 +1,133 @@
use std::collections::HashMap;
use std::fmt::Display;
use std::path::Path;
pub enum Method {
Get,
Post,
Uninitialized
}
impl From<&str> for Method {
fn from(s: &str) -> Method {
match s{
"GET" => Method::Get,
"POST" => Method::Post,
_ => Method::Uninitialized
}
}
}
impl Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let method = match self {
Method::Get => {"GET"}
Method::Post => {"POST"}
Method::Uninitialized => {"Uninitialized"}
};
write!(f, "{}", method)
}
}
impl PartialEq for Method {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Method::Get, Method::Get) => true,
(Method::Post, Method::Post) => true,
(Method::Uninitialized, Method::Uninitialized) => true,
_ => false,
}
}
}
pub enum Version {
V1_1,
Uninitialized
}
impl From<&str> for Version {
fn from(s: &str) -> Version {
match s {
"HTTP/1.1" => Version::V1_1,
_ => Version::Uninitialized
}
}
}
pub enum Resource{
Path(String),
}
impl PartialEq for &Resource {
fn eq(&self, other: &Self) -> bool {
match (self,other) {
(Resource::Path(path), Resource::Path(other)) => path == other,
}
}
}
impl Display for Resource {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Resource::Path(path) => write!(f, "{}", path),
}
}
}
impl From<&str> for Resource {
fn from(s: &str) -> Resource {
match s{
s => Resource::Path(s.to_string()),
}
}
}
pub struct Request{
method: Method,
version: Version,
resource: Resource,
headers: HashMap<String, String>,
}
impl Request{
pub fn build(content:String) -> Option<Request>{
let mut content =content.lines();
let request_line =content.next().unwrap_or("");
if request_line.is_empty(){return None}
let request_line:Vec<_> = request_line.split_whitespace().collect();
if request_line.len()<3 {return None}
let method=request_line[0];
let resource=request_line[1];
let version=request_line[2];
if method.is_empty()||resource.is_empty()||version.is_empty() {return None}
let mut headers =HashMap::<String, String>::new();
for i in content {
if i.len()==0 { break;}
let parts:Vec<&str> = i.split(": ").collect();
if parts.len() == 2 {
headers.insert(parts[0].to_string(), parts[1].to_string());
}
};
Some(Request{
method: method.into(),
version: version.into(),
resource: resource.into(),
headers,
})
}
}
impl Request {
pub fn method(&self) -> &Method {
&self.method
}
pub fn path(&self) -> &Resource {
&self.resource
}
}

View File

@ -0,0 +1,97 @@
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio::task;
pub struct Respond{
version:Mutex<String>,
status_code:Mutex<String>,
status_message:Mutex<String>,
headers:Mutex<HashMap<String, String>>,
body:Mutex<String>,
socket:Arc<Mutex<TcpStream>>
}
impl Respond {
pub fn build(socket: TcpStream) -> Arc<Respond> {
Arc::new(Respond {
version: Mutex::new("HTTP/1.1".to_string()),
status_code: Mutex::new("200".to_string()),
status_message: Mutex::new("OK".to_string()),
headers: Mutex::new(HashMap::new()),
body: Mutex::new("Hello, world!".to_string()),
socket: Arc::new(Mutex::new(socket))
})
}
pub fn format_message(self: &Arc<Self>) -> String {
task::block_in_place(|| {
let self_ref = self.clone();
let mut massage = String::new();
let mut header = String::new();
tokio::runtime::Handle::current().block_on(async {
let version = self_ref.version.lock().await;
let status_code = self_ref.status_code.lock().await;
let status_message = self_ref.status_message.lock().await;
massage.push_str(format!("{} {} {}\r\n", version, status_code, status_message).as_str());
for (key, value) in self_ref.headers.lock().await.iter() {
header += &format!("{}: {}\r\n", key, value);
}
massage.push_str(&header);
massage.push_str("\r\n");
let body = self_ref.body.lock().await.to_string();
massage.push_str(body.as_str());
});
massage
})
}
pub fn send(self: &Arc<Self>) {
let self_ref = self.clone();
task::spawn(async move {
let message = self_ref.format_message();
let mut socket = self_ref.socket.lock().await;
if let Ok(_) = socket.write_all(message.as_ref()).await {
if let Err(e) = socket.flush().await {
println!("Failed to flush: {}", e);
}
} else {
println!("Failed to send content");
}
});
}
}
impl Respond {
pub fn body(self: &Arc<Self>,content:String) {
let self_ref = self.clone();
tokio::spawn(async move {
let mut body = self_ref.body.lock().await;
*body = content;
});
}
pub fn status_code(self: &Arc<Self>,code:String) {
let self_ref = self.clone();
tokio::spawn(async move {
let mut status_code = self_ref.status_code.lock().await;
*status_code = code;
});
}
pub fn status_message(self: &Arc<Self>,massage:String) {
let self_ref = self.clone();
tokio::spawn(async move {
let mut status_message = self_ref.status_message.lock().await;
*status_message = massage;
});
}
}

View File

@ -0,0 +1,11 @@
use std::sync::Arc;
use crate::request::{Method,Resource, Request};
use crate::respond::{Respond};
pub(crate) struct Route {
pub path: Resource,
pub method: Method,
pub function: Box<dyn Fn(Request,Arc<Respond>) + Send + Sync>,
}