軽量 Web フレームワークで REST API を実装 - Vert.x, Gretty, Play2 Mini, Socko, Restify
個人的に REST API の実装では JAX-RS (Java)*1 や Sinatra (Ruby) あたりを使っていますが、今回は選択肢を増やす目的で下記のようなフレームワークを試してみました。
- Vert.x (Java, Groovy, JavaScript, Ruby, Python)
- Gretty (Java, Groovy, Scala)
- Play2 Mini (Java, Scala)
- Socko (Scala)
- Restify (Node.js)
ちなみに、今回試した Java 系のフレームワーク(Restify 以外)は内部的に Netty を使っています。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20130106/
はじめに
今回は以下のような単純な REST API を実装する事にします。
- /user/
で JSON 文字列 を GET - /user で JSON 文字列 を POST
動作確認は、以下の Ruby スクリプトで行う事にします。
client.rb
#coding:utf-8 require 'net/http' require 'json/pure' Net::HTTP.start('localhost', 8080) { |http| # GET 処理 res = http.get('/user/1') puts "#{res}, #{res.code}, #{res.content_type}, #{res.body}" data = { 'name' => 'test', 'note' => 'サンプル' } # POST 処理 res = http.post('/user', JSON.generate(data), {'Content-Type' => 'application/json'}) puts "#{res}, #{res.code}, #{res.body}" }
Vert.x
- Vert.x 1.3.0
id:fits:20120513 や id:fits:20120708 で扱った Vert.x です。
Vert.x は JVM 用の Node.js ライクなサーバーフレームワークで、Java・JavaScript・CoffeeScript・Groovy・Ruby・Python と多様な言語をサポートしています。
RouteMatcher を使えば Sinatra のように HTTP Method と URL パターンの組み合わせで処理を実装できます。
下記サンプルは Groovy で実装しました。
vert.x/server.groovy
import org.vertx.groovy.core.http.RouteMatcher import org.vertx.java.core.json.impl.Json def rm = new RouteMatcher() rm.get '/user/:id', { req -> def res = req.response res.putHeader('Content-Type', 'application/json') res.end Json.encode([ id: req.params['id'], name: 'vert.x sample' ]) } rm.post '/user', { req -> req.bodyHandler { // JSON を Map へデコード def data = Json.decodeValue(it.toString(), Map) println data req.response.end() } } vertx.createHttpServer().requestHandler(rm.asClosure()).listen 8080 println "server started ..."
実行と動作確認結果(サーバー側)
> vertx run server.groovy server started ... [name:test, note:サンプル]
動作確認結果(クライアント側)
> jruby client.rb #<Net::HTTPOK:0x889b125>, 200, application/json, {"id":"1","name":"vert.x sample"} #<Net::HTTPOK:0x7ff843da>, 200,
Gretty
- Gretty 0.4.302
Gretty は Netty をベースにしたサーバーフレームワークで Java・Groovy・Scala をサポートしています。
下記サンプルは Groovy++ で実装していますが、Groovy++ は今のところ Groovy 2.0 をサポートしていないようで、Groovy 1.8 で実行する必要がありました。
gretty/server.groovy
/* * Groovy 1.8 で実行する必要あり * Groovy 2.0 では ExceptionInitilizerError が発生 */ @GrabResolver(name = 'gretty', root = 'http://groovypp.artifactoryonline.com/groovypp/libs-releases-local/') @Grab('org.mbte.groovypp:gretty:0.4.302') import static java.nio.charset.StandardCharsets.* import static org.mbte.gretty.JacksonCategory.* import org.mbte.gretty.httpserver.GrettyServer GrettyServer server = [] server.groovy = [ localAddress: new InetSocketAddress('localhost', 8080), '/user/:id': { get { response.json = [ id: request.parameters['id'], name: 'gretty sample' ] } }, '/user': { post { /* * request.contentText を使うとプラットフォームの * デフォルトエンコードが使われるようなので * 明示的に UTF-8 で処理 */ def data = fromJson(Map, request.content.toString(UTF_8)) println data response.json = '' } } ] server.start()
実行と動作確認結果(サーバー側)
> groovy server.groovy 1 06, 2013 10:25:00 午前 org.mbte.gretty.AbstractServer 情報: Started server on localhost/127.0.0.1:8080 [name:test, note:サンプル]
動作確認結果(クライアント側)
> jruby client.rb #<Net::HTTPOK:0x12da7d5f>, 200, application/json, {"id":"1","name":"gretty sample"} #<Net::HTTPOK:0x889b125>, 200,
Play2 Mini
- Play2 Mini 2.0.3
Play2 Mini は Play2 をベースにした簡易フレームワークで、Java や Scala をサポートしています。
Scala で URL パスを正規表現マッチさせるには Through を使います。 *2
Through は引数の種類によってブロックに渡る引数の内容(下記サンプルの groups: List[String])が異なります。
Throughの引数 | ブロックの引数 | Throughの引数例 | URL例 | ブロックの引数例(List[String]) |
---|---|---|---|---|
正規表現 | 正規表現のグループ | "/user/(.*)".r | /user/a/b | ["a/b"] |
文字列 | 指定の文字列以降を "/" でスプリットしたもの | "/user" | /user/a/b | ["a", "b"] |
なお、Play2 Mini では以下のようにして実装します。
- (1) com.typesafe.play.mini.Application トレイトを extends して route を実装
- (2) (1) を指定した com.typesafe.play.mini.Setup を extends したグローバルパッケージの Global を作成
- (3) play.core.server.NettyServer で実行
play-mini/Server.scala (1)
package fits.sample import com.typesafe.play.mini._ import play.api.mvc._ import play.api.mvc.Results._ import play.api.libs.json._ object Server extends Application { def route = Routes( Through("/user/([^/]*)".r) { groups: List[String] => Action { val id :: Nil = groups Ok(Json.toJson { Map( "id" -> id, "name" -> "play-mini sample" ) }) } }, { case POST(Path("/user")) => Action { req => val data = req.body.asJson data.foreach(println) Ok("") } } ) }
play-mini/Global.scala (2)
object Global extends com.typesafe.play.mini.Setup(fits.sample.Server)
play-mini/build.sbt (3)
scalaVersion := "2.9.2" resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" libraryDependencies += "com.typesafe" %% "play-mini" % "2.0.3" mainClass in (Compile, run) := Some("play.core.server.NettyServer")
デフォルトのポート番号 9000 ではなく 8080 で実行するため、sbt run 時に -Dhttp.port=8080 を指定します。
実行と動作確認結果(サーバー側)
> sbt -Dhttp.port=8080 run ・・・ [info] play - Listening for HTTP on port 8080... {"name":"test","note":"サンプル"}
動作確認結果(クライアント側)
> jruby client.rb #<Net::HTTPOK:0x7ff843da>, 200, application/json, {"id":"1","name":"play-mini sample"} #<Net::HTTPOK:0x1d7ae341>, 200,
Socko
- Socko 0.2.3
Socko は Netty と Akka をベースとした Web サーバーフレームワークです。
Akka の Actor として処理を実装するので、多少コード量が多くなります。
socko/Server.scala
package fits.sample import scala.util.parsing.json.{JSON, JSONObject} import org.mashupbots.socko.events.HttpRequestEvent import org.mashupbots.socko.routes.{Routes, GET, POST} import org.mashupbots.socko.webserver.{WebServer, WebServerConfig} import akka.actor.{Actor, ActorSystem, Props} object Server extends App { class UserGetHandler extends Actor { def receive = { case req: HttpRequestEvent => val path = req.request.endPoint.path val id = path.replace("/user/", "").split("/").head req.response.write( JSONObject( Map( "id" -> id, "name" -> "socko sample" ) ).toString(), "application/json" ) context.stop(self) } } class UserPostHandler extends Actor { def receive = { case req: HttpRequestEvent => val content = req.request.content.toString val data = JSON.parseFull(content) data.foreach(println) req.response.write("") context.stop(self) } } val actorSystem = ActorSystem("SampleActorSystem") val routes = Routes({ case GET(req) => actorSystem.actorOf(Props[UserGetHandler]) ! req case POST(req) if req.endPoint.path == "/user" => actorSystem.actorOf(Props[UserPostHandler]) ! req }) val server = new WebServer(WebServerConfig(port = 8080), routes, actorSystem) server.start() Runtime.getRuntime.addShutdownHook(new Thread() { override def run { server.stop() } }) }
socko/build.sbt
scalaVersion := "2.9.2" resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" libraryDependencies += "org.mashupbots.socko" %% "socko-webserver" % "0.2.3" mainClass in (Compile, run) := Some("fits.sample.Server")
実行と動作確認結果(サーバー側)
> sbt run ・・・ [info] Running fits.sample.Server 11:14:00.300 [run-main] INFO o.m.socko.webserver.WebServer - Socko server 'WebServer' started on localhost:8080 11:14:21.032 [New I/O worker #1] DEBUG o.m.socko.webserver.RequestHandler - HTTP EndPoint(GET,localhost:8080,/user/1) CHANNEL=-384208271 11:14:21.098 [New I/O worker #1] DEBUG o.m.socko.webserver.RequestHandler - HTTP EndPoint(POST,localhost:8080,/user) CHANNEL=-384208271 Map(name -> test, note -> サンプル)
動作確認結果(クライアント側)
> jruby client.rb #<Net::HTTPOK:0x7ff843da>, 200, application/json, {"id" : "1", "name" : "socko sample"} #<Net::HTTPOK:0x6eddcf85>, 200,
Restify
- Restify 1.4.2
Restify は Express によく似た Node.js 用のフレームワークで、REST API の実装に特化しています。
最新バージョンは Restify 2.0.4 でしたが、Windows OS 上の npm install に失敗するので、今回は古いバージョンを使っています。
app.coffee
restify = require 'restify' server = restify.createServer() server.use restify.bodyParser() server.get '/user/:id', (req, res, next) -> res.json id: req.params.id name: 'restify sample' next() server.post '/user', (req, res, next) -> data = JSON.parse req.body console.log data res.json null next() server.listen 8080, -> console.log "server started ..."
実行と動作確認結果(サーバー側)
> coffee app.coffee server started ... { name: 'test', note: 'サンプル' }
動作確認結果(クライアント側)
> jruby client.rb #<Net::HTTPOK:0x14980563>, 200, application/json, {"id":"1","name":"restify sample"} #<Net::HTTPOK:0x42eded9>, 200,