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의 철학에 따라 자동으로 수집됩니다. 모든 경로를 한군데에 설정할 필요가 없습니다. 분산 라우팅처럼 이 기능 덕분에 어플리케이션을 다른 어플리케이션에 통합할 수 있습니다. 만약 블로그 엔진을 만든다면 패키징된 JAR파일을 다른 어플리케이션으로 가져와서 즉시 블로그 기능을 사용할 수 있습니다. 라우팅에는 두 가지 특징이 있습니다. 안전한 방법으로 루트를 재생성하거나(리버스 라우팅) `Swagger Doc `_ 이라고 하는 문서를 통해서 만들 수 있습니다. 루트 캐싱 ------- 어플리케이션 시작시 속도 향상을 위해 루트는 ``routes.cache`` 파일에 캐쉬됩니다. 개발 시에는 ``target`` 파일에 있는 클래스 파일의 루트는 캐쉬되지 않습니다. 만약 루트를 포함하여 라이브러리가 업데이트 된 경우에는 ``routes.cache`` 파일을 삭제하세요. 또한, 이 파일은 소스 저장소에 커밋되지 않도록 주의해야 합니다. 루트의 우선순위(first, last) ------------------------ 다음과 같은 루트를 만든 경우 :: /articles/:id --> ArticlesShow /articles/new --> ArticlesNew 두 번째 루트를 우선시 할 경우 ``@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은 typesafe하므로 URL을 직접 사용하면 안 됩니다. 다음처럼 사용하세요: :: myArticle.id)}>{myArticle.title} 다른 액션으로 리디랙션 -------------------------- 참고 `what redirection is `_. :: 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) Anti-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 `_ 이 템플릿 내에서 사용되는 경우, 토큰은 GET 요청을 제외한 모든 jQuery 의 Ajax 요청에 ``X-CSRF-Token`` 을 포함합니다. xitrum.js은 ``jsDefaults`` 에 포함되어 있습니다. 만약 ``jsDefaults`` 를 사용하지 않고 xitrum.js를 사용하고 싶다면 다음과 같이 사용합니다. :: antiCsrfInput 과 antiCsrfToken -------------------------------------- Xitrum은 CSRF토큰을 ``X-CSRF-Token`` 의 요청헤더에서 가져옵니다. 만약 요청헤더가 없다면 ``csrf-token`` 의 바디 파라미터에서 가져옵니다. (URL의 파라미터가 아닙니다) Form을 직접 작성할 때에 메타 태그와 xitrum.js을 사용하지 않는다면 ``antiCsrfInput`` 또는 ``antiCsrfToken`` 을 사용해야 합니다. :: form(method="post" action={url[AdminAddGroup]}) != antiCsrfInput :: form(method="post" action={url[AdminAddGroup]}) input(type="hidden" name="csrf-token" value={antiCsrfToken}) CSRF 체크 생략 ------------------ 스마트폰과 같은 기기를 위해서 API를 작성할 경우 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 `_. Example: :: import xitrum.{Config, Server} object Boot { def main(args: Array[String]) { // You can modify routes before starting the server val routes = Config.routes // Remove routes to an action by its class routes.removeByClass[MyClass]() if (demoVersion) { // Remove routes to actions by a prefix routes.removeByPrefix("premium/features") // This also works 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`` 태그를 문서화하고 싶은 API에 명시하면 됩니다. 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("image", "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`` 에 접근할때 `JSON For Swagger `_ 가 생성됩니다. Swagger UI는 이 JSON 정보를 바탕으로 인터랙티브한 API 문서를 만듭니다. 여기에 있는 Swagger.IntPath, Swagger.OptStringQuery, 이외에도 BytePath, IntQuery, OptStringForm 등이 form에 명시되어 있습니다. * ```` (필수 값) * ``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 `_ 를 참고하세요.