Add HEAD support in MVC/WebFlux Resource handling
This commit introduces explicit HEAD support in Spring MVC's ResourceHttpRequestHandler and WebFlux's ResourceWebHandler, adding just headers but no body. Closes gh-28291
This commit is contained in:
@@ -116,8 +116,27 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
|
||||
private Mono<Void> writeResource(Resource resource, ResolvableType type, @Nullable MediaType mediaType,
|
||||
ReactiveHttpOutputMessage message, Map<String, Object> hints) {
|
||||
|
||||
addHeaders(message, resource, mediaType, hints);
|
||||
|
||||
return zeroCopy(resource, null, message, hints)
|
||||
.orElseGet(() -> {
|
||||
Mono<Resource> input = Mono.just(resource);
|
||||
DataBufferFactory factory = message.bufferFactory();
|
||||
Flux<DataBuffer> body = this.encoder.encode(input, factory, type, message.getHeaders().getContentType(), hints);
|
||||
if (logger.isDebugEnabled()) {
|
||||
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
|
||||
}
|
||||
return message.writeWith(body);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the default headers for the given resource to the given message.
|
||||
* @since 6.0
|
||||
*/
|
||||
public void addHeaders(ReactiveHttpOutputMessage message, Resource resource, @Nullable MediaType contentType, Map<String, Object> hints) {
|
||||
HttpHeaders headers = message.getHeaders();
|
||||
MediaType resourceMediaType = getResourceMediaType(mediaType, resource, hints);
|
||||
MediaType resourceMediaType = getResourceMediaType(contentType, resource, hints);
|
||||
headers.setContentType(resourceMediaType);
|
||||
|
||||
if (headers.getContentLength() < 0) {
|
||||
@@ -126,17 +145,7 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter<Resource> {
|
||||
headers.setContentLength(length);
|
||||
}
|
||||
}
|
||||
|
||||
return zeroCopy(resource, null, message, hints)
|
||||
.orElseGet(() -> {
|
||||
Mono<Resource> input = Mono.just(resource);
|
||||
DataBufferFactory factory = message.bufferFactory();
|
||||
Flux<DataBuffer> body = this.encoder.encode(input, factory, type, resourceMediaType, hints);
|
||||
if (logger.isDebugEnabled()) {
|
||||
body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger));
|
||||
}
|
||||
return message.writeWith(body);
|
||||
});
|
||||
headers.set(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||
}
|
||||
|
||||
private static MediaType getResourceMediaType(
|
||||
|
||||
@@ -122,6 +122,14 @@ public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<R
|
||||
return (contentLength < 0 ? null : contentLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the default headers for the given resource to the given message.
|
||||
* @since 6.0
|
||||
*/
|
||||
public void addDefaultHeaders(HttpOutputMessage message, Resource resource, @Nullable MediaType contentType) throws IOException {
|
||||
addDefaultHeaders(message.getHeaders(), resource, contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(Resource resource, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
||||
@@ -438,10 +438,17 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
|
||||
// Content phase
|
||||
ResourceHttpMessageWriter writer = getResourceHttpMessageWriter();
|
||||
Assert.state(writer != null, "No ResourceHttpMessageWriter");
|
||||
return writer.write(Mono.just(resource),
|
||||
null, ResolvableType.forClass(Resource.class), mediaType,
|
||||
exchange.getRequest(), exchange.getResponse(),
|
||||
Hints.from(Hints.LOG_PREFIX_HINT, exchange.getLogPrefix()));
|
||||
if (HttpMethod.HEAD == httpMethod) {
|
||||
writer.addHeaders(exchange.getResponse(), resource, mediaType,
|
||||
Hints.from(Hints.LOG_PREFIX_HINT, exchange.getLogPrefix()));
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
else {
|
||||
return writer.write(Mono.just(resource),
|
||||
null, ResolvableType.forClass(Resource.class), mediaType,
|
||||
exchange.getRequest(), exchange.getResponse(),
|
||||
Hints.from(Hints.LOG_PREFIX_HINT, exchange.getLogPrefix()));
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Mono.error(ex);
|
||||
|
||||
@@ -120,7 +120,10 @@ public class ResourceWebHandlerTests {
|
||||
assertThat(headers.containsKey("Last-Modified")).isTrue();
|
||||
assertThat(resourceLastModifiedDate("test/foo.css") / 1000).isEqualTo(headers.getLastModified() / 1000);
|
||||
assertThat(headers.getFirst("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(headers.get("Accept-Ranges").size()).isEqualTo(1);
|
||||
assertThat(headers.get("Accept-Ranges")).hasSize(1);
|
||||
|
||||
StepVerifier.create(exchange.getResponse().getBody())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -585,7 +585,14 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
|
||||
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
|
||||
if (request.getHeader(HttpHeaders.RANGE) == null) {
|
||||
Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
|
||||
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
|
||||
|
||||
if (HttpMethod.HEAD.matches(request.getMethod())) {
|
||||
this.resourceHttpMessageConverter.addDefaultHeaders(outputMessage, resource, mediaType);
|
||||
outputMessage.flush();
|
||||
}
|
||||
else {
|
||||
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
|
||||
|
||||
@@ -118,7 +118,8 @@ public class ResourceHttpRequestHandlerTests {
|
||||
assertThat(this.response.containsHeader("Last-Modified")).isTrue();
|
||||
assertThat(this.response.getDateHeader("Last-Modified") / 1000).isEqualTo(resourceLastModified("test/foo.css") / 1000);
|
||||
assertThat(this.response.getHeader("Accept-Ranges")).isEqualTo("bytes");
|
||||
assertThat(this.response.getHeaders("Accept-Ranges").size()).isEqualTo(1);
|
||||
assertThat(this.response.getHeaders("Accept-Ranges")).hasSize(1);
|
||||
assertThat(this.response.getContentAsByteArray()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user