spring boot迁移计划 第Ⅰ章 --chapter 1. rust hyper 结合rust nacos-client开发nacos网关 part ② hyper网关

1. toml依赖

hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }

2. 代码

2025-02-17更新:更新跨域设置;
2025-01-24更新:更新跨域设置,符合RFC标准,参考spring boot的处理方式;
2025-01-23更新:新增跨域设置;
2025-01-17更新:全局服务静态缓存添加,转发至实际请求微服务地址可用,由于nacos不广播服务权重变化,缓存的使用方法还在考虑,有人有好的想法可以留言告知,感谢;
题外话:在写代码的过程中发现每个函数的参数是否可变原来不具有继承性,比如下面的代码是可以运行的, retuen_new_user函数的参数user是不可变的,但是可以将它直接传递给一个要求可变参数的函数,还真是奇怪嘿XD

pub fn retuen_new_user(user: User) -> User {
     handle(user)
}
pub fn handle(mut user: User)-> User {
    user.id = "11111".to_string;
    user
}

2025-01-02更新:部分功能代码未完成后续完成后更新
以下代码参考hyper的官方example文件夹下的内容编写

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},
};
//由于nacos不广播权重配置的更改,而且nacos内置了raft和负载均衡算法所以改为每次去注册中心请求一个服务地址
//use crate::init::nacos::NAMING_MAP;
use crate::{
    init::{self, config::Config, constant::AUTHORIZATION, nacos},
    util::jwt,
};

pub async fn init_hyper() -> io::Result<()> {
    //Config::global()获取一个LazyLock全局静态变量,仅支持rust 1.8.0以上版本
    let server_ip = Config::global().server_ip();
    let server_port = Config::global().server_port();
    //创建soket
    let addr: SocketAddr = format!("{}:{}", server_ip, server_port).parse().unwrap();
    //创建监听器
    let listener = TcpListener::bind(addr).await?;
    info!("service listening on {}", addr);
    //创建http监听
    let http = http1::Builder::new();
    //调用hyper的关闭监听
    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");
                // stop the accept loop
                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 {
        //鉴权不通过统统403
        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;
    // let map = NAMING_MAP.load();
    // if map.contains_key(req_server_name) {
    //     let instances_list = map.get(req_server_name).expect("get server addr error");
    //     service_addr = instances_list[0].ip.clone() + ":" + &instances_list[0].port.to_string();
    // } else {
    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)
}

//优雅的关闭tokio运行时任务
async fn shutdown_signal() {
    // Wait for the CTRL+C signal
    tokio::signal::ctrl_c()
        .await
        .expect("failed to install CTRL+C signal handler");
}


posted @ 2024-12-31 21:59  JiajieZeee  阅读(57)  评论(0)    收藏  举报