자바 개발자가 https://www.playframework.com/documentation/2.6.x/ScalaHttpFilters를 보면서 스칼라 Play 애플리케이션을 만들 때 조금 헤맬 수 있다.
아래와 같은 코드가 있다고 가정하자. 특별히 소스를 분석하지 않아도 적당히 문서를 읽으면서 알수도 있지만,,
head first로 개념을 이해할 수 있다.
Filters
package filters
import javax.inject.Inject
import play.api.http.{DefaultHttpFilters, EnabledFilters}
import play.filters.gzip.GzipFilter
class Filters @Inject() (
defaultFilters: EnabledFilters,
gzip: GzipFilter,
logging: LoggingFilter
) extends DefaultHttpFilters
(defaultFilters.filters :+ gzip :+ logging: _*)
LoggingFilter
package filters
import javax.inject.Inject
import akka.stream.Materializer
import play.api.Logger
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}
class LoggingFilter @Inject() (implicit val mat: Materializer,
ec: ExecutionContext) 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
val log = s"${requestHeader.method} ${requestHeader.uri}
took ${requestTime}ms and returned ${result.header.status}"
if (requestTime > 3000) {
Logger.warn(log)
} else {
Logger.debug(log)
}
result
}
}
}
또는 아래와 같이 사용한다.
play.filters.enabled += filters.LoggingFilter
만약 play.filters.enabled를 사용하지 않으면.. 아래와 같이 써야 한다.
play.http.filters = filters.Filters
아래와 같이 사용하면 다음 에러가 발생한다.
play.http.filters += filters.LoggingFilter
Configuration error: Configuration error[reference.conf @ jar:file:/Users/samuel.kim/.ivy2/cache/com.typesafe.play/play_2.12/jars/play_2.12-2.6.6.jar!/reference.conf: 69: Cannot concatenate object or list with a non-object-or-list, ConfigNull(null) and SimpleConfigList(["filters.Filters"]) are not compatible]
아래와 같이 사용하면 에러가 발생한다.
play.filters.enabled += filters.Filters
play.api.UnexpectedException: Unexpected exception[ProvisionException: Unable to provision, see the following errors:
1) Found a circular dependency involving play.api.http.EnabledFilters, and circular dependencies are disabled.
at play.utils.Reflect$.bindingsFromConfiguration(Reflect.scala:58):
Binding(class play.api.http.EnabledFilters to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
while locating play.api.http.EnabledFilters
for the 1st parameter of filters.Filters.<init>(Filters.scala:12)
while locating filters.Filters
at play.api.http.EnabledFilters.<init>(HttpFilters.scala:68)
at play.utils.Reflect$.bindingsFromConfiguration(Reflect.scala:58):
Binding(class play.api.http.EnabledFilters to self) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
while locating play.api.http.EnabledFilters
while locating play.api.http.HttpFilters
for the 4th parameter of play.api.http.JavaCompatibleHttpRequestHandler.<init>(HttpRequestHandler.scala:222)
while locating play.api.http.JavaCompatibleHttpRequestHandler
while locating play.api.http.HttpRequestHandler
for the 6th parameter of play.api.DefaultApplication.<init>(Application.scala:236)
at play.api.DefaultApplication.class(Application.scala:235)
while locating play.api.DefaultApplication
while locating play.api.Application
클래스를 보면 Filters의 첫 번째 매개변수인 EnabledFilters의 내부를 보면.. 필터를 구성하는 개념이다.
private val enabledKey = "play.filters.enabled"
private val disabledKey = "play.filters.disabled"
즉 Filters는 상위 개념인데, 처음에는 None으로 지정되어 있다. 따러서 ConfigNull이 이미 들어가 있다. 따라서 Null에 List를 추가하면 당연히 에러가 발생할 것이다. play.http.filters는 애플리케이션에서 지정하는 filter 목록을 정의하는 것이라 할 수 있겠다.
play.http.filters += filters.Filters
=> (애플리케이션 정의 Filter)
play.http.filters = filters.MyFilters
다시 이전 Filters 클래스를 살펴보면, 필터가 3개가 추가된다. EnableFilter(enable/disable 할 수 있는 filter 리스트), Gzip, LoggingFilter 매개 변수로 추가되어 Injection 되었다.
부모 클래스인 DefaultHttpFilters에서 사용하는 형태로 되어 있다. 그래서 play.filters.enable/play.filters.disable 리트스 모두와 gizp, logging을 몽땅 리스트로 묶도록 되어 있다. 이게 사용자 정의 Filter인 셈이다.
extends DefaultHttpFilters(defaultFilters.filters :+ gzip :+ logging: _*)
자바 개발자라면 황당할 수 있을 것 같다.
Inject와 extends를 이용한 간단 코드이지만.. ㄷ ㄷ ㄷ
DefaultHttpFilters는 여러 개의 EseentailFilter를 받는다.
class DefaultHttpFilters @Inject() (val filters: EssentialFilter*)
Filter에서는 아래 filters는 그냥 Seq인데.
defaultFilters.filters :+ gzip :+ logging)
이를 : _*) 를 추가하면 EssentailFilter* 타입이 된다.
자바에서는 varargs인데.
스칼라에서는 : _*으로 사용하면 컴파일러에게 seq/array를 varargs로 변환하라는 신호이다.
scala> def foo(args: Int*) = args.map{_ + 1}
foo: (args: Int*)Seq[Int]
scala> foo(-1, 0, 1)
res0: Seq[Int] = ArrayBuffer(0, 1, 2)