如果我有一个名为HomeController
的控制器收到GET /foo
请求,标题为X-Foo: Bar
,我想创建一个WS客户端过滤器来读取{{1}在上下文中,将标头值复制到传出的WS请求。
示例控制器:
RequestHeader
引入过滤器的WSClient包装器:
import play.api.libs.ws.{StandaloneWSRequest, WSClient, WSRequest, WSRequestExecutor, WSRequestFilter}
import play.api.mvc._
import scala.concurrent.ExecutionContext
@Singleton
class HomeController @Inject()(cc: ControllerComponents,
myWsClient: MyWSClient)
(implicit executionContext: ExecutionContext)
extends AbstractController(cc) {
def index = Action.async {
myWsClient.url("http://www.example.com")
.get()
.map(res => Ok(s"${res.status} ${res.statusText}"))(executionContext)
}
}
最后是WS过滤器本身:
@Singleton
class MyWSClient @Inject()(delegate: WSClient, fooBarFilter: FooBarFilter) extends WSClient {
override def underlying[T]: T = delegate.underlying.asInstanceOf[T]
override def url(url: String): WSRequest = {
delegate.url(url)
.withRequestFilter(fooBarFilter)
}
override def close(): Unit = delegate.close()
}
最后,期望请求@Singleton
class FooBarFilter extends WSRequestFilter {
override def apply(executor: WSRequestExecutor): WSRequestExecutor = {
(request: StandaloneWSRequest) => {
request.addHttpHeaders(("X-Foo", "<...>")) // INSERT CORRECT VALUE HERE!
executor.apply(request)
}
}
}
包含标题GET http://www.example.com
。
使这更有趣的特殊要求是:
X-Foo: Bar
类。MyWsClient
类FooBarFilter
。答案 0 :(得分:1)
我还没有尝试将其放入实际代码并测试它是否有效但这是一个想法:它看起来像Play 2.1 Http.Context is propagated even across async call。还有Http.Context._requestHeader。因此,您可以尝试更改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"
}
}
这个想法很简单:创建Http.Context.current()
时从WSRequest
中提取标题,然后使用WSRequestFilter
更新:在Scala API中使用
正如评论中指出的那样,这种方法在Scala API中不起作用,因为Http.Context
未初始化并且不在线程之间传递。为了使它工作,需要更高级别的魔法。即你需要:
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
困难的部分是这样的:
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"
不要忘记更新&#34; so
&#34;和你一起真正的包裹。此外,如果您使用更多自定义执行程序或ExecutionContext
,您也应该修补(换行)它们以便在异步调用中传递Http.Context
。