Http Route(异步处理器)

type Route = RequestContext => Future[RouteResult]

Fusion基于Akka HTTP实现了HTTP 1.0、1.1和2.0协议的请求响应处理,同时还支持GRPC。RouteRequestContext => Future[RouteResult]的类型定义,从接收到一个请求开始,返回一个异步的HTTP响应。原始的HttpRequest被包裹在一个RequestContext请求上下文中,而响应结果也使用了RouteResult进行封装。

RequestContext

RequestContext的一个典型实现如下:

trait RequestContext {
  val request: HttpRequest
  val unmatchedPath: Uri.Path
  implicit def executionContext: ExecutionContextExecutor
  implicit def materializer: Materializer
  def log: LoggingAdapter
  def settings: RoutingSettings
  def parserSettings: ParserSettings
  def reconfigure(
    executionContext: ExecutionContextExecutor = executionContext,
    materializer:     Materializer             = materializer,
    log:              LoggingAdapter           = log,
    settings:         RoutingSettings          = settings): RequestContext
  def complete(obj: ToResponseMarshallable): Future[RouteResult]
  def reject(rejections: Rejection*): Future[RouteResult]
  def redirect(uri: Uri, redirectionType: Redirection): Future[RouteResult]
  def fail(error: Throwable): Future[RouteResult]
  // ....
}

request

保存了原始的HttpRequest对象。

unmatchedPath

保存了当前Akka HTTP Routing层级下还未匹配的路径。如下面的路由定义,在account这一级,请求Uri.Path: /api/v4/account/user/page对应的unmatchedPath保持的未匹配路径为:/user/page

complete

请求正常完成时,返回数据通过complete函数来响应。complete函数通过ToResponseMarshallable来决定数据类型该怎么处理(序列化),Akka HTTP默认已提供了大部分数据类型的处理方式,我们可以很方便的对自定义数据实现ToResponseMarshallable。对应JSON类型的数据响应,https://github.com/hseeberger/akka-http-json 是一个很好的起点。

reject

reject顾名思义,调用它将拒绝当前请求。它需要传一个或多个实现了Rejection接口的类型。akka-fusion默认的reject处理如下:

def rejectionBuilder: Builder =
  RejectionHandler
    .newBuilder()
    .handle {
      case MissingQueryParamRejection(parameterName) =>
        complete(jsonEntity(BadRequest, s"请求参数 '$parameterName' 缺失"))

      case MissingCookieRejection(cookieName) =>
        val msg = s"无效的Cookie: $cookieName"
        logger.info(msg)
        complete(jsonEntity(BadRequest, msg))

      case ForbiddenRejection(message, cause) =>
        val msg = s"权限禁止:$message"
        logger.warn(msg, cause.orNull)
        complete(jsonEntity(Forbidden, message))

      case SessionRejection(message, cause) =>
        val msg = s"会话认证失败:$message"
        logger.warn(msg, cause.orNull)
        complete(jsonEntity(Unauthorized, msg))

      case AuthorizationFailedRejection =>
        val msg = "会话认证失败"
        logger.warn(msg)
        complete(jsonEntity(Unauthorized, msg))

      case ValidationRejection(err, _) =>
        val msg = "数据校验失败: " + err
        logger.info(msg)
        complete(jsonEntity(BadRequest, msg))
    }
    .handleAll[MethodRejection] { methodRejections =>
      val description = methodRejections.map(_.supported.name).mkString(" or ")
      val msg = s"不支持的方法!当前支持:$description!"
      logger.info(msg)
      complete(jsonEntity(MethodNotAllowed, msg))
    }
    .handleNotFound {
      extractUri { uri =>
        val msg = s"URI: $uri 路径未找到!"
        logger.info(msg)
        complete(jsonEntity(NotFound, msg))
      }
    }
    .handle {
      case rejection =>
        logger.info(rejection.toString)
        complete(jsonEntity(BadRequest, rejection.toString))
    }

redirect

redirect在你需要向请求方返回重定向响应时使用。它有两个参数:

  1. uri: Uri:需要生定向的地址
  2. redirectionType: Redirection:HTTP响应状态码,现在可选择的状态码有300到308中的一个

fail

返回异常的快捷函数,大多数情况下不需要使用到这个。

RouteResult

akka.http.scaladsl.server.RouteResult接口有两个子类(定义类似):

final case class Complete(response: HttpResponse) extends RouteResult
final case class Rejected(rejections: Seq[Rejection]) extends RouteResult

Complete很直观了,代表请求已完成(HTTP请求已完成,但业务有可能错误)。

Rejected代表请求被拒绝,一般用于HTTP方法不对、URL路径未找到、请求实体过大等情况。