将每个发出HTTP请求的akka​​流链接在一起

时间:2016-09-26 13:21:57

标签: scala akka-stream akka-http

我有一个安静的博客服务器(http://jsonplaceholder.typicode.com)响应这两个URI

/posts/{id}           : get a blog post by ID
/comments?postId={id} : get all the comments on a blog by the blog's id

从一批博客ID开始,我想创建一个执行以下步骤的流程:

  1. 点击/posts端点获取帖子的json
  2. 将json反序列化为Blog案例类
  3. 点击每个博客的/comments端点,然后获取该帖子的JSON评论列表
  4. 将注释JSON反序列化为Comment案例对象列表
  5. 对评论(统计信息收集或垃圾邮件分析)进行一些处理
  6. 是的,我知道如果我有博客ID,我可以直接跳到第3步。假装我不能

    我想在步骤1中向服务器发送一堆HTTP请求。为此,我使用cachedHostConnectionPool。以下是我到目前为止的情况:

    final case class Blog(id: Int, userId: Int, title: String, body: String) 
    final case class Comment(id: Int, postId: Int, name: String, email: String,  body: String)
    
    object AkkaStreamProcessor extends App {
      implicit val actorSystem = ActorSystem("blogProcessor")
      import actorSystem.dispatcher
      implicit val flowMaterializer = ActorMaterializer()
    
      private def getBlogUri(id: Integer): String = "/posts/" + id
      private def getCommentsUri(blog: Blog): String = "/comments?postId=" + blog.id
      private def parseBlogResponse(jsonResponse: String): Blog = Json.parse(jsonResponse).as[Blog]
      private def parseCommentsResponse(jsonResponse: String): List[Comment] = Json.parse(jsonResponse).as[List[Comment]]
    
      val pooledConnectionFlow = {
        val connectionSettings = ConnectionPoolSettings(actorSystem)
          .withMaxConnections(32)
          .withMaxOpenRequests(32)
          .withMaxRetries(3)
        Http().cachedHostConnectionPool[Int](host = "jsonplaceholder.typicode.com", settings = connectionSettings)
      }
    
      val source = Source(1 to 32)
      val fetchBlogsFlow = Flow[Int]
        .map((id: Int) => (getBlogUri(id),id))
        .map{ case(uri:String, id:Int) => (HttpRequest(method = HttpMethods.GET, uri = uri), id) }
        .via(pooledConnectionFlow)
        .map { case(response: Try[HttpResponse], id:Int) => handleBlogResponse(response, id) }
        .map((jsonText: Try[String]) => jsonText.map(j => parseBlogResponse(j)))
    
      val sink = Sink.foreach[Try[Blog]](blog => blog.map(b=> println(b)))
      source.via(fetchBlogsFlow).runWith(sink)
    
      private def handleBlogResponse(response: Try[HttpResponse], id: Int): Try[String] = {
        println(s"Received response for id $id on thread ${Thread.currentThread().getName}")
        response.flatMap((r: HttpResponse) => {
          r.status match {
            case StatusCodes.OK => {
              Success(Await.result(Unmarshal(r.entity).to[String], Duration.Inf))
            }
            case _ => Failure(new RuntimeException("Invalid response : " + r.status.toString()))
          }
        })
      }    
    }
    

    现在我想要的是创建另一个流程来执行第3步和第4步,我将在第一个流程之后链接。但是,我正在努力解决第一个流程中令人讨厌的Try[Blog]输出问题。如何将Try[Blog]传输到另一个HTTP请求中?有没有办法拆分管道,失败是单向的,成功又走向另一条道路?

    以下是我对第二个流程的内容,但我不确定如何在get上调用Try来进行链接工作:

    val processBlogsFlow = Flow[Try[Blog]]
      .map((tryBlog: Try[Blog]) => tryBlog.get)
      .map((blog: Blog) => (HttpRequest(method=HttpMethods.GET, uri=getCommentsUri(blog)), blog.id ))
      .via(pooledConnectionFlow)
    

1 个答案:

答案 0 :(得分:1)

处理Try非常好blog entry。在您的特定示例中,我将保留Try,以便您可以获得有关原始故障的信息:

def blogToTuple(blog : Blog) = 
  (HttpRequest(method=HttpMethods.GET, uri=getCommentsUri(blog)), blog.id )

val processBlogsFlow : Flow[Try[Blog], Try[HttpResponse], _] = 
  Flow[Try[Blog]]
    .map(_ map blogToTuple)
    .mapAsync(1) { _ match {
         case Success(req) => 
           Source.single(req).via(pooledConnectionFlow).runWith(Sink.head)
         case ex => Future { x }
       }
    }

现在Try可以传递给您的SinkSELECT event_cust.* FROM event_cust WHERE TimeValue([start_time]) >= CDate([Forms]![CustEventRptForm]![FromHour]) AND TimeValue([start_time]) <= CDate([Forms]![CustEventRptForm]![ToHour]) 可以报告任何错误消息并报告有效回复。