为什么Tomcat会将HEAD和GET请求的不同标头返回给我的RESTful API?

时间:2015-12-15 13:12:22

标签: http tomcat8 http-method chunked

我最初的目的是验证 HTTP分块传输。但偶然发现了这种不一致。

API旨在将文件返回给客户端。我对它使用HEADGET方法。 返回不同的标头。

对于GET,我得到了这些标题:(这是我的预期。)

Transfer-Encoding: chunked, no Content-Length.

对于HEAD,我会收到以下标题:

Content-Length: 1017118720, no Transfer-Encoding.

根据this threadHEADGET 应该返回相同的标题,但不一定

我的问题是:

如果使用Transfer-Encoding: chunked 因为文件被动态地提供给客户端而Tomcat服务器事先无法知道其大小 ,那么Tomcat如何知道{{ 1}}何时使用Content-Length方法? Tomcat只是干运行处理程序并计算所有文件字节数?为什么它不会返回相同的HEAD标题?

以下是使用 Spring Web MVC 实现的RESTful API:

Transfer-Encoding: chunked

添加1

在我的代码中,我没有明确添加任何标头,然后它必须是Tomcat,它会根据需要添加@RestController public class ChunkedTransferAPI { @Autowired ServletContext servletContext; @RequestMapping(value = "bootfile.efi", method = { RequestMethod.GET, RequestMethod.HEAD }) public void doHttpBoot(HttpServletResponse response) { String filename = "/bootfile.efi"; try { ServletOutputStream output = response.getOutputStream(); InputStream input = servletContext.getResourceAsStream(filename); BufferedInputStream bufferedInput = new BufferedInputStream(input); int datum = bufferedInput.read(); while (datum != -1) { output.write(datum); datum = bufferedInput.read(); } output.flush(); output.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } Content-Length标头。

那么,Tomcat决定发送哪些标头的规则是什么?

ADD 2

也许它与Tomcat的工作方式有关。我希望有人能在这里说清楚。否则,我将调试到Tomcat 8的源代码并共享结果。但这可能需要一段时间。

相关:

2 个答案:

答案 0 :(得分:2)

  

Tomcat只是干运行处理程序并计算所有文件字节吗?

是的,javax.servlet.http.HttpServlet.doHead()的默认实现就是这样做的。

你可以在HttpServlet.java中查看助手类NoBodyResponse,NoBodyOutputStream

DefaultServlet类(用于提供静态文件的Tomcat servlet)更明智。它能够发送正确的Content-Length值,以及为文件的子集(Range标头)提供GET请求。您可以使用

将请求转发到该servlet
  ServletContext.getNamedDispatcher("default").forward(request, response);

答案 1 :(得分:2)

虽然看起来很奇怪,但是仅在响应HEAD请求时发送大小并响应GET请求进行分块可能是有意义的,具体取决于必须的数据类型由服务器返回。

虽然你的API似乎提供了一个静态文件,但你也谈到动态创建的文件或数据,所以我将在这里进行一般性讨论(一般也适用于网络服务器)。

首先让我们看一下GETHEAD的不同用法:

  • 使用GET客户端请求整个文件或数据(或一系列数据),并希望它尽可能快。因此,没有特定的理由让服务器首先发送数据大小,特别是当它可以在分块模式下更快/更快地开始发送时。因此,首选最快的方式(无论如何,客户端将具有下载后的大小)。

  • 另一方面,对于HEAD,客户通常需要一些特定信息。这可能只是检查存在或“最后更改”,但如果客户端需要某个部分数据(带有范围请求,包括检查是否有范围请求),也可以使用它支持该请求),或者只是出于某种原因需要预先知道数据的大小。

了解一些可能的情况:

静态文件

HEAD:没有理由不在响应标头中包含大小,因为该信息可用。

GET:大多数情况下,大小将包含在标题中并且数据一次性发送,除非有特定的性能原因以块的形式发送它。另一方面,你似乎期待为你的文件进行分块传输,所以这在这里有意义。

实时日志文件

好的,有点奇怪,但可能:下载文件,其大小可能会在下载时发生变化。

HEAD:同样,客户端可能想要大小,服务器可以在标题中的特定时间轻松提供文件的大小。

GET:由于可以在下载时添加日志,因此预先知道大小。唯一的选择就是发送chunked。

包含固定大小记录的表格

让我们想象服务器需要发回一个来自多个来源/数据库的固定长度记录的表:

HEAD:客户可能需要大小。服务器可以快速查询每个数据库中的 count ,并将计算出的大小发送回客户端。

GET:不是首先在每个数据库中对 count 进行查询,而是服务器最好开始以块的形式从每个数据库发送结果记录。

动态生成的zip文件

也许并不常见,但却是一个有趣的例子。

想象一下,您希望根据一些参数向用户提供动态生成的zip文件。

让我们先来看一下zip文件的结构:

有两个部分:首先是每个文件的一个块:一个小标题,后跟该文件的压缩数据。然后是zip文件中所有文件的列表(包括尺寸/位置)。

因此,可以在磁盘上预先生成为每个文件准备的块(以及存储在某些数据结构中的名称/大小。

HEAD:客户可能想知道这里的大小。服务器可以很容易地计算出所有需要块的大小+第二部分的大小以及里面的文件列表。

如果客户端想要提取单个文件,它可以直接请求文件的最后一部分(带有范围请求)来获取列表,然后第二个请求请求该单个文件。虽然获取最后n个字节不一定需要大小,但是如果你想将不同的部分存储在与完整zip文件大小相同的稀疏文件中,它可能会很方便。

GET:无需先进行计算(包括生成第二部分以了解其大小)。只需开始以块的形式发送每个块就会更好更快。

完全动态生成的文件

在这种情况下,将大小返回到HEAD请求当然不是非常有效,因为需要生成整个文件才知道它的大小。