Introduction to Akka HTTP – Akka HTTP简介

最后修改: 2018年 12月 27日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, with the help of Akka’s Actor & Stream models, we’ll learn how to set up Akka to create an HTTP API that provides basic CRUD operations.

在本教程中,在Akka的ActorStream模型的帮助下,我们将学习如何设置Akka以创建一个提供基本CRUD操作的 HTTP API。

2. Maven Dependencies

2.Maven的依赖性

To start, let’s take a look at the dependencies required to start working with Akka HTTP:

首先,让我们看一下开始使用Akka HTTP所需的依赖性。

<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http_2.12</artifactId>
    <version>10.0.11</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-stream_2.12</artifactId>
    <version>2.5.11</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http-jackson_2.12</artifactId>
    <version>10.0.11</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-http-testkit_2.12</artifactId>
    <version>10.0.11</version>
    <scope>test</scope>
</dependency>

We can, of course, find the latest version of these Akka libraries on Maven Central.

当然,我们可以在Maven Central上找到这些Akka库的最新版本。

3. Creating an Actor

3.创建一个演员

As an example, we’ll build an HTTP API that allows us to manage user resources. The API will support two operations:

作为一个例子,我们将建立一个HTTP API,使我们能够管理用户资源。该API将支持两种操作。

  • creating a new user
  • loading an existing user

Before we can provide an HTTP API, we’ll need to implement an actor that provides the operations we need:

在我们提供HTTP API之前,我们需要实现一个提供我们所需操作的角色:

class UserActor extends AbstractActor {

  private UserService userService = new UserService();

  static Props props() {
    return Props.create(UserActor.class);
  }

  @Override
  public Receive createReceive() {
    return receiveBuilder()
      .match(CreateUserMessage.class, handleCreateUser())
      .match(GetUserMessage.class, handleGetUser())
      .build();
  }

  private FI.UnitApply<CreateUserMessage> handleCreateUser() {
    return createUserMessage -> {
      userService.createUser(createUserMessage.getUser());
      sender()
        .tell(new ActionPerformed(
           String.format("User %s created.", createUserMessage.getUser().getName())), getSelf());
    };
  }

  private FI.UnitApply<GetUserMessage> handleGetUser() {
    return getUserMessage -> {
      sender().tell(userService.getUser(getUserMessage.getUserId()), getSelf());
    };
  }
}

Basically, we’re extending the AbstractActor class and implementing its createReceive() method.

基本上,我们正在扩展AbstractActor类并实现其createReceive()方法。

Within createReceive(), we’re mapping incoming message types to methods that handle messages of the respective type.

createReceive()中,我们正在将传入的消息类型映射到处理相应类型消息的方法。

The message types are simple serializable container classes with some fields that describe a certain operation. GetUserMessage and has a single field userId to identify the user to load. CreateUserMessage contains a User object with the user data we need to create a new user.

消息类型是简单的可序列化的容器类,有一些描述某种操作的字段GetUserMessage并有一个单字段userId来识别要加载的用户。CreateUserMessage包含一个User对象,其中包含我们创建新用户所需的用户数据。

Later, we’ll see how to translate incoming HTTP requests into these messages.

稍后,我们将看到如何将传入的HTTP请求翻译成这些信息。

Ultimately, we delegate all messages to a UserService instance, which provides the business logic necessary for managing persistent user objects.

最终,我们将所有消息委托给一个UserService实例,该实例提供了管理持久化用户对象所需的业务逻辑。

Also, note the props() method. While the props() method isn’t necessary for extending AbstractActor, it will come in handy later when creating the ActorSystem.

此外,请注意props()方法。虽然props()方法对于扩展AbstractActor来说并不是必须的,但在以后创建ActorSystem时,它将会派上用场。

For a more in-depth discussion about actors, have a look at our introduction to Akka Actors.

关于演员的更深入讨论,请看我们的Akka演员介绍

4. Defining HTTP Routes

4.定义HTTP路由

Having an actor that does the actual work for us, all we have left to do is to provide an HTTP API that delegates incoming HTTP requests to our actor.

有了一个为我们做实际工作的角色,我们剩下要做的就是提供一个HTTP API,将传入的HTTP请求委托给我们的角色。

Akka uses the concept of routes to describe an HTTP API. For each operation, we need a route.

Akka使用路由的概念来描述一个HTTP API。对于每个操作,我们都需要一个路由。

To create an HTTP server, we extend the framework class HttpApp and implement the routes method:

为了创建一个HTTP服务器,我们扩展框架类HttpApp并实现routes方法。

class UserServer extends HttpApp {

  private final ActorRef userActor;

  Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));

  UserServer(ActorRef userActor) {
    this.userActor = userActor;
  }

  @Override
  public Route routes() {
    return path("users", this::postUser)
      .orElse(path(segment("users").slash(longSegment()), id -> route(getUser(id))));
  }

  private Route getUser(Long id) {
    return get(() -> {
      CompletionStage<Optional<User>> user = 
        PatternsCS.ask(userActor, new GetUserMessage(id), timeout)
          .thenApply(obj -> (Optional<User>) obj);

      return onSuccess(() -> user, performed -> {
        if (performed.isPresent())
          return complete(StatusCodes.OK, performed.get(), Jackson.marshaller());
        else
          return complete(StatusCodes.NOT_FOUND);
      });
    });
  }

  private Route postUser() {
    return route(post(() -> entity(Jackson.unmarshaller(User.class), user -> {
      CompletionStage<ActionPerformed> userCreated = 
        PatternsCS.ask(userActor, new CreateUserMessage(user), timeout)
          .thenApply(obj -> (ActionPerformed) obj);

      return onSuccess(() -> userCreated, performed -> {
        return complete(StatusCodes.CREATED, performed, Jackson.marshaller());
      });
    })));
  }
}

Now, there is a fair amount of boilerplate here, but note that we follow the same pattern as before of mapping operations, this time as routes. Let’s break it down a bit.

现在,这里有相当多的模板,但请注意,我们遵循的是与之前相同的映射操作模式,这次是作为路由。让我们把它分解一下。

Within getUser(), we simply wrap the incoming user id in a message of type GetUserMessage and forward that message to our userActor.

getUser()中,我们简单地将传入的用户ID包裹在一个GetUserMessage类型的消息中,并将该消息转发给我们的userActor

Once the actor has processed the message, the onSuccess handler is called, in which we complete the HTTP request by sending a response with a certain HTTP status and a certain JSON body. We use the Jackson marshaller to serialize the answer given by the actor into a JSON string.

一旦角色处理了消息,onSuccess处理程序就会被调用,在这个处理程序中,我们完成HTTP请求,发送一个带有特定HTTP状态和特定JSON体的响应。我们使用Jacksonmarshaller来将角色给出的答案序列化为JSON字符串。

Within postUser(), we do things a little differently, since we’re expecting a JSON body in the HTTP request. We use the entity() method to map the incoming JSON body into a User object before wrapping it into a CreateUserMessage and passing it on to our actor. Again, we use Jackson to map between Java and JSON and vice versa.

postUser()中,我们的做法有些不同,因为我们在HTTP请求中期待一个JSON体。我们使用entity()方法将传入的JSON主体映射为User对象,然后将其封装为CreateUserMessage并将其传递给我们的角色。同样,我们使用Jackson在Java和JSON之间进行映射,反之亦然。

Since HttpApp expects us to provide a single Route object, we combine both routes to a single one within the routes method. Here, we use the path directive to finally provide the URL path at which our API should be available.

由于HttpApp希望我们提供一个单一的Route对象,我们在routes方法中把两个路由合并为一个。在这里,我们使用path指令来最终提供我们的API应该可用的URL路径。

We bind the route provided by postUser() to the path /users. If the incoming request is not a POST request, Akka will automatically go into the orElse branch and expect the path to be /users/<id> and the HTTP method to be GET.

我们将postUser()提供的路由绑定到路径/users。如果传入的请求不是POST请求,Akka会自动进入orElse分支,并期望路径为/users/<id> ,HTTP方法为GET。

If the HTTP method is GET, the request will be forwarded to the getUser() route. If the user does not exist, Akka will return HTTP status 404 (Not Found). If the method is nor a POST nor a GET, Akka will return HTTP status 405 (Method Not Allowed).

如果HTTP方法是GET,请求将被转发到getUser()路由。如果用户不存在,Akka将返回HTTP状态404(未找到)。如果该方法既不是POST也不是GET,Akka将返回HTTP状态405(Method Not Allowed)。

For more information about how to define HTTP routes with Akka, have a look at the Akka docs.

关于如何用Akka定义HTTP路由的更多信息,请看Akka文档

5. Starting the Server

5.启动服务器

Once we have created an HttpApp implementation like above, we can start up our HTTP server with a couple lines of code:

一旦我们像上面那样创建了一个HttpApp实现,我们就可以用几行代码启动我们的HTTP服务器。

public static void main(String[] args) throws Exception {
  ActorSystem system = ActorSystem.create("userServer");
  ActorRef userActor = system.actorOf(UserActor.props(), "userActor");
  UserServer server = new UserServer(userActor);
  server.startServer("localhost", 8080, system);
}

We simply create an ActorSystem with a single actor of type UserActor and start the server on localhost.

我们只需创建一个ActorSystem和一个UserActor类型的演员,并在localhost上启动服务器。

6. Conclusion

6.结语

In this article, we’ve learned about the basics of Akka HTTP with an example showing how to set up an HTTP server and expose endpoints to create and load resources, similar to a REST API.

在这篇文章中,我们已经了解了Akka HTTP的基础知识,通过一个例子展示了如何建立一个HTTP服务器并暴露出端点来创建和加载资源,类似于REST API。

As usual, the source code presented here can be found over on GitHub.

像往常一样,这里的源代码可以在GitHub上找到。