actix_web::middleware 在 Actix Web 框架中扮演偏重要的角色,它答应开发者在处理 HTTP 哀求和响应的过程中插入自定义的逻辑。中心件可以在哀求到达处理函数之前或响应返回给客户端之前实行,从而实现日志记录、身份验证、数据验证、错误处理等功能。
为什么要有中心件?
代码在处理逻辑的时候,通常只处理正常的业务逻辑,而在处理过程中,大概会遇到一些特殊的环境,好比:404错误,这种错误会让客户端的哀求无法进入到代码功能中,这时候就需要中心件来处理这些特殊的环境。
另外,还可以起到Java内里的过滤器功能,在Java内里,过滤器可以在哀求到达处理函数之前或响应返回给客户端之前实行,从而实现对哀求和响应的预处理和后处理。
在Rust开发中,特殊是在使用Actix Web框架时,中心件(middleware)是一种非常有用的计划模式,它答应开发者在处理HTTP哀求和响应的过程中插入自定义的逻辑。以下是使用中心件的几个主要原因:
- 代码模块化和可重用性:中心件可以将通用的功能(如日志记录、身份验证、数据验证等)封装起来,使得这些功能可以被多个处理函数共享和重用。这样可以减少代码重复,提高代码的可读性和可维护性。
- 机动性和可扩展性:通过使用中心件,开发者可以机动地组合和配置不同的功能,以满足应用步伐的特定需求。例如,可以根据不同的路由或用户角色应用不同的中心件。
- 哀求和响应的预处理和后处理:中心件可以在哀求到达处理函数之前或响应返回给客户端之前实行,从而实现对哀求和响应的预处理和后处理。例如,可以在哀求到达之前记录日志、验证用户身份,大概在响应返回之前压缩数据、添加额外的头部信息等。
- 提高开发效率:使用中心件可以减少开发者编写重复代码的工作量,使得开发者可以更专注于业务逻辑的实现。同时,中心件的模块化计划也使得代码的测试和调试更加容易。
- 支持异步编程模子:Actix Web是一个基于异步IO的Web框架,中心件可以很好地适应这种编程模子,答应开发者在不阻塞主线程的环境下实行耗时的操作。
以下是一些常见的中心件及其作用:
- 日志记录:记录每个哀求的详细信息,如哀求方法、路径、时间戳等,有助于调试和监控。
- 身份验证:验证用户的身份,确保只有经过授权的用户才能访问特定的资源或实行特定的操作。
- 数据验证:在哀求到达处理函数之前,验证哀求数据的格式和内容是否符合预期,防止无效或恶意数据进入系统。
- 错误处理:统一处理应用步伐中的错误,提供友好的错误信息给客户端,同时记录错误日志以便后续分析。
- 性能监控:丈量每个哀求的处理时间,帮助开发者辨认性能瓶颈并进行优化。
- 跨域资源共享(CORS):处理跨域哀求,答应或拒绝来自不同域的哀求,确保安全的跨域数据交互。
- 使用中心件可以使代码更加模块化和可重用,开发者可以根据需要组合和配置不同的中心件,以满足应用步伐的特定需求。在 Actix Web 中,中心件通常通过 App::wrap 方法进行注册和应用。
自定义中心件的过程:
使用闭包实现简朴的中心件
例如我要定义一个预先获取哀求中header内里的token,假如没有这个token则直接就返回错误。代码可以这样写:
- use actix_web::dev::Service;
- use actix_web::error;
- use actix_web::{middleware::{self, Logger}, web, App, HttpServer};
- #[actix_web::main]
- async fn main() -> std::io::Result<()> {
- HttpServer::new(move || {
- App::new()
- .wrap(middleware::Logger::default())
- .wrap_fn(|req, srv| {
- let header = req.headers().to_owned();
- let fut = srv.call(req);
- async move {
- let res = fut.await;
- match header.get("token"){
- Some(token) => {
- println!("token:{}", token.to_str().unwrap());
- },
- None => {
- println!("token is None");
- return Err(error::ErrorUnauthorized("Unauthorized"));
- },
- }
- res
- }
- })
- .route("/", web::get().to(|| async { "Hello, World!" }))
- })
- .bind("127.0.0.1:8080")?
- .run()
- .await
- }
复制代码 这里的核心,是使用wrap_fn + 一个闭包来实现中心件。
在示例代码中,wrap_fn 被用来创建一个中心件,这个中心件查抄哀求头中是否包含 token。假如没有 token,它会返回一个未经授权的错误。假如有 token,它会调用下一个服务并返回其结果。
warp_fn的源码如下(看不懂也不要紧,不消懂,我写这里是为了你以后能看懂时候返来查资料用的)
- pub fn wrap_fn<F, Fut>(f: F) -> impl Transform<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, InitError = ()>
- where
- F: Fn(ServiceRequest, &mut dyn Service<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, Future = Fut>) -> Fut,
- Fut: Future<Output = Result<ServiceResponse<Fut::Item>, Fut::Error>> + 'static,
复制代码 内里参数解释如下:
- 输入参数:
- F: 这是一个函数范例,它担当两个参数:ServiceRequest 和一个可变引用 &mut dyn Service<...>。这个函数将返回一个 Fut 范例的 Future。
- Fut: 这是一个 Future 范例,它的输出是 Result<ServiceResponseFut::Item, Fut::Error>。Fut::Item 是响应体的范例,Fut::Error 是错误范例。
- 返回值:
- impl Transform<ServiceRequest, Response = ServiceResponseFut::Item, Error = Fut::Error, InitError = ()>: 这是一个实现了 Transform trait 的范例,它可以转换 ServiceRequest 到 ServiceResponseFut::Item,并且可以处理 Fut::Error 范例的错误。
实行结果如下:
假如是一些发起的逻辑,我们用wrap_fn + 闭包就可以了,但是假如是一些复杂的逻辑,就需要自己实现Transform trait了。
重新实现Transform trait 来实现自定义的中心件
- 中心件初始化:在这个阶段,中心件工厂函数被调用,它接收链中的下一个服务作为参数。这答应中心件在现实处理哀求之前进行任何须要的设置或配置。
- 中心件工厂类是 Transform trait的。
- S - 后续服务的范例:S 代表链中下一个服务的范例,即当前中心件将哀求转达给哪个服务。
- B - 响应体的范例:B 代表响应体(response)的范例,指定了从服务返回的响应内容的格式。
- //中间的工厂类
- pub struct Auth;
- impl<S, B> Transform<S, ServiceRequest> for Auth
- where
- S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
- S::Future: 'static,
- B: 'static,
- {
- type Response = ServiceResponse<B>;
- type Error = Error;
- type InitError = ();
- type Transform = AuthMiddleware<S>;
- type Future = Ready<Result<Self::Transform, Self::InitError>>;
- fn new_transform(&self, service: S) -> Self::Future {
- ready(Ok(AuthMiddleware { service }))
- }
- }
复制代码
- 编写中心件的具体实现,核心是中心件的 call 方法调用:一旦中心件初始化完成,每当有新的哀求到来时,中心件的 call 方法就会被调用,并接收这个普通哀求作为参数。此时,中心件可以对哀求进行处理
- // 中间件的具体实现,里面需要接受工厂类里面过来的service
- pub struct AuthMiddleware<S> {
- service: S,
- }
- //具体实现
- //核心是两个方法:
- // call 具体实现
- // poll_ready
- impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
- where
- S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
- S::Future: 'static,
- B: 'static,
- {
- type Response = ServiceResponse<B>;
- type Error = Error;
- type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
- // 实现 poll_ready 方法,用于检查服务是否准备好处理请求
- //这里用的是forward_ready!宏
- forward_ready!(service);
- // 实现 call 方法,用于处理实际的请求
- fn call(&self, req: ServiceRequest) -> Self::Future {
- // 进行鉴权操作,判断是否有权限
- if has_permission(&req) {
- // 有权限,继续执行后续中间件
- let fut = self.service.call(req);
- Box::pin(async move {
- let res = fut.await?;
- Ok(res)
- })
- } else {
- // 没有权限,立即返回响应
- Box::pin(async move {
- // 鉴权失败,返回未授权的响应,停止后续中间件的调用
- Err(error::ErrorUnauthorized("Unauthorized"))
- })
- }
- }
- }
- fn has_permission(req: &ServiceRequest) -> bool {
- // 实现你的鉴权逻辑,根据需求判断是否有权限
- // 返回 true 表示有权限,返回 false 表示没有权限
- // unimplemented!()
- let value = HeaderValue::from_str("").unwrap();
- match req.path().to_ascii_lowercase().as_str(){
- "/login" => true,
- _ => {
- let token = req.headers().get("token").unwrap_or(&value);
- if token.len() <=0{
- false
- }else{
- println!("验证一下token,看看是否合法");
- true
- }
- }
- }
- }
复制代码 核心方法说明:
- poll_ready: 这个方法用于查抄服务是否准备利益理哀求。 它返回一个Poll范例的结果,表示服务是否停当。 假如服务已经停当,返回Poll::Ready(Ok(()))。 假如服务尚未停当,返回Poll:ending,表示需要等待一段时间后再次查抄。 在middleware中,poll_ready方法通常用于确保在处理哀求之前,所有依赖的资源或服务都已经准备停当。
- call: 这个方法用于处理现实的哀求。 它接收一个ServiceRequest范例的参数,并返回一个ServiceResponse范例的结果。 在middleware中,call方法通常用于对哀求进行预处理或后处理,例如添加日志记录、验证哀求、修改响应等。 call方法的返回值是一个Future,表示异步处理的结果。
所以也可以自己实现poll_ready
- fn poll_ready(&self, ctx: &mut core::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
- if self.service.poll_ready(ctx).is_pending() {
- // 如果服务尚未准备好,返回Pending
- return std::task::Poll::Pending;
- }
- // 如果服务已准备好,返回Ready(Ok(()))
- std::task::Poll::Ready(Ok(()))
- }
复制代码 使用中心件
- #[actix_web::main]
- async fn main() -> std::io::Result<()> {
- //中间件的顺序是从下到上的,最后注册的中间件会最先执行
- HttpServer::new(move || {
- App::new()
- .wrap(middleware::Logger::default())
- .wrap(Auth::Auth)
- // 注册其他路由和处理函数
- .route("/", web::get().to(|| async { "Hello, World!" }))
- .route("/login", web::get().to(|| async { "Hello, login" }))
- })
- .bind("127.0.0.1:8080")?
- .run()
- .await
- }
复制代码 实行结果如下:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |