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()) 
 
}

 

 

 

 

 

 

 

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/12830.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论