자바 개발자가 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)



Posted by 김용환 '김용환'