If you want to know how to use `akka-http` to build your own server, you may find it worth your while reading this article. You'll learn how to build a websocket server. Some other topics are covered here, e.g. basic REST responses or akka-stream processing.
If you’re looking for the best library to build concurrent and distributed applications, probably sooner than later you’ll come across Akka. It’s a very powerful open source library maintained by Typesafe for making such apps.
If you’re looking for a good library to build concurrent and distributed HTTP Server (or Client), you will probably find Spray. It’s a well-designed and mature Akka-based HTTP implementation. In short, Spray offers immutable http-model, based on case classes, with efficient logic for HTTP parsing and rendering. It also has a powerful DSL REST API definition with complex testkit for it. Spray will fit your needs in most cases.
Spray a has few caveats: it is hard to understand and debug structures based on implicits and its Routing DSL can become unintuitive in some cases. But the big miss is its lack of websocket support. It also makes it hard to deal with chunked or very large requests, and such features are quite often desired by modern applications.
Typesafe wanted to take Spray’s heritage, clear out bad things and release it as a powerful HTTP library.
And they made it as
akka-http (also known as Spray 2.0). What has changed? Under the hood - a lot. The core is based on
akka-streams now, so it’s easier to manage data streams and process them concurrently.
On the other hand, lightweight High level routing DSL API from Spray was kept. There’s also one, very important addition: like with the other
akka libraries, you get Java API if you aren’t a Scala guy or gal.
The goal is to show how to use
akka-http API to build a Websocket server. To do that, we will use
akka-streams to process incoming and outcoming messages.
Finally we’ll write basic Websocket client to help us with testing the server (sadly,
akka-http doesn’t support websocket client yet, and we have to use another library for this).
Let’s start our project. All you need to start is add
com.typesafe.akka-akka-http-experimental to your dependencies.
At the time of writing of this article, the most recent version is
Time to explain few things.
akka-http is based on Akka and, like everything dependend on this library, needs
ActorSystem to run.
There is no difference here, in first line we instantiated one. In the next line you can find another implicit constant -
Materializer to operate.
But wait. What is
Flow is a part of
akka-stream, and in short: Flow is a pipe for transporting data; we will discuss it in more detail later on.
Most of the code is self-explanatory, so let’s concentrate on ‘route’. For every server instance you need to define routing.
Routing is a set of server rules. It’s a schema that defines what to respond with to specific request. You can filter requests by URL, parameters,
HTTP Method and so on. Importing
Directives._ allows us to use nice and simple DSL for writing these rules.
Every routing definition should be of type Route.
The example above states that we should only respond to
http://localhost:8080/ URLs, fetched with
HTTP GET method.
Run the server with
sbt run and point your browser to the address above. You should see a Welcome message sent by our server.
Prior to WebSockets, the only way to use HTTP was to send stateless requests. We tried to emulate state and persistent connections to server by techniques like cookies and long polling, but sometimes these were hard to work with. WebSockets were a game changer. In WebSockets, both sides of the comunication - the server and the client - can send and receive data. With this, it’s far easier to build rich experiences for end users.
In most cases, websocket channel runs through an upgraded HTTP/s connection.
Websockets allow sending data both sides, but there is no option to transport more than one message in the one direction at the same time.
And because message size is not known (theoretically it’s possible for it to be infinitely long), the data of the message is represented as a stream.
akka-http implementation, websocket transmission is divided into small pieces, and one such piece is represented by Message trait.
You can send two types of message data -
ByteString. Both types have proper subtypes of
Message, accordingly: TextMessage and BinaryMessage.
Both of these subtypes also have their own
Strict subclasses that contain raw data (
ByteString) which is a natural choice for sending completely assembled messages.
akka-http server receives a message through a WebSocket stream, it tries to bundle the data as an instance of the
Strict class if it’s possible (i.e if complete data was received in one part).
If it’s not possible, it can also handle streamed data.
When implementing a server, keep in mind the
Strict means complete data; if you want to be prepared for incompleted data - and I suggest you do - the streamed messages should also be handled.
If you want to upgrade your connection to websocket, your implementation should include
akka.http.scaladsl.model.ws.UpgradeToWebsocket header in every initial request.
If the Server gets request with such header, connection is able to upgrade to a websocket connection. In this case, the server will have to provide a proper
Flow to handle websocket messages.
Response for these messages is made by these header functions.
UpgradeToWebsocket also handles Handshaking(protocol negotiation),
but it’s well hidden in the application and you don’t need to know all the details.
It was quite a large part of rough theory so now it’s time to add websocket suport to our server.
Let’s try to do simple echo server that sends back incoming messages. First we need to prepare another endpoint for that.
In our example, echo-websocket will be accessible under
ws-echo path. First we modify the route:
~) is used for composition of few routes in one rule,
path("ws-echo") defines which path we want to process in this rule,
get as above is HTTP GET method.
handleWebsocketMessages method will upgrade connections to websockets using
echoService handler that we will implement shortly.
akka-http under the hood uses akka-streams for data processing. To handle wesocket messages you should also use such stream.
To be more precise, in this case you wil have to use
Flow[Message, Message, Any]. So,
akka-http websocket handler includes a stream which is waiting for
Message at input and offers
Message at output.
Yep, that’s all. It’s a proof that Websocket is easier to do than to explain :)
Time to test it. Start the server and websocket client. We didn’t make one ourselves yet, so use something external like Dark Websocket terminal or Simple Websocket Client (both available as google chrome extensions).
You should achieve similiar result as on the image below:
Current app stage can be found here
Having the understoanding of the basics, we can move to something more complicated - multichannel chat. First, let’s create a
ChatRoom - class for keeping user information.
ChatRoom class will be created for each separate room. Inside the
ChatRoom class is an actor which is keeping endpoints for every connected user.
There is also
sendMessage helper which redirects incoming messages to inner actor.
ChatMessage is a simple case class that holds the content
of the message and its author. For now let’s ignore
websocketFlow. It will be explained later on, when discussing akka-streams.
Next we will add a class to hold all the rooms. I’ll use simple
Map[Int, ChatRoom] for this. We want to make it as simple as possible.
In this singleton class there are only two members:
findOrCreate. I think functions are self-explanatory, so we can go further.
Chat service should know which room it should connect to. This is the reason why room number should be included in the URL.
Assume the correct path to our chat room is
XXX is room number and
YYY is an username. In real application it would be a little more
complicated, but we will keep the example simple ;)
Time for route definition.
Few words of explanation. Our route consists of two directives.
pathPrefix makes sure to match every url starting with “ws-chat” followed by a slash character and then an Integer.
parameter directive matches and extracts “name” query parameter, which in this case is obligatory (for more informations about paramteres go here).
Having all the data extracted we look up a chat room or create a new one.
handleWebsocketMessages was explained above, in short, it’s a handler for websocket connection. This function is provided by
ChatRoom and we didn’t create it yet.
Now let’s take a look at
Such actor is kept in every
CharRoom instance separately; it manages only the needed types of messages. We also use it for storing user data.
Keep in mind that the
participants map is a websocket endpoint for connected client.
There is only one missing point in our simple chat project, but before we continue, let’s review how the entre workflow works.
If the client hits
/ws-chat/123?name=Mario url, application tries to find
ChatRoom instance for room
123, if it can’t find a room that already exists, it creates a new one.
ChatRoom takes function
websocketFlow for user
Mario and uses it as a websocket connection processor.
Lets assume some scenarios:
- If a new message comes through the websocket connection, it should be sent to inner actor of our
ChatRoom for propagation.
- If websocket is created, it should create a new actor and register it into proper
ChatRoom instance with client’s (user’s) name.
Still a lot of things to do. I think now it’s good time to talk a little about
akka-streams is a concurrent data processing library in line of Reactive Streams initiative.
akka-streams you are able to build an entire data processing pipeline using small blocks.
The main feature of this library is
back-pressure, which allows consumers to control how much data they are given by producers.
It prevents buffer overflows which often result in
In context of
akka-streams you need to know only 4 basic definitions:
Graph. All of them are the blocks used to build data process pipeline.
Source has no input and only one output, so it’s used as a starting point for our data.
Sink has no output and only one input, so it’s used as an endpoint for our data.
Graph are blocks between
Sink. The difference between both is the first have exactly one input and one output,
Graph has no such restrictions.
All the blocks are customizable and multilevel, so for example, we can use multiple
Graph to make one
Such structure is called
Shape and it is a common abstract class ancestor for every mentioned block type. One more thing you should know is that the constructed shape is only a blueprint for
future uses. It’s not executed directly while being created. Additionally, not all processed data is accessible inside a structure. If you have to use such data, you have to
materialize it before use.
In our application,
Sink are provided by the library. Both produce/consume objects of type
Message. So, all we need to process all
websocket data is to make
Flow that takes one message (which is delivered from the client), process it, and return another message (which is sent to the client).
So it’s time to look into our final function:
This function contains a lot of members and each of them needs some explanation.
In akka-streams each stream element has ports; port for incoming messages is of type
Inlet and port that pushes outgoing message is of type
Outlet. In function above we should return type
Flow, so it’s an element with
inlet and one
outlet. First element of our structure is
fromWebsocket and the last is
backToWebsocket. Both are flows, and because
Flow hasn’t exposed ports directly, you must wrap it with
implicitly inside this factory method. Because we did it, last element of structure is return type of
Tuple2 with defined incoming and outgoing ports of the entire flow.
If you want to connect two ports, you have to use such functions like
addEdge. Shortcut for these is
~> sign. For instance
chatSource ~> backToWebsocket means that
chatSource’s outlet is connected with
inlet, in other words: Everything that is returned by
chatSource is pushed to
backToWebsocket. Helpfuly both elements have only one proper port, otherwise you should define directly which ports should be used. In the function above you can see
Merge has two incoming ports and one outgoing port, so in every case you want to push message to one of the incoming ports, you are obliged to choose which one it is.
In general, when we’re creating this
Flow, it should do two things: register an Actor that is used as entry point for a websocket channel, and send every incoming message to the
ChatRoom. Because this function is inside
ChatRoom, we already have access to the
inner Actor which is responsible for broadcasting messages to every user in the room. The same actor keeps information about connected users.
We have two ways of connecting our custom actor with the stream. You can create your own actor which extends
ActorSubscriber. In this case you have to define our entire message’s processing flow manually. But if functionality of such actor isn’t so complicated, you can use a simpler way.
Sink with inlet of type
T. All incoming messages are sent to the actor which is provided as the first parameter of the function. Second parameter defines the message what is sent to this actor while stream is completed.
In this fragment of code we’re creating a Sink which is sending all incoming messages of type
ChatEvent to the
chatRoomActor. When stream completes (user client disconnects) it will send a
Connecting our Actor to stream as source is similiar. You can use
ActorPublisher as parent and add your message processing code, or use simple
actorRef functions are similiar - both create an Actor instance-
parameters in both cases are different: Source doesn’t need to provide a custom actor. Instead, it needs the size of a buffer and strategy to manage its overflow. It’s enough information to create an anonymous actor
which accepts all messages of defined type and sends them to the stream.
I’m using such actor as a starting point for all incoming messages. To do so, we need to register this actor inside
We have already defined
ChatEvent responsible for doing this work. All we need to do is wrap this actor inside a
But inside the flow not everything is accessible. Imagine the
Flow definition as a blueprint which will be used while the stream runs.
From this point of view you don’t know what kind of data will be inside the stream. If you want to work directly with values flowing through the stream, you will need to materialize them.
When Source is initialized inside construction block, it isn’t accessible by
But, we put our source as parameter of factory method. In this case, it can be materialized later.
Allow us for actor’s materialization and sends it inside Event object.
In the function above, we created and attached our
ChatRoom instance as Sink of the flow. This actor broadcasts every incoming message
to all registered clients. In the flow we have two message sources: all incoming messages from controller’s websocket handler,
and selfgenerated Event responsible for registering this new WebSocket client. Both sources are merged and messages from both are sent to the sink.
After registering actor as websocket endpoint, this actor is also responsible for sending messages back to the client.
The last stage is to write a Websocket Client. For basic purposes we want to make a client that connects to a websocket server, listens on a connection and logs every message to console. Also, from time to time, it sends short messages to the server.
akka-http doesn’t support WebSockets on the client side. Maybe it’ll change in the future. But for now I have to use another library. I decided to use Java Websockets
It’s a short piece of code. Most important logic is within extended
We initialize it with chosen url and
Draft_17 as protocol to use for connection (Draft 17, was copied as official RFC 6455 standard).
All we need in this class is to override four methods - callbacks for messages and connection events.
There is also
spam function which creates anonymous actor instance and sends particular amount of messages to the websocket channel.
Server class that runs the client when you add
with-client parameter to start command.
You can find the entire code in our repository
Over the entire article, I tried overview
akka-http as a library ready to build a reactive HTTP server.
There is also a few words about
akka-streams in general and about websockets standards. We built a ready-to-use websocket server.
You can run it on your own machine or use it as basis for a larger websystem.
If you want to run it in a command line:
If you want to run Server with a background Client, use:
sbt "run with-client"