Scala 在Play 2.6中,如何编写一个WS-Client筛选器来转发来自父请求的头?

Scala 在Play 2.6中,如何编写一个WS-Client筛选器来转发来自父请求的头?,scala,playframework,playframework-2.6,Scala,Playframework,Playframework 2.6,如果我有一个名为HomeController的控制器,它接收一个带有标题X-foo:Bar的请求,如GET/foo,我想创建一个WS-client过滤器,该过滤器将在上下文中读取RequestHeader,并将标题值复制到传出的WS-request 控制器示例: import play.api.libs.ws.{StandaloneWSRequest,WSClient,WSRequest,WSRequestExecutor,WSRequestFilter} 导入play.api.mvc_ 导入s

如果我有一个名为
HomeController
的控制器,它接收一个带有标题
X-foo:Bar
的请求,如
GET/foo
,我想创建一个WS-client过滤器,该过滤器将在上下文中读取
RequestHeader
,并将标题值复制到传出的WS-request

控制器示例:

import play.api.libs.ws.{StandaloneWSRequest,WSClient,WSRequest,WSRequestExecutor,WSRequestFilter}
导入play.api.mvc_
导入scala.concurrent.ExecutionContext
@独生子女
类HomeController@Inject()(cc:ControllerComponents,
myWsClient:myWsClient)
(隐式executionContext:executionContext)
扩展抽象控制器(cc){
def index=Action.async{
myWsClient.url(“http://www.example.com")
.get()
.map(res=>Ok(s“${res.status}${res.statusText}”)(executionContext)
}
}
WSClient周围引入过滤器的包装器:

@Singleton
类MyWSClient@Inject()(委托:WSClient,fooBarFilter:fooBarFilter)扩展了WSClient{
重写基础定义[T]:T=delegate.undernative.asInstanceOf[T]
覆盖def url(url:字符串):WSRequest={
delegate.url(url)
.withRequestFilter(fooBarFilter)
}
覆盖def close():Unit=delegate.close()
}
最后是WS-filter本身:

@Singleton
类FooBarFilter扩展了WSRequestFilter{
覆盖def应用(执行器:WSRequestExecutor):WSRequestExecutor={
(请求:StandaloneWSRequest)=>{
request.addHttpHeaders((“X-Foo”,“”)//在此处插入正确的值!
执行人申请(请求)
}
}
}
最后,我们期望请求
GEThttp://www.example.com
包含标题
X-Foo:Bar

使其更有趣的特殊要求包括:

  • 您可以修改
    MyWsClient
  • 您可以修改
    FooBarFilter
  • 如果有帮助,您可以创建HTTP控制器筛选器(
    play.api.mvc.(基本)筛选器
  • 您可以创建其他类/对象等
  • 您不能修改控制器(因为在我们的情况下,我们不能期望修改所有现有控制器)
  • 即使在控制器和WSClient调用之间有一个“服务”层,并且不涉及到处传递对象,该解决方案也应该可以工作
  • 该解决方案可以改变其他Play/Akka机制,比如默认的调度程序

我还没有试着把它放到实际的代码中,并测试它是否有效,但这里有一个想法:它看起来像是从第2.1节开始的。所以你可以尝试做的是像这样更改
MyWSClient
FooBarFilter

@Singleton
class MyWSClient @Inject()(delegate: WSClient) extends WSClient {
  override def underlying[T]: T = delegate.underlying.asInstanceOf[T]

  override def url(url: String): WSRequest = {
    val fooHeaderOption = Http.Context.current()._requestHeader().headers.get(FooHeaderFilter.fooHeaderName)
    val baseRequest = delegate.url(url)
    if (fooHeaderOption.isDefined)
      baseRequest.withRequestFilter(new FooHeaderFilter(fooHeaderOption.get))
    else
      baseRequest
  }

  override def close(): Unit = delegate.close()

  class FooHeaderFilter(headerValue: String) extends WSRequestFilter {

    import FooHeaderFilter._

    override def apply(executor: WSRequestExecutor): WSRequestExecutor = {
      (request: StandaloneWSRequest) => {
        request.addHttpHeaders((fooHeaderName, headerValue))
        executor.apply(request)
      }
    }
  }

  object FooHeaderFilter {
    val fooHeaderName = "X-Foo"
  }

}
class HttpContextWrapperExecutorService(val delegateEc: ExecutorService) extends AbstractExecutorService {
  override def isTerminated = delegateEc.isTerminated

  override def awaitTermination(timeout: Long, unit: TimeUnit) = delegateEc.awaitTermination(timeout, unit)

  override def shutdownNow() = delegateEc.shutdownNow()

  override def shutdown() = delegateEc.shutdown()

  override def isShutdown = delegateEc.isShutdown

  override def execute(command: Runnable) = {
    val newContext = Http.Context.current.get()
    delegateEc.execute(() => {
      val oldContext = Http.Context.current.get() // might be null!
      Http.Context.current.set(newContext)
      try {
        command.run()
      }
      finally {
        Http.Context.current.set(oldContext)
      }
    })
  }
}


class HttpContextExecutorServiceConfigurator(config: Config, prerequisites: DispatcherPrerequisites) extends ExecutorServiceConfigurator(config, prerequisites) {
  val delegateProvider = new ForkJoinExecutorConfigurator(config.getConfig("fork-join-executor"), prerequisites)

  override def createExecutorServiceFactory(id: String, threadFactory: ThreadFactory): ExecutorServiceFactory = new ExecutorServiceFactory {
    val delegateFactory = delegateProvider.createExecutorServiceFactory(id, threadFactory)

    override def createExecutorService: ExecutorService = new HttpContextWrapperExecutorService(delegateFactory.createExecutorService)
  }
}
想法很简单:当创建
WSRequest
时,从
Http.Context.current()
提取头,并使用
WSRequestFilter

更新:使其在Scala API中工作

正如在评论中指出的,这种方法在Scala API中不起作用,因为
Http.Context
未初始化且未在线程之间传递。要使其工作,需要更高级别的魔法。即,您需要:

  • 简单:一个过滤器将初始化Scala处理的请求的Http.Context
  • 硬:重写的
    ExecutorServiceConfigurator
    以创建自定义
    ExecutorService
    ,该服务将在线程开关之间传递
    Http.Context
  • 过滤器很简单:

    import play.mvc._
    @Singleton
    class HttpContextFilter @Inject()(implicit ec: ExecutionContext) extends EssentialFilter {
      override def apply(next: EssentialAction) = EssentialAction { request => {
        Http.Context.current.set(new Http.Context(new Http.RequestImpl(request), null))
        next(request)
      }
      }
    }
    
    然后将其添加到application.conf中的
    play.filters.enabled

    最难的部分是这样的:

    @Singleton
    class MyWSClient @Inject()(delegate: WSClient) extends WSClient {
      override def underlying[T]: T = delegate.underlying.asInstanceOf[T]
    
      override def url(url: String): WSRequest = {
        val fooHeaderOption = Http.Context.current()._requestHeader().headers.get(FooHeaderFilter.fooHeaderName)
        val baseRequest = delegate.url(url)
        if (fooHeaderOption.isDefined)
          baseRequest.withRequestFilter(new FooHeaderFilter(fooHeaderOption.get))
        else
          baseRequest
      }
    
      override def close(): Unit = delegate.close()
    
      class FooHeaderFilter(headerValue: String) extends WSRequestFilter {
    
        import FooHeaderFilter._
    
        override def apply(executor: WSRequestExecutor): WSRequestExecutor = {
          (request: StandaloneWSRequest) => {
            request.addHttpHeaders((fooHeaderName, headerValue))
            executor.apply(request)
          }
        }
      }
    
      object FooHeaderFilter {
        val fooHeaderName = "X-Foo"
      }
    
    }
    
    class HttpContextWrapperExecutorService(val delegateEc: ExecutorService) extends AbstractExecutorService {
      override def isTerminated = delegateEc.isTerminated
    
      override def awaitTermination(timeout: Long, unit: TimeUnit) = delegateEc.awaitTermination(timeout, unit)
    
      override def shutdownNow() = delegateEc.shutdownNow()
    
      override def shutdown() = delegateEc.shutdown()
    
      override def isShutdown = delegateEc.isShutdown
    
      override def execute(command: Runnable) = {
        val newContext = Http.Context.current.get()
        delegateEc.execute(() => {
          val oldContext = Http.Context.current.get() // might be null!
          Http.Context.current.set(newContext)
          try {
            command.run()
          }
          finally {
            Http.Context.current.set(oldContext)
          }
        })
      }
    }
    
    
    class HttpContextExecutorServiceConfigurator(config: Config, prerequisites: DispatcherPrerequisites) extends ExecutorServiceConfigurator(config, prerequisites) {
      val delegateProvider = new ForkJoinExecutorConfigurator(config.getConfig("fork-join-executor"), prerequisites)
    
      override def createExecutorServiceFactory(id: String, threadFactory: ThreadFactory): ExecutorServiceFactory = new ExecutorServiceFactory {
        val delegateFactory = delegateProvider.createExecutorServiceFactory(id, threadFactory)
    
        override def createExecutorService: ExecutorService = new HttpContextWrapperExecutorService(delegateFactory.createExecutorService)
      }
    }
    
    并在使用时注册

    akka.actor.default-dispatcher.executor = "so.HttpContextExecutorServiceConfigurator"
    

    不要忘记用真实的包更新“
    so
    ”。另外,如果您使用更多的自定义执行器或
    ExecutionContext
    s,您也应该修补(包装)它们,以便在异步调用中传递
    Http.Context

    这不起作用(至少对于Scala API),因为
    Http.Context.current()调用
    方法时,抛出一个
    运行时异常
    ,并显示一条消息:
    “这里没有可用的HTTP上下文。”
    @Alejandro,是的,对于Scala API,看起来您需要一个更核心的魔法(实际上是Java API的一个副本)要使其正常工作,请参阅我的更新。
    Http.Context
    从play 2.7开始就被弃用了。无论如何,这对scala来说是一个相当肮脏的攻击。它始终是一个java api。