【问题标题】:Spring 4.3+ Override Default Behavior of @RequestParam for RequestMethod.HEAD RequestsSpring 4.3+ 为 RequestMethod.HEAD 请求覆盖 ​​@RequestParam 的默认行为
【发布时间】:2018-09-01 12:15:29
【问题描述】:

TL:DR

我们如何覆盖 Spring 4.3+ 的当前行为,该行为强制对 HEAD 请求使用 RequestMethod.GET 或 @GetMapping,以便我们可以返回 Content-Length 标头而无需将所有数据写入响应 OutputStream?


加长版:

我刚刚注意到 Spring 改变了默认处理 GET/HEAD 请求的方式:

HTTP 头,选项

@GetMapping — 还有@RequestMapping(method=HttpMethod.GET),支持 HTTP HEAD 透明地用于请求映射目的。控制器 方法不需要改变。一个响应包装器,应用于 javax.servlet.http.HttpServlet,确保“Content-Length”标头是 设置为写入的字节数,而不实际写入 回应。

@GetMapping — 还有@RequestMapping(method=HttpMethod.GET),都是 隐式映射到并且还支持 HTTP HEAD。 HTTP HEAD 请求 像 HTTP GET 一样处理,但不是写 body、字节数和“Content-Length”标头 设置。

默认情况下,HTTP OPTIONS 通过设置“允许”响应来处理 标头到所有 @RequestMapping 中列出的 HTTP 方法列表 具有匹配 URL 模式的方法。

对于没有 HTTP 方法声明的 @RequestMapping,“允许” 标头设置为“GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS”。控制器 方法应始终声明支持的 HTTP 方法,例如 通过使用 HTTP 方法特定的变体 — @GetMapping, @PostMapping 等

@RequestMapping 方法可以显式映射到 HTTP HEAD 和 HTTP OPTIONS,但在一般情况下这不是必需的。

来源:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-head-options
https://stackoverflow.com/a/45412434/42962

我们如何覆盖这个默认行为,以便我们可以自己处理 HEAD 响应并设置 Content-Length 标头?

我们希望这样做,因为我们通过 Web 应用程序处理大文件(考虑超过 10 gigs 的大小),如果可能,我们希望不必将所有字节读入 Response 的 OutputStream。

这是我们当前代码的示例。只有第二种方法(带有 RequestMethod.GET 的 handleRequest)被调用。

@RequestMapping(value = "/file/{fileName:.+}", method = RequestMethod.HEAD)
public void handleHeadRequest(@RequestParam(value = "fileName") String fileName, HttpServletRequest request, HttpServletResponse response) {

    File file = fileRepository.getFileByName(fileName)
    response.addHeader("Accept-Ranges", "bytes");
    response.addDateHeader("Last-Modified", file.lastModified());

    Long fileSize = file.length();
    response.addHeader(HttpHeaderConstants.CONTENT_LENGTH, fileSize.toString());
}

@RequestMapping(value = "/file/{fileName:.+}", headers = "!Range", method = RequestMethod.GET)
public void handleRequest(@PathVariable(value = "fileName") String fileName, HttpServletRequest request, HttpServletResponse response) throws Exception {

    File file = fileRepository.getFileByName(fileName)
    response.addHeader("Accept-Ranges", "bytes");
    response.addDateHeader("Last-Modified", file.lastModified());

    Long fileSize = file.length();
    response.addHeader(HttpHeaderConstants.CONTENT_LENGTH, fileSize.toString());

    // Stream file to end user client.
    fileDownloadHandler.handle(request, response, file);
}

【问题讨论】:

  • 您可以将HttpServletResponse response 作为参数添加到您的方法中,并使用@GetMapping 进行注释并执行response.setHeader("Content-Length", "12345"); 之类的操作
  • @ImpulseTheFox 这就是我们想要做的。我添加了一个代码,用于在 Spring 4.3 的更改之前工作。根本不再调用 handleHeadRequest 方法。 :(
  • 您的问题是如何制作下载服务控制器?
  • @Byeon0gam,不,不是真的。我在问如何让 Spring 调用上面的 handleHeadRequest 方法而不是 HTTP HEAD 方法请求的 handleRequest 方法。
  • @hooknc,你查看我的帖子了吗?

标签: java spring spring-mvc


【解决方案1】:

HTTP HEAD 方法请求返回的标头,如果 将使用 HTTP GET 方法请求指定的资源。这样一个 可以在决定将大型资源下载到之前完成请求 例如,节省带宽。

对 HEAD 方法的响应不应该有正文。如果是这样,它必须是 忽略。即便如此,描述正文内容的实体标头, 像 Content-Length 可能包含在响应中。他们不相关 到 HEAD 响应的主体,它应该是空的,但是到 使用 GET 方法的类似请求的主体将作为 回应。

如果 HEAD 请求的结果显示缓存资源在 GET 请求现在已经过时,缓存失效,即使没有 GET 已提出请求。

  • 请求的正文没有
  • 成功的响应有正文号
  • 安全是的
  • 幂等是的
  • 可缓存是
  • 在 HTML 表单中允许 否

隐式 HEAD 支持 来自 Spring 的 MVC 文档:

@RequestMapping 映射到“GET”的方法也被隐式映射到 “HEAD”,即不需要显式声明“HEAD”。一个 HTTP HEAD 请求被当作 HTTP GET 处理,除了 而不是写正文,只计算字节数和 “Content-Length”标头集。

检查点: 这意味着我们永远不必为 HTTP HEAD 动词单独创建一个处理程序方法,因为 Spring 隐式支持这一点,因为已经为目标 URL 定义了 GET 动词。


示例

控制者

让我们创建一个非常简单的控制器,其中包含填充一些标头的处理程序方法:

@Controller
public class MyController {
    Logger logger = Logger.getLogger(MyController.class.getSimpleName());

    @RequestMapping(value = "test", method = {RequestMethod.GET})
    public HttpEntity<String> handleTestRequest () {

        MultiValueMap<String, String> headers = new HttpHeaders();
        headers.put("test-header", Arrays.asList("test-header-value"));

        HttpEntity<String> responseEntity = new HttpEntity<>("test body", headers);


        logger.info("handler finished");
        return responseEntity;
    }
} 

JUnit 测试

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = MyWebConfig.class)
public class ControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup () {
        DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
        this.mockMvc = builder.build();
    }

    @Test
    public void testGet () throws Exception {

        MockHttpServletRequestBuilder builder =
                            MockMvcRequestBuilders.get("/test");

        this.mockMvc.perform(builder)
                    .andExpect(MockMvcResultMatchers.status()
                                                    .isOk())
                    .andDo(MockMvcResultHandlers.print());
    }

    @Test
    public void testHead () throws Exception {

        MockHttpServletRequestBuilder builder =
                            MockMvcRequestBuilders.head("/test");

        this.mockMvc.perform(builder)
                    .andExpect(MockMvcResultMatchers.status()
                                                    .isOk())
                    .andDo(MockMvcResultHandlers.print());
    }
}

隐式选项支持 来自 Spring 的 MVC 文档:

@RequestMapping 方法具有对 HTTP OPTIONS 的内置支持。经过 默认通过设置“允许”来处理 HTTP OPTIONS 请求 对所有显式声明的 HTTP 方法的响应标头 具有匹配 URL 模式的 @RequestMapping 方法。没有 HTTP 时 方法被显式声明,“允许”标头设置为 "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"

检查点: 这意味着,我们永远不必为 HTTP OPTIONS 动词单独创建处理程序方法,因为 spring 隐式支持,因为所有处理程序方法都显式指定了每个处理程序的 HTTP 方法目标 URL 的 @RequestMapping。


示例

让我们继续上面的示例,只需为 HTTP OPTIONS 动词添加一个测试:

@Test
public void testOptions () throws Exception {

    ResultMatcher accessHeader = MockMvcResultMatchers.header()
                                                      .string("Allow", "GET,HEAD");

    MockHttpServletRequestBuilder builder =
                        MockMvcRequestBuilders.options("/test");

    this.mockMvc.perform(builder)
                .andExpect(MockMvcResultMatchers.status()
                                                .isOk())
                .andExpect(accessHeader)
                .andDo(MockMvcResultHandlers.print());
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-10
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多