为什么Tomcat对我的RESTful API发出的HEAD和GET请求返回不同的头信息?

13

我的初始目的是验证 HTTP分块传输,但无意中发现了这个不一致之处。

该API旨在将文件返回给客户端。我使用HEADGET方法访问它。返回的头信息不同

对于GET,我获得了这些头:(这就是我预期的)

Transfer-Encoding: chunked, no Content-Length.

对于HEAD,我获得了这些头:

Content-Length: 1017118720, no Transfer-Encoding.

根据此线程HEADGET应该返回相同的头部,但不一定

我的问题是:

如果Transfer-Encoding: chunked是用于动态提供文件给客户端,并且Tomcat服务器不能预先知道其大小,那么当使用HEAD方法时,Tomcat如何知道Content-Length? Tomcat是否只是模拟运行处理程序并计算文件字节数?为什么它不简单地返回相同的Transfer-Encoding: chunked头部?

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

@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();
        }

    }

}

添加1

在我的代码中,我没有显式地添加任何头文件,那么肯定是Tomcat根据需要添加Content-LengthTransfer-Encoding头文件。

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

添加2

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

相关:

2个回答

3

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

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

您可以查看HttpServlet.java中的NoBodyResponse、NoBodyOutputStream帮助类。

DefaultServlet类(用于提供静态文件的Tomcat Servlet)更加聪明。它能够发送正确的Content-Length值,并为文件的子集(Range header)提供GET请求服务。您可以将请求转发到该Servlet。

  ServletContext.getNamedDispatcher("default").forward(request, response);

3
尽管看起来有点奇怪,但对于要由服务器返回的数据类型,根据HEAD请求只在响应中发送大小,在GET请求中发送分块是有意义的。
虽然你的API似乎提供了静态文件,但你还谈到了动态创建的文件或数据,因此我将从一般情况(也适用于Web服务器)讨论这个问题。
首先,让我们看一下GETHEAD的不同用法:
- 对于GET,客户端正在请求整个文件或数据(或数据范围),并希望尽快获得数据。因此,服务器没有必要首先发送数据的大小,特别是当它可以在分块模式下更快地/更早地开始发送时。因此,这里优选最快的方式(客户端在下载完成后将会拥有文件大小)。 - 对于HEAD,客户端通常想要一些特定的信息。这可能仅仅是一个存在性检查或“最后修改”时间的检查,但如果客户端想要数据的某个特定部分(使用范围请求,包括一个检查以查看该请求是否支持范围请求),或者只是需要提前知道数据的大小,那么也可以使用它。
接下来,让我们看一些可能的场景:
- 静态文件: - 对于HEAD,没有理由不在响应头中包含大小信息,因为这些信息是可用的。 - 对于GET,大多数时候,大小将包含在标题中,并且数据将一次性发送,除非有特定的性能原因需要分块传输。另一方面,似乎你期望对文件进行分块传输,在这种情况下这样做是有意义的。 - 实时日志文件: - 下载时大小可能会发生变化,有点奇怪,但是仍然是可能的。 - 对于HEAD,客户端可能再次想要大小,服务器可以轻松地在头中提供该特定时间的文件大小。 - 对于GET,由于下载时可能会添加日志行,因此大小事先未知。唯一的选择是以分块方式发送数据。 - 具有固定大小记录的表: - 假设需要从多个来源/数据库返回具有固定长度记录的表。 - 对于HEAD,客户端可能需要大小。服务器可以快速查询每个数据库中的计算count,并将计算的大小发送回给客户端。 - 对于GET,服务器最好从每个数据库开始按块发送结果记录,而不是首先在每个数据库中查询count。 - 动态生成的zip文件:
也许不常见,但是一个有趣的例子。
想象一下,您想根据某些参数为用户提供动态生成的zip文件。
首先让我们看一下zip文件的结构:
有两个部分:首先对于每个文件都有一个块:一个小标题,后面是该文件的压缩数据。然后有一个包含zip文件中所有文件的列表(包括大小/位置)。
因此,可以在磁盘上预先生成每个文件的准备好的块(并将名称/大小存储在某些数据结构中)。
HEAD:客户端可能想知道这里的大小。服务器可以轻松地计算出所需块的所有大小以及第二部分包含的文件列表的大小。
如果客户端想要提取单个文件,则可以直接请求文件的最后一部分(使用范围请求)以获取列表,然后使用第二个请求请求该单个文件。尽管不一定需要获取最后n个字节的大小,但如果例如您想要将不同的部分存储在与完整zip文件相同大小的稀疏文件中,则可能会很方便。
GET:无需首先进行计算(包括生成第二部分以了解其大小)。最好和更快的方法是开始以块为单位发送每个块。
完全动态生成的文件:
在这种情况下,当然没有必要返回HEAD请求的大小,因为需要生成整个文件才能知道其大小,这样不太有效率。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接