我一般都是Play框架的新手,以及如何在Scala中使用它。我想为大型Json对象构建代理。到目前为止,我已经实现了json存储在缓存中,如果它不存在,则从Web服务请求。
但是当两个请求进入时,针对相同的端点(webservice和path是相同的),只应执行一个调用,而另一个请求应等待第一个调用的结果。目前,每次请求都会对服务进行调用。
这是我的控制者:
@Singleton
class CmsProxyController @Inject()(val cmsService: CmsProxyService) extends Controller {
implicit def ec : ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
def header(path: String) = Action.async { context =>
cmsService.head(path) map { title =>
Ok(Json.obj("title" -> title))
}
}
def teaser(path: String) = Action.async { context =>
cmsService.teaser(path) map { res =>
Ok(res).as(ContentTypes.JSON)
}
}
}
这是服务:
trait CmsProxyService {
def head(path: String): Future[String]
def teaser(path: String): Future[String]
}
@Singleton
class DefaultCmsProxyService @Inject()(cache: CacheApi, cmsCaller: CmsCaller) extends CmsProxyService {
private val BASE = "http://foo.com"
private val CMS = "bar/rest/"
private val log = Logger("application")
override def head(path: String) = {
query(url(path), "$.payload[0].title")
}
override def teaser(path: String) = {
query(url(path), "$.payload[0].content.teaserText")
}
private def url(path: String) = s"${BASE}/${CMS}/${path}"
private def query(url: String, jsonPath: String): Future[String] = {
val key = s"${url}?${jsonPath}"
val payload = findInCache(key)
if (payload.isDefined) {
log.debug("found payload in cache")
Future.successful(payload.get)
} else {
val queried = parse(fetch(url)) map { json =>
JSONPath.query(jsonPath, json).as[String]
}
queried.onComplete(value => saveInCache(key, value.get))
queried
}
}
private def parse(fetched: Future[String]): Future[JsValue] = {
fetched map { jsonString =>
Json.parse(jsonString)
}
}
//retrieve the requested value from the cache or from ws
private def fetch(url: String): Future[String] = {
val body = findInCache(url)
if (body.isDefined) {
log.debug("found body in cache")
Future.successful(body.get)
} else {
cmsCaller.call(url)
}
}
private def findInCache(key: String): Option[String] = cache.get(key)
private def saveInCache(key: String, value: String, duration: FiniteDuration = 5.minutes) = cache.set(key, value, 5.minutes)
}
最后调用webservice:
trait CmsCaller {
def call(url: String): Future[String]
}
@Singleton
class DefaultCmsCaller @Inject()(wsClient: WSClient) extends CmsCaller {
import scala.concurrent.ExecutionContext.Implicits.global
//keep those futures which are currently requested
private val calls: Map[String, Future[String]] = TrieMap()
private val log = Logger("application")
override def call(url: String): Future[String] = {
if(calls.contains(url)) {
Future.successful("ok")
}else {
val f = doCall(url)
calls put(url, f)
f
}
}
//do the final call
private def doCall(url: String): Future[String] = {
val request = ws(url)
val response = request.get()
val mapped = mapResponse(response)
mapped.onComplete(_ => cmsCalls.remove(url))
mapped
}
private def ws(url: String): WSRequest = wsClient.url(url)
//currently executed with every request
private def mapResponse(f: Future[WSResponse]): Future[String] = {
f.onComplete(_ => log.debug("call completed"))
f map {res =>
val status = res.status
log.debug(s"ws called, response status: ${status}")
if (status == 200) {
res.body
} else {
""
}
}
}
}
我的问题是:如何才能执行一次调用webservice beeing?即使对同一目标有多个请求。我不想阻止它,另一个请求(不确定我是否在这里使用正确的词)将被告知已经有一个web服务呼叫。
头部和预告片的请求,请参阅控制器,只能对网络服务执行一次调用。
答案 0 :(得分:0)
使用Scala lazy关键字的简单回答
case class Request(value: String)
class RequestManager extends Actor {
var mayBeResult: Option[String] = None
var reqs = List.empty[(ActorRef, Request)]
def receive = {
case req: Request =>
context become firstReq
self ! req
}
def firstReq = {
case req: Request =>
process(req).onSuccess { value =>
mayBeResult = Some(value)
context become done
self ! "clear_pending_reqs"
}
context become processing
}
def processing = {
case req: Request =>
//queue requests
reqs = reqs ++ List(sender -> req)
}
def done = {
case "clear_pending_reqs" =>
reqs.foreach { case (sender, _) =>
//send value to the sender
sender ! value.
}
}
}
第一个请求将触发其余请求,所有其他请求将使用缓存结果。使用map和flatMap链接请求。
使用Actors的复杂答案
使用Actor对请求进行排队并缓存第一个成功请求json结果的结果。所有其他请求将读取第一个请求的结果。
uint sum = 0;
uint4 S;
for(int i = 0; i < row; i++)
{
S += convert_uint4(vload4(0, arr1) - vload4(0, arr2));
arr1 += offset1;
arr2 += offset2;
}
S.s01 = S.s01 + S.s23;
sum = S.s0 + S.s1;
处理第一个请求失败的情况。在上面的代码块中,如果第一个请求失败,那么actor将永远不会进入完成状态。
答案 1 :(得分:0)
我通过同步服务中的缓存解决了我的问题。我不确定这是否是一个优雅的解决方案,但它对我有用。
transform_iterator(const Functor& f, const Iterator& it) :
functor(f),
iterator(it)
{}
transform_iterator(Functor&& f, Iterator&& it) :
functor(f),
iterator(it)
{}
请注意,我在这里省略了特征UrlBuilder和CacheAccessor,因为它们很简单。