HTTP服务器基础的路由功能
This commit is contained in:
parent
0597ace088
commit
aae863abe8
8
rust/code/HttpSevre/Cargo.toml
Normal file
8
rust/code/HttpSevre/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "temp"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.22"
|
||||
tokio = { version = "1", features = ["full"] }
|
94
rust/code/HttpSevre/src/core/mod.rs
Normal file
94
rust/code/HttpSevre/src/core/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
17
rust/code/HttpSevre/src/main.rs
Normal file
17
rust/code/HttpSevre/src/main.rs
Normal 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();
|
||||
}
|
133
rust/code/HttpSevre/src/request/mod.rs
Normal file
133
rust/code/HttpSevre/src/request/mod.rs
Normal 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
|
||||
}
|
||||
}
|
97
rust/code/HttpSevre/src/respond/mod.rs
Normal file
97
rust/code/HttpSevre/src/respond/mod.rs
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
11
rust/code/HttpSevre/src/route/mod.rs
Normal file
11
rust/code/HttpSevre/src/route/mod.rs
Normal 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>,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user