专栏首页函数式编程语言及工具Akka(31): Http:High-Level-Api,Route rejection handling

Akka(31): Http:High-Level-Api,Route rejection handling

   Route 是Akka-http routing DSL的核心部分,使用户能比较方便的从http-server的角度筛选http-request、进行server运算、构建回复的http-response。所谓筛选http-request主要目的是容许request进入下一内层Route,或者拒绝reject request。比如这个~符号:它连接了上下两个独立的Route。如果上面的Route拒绝了一个request,那么下面的Route就会接着尝试这个request。一般来说:当一个筛选功能的Directive如get遇到一个不符合筛选条件的request时,它会拒绝reject这个request进入下一层Route。这时用~符号链接的下一组Route会接着尝试,直到链条最后一组Route。整个过程中的这些rejection事件会被记录下来最后由某个隐式或明式的RejectionHandler实例把这组rejection转化成HttpResponse返回用户。rejection也可以直接调用requestContext.reject(...)产生。Akka-http是通过在运行Route时用Route.seal(route)的方式来确保所有rejection在最终都会得到处理:

  override def seal(system: ActorSystem, materializer: Materializer): Route = {
    implicit val s = system
    implicit val m = materializer

    RouteAdapter(scaladsl.server.Route.seal(delegate))
  }

下面是Route.seal()函数定义:

  /**
   * "Seals" a route by wrapping it with default exception handling and rejection conversion.
   *
   * A sealed route has these properties:
   *  - The result of the route will always be a complete response, i.e. the result of the future is a
   *    ``Success(RouteResult.Complete(response))``, never a failed future and never a rejected route. These
   *    will be already be handled using the implicitly given [[RejectionHandler]] and [[ExceptionHandler]] (or
   *    the default handlers if none are given or can be found implicitly).
   *  - Consequently, no route alternatives will be tried that were combined with this route
   *    using the ``~`` on routes or the [[Directive.|]] operator on directives.
   */
  def seal(route: Route)(implicit
    routingSettings: RoutingSettings,
                         parserSettings:   ParserSettings   = null,
                         rejectionHandler: RejectionHandler = RejectionHandler.default,
                         exceptionHandler: ExceptionHandler = null): Route = {
    import directives.ExecutionDirectives._
    // optimized as this is the root handler for all akka-http applications
    (handleExceptions(ExceptionHandler.seal(exceptionHandler)) & handleRejections(rejectionHandler.seal))
      .tapply(_ ⇒ route) // execute above directives eagerly, avoiding useless laziness of Directive.addByNameNullaryApply
  }

RejectionHandler.default是Akka-http提供的默认handler。我们也可以把自定义的隐式RejectionHandler实例放在可视域内就会自动被调用了。下面是一个自定义RejectionHandler例子:

      RejectionHandler.newBuilder()
        .handle { case MissingCookieRejection(cookieName) =>
          complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
        }
        .handle { case AuthorizationFailedRejection =>
          complete((Forbidden, "You're out of your depth!"))
        }
        .handle { case ValidationRejection(msg, _) =>
          complete((InternalServerError, "That wasn't valid! " + msg))
        }
        .handleAll[MethodRejection] { methodRejections =>
          val names = methodRejections.map(_.supported.name)
          complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
        }
        .handleNotFound { complete((NotFound, "Not here!")) }
        .result()

所有Rejection类型都在Rejection.scala里定义。result()函数返回Rejection类型:

   def result(): RejectionHandler =
      new BuiltRejectionHandler(cases.result(), notFound, isDefault)

我们也可以用mapRejetionResponse对现成handler中产生的HttpResponse进行转换: 

      RejectionHandler.default
        .mapRejectionResponse {
          case res @ HttpResponse(_, _, ent: HttpEntity.Strict, _) =>
            // since all Akka default rejection responses are Strict this will handle all rejections
            val message = ent.data.utf8String.replaceAll("\"", """\"""")
            
            // we copy the response in order to keep all headers and status code, wrapping the message as hand rolled JSON
            // you could the entity using your favourite marshalling library (e.g. spray json or anything else) 
            res.copy(entity = HttpEntity(ContentTypes.`application/json`, s"""{"rejection": "$message"}"""))
            
          case x => x // pass through all other types of responses
        }

下面是一个比较全面的RejectionHandle应用示范:

akka.actor._
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import akka.http.scaladsl.server.Directives._
import akka.stream._
import akka.stream.scaladsl._
import akka._
import StatusCodes._
import scala.concurrent._

object RejectionHandlers {
  implicit val rejectionHandler =
    (RejectionHandler.newBuilder()
      .handle { case MissingCookieRejection(cookieName) =>
        complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
      }
      .handle { case AuthorizationFailedRejection =>
        complete((Forbidden, "You're out of your depth!"))
      }
      .handle { case ValidationRejection(msg, _) =>
        complete((InternalServerError, "That wasn't valid! " + msg))
      }
      .handleAll[MethodRejection] { methodRejections =>
      val names = methodRejections.map(_.supported.name)
      complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
      }
      .handleNotFound {
        extractUnmatchedPath { p =>
          complete((NotFound, s"The path you requested [${p}] does not exist."))
        }
      }
      .result())
      .mapRejectionResponse {
        case res @ HttpResponse(_, _, ent: HttpEntity.Strict, _) =>
          // since all Akka default rejection responses are Strict this will handle all rejections
          val message = ent.data.utf8String.replaceAll("\"", """\"""")

          // we copy the response in order to keep all headers and status code, wrapping the message as hand rolled JSON
          // you could the entity using your favourite marshalling library (e.g. spray json or anything else)
          res.copy(entity = HttpEntity(ContentTypes.`application/json`, s"""{"rejection mapped response": "$message"}"""))

        case x => x // pass through all other types of responses
      }
}

object RejectionHandlerDemo extends App {
  import RejectionHandlers._

  implicit val httpSys = ActorSystem("httpSys")
  implicit val httpMat = ActorMaterializer()
  implicit val httpEc = httpSys.dispatcher

  val (port, host) = (8011,"localhost")

  val route: Flow[HttpRequest, HttpResponse, NotUsed] = pathPrefix("hello" ~ PathEnd) {
    get {
      complete {Future("OK")}
      //HttpEntity(ContentTypes.`text/html(UTF-8)`,"<h1> hello http server </h1>")}
    }
  }

  val bindingFuture: Future[Http.ServerBinding] = Http().bindAndHandle(route,host,port)

  println(s"Server running at $host $port. Press any key to exit ...")

  scala.io.StdIn.readLine()

  bindingFuture.flatMap(_.unbind())
    .onComplete(_ => httpSys.terminate())

}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Akka(41): Http:DBTable-rows streaming - 数据库表行交换

      在前面一篇讨论里我们介绍了通过http进行文件的交换。因为文件内容是以一堆bytes来表示的,而http消息的数据部分也是byte类型的,所以我们可以直接用...

    用户1150956
  • PICE(4):MongoDBStreaming - gRPC Protobuf conversion

       前两篇我们介绍了JDBC和Cassandra的gRPC streaming实现。相对MongoDB来说,JDBC和Cassandra支持字符类型的quer...

    用户1150956
  • Akka-CQRS(12)- akka-http for http-web-service: Routing-服务项目接口

    上篇提到,按当前对web-service功能需要,我们需要完成数据转换marshalling,服务接口routing这两部分的调研和示范。上篇已经完成了对...

    用户1150956
  • 【BUF大事件】世界杯结束,来看幕后的安全隐患;快压病毒事件后续进展

    本周BUF大事件还是为大家带来了新鲜有趣的安全新闻,下周三,由FreeBuf主办的2018 SOC & EDR应用建设高峰论坛将在深圳召开,你要来么?世界杯结束...

    FB客服
  • SQL中CASE表达式的妙用

    case 表达式分为搜索表达式和简单表达式,由于搜索表达式包含了简单表达式的所有用法,此处仅介绍搜索表达式的用法。

    披头
  • Android ListView中headerview的动态显示和隐藏的实现方法

    将header的布局写在list item的布局文件中,在adapter中通过判断position的值是否为0动态控制其显示或隐藏。

    砸漏
  • 003.etcd集群部署-静态发现

    静态启动etcd集群要求每个成员都知道集群中的另一个成员。Etcd运行在集群的每个coreos节点上,可以保证coreos集群的稳定,可靠的运行。当集群网络出现...

    木二
  • asp.net mvc ChildActionOnly 和ActionName的用法

    1.ChildActionOnly的目的主要就是让这个(方法)Action不通过直接在地址栏输入地址来访问也即不通过form表单与ajax的url来访问,而是需...

    wfaceboss
  • mysql主从复制精简模式配置 原

    默认主从会把所有字段记录下来,包含所有未修改的和修改,这样对于一些大表就会占用很大的空间,mysql5.7后支持只记录修改的字段,

    尚浩宇
  • 气象编程|利用Python对夏季降水和同期大西洋海温进行SVD分析

    链接 | https://www.jianshu.com/p/354b7cd158ec

    zhangqibot

扫码关注云+社区

领取腾讯云代金券