Spring 4.3+ 覆盖 RequestMethod.HEAD 请求的默认@RequestParam行为



TL:DR

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

<小时 />

更长版本:

我刚刚注意到,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,除了但不编写 正文,计算字节数和"内容长度"标头 设置。

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

对于没有 HTTP 方法声明的@RequestMapping,"允许" 标题设置为"获取,头,发布,放置,补丁,删除,选项"。控制器 方法应始终声明支持的 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 个演出),我们希望如果可能的话,不必将所有字节读取到响应的输出流中。

下面是我们当前代码的示例。 只有第二个方法(handleRequest with the Request.GET)被调用。

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

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

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

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

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

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

映射到"GET"@RequestMapping方法也隐式映射到 "HEAD",即没有必要明确声明"HEAD"。一 HTTP HEAD 请求的处理方式就好像它是 HTTP GET 一样,除了 不是写入正文,而是只计算字节数和 "内容长度"标头集。

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


控制器

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

@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 选项的支持。由 默认情况下,HTTP 选项请求通过设置"允许"来处理 在所有方法上显式声明的 HTTP 方法的响应标头 @RequestMapping具有匹配 URL 模式的方法。当没有 HTTP 时 方法显式声明"允许"标头设置为 "获取,头,发布,放置,补丁,删除,选项">

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


让我们继续上面的例子,只是为 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());
}

最新更新