akka 简介

by Miguel Lopez

由Miguel Lopez

Akka HTTP路由简介 (An introduction to Akka HTTP routing)

Akka HTTP’s routing DSL might seem complicated at first, but once you get the hang of it you’ll see how powerful it is.

Akka HTTP的路由DSL乍一看似乎很复杂,但是一旦掌握了它,您就会发现它的功能强大。

In this tutorial we will focus on creating routes and their structure. We won’t cover parsing to and from JSON, we have other tutorials that cover that topic.

在本教程中,我们将重点介绍创建路线及其结构。 我们将不介绍与JSON之间的解析,也有涉及该主题的其他教程 。

什么是指令? (What are directives?)

One of the first concepts we’ll find when learning server-side Akka HTTP (there’s a client-side library as well) is directives.

当学习服务器端Akka HTTP(也有一个客户端库)时,我们会发现第一个概念是指令

So, what are they?

那是什么

You can think of them as building blocks, Lego pieces if you will, that you can use to construct your routes. They are composable, which means we can create directives on top of other directives.

您可以将它们视为积木,也可以视作乐高积木,用于构建路线。 它们是可组合的,这意味着我们可以在其他指令之上创建指令。

If you want a more in-depth reading, feel free to check out Akka HTTP’s official documentation.

如果您想更深入地阅读,请随时查阅Akka HTTP的官方文档 。

Before moving on, let’s discuss what we’ll build.

在继续之前,让我们讨论一下我们将要构建的内容。

类博客API (Blog-like API)

We’ll create a sample of a public facing API for a blog, where we will allow users to:

我们将为博客创建一个面向公众的API样本,我们将允许用户执行以下操作:

  • query a list of tutorials查询教程列表
  • query a single tutorial by ID通过ID查询单个教程
  • query the list of comments in a tutorial查询教程中的评论列表
  • add comments to a tutorial在教程中添加评论

The endpoints will be:

端点将是:

- List all tutorials GET /tutorials
- Create a tutorial GET /tutorials/:id
- Get all comments in a tutorial GET /tutorials/:id/comments
- Add a comment to a tutorial POST /tutorials/:id/comments

We will only implement the endpoints, no logic in them. This way we’ll learn how to create this structure and the common pitfalls when starting with Akka HTTP.

我们将只实现端点,而没有逻辑。 这样,我们将学习从Akka HTTP开始如何创建此结构以及常见的陷阱。

项目设置 (Project Setup)

We’ve created a repo for this tutorial, in it you’ll find a branch per each section that requires coding. Feel free to clone it and use it as a base project or even just change between branches to look at the differences.

我们已经为本教程创建了一个仓库 ,其中每个需要编码的部分都会找到一个分支。 随意克隆它并将其用作基础项目,甚至只是在分支之间进行更改以查看差异。

Otherwise, create a new SBT project, and then add the dependencies in the build.sbt file:

否则,请创建一个新的SBT项目,然后在build.sbt文件中添加依赖build.sbt

name := "akkahttp-routing-dsl"
version := "0.1"
scalaVersion := "2.12.7"
val akkaVersion = "2.5.17" val akkaHttpVersion = "10.1.5"
libraryDependencies ++= Seq(   "com.typesafe.akka" %% "akka-actor" % akkaVersion,   "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test,  "com.typesafe.akka" %% "akka-stream" % akkaVersion,   "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test,   "com.typesafe.akka" %% "akka-http" % akkaHttpVersion,   "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,      "org.scalatest" %% "scalatest" % "3.0.5" % Test )

We added Akka HTTP and its dependencies, Akka Actor and Streams. And we will also use Scalatest for testing.

我们添加了Akka HTTP及其依赖项,Akka Actor和Streams。 我们还将使用Scalatest进行测试。

列出所有教程 (Listing all the tutorials)

We’ll take a TDD approach to build our directive hierarchy, creating the tests first to make sure when don’t break our routes when adding others. Taking this approach is quite helpful when starting with Akka HTTP.

我们将采用TDD方法来构建指令层次结构,首先创建测试以确保添加其他路由时不中断我们的路由。 从Akka HTTP开始时,采用这种方法非常有帮助。

Let’s start with our route to listing all the tutorials. Create a new file under src/test/scala (if the folders don't exist, create them) named RouterSpec:

让我们从列出所有教程的路线开始。 在src/test/scala下创建一个名为RouterSpec的新文件(如果文件夹不存在,请创建它们):

import akka.http.scaladsl.testkit.ScalatestRouteTest import org.scalatest.{Matchers, WordSpec}
class RouterSpec extends WordSpec with Matchers with ScalatestRouteTest {
}

WordSpec and Matchers are provided by Scalatest, and we'll use them to structure our tests and assertions. ScalatestRouteTest is a trait provided by Akka HTTP's test kit, it will allow us to test our routes in a convenient way. Let's see how we can accomplish that.

WordSpecMatchers是由Scalatest提供,我们将用它们来构建我们的测试和断言。 ScalatestRouteTest是Akka HTTP测试工具提供的特征,它将使我们能够以方便的方式测试路由。 让我们看看我们如何做到这一点。

Because we’re using Scalatest’s WordSpec, we’ll start by creating a scope for our Router object that we will create soon and the first test:

因为我们使用的是Scalatest的WordSpec ,所以我们将从为我们的Router对象创建一个范围开始,这将是我们即将创建的第一个测试:

"A Router" should {   "list all tutorials" in {   } }

Next, we want to make sure can send a GET request to the path /tutorials and get the response we expect, let's see how we can accomplish that:

接下来,我们要确保可以将GET请求发送到路径/tutorials并获得我们期望的响应,让我们看一下如何实现:

Get("/tutorials") ~> Router.route ~> check {   status shouldBe StatusCodes.OK   responseAs[String] shouldBe "all tutorials" }

It won’t even compile because we haven’t created our Router object. Let's do that now.

它甚至不会编译,因为我们还没有创建Router对象。 现在开始吧。

Create a new Scala object under src/main/scala named Router. In it we will create a method that will return a Route:

src/main/scala下创建一个名为Router的新Scala对象。 在其中,我们将创建一个将返回Route

import akka.http.scaladsl.server.Route
object Router {
def route: Route = ???
}

Don’t worry too much about the ???, it's just a placeholder to avoid compilation errors temporarily. However, if that code is executed, it'll throw a NotImplementedError as we'll see soon.

不用担心??? ,这只是暂时避免编译错误的占位符。 但是,如果执行了该代码,它将抛出NotImplementedError ,我们将很快看到。

Now that our tests and project are compiling, let’s run the tests (Right-click the spec and “Run ‘RouterSpec’”).

现在我们的测试和项目正在编译,让我们运行测试(右键单击spec并“运行'RouterSpec'”)。

The test failed with the exception we were expecting, we haven’t implemented our routes. Let’s begin!

测试失败,除了我们所期望的例外,我们还没有实现我们的路线。 让我们开始!

创建上市路线 (Creating the listing route)

By looking into the official documentation we see that the route begins with the path directive. Let's mimic what they're doing and build our route:

通过查看官方文档,我们发现路由以path指令开头。 让我们模仿他们在做什么,并建立我们的路线:

import akka.http.scaladsl.server.{Directives, Route}
object Router extends Directives {
def route: Route = path("tutorials") {    get {      complete("all tutorials")    }  }}

Seems reasonable, let’s run our spec. And it passes, great!

似乎合理,让我们运行我们的规范。 它过去了,太好了!

For reference, our entire RouterSpec now looks like:

作为参考,我们的整个RouterSpec现在看起来像:

import akka.http.scaladsl.model.StatusCodesimport akka.http.scaladsl.testkit.ScalatestRouteTestimport org.scalatest.{Matchers, WordSpec}class RouterSpec extends WordSpec with Matchers with ScalatestRouteTest {  "A Router" should {    "list all tutorials" in {      Get("/tutorials") ~> Router.route ~> check {        status shouldBe StatusCodes.OK        responseAs[String] shouldBe "all tutorials"      }    }  }}

通过ID获取单个教程 (Getting a single tutorial by ID)

Next, we will allow our users to retrieve a single tutorial.

接下来,我们将允许我们的用户检索单个教程。

Let’s add a test for our new route:

让我们为新路线添加一个测试:

"return a single tutorial by id" in {  Get("/tutorials/hello-world") ~> Router.route ~> check {    status shouldBe StatusCodes.OK    responseAs[String] shouldBe "tutorial hello-world"  }}

We expect to get back a message that includes the tutorial ID.

我们希望获得一条包含教程ID的消息。

The test will fail because we haven’t created our route, let’s do that now.

由于我们尚未创建路线,因此测试将失败,让我们现在开始。

From the same resource we used earlier to base our route on, we can see how we can place multiple directives at the same level in the hierarchy using the ~ directive.

从之前使用的相同资源开始,我们可以看到如何使用~指令将多个指令放置在层次结构中的同一级别上。

We will have to nest path directives because need another segment after the /tutorials route for the tutorial ID. In the documentation they use IntNumber to extract a number from the path, but we'll use a string and for that we use can Segment instead.

我们将必须嵌套path指令,因为在/tutorials路由后需要另一个段来获取教程ID。 在文档中,他们使用IntNumber从路径中提取数字,但是我们将使用字符串,为此,我们可以使用Segment

Our route looks like:

我们的路线如下:

def route: Route = path("tutorials") {  get {    complete("all tutorials")  } ~ path(Segment) { id =>    get {      complete(s"tutorial $id")    }  }}

Let’s run the tests. And you should get a similar error:

让我们运行测试。 而且您应该得到类似的错误:

Request was rejectedScalaTestFailureLocation: RouterSpec at (RouterSpec.scala:17)org.scalatest.exceptions.TestFailedException: Request was rejected

What’s going on?!

这是怎么回事?!

Well, a request is rejected when it doesn’t match our directive hierarchy. This is one of the things that got me when starting.

好吧,当请求与我们的指令层次结构不匹配时,该请求将被拒绝。 这是开始时让我着迷的事情之一。

Now is probably a good time to look into how these directives match the incoming request as it goes through the hierarchy.

现在可能是研究这些指令如何与传入请求通过层次结构进行匹配的好时机。

Different directives will match different aspects of an incoming request, we’ve seen path and get, one matches the URL of the request and the other the method. If a request matches a directive it will go inside it, if it doesn't it will continue to the next one. This also tells us that order matters. If it doesn't match any directive the request is rejected.

不同的指令将匹配传入请求的不同方面,我们已经看到pathget ,一个匹配请求的URL,另一个匹配方法。 如果一个请求与一个指令相匹配,它将进入其中,如果不匹配,它将继续到下一个指令。 这也告诉我们订单很重要。 如果它与任何指令都不匹配,则请求被拒绝。

Now that we now that our request is not matching our directives, let’s start looking into why.

现在,我们的请求与指令不匹配,让我们开始研究原因。

If we look the documentation for the path directive (Cmd + Click on Mac) we'll find:

如果我们查看path指令的文档(Cmd +在Mac上单击),我们会发现:

/** * Applies the given [[PathMatcher]] to the remaining unmatched path after consuming a leading slash. * The matcher has to match the remaining path completely. * If matched the value extracted by the [[PathMatcher]] is extracted on the directive level. * * @group path */

So, the path directive has to match exactly the path, meaning our first path directive will only match /tutorials and never /tutorials/:id.

因此, path指令必须与路径完全匹配,这意味着我们的第一个path指令将仅匹配/tutorials而不会匹配/tutorials/:id

In the same PathDirectives trait that contains the path directive we can see another directive named pathPrefix:

在包含path指令的同一PathDirectives特性中,我们可以看到另一个名为pathPrefix指令:

/** * Applies the given [[PathMatcher]] to a prefix of the remaining unmatched path after consuming a leading slash. * The matcher has to match a prefix of the remaining path. * If matched the value extracted by the PathMatcher is extracted on the directive level. * * @group path */

pathPrefix matches only a prefix and removes it. Sounds like this is what we're looking for, let's update our routes:

pathPrefix仅匹配前缀,并将其删除。 听起来这就是我们想要的,让我们更新路线:

def route: Route = pathPrefix("tutorials") {  get {    complete("all tutorials")  } ~ path(Segment) { id =>    get {      complete(s"tutorial $id")    }  }}

Run the tests, and… we get another error. ?

运行测试,然后…我们得到另一个错误。 ?

"[all tutorials]" was not equal to "[tutorial hello-world]"ScalaTestFailureLocation: RouterSpec at (RouterSpec.scala:18)Expected :"[tutorial hello-world]"Actual   :"[all tutorials]"

Looks like our request matched the first get directive. It now matches the pathPrefix, and because it also is a GET request it will match the first get directive. Order matters.

看起来我们的请求与第一个get指令匹配。 现在,它与pathPrefix匹配,并且由于它也是一个GET请求,因此它将与第一个get指令匹配。 顺序很重要。

There are a couple of things we can do. The simplest solution would be to move the first get request to the end of the hierarchy, however, we would have to remember this or document it. Not ideal.

我们可以做几件事。 最简单的解决方案是将第一个get请求移至层次结构的末尾,但是,我们必须记住此要求或将其记录下来。 不理想。

Personally, I prefer avoiding such solutions and instead make the intend clear through code. If we look in the PathDirectives trait from earlier, we'll find a directive called pathEnd:

就我个人而言,我更喜欢避免此类解决方案,而是通过代码明确意图。 如果我们从较早的版本开始查看PathDirectives特性,我们将找到一个名为pathEnd的指令:

/** * Rejects the request if the unmatchedPath of the [[RequestContext]] is non-empty, * or said differently: only passes on the request to its inner route if the request path * has been matched completely. * * @group path */

That’s exactly what we want, so let’s wrap our first get directive with pathEnd:

这正是我们想要的,所以让我们用pathEnd包装第一个get指令:

def route: Route = pathPrefix("tutorials") {  pathEnd {    get {      complete("all tutorials")    }  } ~ path(Segment) { id =>    get {      complete(s"tutorial $id")    }  }}

Run the tests again, and… finally, the tests are passing! ?

再次运行测试,……最后,测试通过了! ?

列出教程中的所有评论 (Listing all comments in a tutorial)

Let’s put into practice what we learned about nesting routes by taking it a bit further.

让我们进一步实践嵌套路由所学到的知识。

First the test:

首先测试:

"list all comments of a given tutorial" in {  Get("/tutorials/hello-world/comments") ~> Router.route ~> check {    status shouldBe StatusCodes.OK    responseAs[String] shouldBe "comments for the hello-world tutorial"  }}

It’s a similar case as before: we know we’ll need to place a route next to another one, which means we need to:

这与之前的情况类似:我们知道我们需要在另一条路线旁边放置一条路线,这意味着我们需要:

  • change the path(Segmenter) to pathPrefix(Segmenter)

    path(Segmenter)更改为pathPrefix(Segmenter)

  • wrap the first get with the pathEnd directive

    pathEnd指令包装第一个get

  • place the new route next to the pathEnd

    将新路线放置在pathEnd

Our routes end up looking like:

我们的路线最终看起来像:

def route: Route = pathPrefix("tutorials") {  pathEnd {    get {      complete("all tutorials")    }  } ~ pathPrefix(Segment) { id =>    pathEnd {      get {        complete(s"tutorial $id")      }    } ~ path("comments") {      get {        complete(s"comments for the $id tutorial")      }    }  }}

Run the tests, and they should pass! ?

运行测试,它们应该通过! ?

在教程中添加评论 (Adding comments to a tutorial)

Our last endpoint is similar to the previous, but it will match POST requests. We’ll use this example to see the difference between implementing and testing a GET request versus a POST request.

我们的最后一个端点与先前的端点相似,但是它将匹配POST请求。 我们将使用此示例查看实现和测试GET请求与POST请求之间的区别。

The test:

考试:

"add comments to a tutorial" in {  Post("/tutorials/hello-world/comments", "new comment") ~> Router.route ~> check {    status shouldBe StatusCodes.OK    responseAs[String] shouldBe "added the comment 'new comment' to the hello-world tutorial"  }}

We’re using the Post method instead of the Get we've been using, and we're giving it an additional parameter which is the request body. The rest is familiar to us now.

我们使用的是Post方法而不是我们一直使用的Get方法,并且为其提供了一个附加参数,即请求正文。 其余的现在对我们来说已经很熟悉了。

To implement our last route, we can refer to the documentation and look at how it’s usually done.

要实现我们的最后一条路线,我们可以参考文档并查看它通常是如何完成的。

We have a post directive just as we have a get one. To extract the request body we need two directives, entity and as, to which we supply the type we expect. In our case it's a string.

我们有一个post指令,就像我们有一个get指令。 为了提取请求主体,我们需要两个指令, entityas ,我们向它们提供期望的类型。 在我们的例子中,它是一个字符串。

Let’s give that a try:

让我们尝试一下:

post {  entity(as[String]) { comment =>    complete(s"added the comment '$comment' to the $id tutorial")  }}

Looks reasonable. We extract the request body as a string and use it in our response. Let’s add it to our route method next to the previous route we worked on:

看起来很合理。 我们将请求主体提取为字符串,并在响应中使用它。 让我们将其添加到我们之前处理过的路由旁边的route方法中:

def route: Route = pathPrefix("tutorials") {  pathEnd {    get {      complete("all tutorials")    }  } ~ pathPrefix(Segment) { id =>    pathEnd {      get {        complete(s"tutorial $id")      }    } ~ path("comments") {      get {        complete(s"comments for the $id tutorial")      } ~ post {        entity(as[String]) { comment =>          complete(s"added the comment '$comment' to the $id tutorial")        }      }    }  }}

If you’d like to learn how to parse Scala classes to and from JSON we’ve got tutorials for that as well.

如果您想学习如何在JSON中解析Scala类以及从JSON解析出Scala类, 我们也有相应的教程 。

Run the tests, and they should all pass.

运行测试,它们都应该通过。

结论 (Conclusion)

Akka HTTP’s routing DSL might seem confusing at first, but after overcoming some bumps it just clicks. After a while it’ll come naturally and it can be very powerful.

Akka HTTP的路由DSL乍一看似乎令人困惑,但是在克服了一些麻烦之后,只需单击一下。 一段时间后,它会自然而然地变得强大。

We learned how to structure our routes, but more importantly, we learned how to create that structure guided by tests which will make sure we don’t break them at some point in the future.

我们学会了如何构造路线,但更重要的是,我们学会了如何在测试的指导下建立这种结构,以确保我们在将来的某个时候不会破坏它们。

Even though we only worked on four endpoints, we ended up with a somewhat complicated and deep structure. Stay tuned and we’ll explore different ways to simplify our routes and make them more manageable!

即使我们仅在四个端点上工作,但最终还是有一个复杂而深入的结构。 请继续关注,我们将探索各种方法来简化路线并使其更易于管理!

Learn how to build REST APIs with Scala and Akka HTTP with this step-by-step free course!

通过此分步免费课程,了解如何使用Scala和Akka HTTP构建REST API!

Originally published at www.codemunity.io.

最初在www.codemunity.io上发布。

翻译自: https://www.freecodecamp.org/news/an-introduction-to-akka-http-routing-697b00399cad/

akka 简介

akka 简介_Akka HTTP路由简介相关推荐

  1. 前端路由简介以及vue-router实现原理

    后端路由简介 路由这个概念最先是后端出现的.在以前用模板引擎开发页面时,经常会看到这样 http://www.xxx.com/login 大致流程可以看成这样: 浏览器发出请求 服务器监听到80端口( ...

  2. 【FluidSynth】FluidSynth 简介 ( 相关资源链接 | 简介 | 特征 )

    文章目录 一.相关资源链接 二.FluidSynth 简介 三.FluidSynth 特征 一.相关资源链接 相关资源链接 : fluidsynth.org 官方网站 FluidSynth 支持的 M ...

  3. DL之BP:神经网络算法简介之BP算法简介(链式法则/计算图解释)、案例应用之详细攻略

    DL之BP:神经网络算法简介之BP算法简介(链式法则/计算图解释).案例应用之详细攻略 相关文章:DL之DNN之BP:神经网络算法简介之BP算法/GD算法之不需要额外任何文字,只需要八张图讲清楚BP类 ...

  4. akka之分发和路由

    一 Dispatchers 1.1 理解分发器 分发器是一个通信协调员的角色,主要负责消息的接收和传递.主要依据一定的分发策略,用于控制执行流程,然后将到来的消息或者请求路由给相关的业务进程. 提供飞 ...

  5. sql简介香气和sql简介_香气和SQL简介

    sql简介香气和sql简介 在你开始前 关于本系列 本教程系列讲授从基本到高级SQL和基本的XQuery主题,并展示如何通过使用SQL查询或XQueries将常见的业务问题表达为数据库查询. 开发人员 ...

  6. linux脚本简介,Linux Shell脚本简介

    Shell 诞生于 Unix,是与 Unix/Linux 交互的工具,单独地学习 Shell 是没有意义的,请先参考Unix/Linux入门教程,了解 Unix/Lunix 基础. 近几年来,Shel ...

  7. 【Android 高性能音频】AAudio 音频库 简介 ( AAudio 音频库简介 | 音频流 | 音频设备 | 共享模式 | 数据模式 )

    文章目录 I . AAudio 音频库 简介 II . AAudio 音频流 三要素 ( 设备 | 共享模式 | 数据格式 ) III . AAudio 音频设备 IV . AAudio 音频设备获取 ...

  8. Kestrel简介_Kestrel Web 服务器简介

    Kestrel简介_Kestrel Web 服务器简介 一.Kestrel简介 Kestrel 是一个跨平台的适用于 Kestrel. Kestrel 是包含在 ASP.NET Core 项目模板中的 ...

  9. Jira使用简介 HP ALM使用简介

    我们公司的项目目前使用的是Jira和ALM Jira使用简介 HP ALM使用简介 一 Jira 1.1 Jira简介 1.2 基本概念 1.2.1 Project 项目和Issue事务 1.2.2 ...

最新文章

  1. JQuery图片加载显示loading和加载失败默认图片
  2. java rest tomcat_java – REST服务返回HTTP 204(Tomcat / Linux)
  3. spring事务传播属性与隔离级别
  4. android studio发布apk流程
  5. KEY键盘映射_手焊键盘,使用Python编写,一键放连招,还有什么你不会
  6. c语言给定一个非空整数数组_C程序检查给定整数的所有位是否为一(1)
  7. Unix——学习《Unix环境高级编程》找不到“apue.h”方法
  8. PIC18F26K20
  9. 使用dotMemory Unit发现并修复内存问题
  10. 海龟交易法则10_通用积木
  11. Codeforces Round #161 (Div. 2) B. Squares
  12. [oracle]Oracle 11g DG搭建(备库使用ASM)
  13. vue调用手机扫描二维码
  14. 蓝牙鼠标windows linux,解决Ubuntu 18.04与Windows 10双系统蓝牙鼠标连接的问题
  15. 一张A4纸打印多张财务凭证(分栏报表)
  16. PMP一模考试错题集+解析 之 人员
  17. 李俊的人生病毒:靠熊猫烧香成毒王,网赌7000万二进宫!
  18. matlab中ct值直方图,【CT值与灰度值的总结】
  19. 查看github星数排行榜
  20. 《约会专家》片尾【约会宝典】总结

热门文章

  1. C库函数与Linux系统函数之间的关系
  2. mysql 链式查询_MySQL的链接查询
  3. 浅析STM32之usbh_def.H
  4. Centos 7和 Centos 6开放查看端口 防火墙关闭打开
  5. 运用Appium 实现添加微信好友自动化
  6. 《软件工程》课之-调查问卷的心得体会
  7. Debian 系统安装 Nagios 服务器监控端
  8. dynamic web module消失不见
  9. Nexus3.x.x上传第三方jar
  10. Elixir 初尝试 5 -- 遇见Actor