Reactive Programming:
Server-Side and Client-Side APIs:
High Scalability and Performance:
Streaming Support:
Modular Design:
Routing DSL:
Integration with Akka Ecosystem:
Lightweight:
Pluggable Components:
Akka HTTP is designed around a set of modular components that work together to provide flexible, reactive, and high-performance HTTP functionalities. Here are its core components:
HttpRequest
: Represents an HTTP request.HttpResponse
: Represents an HTTP response.HttpEntity
: Encapsulates the body of a request or response.Authorization
, Content-Type
, etc.val request = HttpRequest(uri = "/api/data")
val response = HttpResponse(entity = "Hello, Akka HTTP!")?
GET
, POST
).val route =
path("hello") {
get {
complete("Hello, World!")
}
}?
path
, pathPrefix
).get
, post
).optionalHeaderValueByName
).entity
).val route =
path("user" / IntNumber) { userId =>
get {
complete(s"Fetching user with ID: $userId")
}
}?
import spray.json._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
case class User(name: String, age: Int)
object UserJsonProtocol extends DefaultJsonProtocol {
implicit val userFormat = jsonFormat2(User)
}
import UserJsonProtocol._
val route =
path("createUser") {
post {
entity(as[User]) { user =>
complete(s"Received user: ${user.name}")
}
}
}?
import akka.stream.scaladsl.Source
val numbers = Source(1 to 100)
val route =
path("stream") {
get {
complete(numbers.map(_.toString))
}
}?
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
val responseFuture = Http().singleRequest(HttpRequest(uri = "https://api.example.com/data"))?
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
val responseFuture = Http().singleRequest(HttpRequest(uri = "https://api.example.com/data"))?
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
class MyRouteTest extends AnyWordSpec with Matchers with ScalatestRouteTest {
val route =
path("test") {
get {
complete("Test successful")
}
}
"The service" should {
"return a successful response for GET /test" in {
Get("/test") ~> route ~> check {
responseAs[String] shouldEqual "Test successful"
}
}
}
}?
Feature | Akka HTTP | Play Framework | Spring Boot |
---|---|---|---|
Programming Model | Reactive, modular library | Full-stack reactive framework | Full-stack MVC framework |
Performance | High (non-blocking I/O) | High (non-blocking I/O) | Moderate (blocking I/O) |
Learning Curve | Steep | Moderate | Easy |
Streaming | Built-in support (Akka Streams) | Supported | Limited |
val route =
path("users" / IntNumber) { userId =>
get {
complete(getUser(userId))
} ~
put {
entity(as[User]) { user =>
complete(updateUser(userId, user))
}
}
}
def getUser(userId: Int): Future[HttpResponse] = {
// Interact with actor(s) to fetch data
}
def updateUser(userId: Int, user: User): Future[HttpResponse] = {
// Interact with actor(s) to update data
}?
implicit val userFormat = jsonFormat3(User)
val jsonResponse = HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, user.toJson.toString()))?
Http().newServerAt("localhost", 8080).bind(route)?
class CustomException(msg: String) extends RuntimeException(msg)
trait CustomExceptionHandler {
implicit def myExceptionHandler: ExceptionHandler =
ExceptionHandler {
case e: CustomException =>
extractUri { uri =>
log.error(s"Request to $uri failed with ${e.getMessage}")
complete(HttpResponse(StatusCodes.BadRequest, entity = e.getMessage))
}
}
}
object MyServer extends App with CustomExceptionHandler {
val route = handleExceptions(myExceptionHandler) {
path("example") {
throw new CustomException("Custom error message")
}
}
Http().bindAndHandle(route, "localhost", 8080)
}?
Aspect | Akka HTTP | Play Framework |
---|---|---|
Core Design | Modular and lightweight, designed as a low-level HTTP toolkit. | Full-stack web framework for building web applications and APIs. |
Reactive Model | Built on Akka Streams for fully reactive, non-blocking processing. | Built on Akka, but abstracts away streams and concurrency details. |
Flexibility | Gives fine-grained control over routing, request handling, and streams. | Provides higher-level abstractions for rapid development with less boilerplate. |
Use Case | Ideal for developers needing granular control or custom HTTP services. | Suitable for building full-featured web applications with MVC architecture. |
Aspect | Akka HTTP | Play Framework |
---|---|---|
Style | Uses a declarative DSL for defining routes and HTTP behavior. | Routes are defined in a configuration file (routes ) or through controllers. |
Flexibility | Highly flexible, allowing dynamic and complex routing. | Simpler, more structured routing with predefined conventions. |
Example (Route) | ```scala | ```scala |
```scala | ```scala | |
path("example") { | GET /example controllers.Example.get |
Feature | Akka HTTP | Play Framework |
---|---|---|
Purpose | Lightweight HTTP server and toolkit for building APIs and microservices. | Full-stack web application framework for building large-scale web applications. |
Architecture | Modular and reactive, built on Akka Streams for fine-grained control. | MVC-based, designed for rapid development with high-level abstractions. |
Routing | Declarative, code-based routing using a flexible DSL. | Configuration-based routing using a routes file, integrated with controllers. |
Ease of Use | Requires more boilerplate and lower-level setup. | Easier for beginners with predefined structure and conventions. |
Learning Curve | Steeper due to its low-level nature and fine-grained control. | Moderate, as it provides higher-level abstractions and hides complexities. |
Performance | Optimized for high-performance, non-blocking HTTP processing. | Performance is good but slightly less fine-tuned for low-level HTTP handling. |
Streaming Support | Built-in streaming capabilities using Akka Streams. | Limited streaming support but sufficient for most standard use cases. |
Integration | Works well in microservices or as part of custom architectures. | Strongly suited for web apps and systems with a defined MVC structure. |
Flexibility | Highly flexible, allowing developers to build from the ground up. | Less flexible, but provides a well-defined structure for rapid development. |
View Rendering | No built-in view rendering; focuses only on HTTP handling. | Provides support for templating engines like Twirl for server-side rendering. |
Asynchronous Model | Fully asynchronous and non-blocking, leveraging Akka Streams. | Asynchronous by default, using Akka under the hood but abstracted for developers. |
WebSocket Support | Robust WebSocket support with fine-grained control. | Provides WebSocket support but with less customization compared to Akka HTTP. |
Dependency Injection | No built-in DI; developers must integrate libraries like Guice or MacWire. | Built-in support for dependency injection (Guice is the default). |
Testing | Offers ScalatestRouteTest for fine-grained testing of HTTP routes. |
Provides built-in testing tools for controllers, routes, and forms. |
Community and Ecosystem | Smaller but focused community with emphasis on Akka-based solutions. | Larger community with plugins and tools for web application development. |
Use Cases | - Microservices |
The role of Akka Streams in Akka HTTP is fundamental, as Akka HTTP leverages Akka Streams to handle and process HTTP requests and responses in a reactive, non-blocking, and backpressure-aware manner. Here's an overview of its role:
Akka HTTP relies on Akka Streams to manage the flow of data through HTTP connections. This ensures:
HTTP entities (like request bodies and response payloads) are modeled as Akka Streams Source
. This allows streaming large data efficiently:
HttpRequest
bodies are exposed as a Source[ByteString, Any]
, enabling streaming and processing large payloads without loading the entire content into memory.HttpResponse
bodies can be constructed using a Source[ByteString, Any]
, allowing you to stream data directly to clients.val route = path("stream") {
get {
val dataStream = Source(1 to 100).map(num => ByteString(s"$num\n"))
complete(HttpResponse(entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, dataStream)))
}
}?
Akka Streams powers WebSocket support in Akka HTTP, allowing for bi-directional streaming communication between the server and clients. WebSocket messages are modeled as Akka Streams Flow
objects, enabling real-time data exchange with backpressure handling.
Example :
val webSocketFlow: Flow[Message, Message, Any] = Flow[Message].map {
case TextMessage.Strict(text) => TextMessage(s"Echo: $text")
case _ => TextMessage("Unsupported message type")
}
val route = path("ws") {
handleWebSocketMessages(webSocketFlow)
}
Akka HTTP directives (e.g., mapAsync
, entity
, extractDataBytes
) work seamlessly with Akka Streams to compose reactive pipelines for processing requests and responses.
Example :
val route = path("upload") {
post {
entity(asSourceOf[ByteString]) { byteSource =>
val lineCountFuture = byteSource
.via(Framing.delimiter(ByteString("\n"), maximumFrameLength = 256, allowTruncation = true))
.runFold(0)((count, _) => count + 1)
onSuccess(lineCountFuture) { lineCount =>
complete(s"Uploaded file contains $lineCount lines")
}
}
}
}
Akka Streams provides efficient handling of TCP connections, which is vital for HTTP server and client operations:
With Akka Streams, you can apply transformations to the data as it flows through the HTTP pipeline:
Akka Streams provides a declarative, compositional API for working with streaming data. Its integration into Akka HTTP makes it a powerful choice for:
In Akka HTTP, routes define how incoming HTTP requests are handled by your application. A route maps an HTTP request (based on its method, URI, headers, or body) to a specific action, such as returning a response or invoking application logic. Routes form the backbone of an Akka HTTP application, enabling the definition of endpoints and their associated behavior.
Declarative DSL: Routes in Akka HTTP are defined using a concise, declarative Domain-Specific Language (DSL) provided by the library.
Composability: Routes can be combined hierarchically, allowing for reusable and modular definitions of complex routing logic.
Pattern Matching: Routes support pattern matching for HTTP methods (e.g., GET, POST), paths, query parameters, headers, and even request entities.
Integration with Directives: Akka HTTP provides powerful directives, which are building blocks used to create routes by defining how requests are processed.
Routes rely on directives, which are composable building blocks used to define behavior. Examples include:
path
: Matches a specific URI path.get
, post
, etc.: Matches HTTP methods.parameter
: Extracts query parameters.headerValueByName
: Extracts headers.entity
: Extracts and processes the request body.In Akka HTTP, the path
and pathPrefix
directives are used to match and handle parts of the request URI. While both are involved in routing based on the path, they differ in how they match and handle the URI. Here's a detailed comparison:
path
Directive :The path
directive is used to match exact path segments in the URI. It requires the entire remaining path to match exactly, with no additional segments allowed unless explicitly defined.
Example :
val route = path("users") {
complete("Users endpoint")
}
?
pathPrefix
Directive :The pathPrefix
directive is used to match the beginning (prefix) of a path. It allows additional segments in the URI after the matched prefix, which can be handled by nested routes.
Example :
val route = pathPrefix("users") {
path("create") {
complete("Create user")
} ~
path(IntNumber) { userId =>
complete(s"Details for user $userId")
}
}?
val jsonType = MediaType.applicationWithFixedCharset("custom-json", HttpCharsets.`UTF-8`)
val xmlType = MediaType.applicationWithFixedCharset("custom-xml", HttpCharsets.`UTF-8`)?
Accept: image/jpeg;q=0.9, image/png;q=0.8
import akka.http.scaladsl.model._
import MediaTypes._
val imageData: Array[Byte] = ...
val contentType = if (request.header[headers.Accept].exists(_.mediaRanges.exists(_.matches(`image/jpeg`)))) {
ContentType(`image/jpeg`)
} else {
ContentType(`image/png`)
}
HttpResponse(entity = HttpEntity(contentType, imageData))?
application.conf
file or programmatically using ConnectionPoolSettings
. Key parameters include:max-connections
: Maximum number of connections per target endpoint; increase for high concurrency.min-connections
: Minimum number of connections kept alive; adjust based on expected load.max-retries
: Number of retries for failed requests; balance between resilience and response time.idle-timeout
: Duration before closing idle connections; decrease to free resources faster or increase to reduce connection overhead.pipelining-limit
: Maximum number of requests sent over a single connection concurrently; set higher if server supports HTTP pipelining.response-entity-subscription-timeout
: Timeout for consuming response entities; tune according to expected response times.
Example : akka.http.host-connection-pool {
max-connections = 50
min-connections = 10
max-retries = 3
idle-timeout = 30s
pipelining-limit = 2
response-entity-subscription-timeout = 15s
}?
akka-http-testkit
” in your build configuration to access the required libraries.class MyRoutesSpec extends WordSpec with Matchers with ScalatestRouteTest {
val myRoutes = new MyRoutes().routes
"MyRoutes" should {
"return a greeting" in {
Get("/greet") ~> myRoutes ~> check {
responseAs[String] shouldEqual "Hello!"
}
}
}
}?
HttpApp
or HttpServerTest
. Start the server before tests and stop it after completion. Send requests using an HTTP client like Http()
and validate responses. class MyServerSpec extends WordSpec with Matchers with BeforeAndAfterAll {
val server = new MyServer()
implicit val system = ActorSystem("test-system")
override def beforeAll(): Unit = server.start()
override def afterAll(): Unit = server.stop()
"MyServer" should {
"return a greeting" in {
Http().singleRequest(HttpRequest(uri = "http://localhost:8080/greet")).map { response =>
response.status shouldEqual StatusCodes.OK
Unmarshal(response.entity).to[String].map(_ shouldEqual "Hello!")
}
}
}
}?
import akka.http.scaladsl.server.Directive1
import akka.http.scaladsl.server.directives._
def apiKeyValidation(apiKey: String): Directive1[String] = {
val validApiKey = "my-secret-key"
if (apiKey == validApiKey) provide(apiKey)
else reject(AuthorizationFailedRejection)
}
val route =
path("secure") {
headerValueByName("api-key") { apiKey =>
apiKeyValidation(apiKey) { _ =>
complete("Authorized access")
}
}
}?
handleWebSocketMessages
directive.fileUpload
” directive and reactive streams. To handle a file upload, extract the uploaded file’s metadata and data bytestring from the request entity. Then, use Sink to write the bytestring into a local file or another storage system.import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
case class User(name: String, age: Int)
object UserJsonProtocol extends DefaultJsonProtocol {
implicit val userFormat = jsonFormat2(User)
}
import UserJsonProtocol._
val route =
path("users") {
post {
entity(as[User]) { user =>
complete(user)
}
}
}?
akka.http.scaladsl.model.sse.ServerSentEvent
class. SSE enables efficient real-time communication between a server and clients by pushing updates to clients over a single, long-lived connection.import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.model.sse.ServerSentEvent
import akka.http.scaladsl.server.Directives._
import akka.stream.scaladsl.Source
val route =
path("scores") {
get {
complete {
HttpEntity(
ContentTypes.`text/event-stream`,
Source.fromPublisher(scoreUpdates)
.map(ServerSentEvent(_))
.keepAlive(1.second, () => ServerSentEvent.heartbeat)
)
}
}
}
Http().bindAndHandle(route, "localhost", 8080)?
FROM openjdk:8-jre
COPY target/scala-2.12/my-akka-http-app.jar /app/
CMD ["java", "-jar", "/app/my-akka-http-app.jar"]?
-t my-akka-http-app
. in the terminal.apiVersion: apps/v1
kind: Deployment
metadata:
name: my-akka-http-app
spec:
replicas: 3
template:
metadata:
labels:
app: my-akka-http-app
spec:
containers:
- name: my-akka-http-app
image: myregistry.com/my-akka-http-app
ports:
- containerPort: 8080?
apiVersion: v1
kind: Service
metadata:
name: my-akka-http-app
spec:
selector:
app: my-akka-http-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer?
akka.http.server.request-timeout
setting, which defines the maximum duration a request can take before being automatically rejected. Connection timeouts are managed by akka.http.client.connecting-timeout
, determining how long to wait for establishing a connection.Source
and Sink
components from Akka Streams API, allowing backpressure control and efficient resource management. Additionally, you can disable request timeout for specific routes using withoutRequestTimeout
directive or increase idle timeout settings (akka.http.[server/client].idle-timeout
) to accommodate longer durations. The complete
directive in Akka HTTP is used to terminate the processing of a route and generate an HTTP response that is sent back to the client. It is a core directive that allows you to specify the content of the response, including the status code, headers, and the response entity (body).
complete
:Send HTTP Responses
complete
directive provides a way to return a response to the client, whether it's a simple string, a JSON object, or a full HttpResponse
object.Terminate Route Handling
complete
directive is executed, no further processing is done for that route.Flexible Response Generation
.async()
to allow different stages of the stream to run concurrently on separate threads. Applicable when independent stages have significant processing time.