How do directives work in Spray?

隐身守侯 提交于 2020-02-24 16:50:28

问题


I'd like to understand how directives in Spray work. As per the documentation:

The general anatomy of a directive is as follows:

name(arguments) { extractions =>
  ... // inner Route
}

My basic understanding is that in the below snippet, 32 is passed as a parameter to method test.

test {
  32
}

However, in the above directive name example, it is said arguments are passed into inner route, which is an anonymous function.

Could someone please help me understand the syntax and the flow starting from how the arguments are extracted and passed into an inner route?


回答1:


You're right that that syntax passes 32 to the function test. What you're missing is that a Directive accepts a function as an argument (remember, we're doing functional programming now so functions are values). If you wanted to write this:

path(IntNumber) {
  userId =>
    complete(s"Hello user $userId")
}

in a less DSL-ey fashion, you could do this:

val innerFunction: Int => Route = {userId => complete(s"Hello user $userId")}
(path(IntNumber))(innerFunction)

or even this:

def innerMethod(userId: Int): Route = complete(s"Hello user $userId")
(path(IntNumber))(innerMethod)

The mechanics of how this is actually accomplished are... complex; this method makes a Directive implicitly convertible to a function:

implicit def pimpApply[L <: HList](directive: Directive[L])(implicit hac: ApplyConverter[L]): hac.In ⇒ Route = f ⇒ directive.happly(hac(f))

This is using the "magnet pattern" to select an appropriate hac, so that it can take a function in the inner path (with an appropriate number of arguments) if the directive extracts parameters, or a value in the inner path (a plain route) if the directive doesn't extract parameters. The code looks more complicated than it is because scala doesn't have direct support for full dependent typing, so we have to emulate it via implicits. See ApplyConverterInstances for the horrible code this necessitates :/.

The actual extracting all happens when we get an actual route, in the happly method of the specific directive. (If everything used HList everywhere, we could mostly avoid/ignore the preceding horrors). Most extract-ey directives (e.g. path) eventually call hextract:

def hextract[L <: HList](f: RequestContext ⇒ L): Directive[L] = new Directive[L] {
  def happly(inner: L ⇒ Route) = ctx ⇒ inner(f(ctx))(ctx)
}

Remember a Route is really just a RequestContext => Unit, so this returns a Route that, when passed a RequestContext:

  1. Runs f on it, to extract the things that need extracting (e.g. URL path components)
  2. Runs inner on that; inner is a function from e.g. path components to the inner route.
  3. Runs that inner route, on the context.

(The following was edited in by a mod from a comment conversation):

Fundamentally it's pretty elegant, and it's great that you can see all the spray code and it's ordinary scala code (I really recommend reading the source when you're confused). But the "bridging" part with the ApplyConverter is complex, and there's really no way around that; it comes of trying to do full dependent types in a language that wasn't really designed for them.

You've got to remember that the spray routing DSL is a DSL; it's the kind of thing that you'd have to have as an external config file in almost any other language. I can't think of a single web framework that offers the same flexibility in routing definitions that spray does with complete compile-time type safety. So yes, some of the things spray does are complex - but as the quote goes, easy things should be easy and hard things should be possible. All the scala-level things are simple; spray is complex, but it would be even more complex (unusably so) in another language.



来源:https://stackoverflow.com/questions/27777668/how-do-directives-work-in-spray

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!