Kris
Kris

Reputation: 4823

Differences between Akka HTTP and Netty

Can someone please explain the major differences between Akka HTTP and Netty? Netty offers other protocols like FTP as well. Akka HTTP can be used in Scala and Java and is build on the actor model. But apart from this both are asynchronous. When would I use Akka HTTP and when Netty? What are the typical use cases for both?

Upvotes: 19

Views: 8708

Answers (2)

Konstantin Pelepelin
Konstantin Pelepelin

Reputation: 1563

Akka HTTP Server is a HTTP and WebSocket server with high-level DSL. Netty is a low-level "asynchronous event-driven network application framework", allowing to implement any TCP/UDP protocol you need.

So, unless you need some low-level networking, you should not use plain Netty. An equivalent for Akka HTTP using Netty would be something like Netty Reactor, and a higher level on top of them could be something like Spring WebFlux.

On the other side, Akka-HTTP is based on Akka Actors, which is a framework suggesting a specific application model. Also, Akka depends on Scala, which could affect a decision if you already know Scala or if you are not ready to learn Scala when debugging your application.

Upvotes: 8

Here are what I see as the primary contrastable areas:

Coding Style

Let's take netty's discard server example which is presumably the easiest example given that it's the first in the documentation.

For akka-http this is relatively simple:

object WebServer {
  def main(args: Array[String]) {

    implicit val system = ActorSystem("my-system")
    implicit val materializer = ActorMaterializer()

    val route =
      extractRequestEntity { entity =>
        onComplete(entity.discardBytes(materializer)) { _ =>
          case _ => complete(StatusCodes.Ok)
        }
      }

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
}

For netty this is much more verbose:

public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // Discard the received data silently.
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

public class DiscardServer {

    private int port;

    public DiscardServer(int port) {
        this.port = port;
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new DiscardServer(8080).run();
    }
}

Directives

One of akka-http's biggest strengths, in my opinion, is Directives, which provide a DSL for complicated request handling logic. Suppose, for example, that we wanted to respond with one message for GET and PUT requests and another message for all other request methods. This is very easy using Directives:

val route = 
  (get | put) {
    complete("You sent a GET or PUT")
  } ~ 
  complete("Shame shame")

Of if you want to get an order item and quantity from a request path:

val route = 
  path("order" / Segment / IntNumber) { (item, qty) =>
    complete(s"Your order: item: $item quantity: $qty")
  }

This functionality does not exist within netty.

Streaming

One last item I would note is around streaming. akka-http is based on akka-stream. Therefore, akka-http handles the streaming nature of request entities well. Take netty's Looking Into the Received Data example, for akka this looks like

//a stream with a Source, intermediate processing steps, and a Sink
val entityToConsole : (RequestEntity) => Future[Done] =
  (_ : RequestEntity)
    .getDataBytes()
    .map(_.utf8String)
    .to(Sink.foreach[String](println))
    .run()

val route = 
  extractRequestEntity { entity =>
    onComplete(entityToConsole(entity)) { _ =>
      case Success(_) => complete(200, "all data written to console")
      case Failure(_) => complete(404, "problem writing to console)
    }
  }

Netty must handle the same problem with byte buffers and while loops:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf in = (ByteBuf) msg;
    try {
        while (in.isReadable()) { // (1)
            System.out.print((char) in.readByte());
            System.out.flush();
        }
    } finally {
        ReferenceCountUtil.release(msg); // (2)
    }
}

Upvotes: 9

Related Questions