Spring 4.3+覆盖@RequestParam对RequestMethod.HEAD请求的

时间:2018-03-22 22:00:25

标签: java spring spring-mvc

TL:DR

我们如何覆盖Spring 4.3+的当前行为,强制使用RequestMethod.GET或@GetMapping来处理HEAD请求,这样我们就可以返回Content-Length头而不必将所有数据都写入响应OutputStream?

更长的版本:

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

  

HTTP HEAD,OPTIONS

     

@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方法列表   匹配网址格式的方法。

     

对于没有HTTP方法声明的@RequestMapping,"允许"   header设置为" GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"。调节器   方法应该总是声明支持的HTTP方法,例如   通过使用HTTP方法特定的变体 - @GetMapping,   @PostMapping等

     

@RequestMapping方法可以显式映射到HTTP HEAD和HTTP   选项,但在通常情况下这不是必需的。

来源:
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个gig),如果可能的话,我们不必将所有字节读入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);
}

1 个答案:

答案 0 :(得分:0)

  

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

     

对HEAD方法的响应不应该有正文。如果是这样,那一定是   忽略。即便如此,描述身体内容的实体标题,   例如Content-Length可以包含在响应中。它们没有联系   HEAD响应的主体应该是空的,但是应该是   使用GET方法的类似请求的主体将返回为   响应。

     

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

  • 请求正文否
  • 成功回复正文
  • 安全是
  • Idempotent Yes
  • 可缓存是
  • 允许使用HTML表单否

隐含的HEAD支持 来自Spring的MVC doc:

  

映射到“GET”的@RequestMapping方法也隐式映射到   “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());
    }
}

隐含OPTIONS支持 来自Spring的MVC doc:

  

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

检查点: 这意味着,我们永远不必单独为HTTP OPTIONS动词创建一个处理程序方法,因为Spring隐式支持它,因为所有处理程序方法都明确指定HTTP方法@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());
}