Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/spring-mvc/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Http 为什么Tomcat为HEAD返回不同的头,并向我的RESTful API获取请求?_Http_Tomcat8_Http Method_Chunked - Fatal编程技术网

Http 为什么Tomcat为HEAD返回不同的头,并向我的RESTful API获取请求?

Http 为什么Tomcat为HEAD返回不同的头,并向我的RESTful API获取请求?,http,tomcat8,http-method,chunked,Http,Tomcat8,Http Method,Chunked,我最初的目的是验证HTTP分块传输。但无意中发现了这种矛盾 API设计用于将文件返回到客户端。我使用HEAD和GET方法来对付它返回不同的标题。 对于GET,我得到以下标题:(这是我所期望的。) 对于标题,我得到以下标题: 根据,HEAD和GET应该返回相同的头,但不一定返回相同的头 我的问题是: 如果使用Transfer Encoding:chunked是因为文件被动态地馈送到客户端,而Tomcat服务器事先无法知道其大小,那么当使用HEAD方法时,Tomcat如何知道内容长度?Tomca

我最初的目的是验证HTTP分块传输。但无意中发现了这种矛盾

API设计用于将文件返回到客户端。我使用
HEAD
GET
方法来对付它返回不同的标题。

对于
GET
,我得到以下标题:(这是我所期望的。)

对于
标题
,我得到以下标题:

根据,
HEAD
GET
应该返回相同的头,但不一定返回相同的头

我的问题是:

如果使用
Transfer Encoding:chunked
是因为文件被动态地馈送到客户端,而Tomcat服务器事先无法知道其大小,那么当使用
HEAD
方法时,Tomcat如何知道
内容长度?Tomcat是否只运行处理程序并计算所有文件字节?为什么它不简单地返回相同的
传输编码:chunked

下面是我用SpringWebMVC实现的RESTfulAPI:

@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添加它认为合适的
内容长度
传输编码
标题

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

添加2

也许这与Tomcat的工作方式有关。我希望有人能给我们一些启示。否则,我将调试到Tomcat8的源代码中并共享结果。但这可能需要一段时间

相关:

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

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

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

DefaultServlet类(用于服务静态文件的TomcatServlet)更明智。它能够发送正确的内容长度值,并为文件子集(
范围
头)的GET请求提供服务。您可以使用

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

尽管这看起来很奇怪,但根据服务器必须返回的数据类型,仅在响应
HEAD
请求时发送大小并在响应
GET
请求时分块可能是有意义的

虽然您的API似乎提供了一个静态文件,但您也讨论了动态创建的文件或数据,所以我将在这里进行概述(一般也针对Web服务器)

首先让我们看看
GET
HEAD
的不同用法:

  • 使用
    GET
    时,客户机请求整个文件或数据(或一系列数据),并希望尽快获得。因此,服务器没有特定的理由首先发送数据的大小,特别是当它可以在分块模式下更快/更快地发送数据时。因此,这里最好采用最快的方式(无论如何,下载后客户端都会有相应的大小)

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

让我们看看一些可能的情况:

静态文件

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

    }

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

GET
:大多数情况下,大小将包含在头中,数据将一次性发送,除非有特定的性能原因需要成批发送。另一方面,您似乎希望为您的文件进行分块传输,所以这在这里是有意义的

实时日志文件

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

    }

}
好吧,有点奇怪,但也有可能:下载一个文件时,文件大小可能会发生变化

HEAD
:同样,客户机可能需要文件大小,服务器可以在文件头的特定时间轻松提供文件大小

GET
:由于可以在下载时添加日志行,因此事先不知道日志行的大小。唯一的选择是发送分块

具有固定大小记录的表

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

    }

}
假设服务器需要发回一个包含来自多个源/数据库的固定长度记录的表:

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

GET
:与其先查询每个数据库中的计数,服务器最好开始以块的形式从每个数据库发送结果记录

动态生成的zip文件

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

    }

}
也许不常见,但这是一个有趣的例子

假设您希望根据一些参数向用户提供动态生成的zip文件

让我们首先看看zip文件的结构:

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

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

HEAD
:客户端可能想知道此处的大小。服务器可以轻松计算所有所需块的大小+第二部分的大小以及其中的文件列表

<