관리 메뉴

HAMA 블로그

[Play2] Filter (번역) 본문

PlayFramework2

[Play2] Filter (번역)

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

플레이는 간단한 필터 API 를 각 요청에 대해 전역적으로 대응하기 위해서 제공한다.

필터 vs  액션  컴포지션

필터 API 는 모든 요청에 대해 동일하게 작동하기 위해서 존재한다. (Cross-Cutting Concern 이라고 함) 예를들어 아래와 같은 공통 관심사 말이다. 

대조적으로  action composition 는 특별한 관심사항에 대해서만 작동하는 의도를 가지고 있다.  인증/인가 및 캐싱 등 말이다. 만약 필터가 모든 라우트에 작동하길 원하지 않는다고 하자. 그 때 사용하라 그러면  더 효과적이다. 당신 스스로의 액션 빌터를 만들수 있다는것을 잊지 말자. 행사(얼개)코드를 상당히 줄일 수 있을 것이다. 

간단한 로깅 필터 만들기

플레이 프레임워크에서 얼마나 오랫동안 리퀘스트가 실행되는지 로깅하는 예제이다. 

import play.api.Logger
import play.api.mvc._
import scala.concurrent.Future
import play.api.libs.concurrent.Execution.Implicits.defaultContext

class LoggingFilter extends Filter {

def apply(nextFilter: RequestHeader => Future[Result])
(requestHeader: RequestHeader): Future[Result] = {

val startTime = System.currentTimeMillis

nextFilter(requestHeader).map { result =>

val endTime = System.currentTimeMillis
val requestTime = endTime - startTime

Logger.info(s"${requestHeader.method} ${requestHeader.uri} " +
s"took ${requestTime}ms and returned ${result.header.status}")

result.withHeaders("Request-Time" -> requestTime.toString)
}
}
}

여기서 일어나는 일을 이해해 보자면  첫 번째로 주목해야 할 것은 theapply 메소드의 서명입니다. 첫 번째 매개변수인 nextFilter는 요청헤더를 가져 와서 결과를 생성하는 함수이고,두 번째 매개 변수 인 requestHeader는 들어오는 요청의 실제 요청 헤더입니다.

nextFilter 매개 변수는 필터 체인의 다음 동작을 나타냅니다.어떤 이유로든 요청을 차단하려는 경우 호출하지 않을 수도 있습니다.

체인에서 다음 필터를 호출하기 전에 타임 스탬프를 저장합니다. 다음 필터를 호출하면 결국 [결과]가 반환되는 Future [결과]가 반환됩니다. 비동기 결과에 대한 자세한 내용은 비동기 결과 처리 장을 참조하십시오. 그런 다음 결과를 취하는 클로저로 map 메소드를 호출하여 Future 의 결과를 조작합니다. request.withHeaders ( "Request-Time"-> requestTime.toString)를 호출하여 요청을 처리하는 데 걸린 시간을 계산하고 응답 한 다음 응답 헤더에서 클라이언트로 다시 보냅니다.

필터 사용하기 

The simplest way to use a filter is to provide an implementation of the HttpFilters trait in the root package:

import javax.inject.Inject
import play.api.http.HttpFilters
import play.filters.gzip.GzipFilter

class Filters @Inject() (
gzip: GzipFilter,
log: LoggingFilter
) extends HttpFilters {

val filters = Seq(gzip, log)
}

다른 환경에서 다른 필터를 사용하거나 이 클래스를 루트 패키지에 포함하지 않으려는 경우 application.conf의 play.http.filters를 다음과 같이 정규 클래스 이름으로 설정하여 Play에서 클래스를 찾을 위치를 구성 할 수 있습니다. 클래스. 예 :

play.http.filters=com.example.MyFilters

필터들은 어디에 적합할까?

필터를 사용하여 라우터에 영향을 주는 경로, 메소드 또는 쿼리 매개 변수를 변환 할 수 없습니다. 그러나 필터에서 직접 해당 작업을 호출하여 다른 작업으로 요청을 보낼 수 있지만 나머지 필터 체인은 무시할 수 있습니다. 라우터를 호출하기 전에 요청을 수정해야하는 경우 논리를Global.onRouteRequest에 배치하는 것이 더 좋습니다.

필터는 라우팅이 완료된 후에 적용되기 때문에 RequestHeader의 태그 맵을 통해 요청으로부터 라우팅 정보에 액세스 할 수 있습니다. 예를 들어, 작업 메소드에 대해 시간을 기록 할 수 있습니다. 이 경우 logTime 메소드를 다음과 같이 업데이트 할 수 있습니다.

import play.api.mvc.{Result, RequestHeader, Filter}
import play.api.{Logger, Routes}
import scala.concurrent.Future
import play.api.libs.concurrent.Execution.Implicits.defaultContext

object LoggingFilter extends Filter {
  def apply(nextFilter: RequestHeader => Future[Result])
           (requestHeader: RequestHeader): Future[Result] = {

    val startTime = System.currentTimeMillis

    nextFilter(requestHeader).map { result =>

      val action = requestHeader.tags(Routes.ROUTE_CONTROLLER) +
        "." + requestHeader.tags(Routes.ROUTE_ACTION_METHOD)
      val endTime = System.currentTimeMillis
      val requestTime = endTime - startTime

      Logger.info(s"${action} took ${requestTime}ms" +
        s" and returned ${result.header.status}")

      result.withHeaders("Request-Time" -> requestTime.toString)
    }
  }
}

Routing tags are a feature of the Play router. If you use a custom router, or return a custom action in Global.onRouteRequest, these parameters may not be available.

좀 더 파워풀한 필터들

Play는 EssentialFilter라는 하위 수준의 필터 API를 제공하여 요청 본문에 대한 모든 액세스 권한을 제공합니다. 이 API를 사용하면 EssentialAction을 다른 작업으로 래핑 할 수 있습니다.

다음은 EssentialFilter로 다시 작성된 필터 예제입니다.

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

class LoggingFilter extends EssentialFilter {
  def apply(nextFilter: EssentialAction) = new EssentialAction {
    def apply(requestHeader: RequestHeader) = {

      val startTime = System.currentTimeMillis

      nextFilter(requestHeader).map { result =>

        val endTime = System.currentTimeMillis
        val requestTime = endTime - startTime

        Logger.info(s"${requestHeader.method} ${requestHeader.uri}" +
          s" took ${requestTime}ms and returned ${result.header.status}")
        result.withHeaders("Request-Time" -> requestTime.toString)

      }
    }
  }
}


여기에서 핵심적인 차이점은 전달 된 다음 액션을 감싸는 새로운 EssentialAction을 만드는 것과 별개로, 우리가 다음에 호출 할 때 반복문을 가져 오는 것입니다. 당신이 원한다면 Enumeratee에서 이것을 변환하여 변환 할 수 있습니다. 그런 다음 iteratee의 결과를 매핑하여 처리합니다.

두 가지 필터 API가있는 것 같지만 EssentialFilter는 하나뿐입니다. 이전 예제 인 extendsEssentialFilter의 간단한 필터 API는 새로운 EssentialAction을 만들어 구현합니다. 전달 된 콜백은 body 파싱 및 나머지 액션이 비동기 적으로 실행되는 동안 theResult에 대한 Promise을 만들어 본문 파싱을 건너 뛰는 것처럼 보입니다.


Comments