仅从播放框架

时间:2017-02-11 12:14:22

标签: json scala web-services playframework future

我一般都是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服务呼叫。

头部和预告片的请求,请参阅控制器,只能对网络服务执行一次调用。

2 个答案:

答案 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,因为它们很简单。