简体   繁体   English

在进行中的单个HTTP连接上同时流进和出数据

[英]Streaming data in and out simultaneously on a single HTTP connection in play

streaming data out of play, is quite easy. 串流播放数据非常简单。
here's a quick example of how I intend to do it (please let me know if i'm doing it wrong): 这是我打算如何做的一个简单示例(请告诉我是否做错了):

def getRandomStream = Action { implicit req =>

  import scala.util.Random
  import scala.concurrent.{blocking, ExecutionContext}
  import ExecutionContext.Implicits.global

  def getSomeRandomFutures: List[Future[String]] = {
    for {
      i <- (1 to 10).toList
      r = Random.nextInt(30000)
    } yield Future {
      blocking {
        Thread.sleep(r)
      }
      s"after $r ms. index: $i.\n"
    }
  }

  val enumerator = Concurrent.unicast[Array[Byte]] {
    (channel: Concurrent.Channel[Array[Byte]]) => {
      getSomeRandomFutures.foreach {
        _.onComplete {
          case Success(x: String) => channel.push(x.getBytes("utf-8"))
          case Failure(t) => channel.push(t.getMessage)
        }
      }
      //following future will close the connection
      Future {
        blocking {
          Thread.sleep(30000)
        }
      }.onComplete {
        case Success(_) => channel.eofAndEnd()
        case Failure(t) => channel.end(t)
      }
    }
  }
  new Status(200).chunked(enumerator).as("text/plain;charset=UTF-8")
}

now, if you get served by this action, you'll get something like: 现在,如果您通过此操作获得服务,您将获得类似以下内容的信息:

after 1757 ms. index: 10.
after 3772 ms. index: 3.
after 4282 ms. index: 6.
after 4788 ms. index: 8.
after 10842 ms. index: 7.
after 12225 ms. index: 4.
after 14085 ms. index: 9.
after 17110 ms. index: 1.
after 21213 ms. index: 2.
after 21516 ms. index: 5.

where every line is received after the random time has passed. 在随机时间过去之后接收每一行的位置。
now, imagine I want to preserve this simple example when streaming data from the server to the client, but I also want to support full streaming of data from the client to the server. 现在,想象一下我想在将数据从服务器流传输到客户端时保留这个简单的示例,但是我还想支持从客户端到服务器的完整数据流。

So, lets say i'm implementing a new BodyParser that parses the input into a List[Future[String]] . 因此,可以说我正在实现一个新的BodyParser ,它将输入解析为List[Future[String]] this means, that now, my Action could look like something like this: 这意味着,现在,我的Action可能看起来像这样:

def getParsedStream = Action(myBodyParser) { implicit req =>

  val xs: List[Future[String]] = req.body

  val enumerator = Concurrent.unicast[Array[Byte]] {
    (channel: Concurrent.Channel[Array[Byte]]) => {
      xs.foreach {
        _.onComplete {
          case Success(x: String) => channel.push(x.getBytes("utf-8"))
          case Failure(t) => channel.push(t.getMessage)
        }
      }
      //again, following future will close the connection
      Future.sequence(xs).onComplete {
        case Success(_) => channel.eofAndEnd()
        case Failure(t) => channel.end(t)
      }
    }
  }
  new Status(200).chunked(enumerator).as("text/plain;charset=UTF-8")
}

but this is still not what I wanted to achieve. 但这仍然不是我想要实现的。 in this case, I'll get the body from the request only after the request was finished, and all the data was uploaded to the server. 在这种情况下,只有请求完成 ,我才会从请求中获取正文,并将所有数据上传到服务器。 but I want to start serving request as I go. 但我想立即开始提供请求。 a simple demonstration, would be to echo any received line back to the user, while keeping the connection alive. 一个简单的演示是将所有收到的线路回传给用户,同时保持连接状态。

so here's my current thoughts: 所以这是我目前的想法:
what if my BodyParser would return an Enumerator[String] instead of List[Future[String]] ? 如果我的BodyParser返回Enumerator[String]而不是List[Future[String]]怎么办?
in this case, I could simply do the following: 在这种情况下,我可以简单地执行以下操作:

def getParsedStream = Action(myBodyParser) { implicit req =>
  new Status(200).chunked(req.body).as("text/plain;charset=UTF-8")
}

so now, i'm facing the problem of how to implement such a BodyParser . 所以现在,我面临着如何实现这样的BodyParser being more precise as to what exactly I need, well: 关于我到底需要什么更精确,好吧:
I need to receive chunks of data to parse as a string, where every string ends in a newline \\n (may contain multiple lines though...). 我需要接收大块数据以字符串形式进行解析,其中每个字符串都以换行符\\n结尾(尽管可能包含多行...)。 every "chunk of lines" would be processed by some (irrelevant to this question) computation, which would yield a String , or better, a Future[String] , since this computation may take some time. 每个“行块”都将通过某种计算(与该问题无关)进行处理,这将产生String或更佳的Future[String] ,因为此计算可能需要一些时间。 the resulted strings of this computation, should be sent to the user as they are ready, much like the random example above. 计算得出的字符串应在准备就绪时发送给用户,就像上面的随机示例一样。 and this should happen simultaneously while more data is being sent. 当发送更多数据时,这应该同时发生。

I have looked into several resources trying to achieve it, but was unsuccessful so far. 我已经研究了几种资源来实现它,但是到目前为止还没有成功。 eg scalaQuery play iteratees -> it seems like this guy is doing something similar to what I want to do, but I couldn't translate it into a usable example. 例如scalaQuery播放iteratees- >看来这个家伙正在做与我想做的事情类似的事情,但是我无法将其转换为可用的示例。 (and the differences from play2.0 to play2.2 API doesn't help...) (而且play2.0和play2.2 API的区别无济于事...)

So, to sum it up: Is this the right approach (considering I don't want to use WebSockets )? 因此,总而言之:这是正确的方法吗(考虑到我不想使用WebSockets )? and if so, how do I implement such a BodyParser ? 如果是的话,如何实现这样的BodyParser

EDIT: 编辑:

I have just stumble upon a note on the play documentation regarding this issue, saying: 我刚刚在播放文档中偶然发现了有关此问题的注释,说:

Note: It is also possible to achieve the same kind of live communication the other way around by using an infinite HTTP request handled by a custom BodyParser that receives chunks of input data, but that is far more complicated. 注意:也可以通过使用由接收输入数据块的自定义BodyParser处理的无限HTTP请求来以其他方式实现相同的实时通信,但这要复杂得多。

so, i'm not giving up, now that I know for sure this is achievable. 所以,我现在不放弃,因为我确定这是可以实现的。

What you want to do isn't quite possible in Play. 您想在Play中做的事不太可能。

The problem is that Play can't start sending a response until it has completely received the request. 问题在于,在Play完全接收到请求之前,Play无法开始发送响应。 So you can either receive the request in its entirety and then send a response, as you have been doing, or you can process requests as you receive them (in a custom BodyParser ), but you still can't reply until you've received the request in its entirety (which is what the note in the documentation was alluding to - although you can send a response in a different connection). 所以,你可以接收请求的全部,然后发送一个响应,因为你一直在做, 或者你接受他们(在自定义的,你可以处理请求BodyParser ),但您仍然无法答复,直到你收到整个请求(这是文档中的注释所暗示的内容-尽管您可以在其他连接中发送响应)。

To see why, note that an Action is fundamentally a (RequestHeader) => Iteratee[Array[Byte], SimpleResult] . 要了解原因,请注意, Action从根本上是(RequestHeader) => Iteratee[Array[Byte], SimpleResult] At any time, an Iteratee is in one of three states - Done , Cont , or Error . 在任何时候,一个Iteratee是三种状态之一- DoneCont ,或Error It can only accept more data if it's in the Cont state, but it can only return a value when it's in the Done state. 如果处于Cont状态,它只能接受更多数据,但是只有处于Done状态时,它才能返回值。 Since that return value is a SimpleResult (ie, our response), this means there's a hard cut off from receiving data to sending data. 由于该返回值是SimpleResult (即我们的响应),因此这意味着从接收数据到发送数据之间存在着硬性的隔off。

According to this answer , the HTTP standard does allow a response before the request is complete, but most browsers don't honor the spec, and in any case Play doesn't support it, as explained above. 根据此答案 ,HTTP标准确实允许在请求完成之前做出响应,但是如上所述,大多数浏览器都不遵循该规范,并且无论如何Play都不支持该规范。

The simplest way to implement full-duplex communication in Play is with WebSockets, but we've ruled that out. 在Play中实现全双工通信的最简单方法是使用WebSocket,但是我们已经排除了这一点。 If server resource usage is the main reason for the change, you could try parsing your data with play.api.mvc.BodyParsers.parse.temporaryFile , which will save the data to a temporary file, or play.api.mvc.BodyParsers.parse.rawBuffer , which will overflow to a temporary file if the request is too large. 如果服务器资源使用是更改的主要原因,则可以尝试使用play.api.mvc.BodyParsers.parse.temporaryFile解析数据,该数据会将数据保存到临时文件或play.api.mvc.BodyParsers.parse.rawBuffer ,如果请求太大,它将溢出到一个临时文件。

Otherwise, I can't see a sane way to do this using Play, so you may want to look at using another web server. 否则,我看不到使用Play进行此操作的明智方法,因此您可能需要考虑使用其他Web服务器。

"Streaming data in and out simultaneously on a single HTTP connection in play" “在进行中的单个HTTP连接上同时流送数据进出”

I haven't finished reading all of your question, nor the code, but what you're asking to do isn't available in HTTP. 我还没有阅读完所有问题,也没有阅读代码,但是您要执行的操作在HTTP中不可用。 That has nothing to do with Play. 这与Play无关。

When you make a web request, you open a socket to a web server and send "GET /file.html HTTP/1.1\\n[optional headers]\\n[more headers]\\n\\n" 发出Web请求时,打开Web服务器的套接字并发送“ GET /file.html HTTP / 1.1 \\ n [可选标头] \\ n [更多标头] \\ n \\ n”

You get a response after (and only after) you have completed your request (optionally including a request body as part of the request). 您将在完成请求之后(并且仅在此之后)得到响应(可以选择将请求正文作为请求的一部分)。 When and only when the request and response are finished, in HTTP 1.1 (but not 1.0) you can make a new request on the same socket (in http 1.0 you open a new socket). 当且仅当请求响应完成时,才可以在HTTP 1.1(而不是1.0)中在同一套接字上发出新请求(在HTTP 1.0中,打开一个新套接字)。

It's possible for the response to "hang" ... this is how web chats work. 响应“挂起”是可能的...这就是网络聊天的工作方式。 The server just sits there, hanging onto the open socket, not sending a response until someone sends you a message. 服务器只是坐在那里,挂在打开的插座上,直到有人向您发送消息后才发送响应。 The persistent connection to the web server eventually provides a response when/if you receive a chat message. 当/如果您收到聊天消息,则到Web服务器的持久连接最终会提供响应。

Similarly, the request can "hang." 同样,请求可以“挂起”。 You can start to send your request data to the server, wait a bit, and then complete the request when you receive additional user input. 您可以开始将请求数据发送到服务器,稍等片刻,然后在收到其他用户输入时完成请求。 This mechanism provides better performance than continually creating new http requests on each user input. 与在每个用户输入上连续创建新的http请求相比,此机制提供了更好的性能。 A server can interpret this stream of data as a stream of distinct inputs, even though that wasn't necessarily the initial intention of the HTTP spec. 服务器可以将此数据流解释为不同输入的流,即使这不一定是HTTP规范的初衷。

HTTP does not support a mechanism to receive part of a request, then send part of a response, then receive more of a request. HTTP不支持接收一部分请求,然后发送一部分响应,然后接收更多请求的机制。 It's just not in the spec. 只是不在规格范围内。 Once you've begun to receive a response, the only way to send additional information to the server is to use another HTTP request. 一旦开始接收响应,向服务器发送附加信息的唯一方法就是使用另一个HTTP请求。 You can use one that's already open in parallel, or you can open a new one, or you can complete the first request/response and issue an additional request on the same socket (in 1.1). 您可以使用已经并行打开的一个,也可以打开一个新的,或者可以完成第一个请求/响应并在同一套接字上发出另一个请求(在1.1中)。

If you must have asynchronous io on a single socket connection, you might want to consider a different protocol other than HTTP. 如果必须在单个套接字连接上具有异步io,则可能需要考虑HTTP以外的其他协议。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM