使用自定义 requireParam() 指令的 Akka-HTTP 编译错误
Akka-HTTP compilation error using custom requireParam() directive
我开发了自定义通用指令,它将提供给定类型的参数(如果存在),否则会因我的自定义异常而拒绝。
import akka.http.scaladsl.common.NameReceptacle
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamDefAux
import akka.http.scaladsl.server.{Directive1, Route}
class MyCustomException(msg: String) extends Exception(msg)
def requireParam[T](name: NameReceptacle[T])
(implicit pdef: ParamDefAux[NameReceptacle[T], Directive1[T]]): Directive1[T] =
parameter(name).recover { _ =>
throw new MyCustomException(s"${name.name} is missed!")
}
它工作正常,如果我想创建路由,使用两个参数,例如:
val negSumParams: Route =
(requireParam("param1".as[Int]) & requireParam("param2".as[Int])) {
(param1, param2) =>
complete((-param1-param2).toString)
}
但是如果我尝试只使用一个参数,则无法编译:
val negParamCompilationFail: Route =
requireParam("param".as[Int]) {
param => // scalac complains about missing type param here
complete((-param).toString)
}
如果我将它与 pass
指令一起使用,它会起作用:
val negParamWithPass: Route =
(pass & requireParam("param".as[Int])) { // this pass usage looks hacky
param =>
complete((-param).toString)
}
如果我明确地写 requireParam()
return 类型,它也有效:
val negParamWithExplicitType: Route =
(requireParam("param".as[Int]): Directive1[Int]) { // DRY violation
param =>
complete((-param).toString)
}
为什么我需要这些技巧?为什么它不能仅与 requireParam("param".as[Int])
一起使用?
Scala 版本 2.12.1,Akka-HTTP 10.0.10。
此错误是由于指令伴随对象应用方法造成的。 IT 允许从带有参数 (T => Route) => Route:
的函数创建一个 Route
object Directive {
/**
* Constructs a directive from a function literal.
*/
def apply[T: Tuple](f: (T ⇒ Route) ⇒ Route): Directive[T] =
new Directive[T] { def tapply(inner: T ⇒ Route) = f(inner) }
}
但是T参数必须是元组。在您的情况下,编译器无法构建 Route。您的 requireParam("param".as[Int]) returns a Directive1[Int] 因此 apply 方法不起作用,因为 Int 不是元组。
要完成这项工作,您应该直接使用 tapply 方法:
(requireParam("param1".as[Int])).tapply((param1) =>
complete((-param1._1).toString))
和
val negSumParams2: Route =
(requireParam("param1".as[Int]) & requireParam("param2".as[Int])).tapply {
case (param1, param2) =>
complete((-param1-param2).toString)
}
所以似乎每个指令都试图将其参数转换为 TupleX。例如:
path("order" / IntNumber) returns 一个 Directive[Tuple1[Int]] 而不是 Directive1[Int]。在你的情况下 requireParam("param1".as[Int]) returns Directive1[Int]
也许有更好的解决办法,避免tapply
Lightbend 的 Johannes 在这里回答了这个问题:https://groups.google.com/forum/#!topic/akka-user/NmQvcrz5sJg答案是:
You discovered one of the reasons for the magnet pattern. If you use
requireParam("param".as[Int]) { abc => ... }
then the { abc => }
block is mistaken as the implicit argument of requireParam
. So,
either you are ok with that and require users to use extra parentheses
((requireParam("param".as[Int])) { abc => ... }
) or you use the
magnet pattern at this level as well. You can read about the magnet
pattern on the old spray blog
(http://spray.io/blog/2012-12-13-the-magnet-pattern/) or just look
into akka-http sources.
A better way to implement the feature would be just to use the
existing implementation ;) and install a custom rejection handler that
produces whatever output you would like. See
https://doc.akka.io/docs/akka-http/10.0.10/scala/http/routing-dsl/rejections.html#customizing-rejection-handling
for how to do that.
我开发了自定义通用指令,它将提供给定类型的参数(如果存在),否则会因我的自定义异常而拒绝。
import akka.http.scaladsl.common.NameReceptacle
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamDefAux
import akka.http.scaladsl.server.{Directive1, Route}
class MyCustomException(msg: String) extends Exception(msg)
def requireParam[T](name: NameReceptacle[T])
(implicit pdef: ParamDefAux[NameReceptacle[T], Directive1[T]]): Directive1[T] =
parameter(name).recover { _ =>
throw new MyCustomException(s"${name.name} is missed!")
}
它工作正常,如果我想创建路由,使用两个参数,例如:
val negSumParams: Route =
(requireParam("param1".as[Int]) & requireParam("param2".as[Int])) {
(param1, param2) =>
complete((-param1-param2).toString)
}
但是如果我尝试只使用一个参数,则无法编译:
val negParamCompilationFail: Route =
requireParam("param".as[Int]) {
param => // scalac complains about missing type param here
complete((-param).toString)
}
如果我将它与 pass
指令一起使用,它会起作用:
val negParamWithPass: Route =
(pass & requireParam("param".as[Int])) { // this pass usage looks hacky
param =>
complete((-param).toString)
}
如果我明确地写 requireParam()
return 类型,它也有效:
val negParamWithExplicitType: Route =
(requireParam("param".as[Int]): Directive1[Int]) { // DRY violation
param =>
complete((-param).toString)
}
为什么我需要这些技巧?为什么它不能仅与 requireParam("param".as[Int])
一起使用?
Scala 版本 2.12.1,Akka-HTTP 10.0.10。
此错误是由于指令伴随对象应用方法造成的。 IT 允许从带有参数 (T => Route) => Route:
的函数创建一个 Routeobject Directive {
/**
* Constructs a directive from a function literal.
*/
def apply[T: Tuple](f: (T ⇒ Route) ⇒ Route): Directive[T] =
new Directive[T] { def tapply(inner: T ⇒ Route) = f(inner) }
}
但是T参数必须是元组。在您的情况下,编译器无法构建 Route。您的 requireParam("param".as[Int]) returns a Directive1[Int] 因此 apply 方法不起作用,因为 Int 不是元组。
要完成这项工作,您应该直接使用 tapply 方法:
(requireParam("param1".as[Int])).tapply((param1) =>
complete((-param1._1).toString))
和
val negSumParams2: Route =
(requireParam("param1".as[Int]) & requireParam("param2".as[Int])).tapply {
case (param1, param2) =>
complete((-param1-param2).toString)
}
所以似乎每个指令都试图将其参数转换为 TupleX。例如:
path("order" / IntNumber) returns 一个 Directive[Tuple1[Int]] 而不是 Directive1[Int]。在你的情况下 requireParam("param1".as[Int]) returns Directive1[Int]
也许有更好的解决办法,避免tapply
Lightbend 的 Johannes 在这里回答了这个问题:https://groups.google.com/forum/#!topic/akka-user/NmQvcrz5sJg答案是:
You discovered one of the reasons for the magnet pattern. If you use
requireParam("param".as[Int]) { abc => ... }
then the{ abc => }
block is mistaken as the implicit argument ofrequireParam
. So, either you are ok with that and require users to use extra parentheses ((requireParam("param".as[Int])) { abc => ... }
) or you use the magnet pattern at this level as well. You can read about the magnet pattern on the old spray blog (http://spray.io/blog/2012-12-13-the-magnet-pattern/) or just look into akka-http sources.A better way to implement the feature would be just to use the existing implementation ;) and install a custom rejection handler that produces whatever output you would like. See https://doc.akka.io/docs/akka-http/10.0.10/scala/http/routing-dsl/rejections.html#customizing-rejection-handling for how to do that.