【问题标题】:Possible to use Java NIO in Play 2 framework to POST and GET files simultaneously?可以在 Play 2 框架中使用 Java NIO 同时 POST 和 GET 文件吗?
【发布时间】:2013-01-11 11:42:27
【问题描述】:

我想使用 Play2 来实现一个接收文件 POST 的服务器,在收到文件时对其进行处理,并在 POST 仍在传输文件时将结果发送到另一个用户的 GET 请求。 Play2可以做到这一点吗?我已经使用 Play2 对用户进行身份验证,所以我想使用 Play2 来处理此交易的身份验证。我使用 Java,但如果需要,我可以使用 Scala。如果是这样,我应该调查什么?如果没有,我想我需要研究一些 Java NIO 框架并实现另一个服务器。

【问题讨论】:

  • Play 绝对可以,但这真的是您想要的吗?如果没有人获取正在上传的文件怎么办?它只是“呆在那里”直到有人来拿它吗?如果有人在排队下载文件,如果没有人上传文件怎么办?下载会等待吗?
  • 上传和下载将在两个当前在线用户之间进行。一旦该用户准备好上传,下载通知将发送给其他用户。如果在一定时间内没有开始下载/上传,整个操作将超时。我们不希望在服务器上为该传输操作保留保存的文件。
  • 我看不出这怎么可能,因为当我从 Play 获取文件时,整个文件已从用户上传到服务器。我看不到获取上传文件的一部分并将其传递给另一个用户的 GET 请求的方法。
  • 您必须使用酷炫的 Play 2 新功能,例如 Iteratees。我自己对它们了解不多,但如果我有时间,我会在这周尝试一下。这里有一些起点。您需要写一个custom PartHandler,并且您需要一种方法将这些数据提供给另一个用户。类似于this demo,向客户端发送音频流。

标签: java playframework-2.0 nio


【解决方案1】:

我认为这在 Java 中是不可能的,但我已经设法组合了一个简单的 Scala 版本。您仍然可以将 Java 用于项目的所有其他部分。

但请注意,这只是一个粗略的演示,专注于“同时上传和下载”部分,而忽略了其他所有内容。我什至不确定这是不是正确的方法,但它似乎有效。

基本上,您要做的是使用Concurrent.broadcast 创建输入/输出流对。输出部分 (Enumeratee) 被流式传输到客户端。然后,您需要一个自定义的PartHandler,它会在上传数据到达时将其输入到输入部分 (Concurrent.Channel)。

请注意,要让本示例正常运行,您必须先进入下载页面,然后开始上传文件。

示例 Application.scala

package controllers

import play.api._
import libs.iteratee.{Enumerator, Input, Iteratee, Concurrent}
import play.api.mvc._
import scala.collection.concurrent

object Application extends Controller {
  val streams = new concurrent.TrieMap[String,(Enumerator[Array[Byte]], Concurrent.Channel[Array[Byte]])]

  def index = Action {
    Ok(views.html.index())
  }

  def download(id: String) = Action {
    val (fileStream, fileInput) = Concurrent.broadcast[Array[Byte]]
    streams += id -> (fileStream, fileInput)
    Ok.stream {
      fileStream.onDoneEnumerating(streams -= id)
    }
  }

  def upload = Action(parse.multipartFormData(myPartHandler)) {
    request => Ok("banana!")
  }

  def myPartHandler: BodyParsers.parse.Multipart.PartHandler[MultipartFormData.FilePart[Result]] = {
    parse.Multipart.handleFilePart {
      case parse.Multipart.FileInfo(partName, filename, contentType) =>
        val (thisStream, thisInput) = streams(partName)

        Iteratee.fold[Array[Byte], Concurrent.Channel[Array[Byte]]](thisInput) { (inp, data) =>
          inp.push(data)
          inp
        }.mapDone { inp =>
          inp.push(Input.EOF)
          Ok("upload Done")
        }
    }
  }
}

示例 index.scala.html

<p>
    <a href="@routes.Application.download("123456")">Download</a>
</p>

<form action="@routes.Application.upload" method="post" enctype="multipart/form-data">
    <input type="file" name="123456">
    <input type="submit">
</form>

【讨论】:

  • 感谢您的帮助。我还没有升级到 Play 2.1,但是我使用你与 PipedInputStream 和 PipedOutputStream 分享的帖子让它工作了。一旦我使用 Play 2.1,我一定会尝试 Concurrent.broadcast。
  • 在我的实现中,如果发送发生在接收之前,Play 将开始缓冲数据。它发生在您的实施中吗?你知道告诉 Play 阻止发送吗?我的理想行为是先发送,但 Play 会阻止它。一旦接收方开始接收,发送将开始上传。我尝试调整枚举器的块大小和 PipedInputStream 的管道大小,但没有帮助。
  • 您可以开始发送,只要streams 映射中有适当的条目。在我的示例中,它只是在下载器端创建的,但您也可以在两侧检查它是否已经存在,如果不存在,则创建它。但再说一遍:我的例子只关注流媒体的东西。你真的需要像这样进行检查,如果有多个人从同一个id上传或下载。
  • 我确实实现了使用不同ID下载的机制。我想我知道我的问题的答案。在我单击链接后,浏览器会自动下载文件,即使我没有选择保存位置。我对这种行为感到困惑,认为上传在我下载之前就开始了。现在我面临另一个问题。下载流通常在 50MB 后冻结而未完成下载。我升级到 Play 2.1 并使用您的实现,但仍然看到同样的问题。我需要调查一下。有什么想法吗?
  • 我用一个 1 GB 的文件测试了代码,它工作得很好。我不确定文件部分有多大,但如果它们很小,它可能有助于将它们重新分成更大的部分(例如,100 kB 或 1 MB),following this example
猜你喜欢
  • 2016-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 1970-01-01
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多