RESTful APIs
============
XitrumではiPhone、Androidなどのアプリケーション用のRESTful APIsを非常に簡単に記述することができます。
::
import xitrum.Action
import xitrum.annotation.GET
@GET("articles")
class ArticlesIndex extends Action {
def execute() {...}
}
@GET("articles/:id")
class ArticlesShow extends Action {
def execute() {...}
}
POST、 PUT、 PATCH、 DELETEそしてOPTIONSと同様に
XitrumはHEADリクエストをボディが空のGETリクエストとして自動的に扱います。
通常のブラウザーのようにPUTとDELETEをサポートしていないHTTPクライアントにおいて、
PUTとDELETEを実現するには、リクエストボディに ``_method=put`` や、 ``_method=delete`` を含めることで
可能になります。
アプリケーションの起動時にXitrumはアプリケーションをスキャンし、ルーティングテーブルを作成し出力します。
以下の様なログからアプリケーションがどのようなAPIをサポートしているか知ることができます。
::
[INFO] Routes:
GET /articles quickstart.action.ArticlesIndex
GET /articles/:id quickstart.action.ArticlesShow
ルーティングはJAX-RSとRailsエンジンの思想に基づいて自動で収集されます。
全てのルートを1箇所に宣言する必要はありません。
この機能は分散ルーティングと捉えることができます。この機能のおかげでアプリケーションを他のアプリケーションに取り込むことが可能になります。
もしあなたがブログエンジンを作ったならそれをJARにして別のアプリケーションに取り込むだけですぐにブログ機能が使えるようになるでしょう。
ルーティングには更に2つの特徴があります。
ルートの作成(リバースルーティング)は型安全に実施され、
`Swagger Doc `_ を使用したルーティングに関するドキュメント作成も可能となります。
ルートのキャッシング
--------------------
起動スピード改善のため、ルートは ``routes.cache`` ファイルにキャッシュされます。
開発時には ``target`` にあるクラスファイル内のルートはキャッシュされません。
もしルートを含む依存ライブラリを更新した場合、 ``routes.cache`` ファイルを削除してください。
また、このファイルはソースコードリポジトリにコミットしないよう気をつけましょう。
ルートの優先順位(first、last)
-----------------------------
以下の様なルートを作成した場合
::
/articles/:id --> ArticlesShow
/articles/new --> ArticlesNew
2番目のルートを優先させるには ``@First`` アノテーションを追加します。
::
import xitrum.annotation.{GET, First}
@GET("articles/:id")
class ArticlesShow extends Action {
def execute() {...}
}
@First // This route has higher priority than "ArticlesShow" above
@GET("articles/new")
class ArticlesNew extends Action {
def execute() {...}
}
``Last`` も同じように使用できます。
Actionへの複数パスの関連付け
----------------------------
::
@GET("image", "image/:format")
class Image extends Action {
def execute() {
val format = paramo("format").getOrElse("png")
// ...
}
}
ドットを含むルート
------------------
::
@GET("articles/:id", "articles/:id.:format")
class ArticlesShow extends Action {
def execute() {
val id = param[Int]("id")
val format = paramo("format").getOrElse("html")
// ...
}
}
正規表現によるルーティング
--------------------------
ルーティングに正規表現を使用することも可能です。
::
GET("articles/:id<[0-9]+>")
パスの残り部分の取得
----------------------
``/`` 文字が特別でパラメータ名に含まれられません。``/`` 文字を使いたい場合、以下のように書きます:
::
GET("service/:id/proxy/:*")
以下のパスがマッチされます:
::
/service/123/proxy/http://foo.com/bar
``:*`` を取得:
::
val url = param("*") // "http://foo.com/bar"となります
アクションへのリンク
--------------------
Xitrumは型安全指向です。URLは直截記載せずにいかのように参照します:
::
myArticle.id)}>{myArticle.title}
他のアクションへのリダイレクト
------------------------------
``redirectTo[AnotherAction]()`` を使用します。
リダイレクトについては `こちら(英語) `_ を参照してください。
::
import xitrum.Action
import xitrum.annotation.{GET, POST}
@GET("login")
class LoginInput extends Action {
def execute() {...}
}
@POST("login")
class DoLogin extends Action {
def execute() {
...
// After login success
redirectTo[AdminIndex]()
}
}
GET("admin")
class AdminIndex extends Action {
def execute() {
...
// Check if the user has not logged in, redirect him to the login page
redirectTo[LoginInput]()
}
}
また、``redirecToThis()`` を使用して現在のアクションへリダイレクトさせることも可能です。
他のアクションへのフォワード
----------------------------
``forwardTo[AnotherAction]()`` を使用します。前述の ``redirectTo`` ではブラウザは別のリクエストを送信しますが、
``forwardTo`` ではリクエストは引き継がれます。
Ajaxリクエストの判定
--------------------
``isAjax`` を使用します。
::
// In an action
val msg = "A message"
if (isAjax)
jsRender("alert(" + jsEscape(msg) + ")")
else
respondText(msg)
CSRF対策
--------
GET以外のリクエストに対して、Xitrumはデフォルトで `Cross-site request forgery `_ 対策を実施します。
``antiCsrfMeta`` Tagsをレイアウト内に記載した場合:
::
import xitrum.Action
import xitrum.view.DocType
trait AppAction extends Action {
override def layout = DocType.html5(
{antiCsrfMeta}
{xitrumCss}
{jsDefaults}
Welcome to Xitrum
{renderedView}
{jsForView}
)
}
出力される ```` は以下のようになります:
::
...
...
...
`xitrum.js `_ をテンプレート内で使用した場合、
このトークンは ``X-CSRF-Token`` ヘッダーとしてGETを除く全てのjQueryによるAjaxリクエストに含まれます。
xitrum.jsは ``jsDefaults`` タグを使用することでロードされます。
もし ``jsDefaults`` を使用したくない場合、以下のようにテンプレートに記載することですることでxitrum.jsをロードすることができます。
::
CSRF対策インプットとCSRF対策トークン
--------------------------------------
XitrumはCSRF対策トークンをリクエストヘッダーの ``X-CSRF-Token`` から取得します。
もしリクエストヘッダーが存在しない場合、Xitrumはリクエストボディの ``csrf-token`` から取得します。
(URLパラメータ内には含まれません。)
前述したメタタグとxitrum.jsを使用せずにformを作成する場合、``antiCsrfInput`` または
``antiCsrfToken`` を使用する必要があります。
::
form(method="post" action={url[AdminAddGroup]})
!= antiCsrfInput
::
form(method="post" action={url[AdminAddGroup]})
input(type="hidden" name="csrf-token" value={antiCsrfToken})
CSRFチェックの省略
------------------
スマートフォン向けアプリケーションなどでCSRFチェックを省略したい場合、
``xitrum.SkipCsrfCheck`` を継承してActionを作成します。
::
import xitrum.{Action, SkipCsrfCheck}
import xitrum.annotation.POST
trait Api extends Action with SkipCsrfCheck
@POST("api/positions")
class LogPositionAPI extends Api {
def execute() {...}
}
@POST("api/todos")
class CreateTodoAPI extends Api {
def execute() {...}
}
ルーティングの操作
------------------
Xitrumは起動時に自動でルーティングを収集します。
収集されたルーティングにアクセスするには、`xitrum.Config.routes `_ を使用します。
例:
::
import xitrum.{Config, Server}
object Boot {
def main(args: Array[String]) {
// サーバーをスタートさせる前にルーティングを操作します。
val routes = Config.routes
// クラスを指定してルートを削除する場合
routes.removeByClass[MyClass]()
if (demoVersion) {
// prefixを指定してルートを削除する場合
routes.removeByPrefix("premium/features")
// '/'が先頭にある場合も同じ効果が得られます
routes.removeByPrefix("/premium/features")
}
...
Server.start()
}
}
リクエストコンテンツの取得
--------------------------
通常リクエストコンテンツタイプが ``application/x-www-form-urlencoded`` 以外の場合、
以下のようにしてリクエストコンテンツを取得することができます。
文字列として取得:
::
val body = requestContentString
文字列として取得し、JSONへのパース:
::
val myJValue = requestContentJValue // => JSON4S (http://json4s.org) JValue
val myMap = xitrum.util.SeriDeseri.fromJValue[Map[String, Int]](myJValue)
より詳細にリクエストを扱う場合、 `request.getContent `_ を使用することで
`ByteBuf `_ としてリクエストを取得することができます。
SwaggerによるAPIドキュメンテーション
-----------------------------------
`Swagger `_ を使用してAPIドキュメントを作成することができます。
``@Swagger`` アノテーションをドキュメント化したいActionに記述します。
Xitrumはアノテーション情報から `/xitrum/swagger.json `_ を作成します。
このファイルを `Swagger UI `_ で読み込むことでインタラクティブなAPIドキュメンテーションとなります。
XitrumはSwagger UI を内包しており、 ``/xitrum/swagger-ui`` というパスにルーティングします。
例: http://localhost:8000/xitrum/swagger-ui.
.. image:: ../img/swagger.png
`サンプル `_ を見てみましょう。
::
import xitrum.{Action, SkipCsrfCheck}
import xitrum.annotation.{GET, Swagger}
@Swagger(
Swagger.Tags("APIs to create images"),
Swagger.Description("Dimensions should not be bigger than 2000 x 2000"),
Swagger.OptStringQuery("text", "Text to render on the image, default: Placeholder"),
Swagger.Produces("image/png"),
Swagger.Response(200, "PNG image"),
Swagger.Response(400, "Width or height is invalid or too big")
)
trait ImageApi extends Action with SkipCsrfCheck {
lazy val text = paramo("text").getOrElse("Placeholder")
}
@GET("image/:width/:height")
@Swagger( // <-- Inherits other info from ImageApi
Swagger.Summary("Generate rectangle image"),
Swagger.IntPath("width"),
Swagger.IntPath("height")
)
class RectImageApi extends Api {
def execute {
val width = param[Int]("width")
val height = param[Int]("height")
// ...
}
}
@GET("image/:width")
@Swagger( // <-- Inherits other info from ImageApi
Swagger.Summary("Generate square image"),
Swagger.IntPath("width")
)
class SquareImageApi extends Api {
def execute {
val width = param[Int]("width")
// ...
}
}
``/xitrum/swagger`` にアクセスすると
`SwaggerのためのJSON `_
が生成されます。
Swagger UIはこの情報をもとにインタラクティブなAPIドキュメンテーションを作成します。
ここででてきたSwagger.IntPath、Swagger.OptStringQuery以外にも、BytePath, IntQuery, OptStringFormなど
以下の形式でアノテーションを使用することができます。
* ```` (必須パラメータ)
* ``Opt`` (オプションパラメータ)
Value type: Byte, Int, Int32, Int64, Long, Number, Float, Double, String, Boolean, Date, DateTime
Param type: Path, Query, Body, Header, Form
詳しくは `value type `_ 、
`param type `_ を参照してください。