Простейший REST-cервис на Scala и Finagle

18 мая 2015

Согласно официальному описанию, Finagle — это расширяемая RPC система для JVM, используемая для построения «сильно многопоточных» (high-concurrent, что бы это ни значило) сервисов. Finagle изначально был создан ребятами из Twitter, но сейчас успешно применяется и в Foursquare, Pinterest, SoundCloud, Tumblr, а также ряде других компаний. В этой заметке мы познакомимся с основами использования Finagle, создав с его помощью очень простой REST-сервис.

Содержимое build.sbt будет таким:

name := "finagle-example"

version := "0.1"

scalaVersion := "2.11.6"

libraryDependencies ++= Seq(
  "com.twitter" %% "finagle-http" % "6.25.0"
  )

(Хорошие новости — после долгих мучений Finagle наконец-то стал совместим со Scala 2.11!)

Минимальное приложение, которое можно представить, будет выглядеть так:

import com.twitter.finagle._
import com.twitter.util._
import org.jboss.netty.handler.codec.http._

object FinagleExample extends App {
  val service = new Service[HttpRequest, HttpResponse] {
    def apply(req: HttpRequest): Future[HttpResponse] =
      Future.value(new DefaultHttpResponse(
        req.getProtocolVersion, HttpResponseStatus.OK))
  }
  val server = Http.serve(":8080", service)
  Await.ready(server)
}

В Finagle сервис — это функция, которая преобразует запрос, в данном случае HttpRequest, в футуру ответа, в данном случае Future[HttpResponse]. Это, если подумать, очень крутая мысль. С футурами мы с вами уже хорошо знакомы. Однако Finagle использует собственные футуры, не из пакета scala.concurrent. Так получилось по историческим причинам, так как до версии 2.10 (если не путаю) в стандартной библиотеке Scala никаких футур не было, а те, что добавили, были старательно слизаны с Finagle’овских. В будущем вроде как планируется перевести Finagle на футуры из пакета scala.concurrent, а пока футуры с легкостью конвертируются одни в другие через имплиситы и промисы. Да, как вы могли заметить по импортам, Finagle является очень тонкой оберткой над Netty (крутейшая вещь!), имеющей намного более приятный API, без ужасов вроде постоянных foo.close(), bar.release() и подобного.

Рассмотрим чуть более сложный пример:

package me.eax.finagle_example

import com.twitter.finagle._
import com.twitter.util._
import org.jboss.netty.handler.codec.http._
import com.twitter.finagle.http.{Http => _, _}
import scala.collection.concurrent.TrieMap

import java.nio.charset.Charset

object FinagleExample extends App {
  val service = new Service[HttpRequest, HttpResponse] {
    val kv = TrieMap.empty[String, String]

    def apply(req: HttpRequest): Future[HttpResponse] = {
      Future {
        val resp = {
          Response(req.getProtocolVersion, HttpResponseStatus.OK)
        }
        val key = req.getUri

        req.getMethod match {
          case HttpMethod.GET =>
            kv.get(key) match {
              case None =>
                resp.setStatus(HttpResponseStatus.NOT_FOUND)
              case Some(value) =>
                resp.setContentString(value)
            }
          case HttpMethod.POST =>
            val value = {
              req.getContent.toString(Charset.forName("UTF-8"))
            }
            kv.update(key, value)
          case HttpMethod.DELETE =>
            kv.remove(key)
          case _ =>
            resp.setStatus(HttpResponseStatus.BAD_REQUEST)
        }
        resp
      }
    }
  }
  val server = Http.serve(":8080", service)
  Await.ready(server)
}

Здесь мы имеем in-memory KV базу данных с как бы REST-интерфейсом:

$ curl localhost:8080/test -D - -o -
HTTP/1.1 404 Not Found
Content-Length: 0

$ curl -XPOST -d 'test-value' localhost:8080/test -D - -o -
HTTP/1.1 200 OK
Content-Length: 0

$ curl localhost:8080/test -D - -o - && echo
HTTP/1.1 200 OK
Content-Length: 10

test-value

$ curl -XDELETE localhost:8080/test -D - -o -
HTTP/1.1 200 OK
Content-Length: 0

$ curl localhost:8080/test -D - -o -
HTTP/1.1 404 Not Found
Content-Length: 0

$ curl -XPUT localhost:8080/test -D - -o -
HTTP/1.1 400 Bad Request
Content-Length: 0

Код, как мне кажется, совершенно тривиальный и в дополнительных пояснениях совершенно не нуждается. Вообще, Finagle — исключительно простой и приятный в использовании фреймворк, этим многих и подкупает. Кстати, размер сервиса после assembly получается 16 Мб, не так уж много.

Есть веские основания полагать, что Finagle также можно легко использовать из Java и Kotlin. Если желаете, можете считать проверку этого утверждения своим домашним заданием!

Ссылки по теме:

А используете ли вы Finagle и если да, то с какими дополнительными пакетами и в каких проектах?

Дополнение: Akka HTTP на примере звонков и посылки SMS через Plivo

Метки: , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.