了解播放过滤器API

随着Play 2.1的热销,很多人开始询问新的Play过滤器API。 实际上,API非常简单:

trait EssentialFilter {
  def apply(next: EssentialAction): EssentialAction
}

本质上,过滤器只是一个执行一个动作并返回另一个动作的函数。 过滤器通常会执行的操作是包装操作,并将其作为委托进行调用。 要将过滤器添加到应用程序中,只需将其添加到Global doFilter方法中即可。 我们提供了一个帮助类来帮助您:

object Global extends WithFilters(MyFilter) {
  ...
}

容易吧? 包装动作,在全局中进行注册。 好吧,这很容易,但前提是您了解Play架构。 这非常重要,因为一旦您了解了Play的体系结构,就可以使用Play进行更多的工作。 我们这里有一些文档,从较高的层次解释了Play的体系结构。 在这篇博客中,我将在过滤器的上下文中解释Play的体系结构,并附带代码片段和用例。

Plays架构简介

我不需要在这里进行深入介绍,因为我已经提供了指向我们的体系结构文档的链接,但是总而言之,Play的体系结构非常适合HTTP请求的流程。 发出HTTP请求时到达的第一件事是请求标头。 因此,Play中的动作必须是接受请求标头的函数。 HTTP请求中接下来会发生什么? 身体被接收。 因此,接收请求的函数必须返回消耗主体的东西。 这是一个迭代器,它是一个反应式流处理程序,在使用流后最终产生单个结果。 您不必为了了解过滤器而需要了解有关迭代操作方式的详细信息,需要了解的重要一点是,迭代最终会产生结果,您可以像使用将来那样使用其map函数进行map 。 有关编写迭代对象的详细信息,请阅读我的博客文章 。 HTTP请求中发生的下一件事是必须发送http响应。 那么iteratee的结果是什么? HTTP响应。 HTTP响应是一组响应标头,后跟一个响应正文。 响应主体是一个枚举器,它是一个反应流生成器。 所有这些都是在Plays EssentialAction特性中捕获的:

trait EssentialAction extends (RequestHeader => Iteratee[Array[Byte], Result])

这表明基本动作是一个函数,该函数采用请求标头并返回迭代器,该迭代器消耗字节数组主体块并最终产生结果。

更简单的方法

在继续之前,我想指出Play提供了一个名为Filter的辅助特性,它比使用EssentialFilter时使编写过滤器更容易。 这类似于Action特质,因为Action无需担心迭代和如何解析主体,从而简化了编写EssentialAction的过程,而只是提供了一个函数,该函数接受具有解析主体的请求,并返回结果。 Filter特质以类似的方式简化了事情,但是我将一直讲到最后,因为我认为最好在开始使用助手类之前先了解过滤器的工作原理。

Noop过滤器

为了演示过滤器的外观,我将展示的第一件事是noop过滤器:

class NoopFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(request: RequestHeader) = {
      next(request)
    }
  }
}

每次执行过滤器时,我们都会创建一个包装它的新EssentialAction 。 由于EssentialAction只是一个函数,我们可以调用它,传递传入的请求。 因此,以上是我们实现EssentialFilter基本模式。

处理请求头

假设我们要查看请求标头,然后根据我们检查的内容有条件地调用包装的操作。 可以执行此操作的过滤器示例可能是网站/admin区域的全面安全策略。 可能看起来像这样:

class AdminFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(request: RequestHeader) = {
      if (request.path.startsWith('/admin') && request.session.get('user').isEmpty) {
        Iteratee.ignore[Array[Byte]].map(_ => Results.Forbidden())
      } else {
        next(request)
      }
    }
  }
}

您可以在此处看到,由于我们是在解析正文之前拦截动作,因此在阻止动作时我们仍然需要提供一个正文解析器。 在这种情况下,我们将返回一个将忽略整个正文的正文解析器,并将其映射为禁止的结果。

处理身体

在某些情况下,您可能想对过滤器中的主体进行处理。 在某些情况下,您可能想解析正文。 如果是这种情况,请考虑改用动作合成 ,因为这样可以在动作解析正文之后挂接到动作处理。 如果要在过滤器级别解析主体,则必须对其进行缓冲,解析,然后再次对其进行流传输以使动作再次解析。 但是,有些事情可以在过滤器级别轻松完成。 一个示例是gzip解压缩。 Play框架已经提供了开箱即用的gzip解压缩功能,但是如果没有的话,它可能是这样(使用我的play extra iteratees项目中的gunzip枚举):

class GunzipFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(request: RequestHeader) = {
      if (request.headers.get('Content-Encoding').exists(_ == 'gzip')) {
        Gzip.gunzip() &>> next(request)
      } else {
        next(request)
      }
    }
  }
}

在这里,我们使用iteratee组成将人体分析器iteratee包裹在gunzip枚举对象中。

处理响应头

当您进行过滤时,您通常会希望对正在发送的响应进行处理。 如果您只想添加标题,或向会话中添加内容,或对响应进行任何写操作,而无需实际读取它,那么这很简单。 例如,假设您要向每个响应添加一个自定义标头:

class SosFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(request: RequestHeader) = {
      next(request).map(result => 
        result.withHeaders('X-Sos-Message' -> 'I'm trapped inside Play Framework please send help'))
    }
  }
}

使用处理身体的iteratee上的map函数,我们可以访问该动作产生的结果,然后可以按照演示进行修改。 但是,如果您想读取结果,则需要解开包装。 播放结果是AsyncResultPlainResult 。 一个AsyncResult是一个Result ,其中包含一个Future[Result] 。 它具有允许最终的PlainResult transform方法。 PlainResult具有标题和正文。 因此,假设您要向每个新创建的会话添加时间戳以记录创建时间。 可以这样完成:

class SessionTimestampFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(request: RequestHeader) = {

      def addTimestamp(result: PlainResult): Result = {
        val session = Session.decodeFromCookie(Cookies(result.header.headers.get(HeaderNames.COOKIE)).get(Session.COOKIE_NAME))
        if (!session.isEmpty) {
          result.withSession(session + ('timestamp' -> System.currentTimeMillis.toString))
        } else {
          result
        }
      }

      next(request).map {
        case plain: PlainResult => addTimestamp(plain)
        case async: AsyncResult => async.transform(addTimestamp)
      }
    }
  }
}

处理响应主体

您可能要做的最后一件事是转换响应主体。 PlainResult有两个实现, SimpleResult (用于没有传输编码的主体)和ChunkedResult (用于分块传输编码的主体)。 SimpleResult包含一个枚举器,而ChunkedResult包含一个接受迭代器以将结果写出的函数。 您可能要执行的操作示例是实现gzip过滤器。 一个非常幼稚的实现(例如,不要使用它,而是使用我的play iteratees项目中的完整实现),如下所示:

class GzipFilter extends EssentialFilter {
  def apply(next: EssentialAction) = new EssentialAction {
    def apply(request: RequestHeader) = {

      def gzipResult(result: PlainResult): Result = result match {
        case simple @ SimpleResult(header, content) => SimpleResult(header.copy(
          headers = (header.headers - 'Content-Length') + ('Content-Encoding' -> 'gzip')
        ), content &> Enumeratee.map(a => simple.writeable.transform(a)) &> Gzip.gzip())
      }

      next(request).map {
        case plain: PlainResult => gzipResult(plain)
        case async: AsyncResult => async.transform(gzipResult)
      }
    }
  }
}

使用更简单的API

现在,您已经了解了如何使用基本的EssentialFilter API来实现所有目标,并希望因此了解了过滤器如何适应Play的体系结构以及如何利用它们来满足您的要求。 现在让我们看一下更简单的API:

trait Filter extends EssentialFilter {
  def apply(f: RequestHeader => Result)(rh: RequestHeader): Result
  def apply(next: EssentialAction): EssentialAction = {
    ...
  }
}

object Filter {
  def apply(filter: (RequestHeader => Result, RequestHeader) => Result): Filter = new Filter {
    def apply(f: RequestHeader => Result)(rh: RequestHeader): Result = filter(f,rh)
  }
}

简而言之,该API允许您编写过滤器而不必担心正文解析器。 它看起来好像动作只是结果的请求标头的功能。 这限制了过滤器的全部功能,但是对于许多用例而言,您根本不需要此功能,因此使用此API提供了一种简单的替代方法。 为了演示,noop过滤器类如下所示:

class NoopFilter extends Filter {
  def apply(f: (RequestHeader) => Result)(rh: RequestHeader) = {
    f(rh)
  }
}

或者,使用Filter随播对象:

val noopFilter = Filter { (next, req) =>
  next(req)
}

请求计时过滤器可能如下所示:

val timingFilter = Filter { (next, req) =>
  val start = System.currentTimeMillis

  def logTime(result: PlainResult): Result = {
    Logger.info('Request took ' + (System.currentTimeMillis - start))
    result
  }

  next(req) match {
    case plain: PlainResult => logTime(plain)
    case async: AsyncResult => async.transform(logTime)
  }
}

参考: James and Beth Roper的博客博客中的JCG合作伙伴 James Roper 了解了Play Filter API

翻译自: https://www.javacodegeeks.com/2013/02/understanding-the-play-filter-api.html

在FastAPI中,可以使用拦截器(middleware)和过滤器(dependency)来处理请求和响应。它们都可以在请求到达路由处理函数之前或之后执行一些操作,但它们的作用和使用方法不太一样。 拦截器是在请求到达路由处理函数之前或之后执行的一系列操作,它们可以用于记录请求日志、身份验证、异常处理等操作。使用FastAPI的拦截器可以很方便地实现这些操作,例如: ```python from fastapi import FastAPI, Request app = FastAPI() @app.middleware("http") async def log_requests(request: Request, call_next): """ 记录请求日志的拦截器 """ print(f"Received request: {request.method} {request.url}") response = await call_next(request) print(f"Sent response: {response.status_code}") return response ``` 在这个例子中,我们定义了一个记录请求日志的拦截器,它会在每次请求到达路由处理函数之前打印请求信息,并在响应返回后打印响应信息。这个拦截器使用了FastAPI的`middleware`装饰器,它指定了拦截器的类型为`http`,表示它要处理HTTP请求。 过滤器是在请求到达路由处理函数之前执行的一系列操作,它们可以用于身份验证、请求参数校验等操作。使用FastAPI过滤器可以很方便地实现这些操作,例如: ```python from fastapi import FastAPI, Depends app = FastAPI() async def check_token(token: str): """ 检查token的依赖性 """ if token != "secret_token": raise HTTPException(status_code=401, detail="Invalid token") @app.get("/") async def read_root(token: str = Depends(check_token)): """ 需要token验证的路由处理函数 """ return {"Hello": "World"} ``` 在这个例子中,我们定义了一个需要token验证的路由处理函数,并使用了`Depends`装饰器来指定依赖性。这个依赖性函数`check_token`会在请求到达路由处理函数之前执行,它会检查请求中的token参数是否正确。如果token不正确,则会抛出一个HTTP异常,返回401错误码。 希望这些信息能够帮助您了解FastAPI中的拦截器和过滤器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值