Akka Http 是Akka社区提供的一个 Http服务端/客户端通用工具包,支持 Http 1.0/1.1标准及WebSocket,现在 Http 2 的支持也在紧锣密鼓的实现中。
这篇文章将介绍Akka HTTP Server,我们将介绍Akka Http的常用功能模块及使用方式。Akka Http提供了一套强大、易用、易扩展的route dsl来构建路由。Akka Http Client因还不支持超时功能,现在不建议在产品中使用。
本文代码:akka-http-starter
第一个服务
我们可以从官方提供的 HttpApp
特质开始,它提供了快捷的方式来启动一个Akka HTTP Server。
1 | class WebServer extends HttpApp { |
path("hello")
定义了一个HTTP访问路由,get
代表这个路由提供了GET请示,而complete
涵数允许我们提供响应结果来完成这个路由定义,这里我们返回了一段文本。Akka Http的路由看起来向声明式的,以一直新颖而又直观的方式来定义HTTP服务。
用户第一次接触这种涵数套涵数(又像树型结构)的代码方式可能不大习惯,其实我们可以换种方式来实现这段代码:
1 | def traditionRoute: Route = { |
路径(Http方法(结果))
,我们用Java式的风格来实现同样的功能。这样是不是更符合你对代码的预期?
让我们来启动服务:
1 | object StartBoot01 { |
通过curl命令来测试下我们的第一个Akka HTTP服务(-i选项可以打印HTTP响应头):
1 | curl -i http://localhost:8888/hello |
Route
Akka HTTP 提供了一个灵活的DSL,它有很多可组合的元素(Directive 指令)以简洁、易读的方式来构建服务。
让我们来看下面这个示例:
1 | path("book") { |
对于上面这个定义,类似的 Play 路由定义如:
1 | GET /book controller.Page.book(name: Option[String], isbn: Option[String], author: Option[String) |
我们可以看到,对一个API路由的定义拆成了几个函数嵌套的形式。path
指定访问路径,get
决定这个API提供HTTP GET服务,parameters
可以抽取请求参数,而complete
将一个字符串返回给前端。
JSON
现在大部分的服务都提供JSON格式的数据,Akka HTTP提供了 Mashaller/Unmashaller机制,用户可以基于此灵活的定制自己的序列化/反序列化方式。这里我们使用 Jackson 来解析/处理 JSON。
首选,我们实现自定义的 Mashaller/Unmashaller:
1 | trait JacksonSupport { |
实现自定义的 Marshaller/Unmarshaller 后,我们就可以在 Akka HTTP 中提供 Json 支持了。
1 | import akka.http.scaladsl.server.Directives._ |
Akka HTTP使用了Scala的隐式转换特性来自定义数据序列化,这是一个非侵入式的设计,用户可以在每个模块选择自己的数据序列化方式。
Route类型
Route 是 Akka HTTP 路由 DSL 里的核心概念,用它构建的所有结构,不管是一条线还是很多条线组成,它们都会是这个类型的实例。
1 | type Route = RequestContext => Future[RouteResult] |
组合路由
Akka HTTP 提供3个基本的操作来让我们创建更复杂的路由链:
- 路由转换:它代理一个“内部”的路由,并在这个过程中改变一些请求传入的属性,然后传出响应或两者。
- 路由过滤:只允许满足给定条件的请求被传递,并拒绝所有其它访问请求。
- 路由链:如果第一个请求被拒绝,将尝试第二个路由。使用
~
操作符连接多个路由定义。
Akka HTTP 实现了很多默认的指令 akka.http.scaladsl.server.Directives._
,你也可以很轻松地创建自己的指令。指令提供了强大和灵活的方式来构建 Akka HTTP。
路由树
当通过嵌套和操作符组合指令和自定义路径时,将构建一个路由结构并形成一颗树。当一个 HTTP 请求进来,它将从根进行这颗树,并以深度优先的方式流过所有分支,直到某个节点完成或全部被拒绝为止。
1 | val route = |
这里由5个指令构建了一个路由树:
- 当 a, b, c都通过,才到到达路由 1
- 当 a 和 b 通过,但 c 被拒绝且 d 通过,将到达路由 2
- 当 a 和 b 通过,但 c 和 d 被拒绝,路由 3 被到达
若路由 3 前面的请求都被拒绝,则它将“捕获”所有请求。这个机制使复杂的过滤逻辑可以很容易的实现。把简单和最具体的放在顶端,一般和普通的放到最后。