| use std::{net::SocketAddr, str}; |
| |
| use http_body_util::{combinators::BoxBody, BodyExt}; |
| use hyper::{ |
| body::{Bytes, Incoming}, |
| header::{ |
| HeaderValue, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, |
| ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, |
| ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, REFERER, |
| }, |
| server::conn::http1, |
| service::service_fn, |
| HeaderMap, Method, Request, Response, StatusCode, |
| }; |
| use hyper_util::rt::TokioIo; |
| use log::{error, info}; |
| use tokio::{ |
| io, |
| net::{TcpListener, TcpStream}, |
| }; |
| |
| |
| use crate::{ |
| init::{self, config::Config, constant::AUTHORIZATION, nacos}, |
| util::jwt, |
| }; |
| |
| pub async fn init_hyper() -> io::Result<()> { |
| |
| let server_ip = Config::global().server_ip(); |
| let server_port = Config::global().server_port(); |
| |
| let addr: SocketAddr = format!("{}:{}", server_ip, server_port).parse().unwrap(); |
| |
| let listener = TcpListener::bind(addr).await?; |
| info!("service listening on {}", addr); |
| |
| let http = http1::Builder::new(); |
| |
| let graceful = hyper_util::server::graceful::GracefulShutdown::new(); |
| let mut signal = std::pin::pin!(shutdown_signal()); |
| loop { |
| |
| tokio::select! { |
| Ok((stream, _addr)) = listener.accept() => { |
| let io = TokioIo::new(stream); |
| let conn = http.serve_connection(io, service_fn(handle_proxy)); |
| let fut = graceful.watch(conn); |
| tokio::spawn(async move { |
| if let Err(err) = fut.await { |
| error!("Error input connection: {}", err); |
| } |
| }); |
| }, |
| _ = &mut signal => { |
| info!("graceful shutdown signal received"); |
| |
| break; |
| } |
| } |
| } |
| |
| tokio::select! { |
| _ = graceful.shutdown() => { |
| info!("all connections gracefully closed"); |
| }, |
| _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => { |
| error!("timed out wait for all connections to close"); |
| } |
| } |
| Ok(()) |
| } |
| |
| |
| async fn handle_proxy( |
| req: Request<hyper::body::Incoming>, |
| ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { |
| |
| if req.method() == Method::OPTIONS { |
| return return_options(req.headers()); |
| } |
| if req.uri().path().contains("login") || req.uri().path().contains("app/auth") { |
| proxy_to_service(req).await |
| } else if req.headers().contains_key(AUTHORIZATION) { |
| if is_valid_token(req.headers()) { |
| proxy_to_service(req).await |
| } else { |
| return_forbidden() |
| } |
| } else { |
| |
| return_forbidden() |
| } |
| } |
| |
| fn is_valid_token(headers: &HeaderMap<HeaderValue>) -> bool { |
| let token = headers.get(AUTHORIZATION); |
| if token.is_none() { |
| return false; |
| } |
| let token = token.unwrap().to_str().unwrap(); |
| is_valid_user_token(token) || is_valid_app_token(token) |
| } |
| |
| fn is_valid_app_token(token: &str, ip: &str) -> bool { |
| ture |
| } |
| |
| fn is_valid_user_token(token: &str) -> bool { |
| ture |
| } |
| |
| |
| async fn proxy_to_service( |
| mut req: Request<Incoming>, |
| ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { |
| let req_server_name = req.uri().path().split("/").collect::<Vec<&str>>()[1]; |
| let service_addr: String; |
| |
| |
| |
| |
| |
| service_addr = nacos::select_service(req_server_name).await; |
| if service_addr.is_empty() { |
| return return_not_found(&req.headers()); |
| } |
| |
| let req_header = req.headers().clone(); |
| info!( |
| "request uri : {} with X-Custom-Version {:?}", |
| req.uri(), |
| req_header.get("X-Custom-Version") |
| ); |
| *req.uri_mut() = req |
| .uri() |
| .to_string() |
| .split(req_server_name) |
| .collect::<Vec<&str>>()[1] |
| .parse() |
| .unwrap(); |
| let uri = req.uri().clone(); |
| match TcpStream::connect(service_addr).await { |
| Ok(stream) => { |
| let io = TokioIo::new(stream); |
| match hyper::client::conn::http1::handshake(io).await { |
| Ok((mut sender, conn)) => { |
| tokio::task::spawn(async move { |
| if let Err(err) = conn.await { |
| error!("Connection failed: {}", err); |
| } |
| }); |
| match sender.send_request(req).await { |
| Ok(mut res) => { |
| insert_cors_header(&req_header, res.headers_mut()); |
| Ok(res.map(|b| b.boxed())) |
| } |
| Err(err) => { |
| error!("Error proxy sending request: {} when request {}", err, uri); |
| return_bad_gateway(&req_header) |
| } |
| } |
| } |
| Err(err) => { |
| error!("Error proxy handshanke: {} when request {}", err, uri); |
| return_bad_gateway(&req_header) |
| } |
| } |
| } |
| Err(err) => { |
| error!("Error proxy connection :{} when request {}", err, uri); |
| return_bad_gateway(&req_header) |
| } |
| } |
| } |
| |
| fn insert_cors_header(request_header: &HeaderMap, response_header: &mut HeaderMap) { |
| match request_header.get(ORIGIN) { |
| Some(origin) => { |
| response_header.insert(ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone()); |
| response_header.insert( |
| ACCESS_CONTROL_ALLOW_CREDENTIALS, |
| HeaderValue::from_str("true").unwrap(), |
| ); |
| } |
| None => (), |
| }; |
| } |
| |
| fn return_options( |
| request_header: &HeaderMap, |
| ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { |
| let mut response = Response::new(BoxBody::default()); |
| *response.status_mut() = StatusCode::NO_CONTENT; |
| insert_cors_header(request_header, response.headers_mut()); |
| match request_header.get(ACCESS_CONTROL_REQUEST_METHOD) { |
| None => (), |
| Some(method) => { |
| response |
| .headers_mut() |
| .insert(ACCESS_CONTROL_ALLOW_METHODS, method.clone()); |
| } |
| }; |
| match request_header.get(ACCESS_CONTROL_REQUEST_HEADERS) { |
| None => (), |
| Some(method) => { |
| response |
| .headers_mut() |
| .insert(ACCESS_CONTROL_ALLOW_HEADERS, method.clone()); |
| } |
| }; |
| response |
| .headers_mut() |
| .insert(ACCESS_CONTROL_MAX_AGE, HeaderValue::from(3600)); |
| Ok(response) |
| } |
| |
| fn return_forbidden(request_header: &HeaderMap,) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { |
| let mut response = Response::new(BoxBody::default()); |
| *response.status_mut() = StatusCode::FORBIDDEN; |
| insert_cors_header(request_header, response.headers_mut()); |
| Ok(response) |
| } |
| |
| fn return_bad_gateway(request_header: &HeaderMap,) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { |
| let mut response = Response::new(BoxBody::default()); |
| *response.status_mut() = StatusCode::BAD_GATEWAY; |
| insert_cors_header(request_header, response.headers_mut()); |
| Ok(response) |
| } |
| |
| fn return_not_found(request_header: &HeaderMap,) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { |
| let mut response = Response::new(BoxBody::default()); |
| *response.status_mut() = StatusCode::NOT_FOUND; |
| insert_cors_header(request_header, response.headers_mut()); |
| Ok(response) |
| } |
| |
| |
| async fn shutdown_signal() { |
| |
| tokio::signal::ctrl_c() |
| .await |
| .expect("failed to install CTRL+C signal handler"); |
| } |
| |
| |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇