我最初的目的是验证 HTTP分块传输。但偶然发现了这种不一致。
API旨在将文件返回给客户端。我对它使用HEAD
和GET
方法。 返回不同的标头。
对于GET
,我得到了这些标题:(这是我的预期。)
对于HEAD
,我会收到以下标题:
根据this thread,HEAD
和GET
应该返回相同的标题,但不一定。
我的问题是:
如果使用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的源代码并共享结果。但这可能需要一段时间。
相关:
答案 0 :(得分:2)
Tomcat只是干运行处理程序并计算所有文件字节吗?
是的,javax.servlet.http.HttpServlet.doHead()的默认实现就是这样做的。
你可以在HttpServlet.java中查看助手类NoBodyResponse,NoBodyOutputStream
DefaultServlet类(用于提供静态文件的Tomcat servlet)更明智。它能够发送正确的Content-Length值,以及为文件的子集(Range
标头)提供GET请求。您可以使用
ServletContext.getNamedDispatcher("default").forward(request, response);
答案 1 :(得分:2)
虽然看起来很奇怪,但是仅在响应HEAD
请求时发送大小并响应GET
请求进行分块可能是有意义的,具体取决于必须的数据类型由服务器返回。
虽然你的API似乎提供了一个静态文件,但你也谈到动态创建的文件或数据,所以我将在这里进行一般性讨论(一般也适用于网络服务器)。
首先让我们看一下GET
和HEAD
的不同用法:
使用GET
客户端请求整个文件或数据(或一系列数据),并希望它尽可能快。因此,没有特定的理由让服务器首先发送数据大小,特别是当它可以在分块模式下更快/更快地开始发送时。因此,首选最快的方式(无论如何,客户端将具有下载后的大小)。
另一方面,对于HEAD
,客户通常需要一些特定信息。这可能只是检查存在或“最后更改”,但如果客户端需要某个部分数据(带有范围请求,包括检查是否有范围请求),也可以使用它支持该请求),或者只是出于某种原因需要预先知道数据的大小。
了解一些可能的情况:
静态文件:
HEAD
:没有理由不在响应标头中包含大小,因为该信息可用。
GET
:大多数情况下,大小将包含在标题中并且数据一次性发送,除非有特定的性能原因以块的形式发送它。另一方面,你似乎期待为你的文件进行分块传输,所以这在这里有意义。
实时日志文件:
好的,有点奇怪,但可能:下载文件,其大小可能会在下载时发生变化。
HEAD
:同样,客户端可能想要大小,服务器可以在标题中的特定时间轻松提供文件的大小。
GET
:由于可以在下载时添加日志,因此预先知道大小。唯一的选择就是发送chunked。
包含固定大小记录的表格:
让我们想象服务器需要发回一个来自多个来源/数据库的固定长度记录的表:
HEAD
:客户可能需要大小。服务器可以快速查询每个数据库中的 count ,并将计算出的大小发送回客户端。
GET
:不是首先在每个数据库中对 count 进行查询,而是服务器最好开始以块的形式从每个数据库发送结果记录。
动态生成的zip文件:
也许并不常见,但却是一个有趣的例子。
想象一下,您希望根据一些参数向用户提供动态生成的zip文件。
让我们先来看一下zip文件的结构:
有两个部分:首先是每个文件的一个块:一个小标题,后跟该文件的压缩数据。然后是zip文件中所有文件的列表(包括尺寸/位置)。
因此,可以在磁盘上预先生成为每个文件准备的块(以及存储在某些数据结构中的名称/大小。
HEAD
:客户可能想知道这里的大小。服务器可以很容易地计算出所有需要块的大小+第二部分的大小以及里面的文件列表。
如果客户端想要提取单个文件,它可以直接请求文件的最后一部分(带有范围请求)来获取列表,然后第二个请求请求该单个文件。虽然获取最后n个字节不一定需要大小,但是如果你想将不同的部分存储在与完整zip文件大小相同的稀疏文件中,它可能会很方便。
GET
:无需先进行计算(包括生成第二部分以了解其大小)。只需开始以块的形式发送每个块就会更好更快。
完全动态生成的文件:
在这种情况下,将大小返回到HEAD
请求当然不是非常有效,因为需要生成整个文件才知道它的大小。