【问题标题】:Where do file upload streams get the content from?文件上传流从哪里获取内容?
【发布时间】:2013-12-13 02:33:55
【问题描述】:

我有一个关于文件上传的问题,这与它的工作原理有关,而不是代码问题。我上网查了一下,没有找到合适的答案。

我有一个在 tomcat 上运行的 Web 应用程序,它处理文件上传(通过 servlet)。假设我现在要上传大文件(> 1 Gb)。我的理解是,一旦整个文件被实际传输,HTTP 请求的多部分内容就可以在我的 servlet 中使用。

我的问题是请求的内容实际存储在哪里?当一个人调用HttpServletRequest.getParts() 时,InputStreamPart 对象上可用。但是,流从哪里读取? Tomcat 是否将其存储在某个地方?

我想这可能不够清楚,所以我会根据你的 cmets 更新帖子,如果有的话。

谢谢

【问题讨论】:

  • 这不是一个直接的答案,但对于这种大小的文件,HTTP 并不是最好的协议。 Base 64 编码进一步增加了文件大小,并且不支持恢复中断的上传。对于快速网络上的企业对企业的东西来说,这并没有那么糟糕,但如果家庭用户在上传数小时的过程中出现短暂的网络中断,他们将不会高兴。

标签: java http tomcat file-upload tcp


【解决方案1】:

Tomcat 将Parts 存储在“X:\some\path\Tomcat 7.0\temp”(/some/path/apache-tomcat-7.0.x/temp)目录中。

在解析多部分请求时,如果单个部分的大小超过阈值,则会为该部分创建一个临时文件。

当所有部分的传输完成后,您的 servlet/jsp 将被调用。

当请求被销毁时,所有临时文件也会被删除。

如果您对多部分解析阶段感兴趣,请查看 apache commons-fileupload(特别是 ServletFileUpload.parseRequest()),tomcat 是基于它的一个变体

更新

您可以将其配置为 java arg,即在 windows 中:

【讨论】:

  • 我找不到可以配置路径的位置。你知道吗?
  • 在 web.xml 或 @MultiPartConfig 注解中使用 元素并设置“位置”要容易得多。这允许您设置将在 每个 servlet 基础上写入文件的目录的路径,而不是更改整个服务器的配置。
  • “当请求被销毁时,所有临时文件也被删除”。这在任何地方都有记录吗?
  • @GregBrow 我不知道它是否以及在哪里记录,我认为它不是规范的一部分,它取决于实现。对于 Tomcat,有两种不同的“干净”机制:Request.recycle() 和 FileCleaningTracker(见下一条评论,太长了)
【解决方案2】:

InputStream 通常会从多部分框架在请求期间创建的临时文件中读取。临时文件通常存储在应用程序服务器的临时区域 - 由 servlet 上下文属性 javax.servlet.context.tempdir 指定。在 Tomcat 中,它位于$CATALINA_HOME/work 之下。请求完成后,该文件将被删除。

对于小文件大小,多部分框架可能会将整个上传保存在内存中 - 在这种情况下,InputStream 将直接从内存中读取。

如果您使用 Spring 的 CommonsMultipartResolver,那么您可以通过 maxInMemorySize 属性设置内存中允许的最大上传大小。如果上传的文件比这个大,那么它将作为临时文件存储在磁盘上。

【讨论】:

  • 感谢您的回答。我对框架的“之前”更感兴趣。我猜他们都依赖于底层的 servlet api 方法(至少现在是这样)。当您从流中读取数据时,数据必须位于“servlet api 实现中的某个位置”。然后,tomcat 是否会在框架启动之前为它自己存储的文件提供流?希望这是有道理的。
  • 我猜 Tomcat 只是将整个 HttpServletRequest 传递给多部分框架,然后该框架依赖于 HttpServletRequest.getInputStream(),然后它将读取请求的主体(大概从低-水平插座)。不过,恐怕我不能确定。需要深入研究那个源代码。
  • 如果内容是流式传输的,那么为什么浏览器会等待整个文件上传?
  • @benjamin.d - 也许浏览器正在等待服务器返回响应?应用收到图像后如何处理图像?如果涉及耗时处理,则需要在单独的线程中处理,以便服务器可以立即返回“获取文件”响应。
  • @benjamin.d 您应该阅读有关 MultipartConfig(或 )的信息。您可以控制文件是写入磁盘还是仅缓冲在内存中。
【解决方案3】:

我认为我们应该退后一步,考虑一下网络基础架构。首先HTTP传输文本数据,所以二进制信息编码为base 64,这样数据就不会乱了。这最终会导致大量数据,从而产生了多部分形式,它将数据分解为带有特殊标记的编码文本的部分,允许服务器将所有内容组装在一起。但要使用这些数据,我们必须先对其进行解码,为此我必须使用表单的多个部分。

[休息一下,让我们可以呼吸]

继续,因此浏览器需要发送大量数据(如您在示例中提到的 1GB),此数据用 base64 编码,然后用标记分成几部分(多部分形式),然后浏览器开始发送将这些片段发送到服务器,但服务器仅在完成接收处理 HTTP 请求后才返回 HTTP 响应(或者如果发生超时,则会在浏览器屏幕上引发错误)。

这里可以假设是Tomcat可以(我没有检查内部)开始解码已经到达的多部分的每个部分(从临时文件或从内存中)传递输入流给用户,因为输入流读取是一个阻塞操作,服务器将等待下一条数据传递给 Tomcat,然后将它传递给正在处理数据的程序。

一旦所有数据都到达服务器,程序将准备 Tomcat 将返回给浏览器的响应,完成 HTTP 请求-响应周期关闭连接(因为 HTTP 是无连接协议)。

希望对你有帮助:)

【讨论】:

    【解决方案4】:

    Tomcat 遵循 Servlet 3.0 规范,该规范允许您指定诸如多部分“部分”在(临时)存储在磁盘上之前的大小、写入临时文件的位置、最大大小一个文件,以及整个请求的最大大小是多少。您可以找到有关配置分段上传(在 Tomcat 或任何其他符合规范 3.0 的服务器中)herehere 的各种有用信息。

    Tomcat 的实现细节并不是很相关:它遵循规范。如果要上传的文件小于设置的阈值,那么您应该能够从内存中读取文件的字节(即不涉及磁盘)。如果文件较大,则将首先(完整地)将其写入磁盘,然后您可以从容器中获取字节。

    因此,如果您想接收 1GiB 文件并且没有那种可用内存(我不建议允许客户端在每次上传时使用 1GiB 的上传数据填充您的堆......如果您只需同时启动几个 1GiB 上传,您就可以了),然后 Tomcat(或您正在使用的任何容器)会将文件(再次,完整地)读取到磁盘上,当您的 servlet 获得控制权时,您可以读取字节从那个文件返回。

    请注意,容器必须在您的任何代码真正运行之前处理整个多部分请求。这是为了防止你通过部分阅读请求的InputStream 或类似的东西来破坏任何东西。处理多部分请求并非易事,而且很容易出错。

    如果您希望能够流式处理大型文件(例如,可以串行处理的大型 XML 文件),那么您将需要自己处理多部分解析。这样,您不需要大量的堆来缓冲文件,也不需要在开始处理文件之前将文件存储在磁盘上。 (如果这是您的用例,我建议使用 HTTP PUT 或 HTTP POST,而不是使用多部分请求。)

    (值得一提的是,在多部分处理的任何规范中甚至都没有提到 base64 编码。一些人在这里提到了 base64,但我从未见过标准的 Web 客户端使用 base64 来使用 multipart/form 上传文件-数据。HTTP 可以很好地处理二进制上传,谢谢。)

    【讨论】:

      【解决方案5】:

      这里是

      1. 用户浏览器组成http多部分请求
      2. 用户操作系统的 TCP/IP 堆栈将它们分成数据包
      3. 互联网上的路由器将这些数据包传递到您的服务器
      4. 服务器操作系统的 Tcp/ip 堆栈取回有效负载并传递它们 到 tcp 端口监听器
      5. Tomcat http 连接器解码来自 tcp 数据的 http post 请求 (源代码是 https://github.com/apache/tomcat/tree/trunk/java/org/apache/coyote)
      6. Tomcat http 连接器包装一个 Http 请求并最终转发到 你的 servlet (https://github.com/apache/tomcat/blob/trunk/java/org/apache/catalina/connector/Request.java)
      7. 在您的代码读取 Http Request 内容之前和期间,tomcat 会在内部缓冲 http 请求正文
      8. 在调用 request.getParts() (https://github.com/apache/tomcat/blob/trunk/java/org/apache/catalina/connector/Request.java#L2561) 之前,Tomcat 不会解析多个部件主体,因此调用之前没有部件的临时文件。
      9. Tomcat 将上传的文件存储到您的 servlet 代码中由 @MultipartConfig 注释指向的位置,除非您的代码没有提供它并且设置了 allowCasualMultipartParsing (http://tomcat.apache.org/tomcat-7.0-doc/config/context.html#Common_Attributes)
      10. 考虑到 allowCasualMultipartParsing 默认为 false,您不必担心 tomcat 存储文件的位置,尽管它很容易挖掘出来。

      我提到 1~5 是因为了解 request.getInputStream() 返回的流很重要,这在 Servlet 3.x request.getParts() 功能之前是必需的。通常情况下,tomcat 会很快将请求传递给 web 应用程序,不需要等待客户端完成上传,因此 tomcat 不需要缓冲大量数据。在 JSR-000315 获得批准之前,我已经离开 java 服务器端了几年:-)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-19
        • 1970-01-01
        • 2012-12-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-29
        相关资源
        最近更新 更多