관리 메뉴

HAMA 블로그

[Play2] Action composition (번역) 본문

PlayFramework2

[Play2] Action composition (번역)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 9. 28. 11:53

액션 컴포지션 

커스텀 액션 빌더


이전에 요청 매개 변수없이 요청 매개 변수를 사용하고 본문 파서 등을 사용하여 여러 가지 방법으로 동작을 선언하는 방법을 보았습니다. 실제로는 비동기 프로그래밍에 대한 장에서 설명 할 것입니다.

이러한 액션 작성 메소드는 실제로 모두 ActionBuilder라는 특성에 의해 정의되며 우리가 액션을 선언하는 데 사용하는 Action 객체는이 특성의 인스턴스에 지나지 않습니다. 자신의 ActionBuilder를 구현함으로써 재사용 가능한 액션 스택을 선언 할 수 있으며, 액션 스택을 사용하여 액션을 빌드 할 수 있습니다.

로깅 데코레이터의 간단한 예제부터 시작하여이 액션에 대한 각 호출을 기록하려고합니다.

첫 번째 방법은 invokeBlock 메서드에서이 기능을 구현하는 것입니다.이 메서드는 ActionBuilder에서 빌드 한 모든 액션에 대해 호출됩니다.

import play.api.mvc._

object LoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    Logger.info("Calling action")
    block(request)
  }
}

Now we can use it the same way we use Action:

def index = LoggingAction {
  Ok("Hello World")
}

Since ActionBuilder provides all the different methods of building actions, this also works with, for example, declaring a custom body parser:

ActionBuilder는 액션을 구현하는 다양한 방법을 제공하기 때문에, 예를 들어 커스텀 바디 파서를 선언하는 경우에도 사용할 수 있습니다.

def submit = LoggingAction(parse.text) { request =>
  Ok("Got a body " + request.body.length + " bytes long")
}

Composing actions

대부분의 애플리케이션에서 여러 액션 빌더를 원할 것입니다. 일부는 다른 유형의 인증을 수행하고, 일부는 다양한 유형의 일반 기능을 제공합니다.이 경우, 각 유형에 대한 로깅 조치 코드를 다시 작성하지 않을 것입니다 우리는 재사용 가능한 방법으로 그것을 정의하고 싶을 것이다.


재사용 가능한 액션 코드는 액션을 래핑하여 구현 될 수 있습니다.

import play.api.mvc._

case class Logging[A](action: Action[A]) extends Action[A] {

  def apply(request: Request[A]): Future[Result] = {
    Logger.info("Calling action")
    action(request)
  }

  lazy val parser = action.parser
}

We can also use the Action action builder to build actions without defining our own action class:

import play.api.mvc._

def logging[A](action: Action[A])= Action.async(action.parser) { request =>
  Logger.info("Calling action")
  action(request)
}

Actions can be mixed in to action builders using the composeAction method:

object LoggingAction extends ActionBuilder[Request] {
  def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
    block(request)
  }
  override def composeAction[A](action: Action[A]) = new Logging(action)
}

Now the builder can be used in the same way as before:

def index = LoggingAction {
  Ok("Hello World")
}

We can also mix in wrapping actions without the action builder:

def index = Logging {
  Action {
    Ok("Hello World")
  }
}

More complicated actions

지금까지 요청에 전혀 영향을 미치지 않는 작업 만 보여주었습니다. 물론 들어오는 요청 객체를 읽고 수정할 수도 있습니다.

import play.api.mvc._

def xForwardedFor[A](action: Action[A]) = Action.async(action.parser) { request =>
  val newRequest = request.headers.get("X-Forwarded-For").map { xff =>
    new WrappedRequest[A](request) {
      override def remoteAddress = xff
    }
  } getOrElse request
  action(newRequest)
}

Note: Play already has built in support for X-Forwarded-For headers.

We could block the request:

import play.api.mvc._

def onlyHttps[A](action: Action[A]) = Action.async(action.parser) { request =>
  request.headers.get("X-Forwarded-Proto").collect {
    case "https" => action(request)
  } getOrElse {
    Future.successful(Forbidden("Only HTTPS requests allowed"))
  }
}

And finally we can also modify the returned result:

import play.api.mvc._
import play.api.libs.concurrent.Execution.Implicits._

def addUaHeader[A](action: Action[A]) = Action.async(action.parser) { request =>
  action(request).map(_.withHeaders("X-UA-Compatible" -> "Chrome=1"))
}

Different request types

작업 구성을 사용하면 HTTP 요청 및 응답 수준에서 추가 처리를 수행 할 수 있지만 종종 요청 자체에 대한 컨텍스트를 추가하거나 유효성 검사를 수행하는 데이터 변환의 파이프 라인을 작성하려고합니다. ActionFunction은 요청에서 입력 요청 유형과 다음 계층으로 전달되는 출력 유형에 대해 매개 변수화 된 함수로 생각할 수 있습니다. 각 작업 함수는 인증, 개체에 대한 데이터베이스 조회, 사용 권한 확인 또는 여러 작업에서 작성하고 다시 사용하려는 기타 작업과 같은 모듈 식 처리를 나타낼 수 있습니다.


다양한 유형의 처리에 유용한 ActionFunction을 구현하는 몇 가지 사전 정의 된 특성이 있습니다.

  • ActionTransformer can change the request, for example by adding additional information.
  • ActionFilter can selectively intercept requests, for example to produce errors, without changing the request value.
  • ActionRefiner is the general case of both of the above.
  • ActionBuilder is the special case of functions that take Request as input, and thus can build actions.

You can also define your own arbitrary ActionFunction by implementing theinvokeBlock method. Often it is convenient to make the input and output types instances of Request (using WrappedRequest), but this is not strictly necessary.

또한 invokeBlock 메서드를 구현하여 임의의 ActionFunction을 정의 할 수도 있습니다. 종종 WrappedRequest를 사용하여 Request의 입력 및 출력 유형 인스턴스를 만드는 것이 편리하지만, 이는 꼭 필요한 것은 아닙니다.

Authentication

액션 함수의 가장 일반적인 사용 사례 중 하나가 인증입니다. 원래 요청에서 사용자를 결정하고이를 새로운 UserRequest에 추가하는 자체 인증 조치 변환기를 쉽게 구현할 수 있습니다. 이것은 간단한 요청을 입력으로 받기 때문에 anActionBuilder이기도합니다.

import play.api.mvc._

class UserRequest[A](val username: Option[String], request: Request[A]) extends WrappedRequest[A](request)

object UserAction extends
    ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
  def transform[A](request: Request[A]) = Future.successful {
    new UserRequest(request.session.get("username"), request)
  }
}

Play also provides a built in authentication action builder. Information on this and how to use it can be found here.


참고 : 내장 된 인증 작업 빌더는 간단한 경우 인증을 구현하는 데 필요한 코드를 최소화하는 편리한 도우미 일 뿐이며 구현은 위의 예와 매우 비슷합니다.

내장 된 인증 조치로 충족 될 수있는 것보다 더 복잡한 요구 사항이있는 경우 자체 구현은 단순하지 않을뿐만 아니라 권장됩니다.

Adding information to requests

이제 Item 유형의 객체와 함께 작동하는 REST API를 살펴 보겠습니다. / item / : itemId 경로 아래에 경로가 많이있을 수 있으며 각 경로는 항목을 조회해야합니다. 이 경우,이 논리를 활동 함수에 넣는 것이 유용 할 수 있습니다.


우선, UserRequest에 Item을 추가하는 요청 객체를 생성합니다.

import play.api.mvc._

class ItemRequest[A](val item: Item, request: UserRequest[A]) extends WrappedRequest[A](request) {
  def username = request.username
}

이제는 해당 항목을 조회하고 오류 (Left) 또는 새 ItemRequest (Right)를 반환하는 액션 구체화기를 만듭니다. 이 액션 구체화자는 아이템의 ID를 취하는 메소드 내에 정의됩니다.

def ItemAction(itemId: String) = new ActionRefiner[UserRequest, ItemRequest] {
  def refine[A](input: UserRequest[A]) = Future.successful {
    ItemDao.findById(itemId)
      .map(new ItemRequest(_, input))
      .toRight(NotFound)
  }
}

Validating requests

마지막으로 요청을 계속할지 여부를 확인하는 액션 함수가 필요할 수 있습니다. 예를 들어, UserAction의 사용자가 ItemAction의 항목에 액세스 할 수있는 권한이 있는지 여부를 확인하고 오류가 아닌 경우 오류를 반환합니다.

object PermissionCheckAction extends ActionFilter[ItemRequest] {
  def filter[A](input: ItemRequest[A]) = Future.successful {
    if (!input.item.accessibleByUser(input.username))
      Some(Forbidden)
    else
      None
  }
}

Putting it all together

이제 액션을 생성하기 위해 andThen을 사용하여 ActionBuilder로 시작하는 이들 액션 함수를 함께 연결할 수 있습니다.

def tagItem(itemId: String, tag: String) =
  (UserAction andThen ItemAction(itemId) andThen PermissionCheckAction) { request =>
    request.item.addTag(tag)
    Ok("User " + request.username + " tagged " + request.item.id)
  }

Play also provides a global filter API , which is useful for global cross cutting concerns.

Comments