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机制,比如默认的调度程序
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
未初始化且未在线程之间传递。要使其工作,需要更高级别的魔法。即,您需要:
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。