Простейший REST-cервис на Scala и Finagle
18 мая 2015
Согласно официальному описанию, Finagle — это расширяемая RPC система для JVM, используемая для построения «сильно многопоточных» (high-concurrent, что бы это ни значило) сервисов. Finagle изначально был создан ребятами из Twitter, но сейчас успешно применяется и в Foursquare, Pinterest, SoundCloud, Tumblr, а также ряде других компаний. В этой заметке мы познакомимся с основами использования Finagle, создав с его помощью очень простой REST-сервис.
Содержимое build.sbt будет таким:
version := "0.1"
scalaVersion := "2.11.6"
libraryDependencies ++= Seq(
"com.twitter" %% "finagle-http" % "6.25.0"
)
(Хорошие новости — после долгих мучений Finagle наконец-то стал совместим со Scala 2.11!)
Минимальное приложение, которое можно представить, будет выглядеть так:
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()
и подобного.
Рассмотрим чуть более сложный пример:
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-интерфейсом:
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;
- Исходники и открытые тикеты на GitHub;
- Рассылка finaglers@ в группах Google;
- Хождение в PostgreSQL, биндинги к Clojure и другие навороты;
- Исходники к этой заметке;
А используете ли вы Finagle и если да, то с какими дополнительными пакетами и в каких проектах?
Дополнение: Akka HTTP на примере звонков и посылки SMS через Plivo
Метки: Scala, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.