WebFlux supports HTTP HEAD
Issue: SPR-15994
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.http.server.reactive;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
|
||||
/**
|
||||
* {@link ServerHttpResponse} decorator for HTTP HEAD requests.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
|
||||
|
||||
|
||||
public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply {@link Flux#reduce(Object, BiFunction) reduce} on the body, count
|
||||
* the number of bytes produced, release data buffers without writing, and
|
||||
* set the {@literal Content-Length} header.
|
||||
*/
|
||||
@Override
|
||||
public final Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
|
||||
|
||||
// After Reactor Netty #171 is fixed we can return without delegating
|
||||
|
||||
return getDelegate().writeWith(
|
||||
Flux.from(body)
|
||||
.reduce(0, (current, buffer) -> {
|
||||
int next = current + buffer.readableByteCount();
|
||||
DataBufferUtils.release(buffer);
|
||||
return next;
|
||||
})
|
||||
.doOnNext(count -> getHeaders().setContentLength(count))
|
||||
.then(Mono.empty()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke {@link #setComplete()} without writing.
|
||||
*
|
||||
* <p>RFC 7302 allows HTTP HEAD response without content-length and it's not
|
||||
* something that can be computed on a streaming response.
|
||||
*/
|
||||
@Override
|
||||
public final Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
|
||||
// Not feasible to count bytes on potentially streaming response.
|
||||
// RFC 7302 allows HEAD without content-length.
|
||||
return setComplete();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import reactor.ipc.netty.http.server.HttpServerResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -54,8 +55,8 @@ public class ReactorHttpHandlerAdapter
|
||||
public Mono<Void> apply(HttpServerRequest request, HttpServerResponse response) {
|
||||
|
||||
NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc());
|
||||
ReactorServerHttpRequest adaptedRequest;
|
||||
ReactorServerHttpResponse adaptedResponse;
|
||||
ServerHttpRequest adaptedRequest;
|
||||
ServerHttpResponse adaptedResponse;
|
||||
try {
|
||||
adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory);
|
||||
adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory);
|
||||
@@ -66,6 +67,10 @@ public class ReactorHttpHandlerAdapter
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
if (HttpMethod.HEAD.equals(adaptedRequest.getMethod())) {
|
||||
adaptedResponse = new HttpHeadResponseDecorator(adaptedResponse);
|
||||
}
|
||||
|
||||
return this.httpHandler.handle(adaptedRequest, adaptedResponse)
|
||||
.onErrorResume(ex -> {
|
||||
logger.error("Could not complete request", ex);
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.reactivestreams.Subscription;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -110,6 +111,10 @@ public class ServletHttpHandlerAdapter implements Servlet {
|
||||
ServerHttpRequest httpRequest = createRequest(((HttpServletRequest) request), asyncContext);
|
||||
ServerHttpResponse httpResponse = createResponse(((HttpServletResponse) response), asyncContext);
|
||||
|
||||
if (HttpMethod.HEAD.equals(httpRequest.getMethod())) {
|
||||
httpResponse = new HttpHeadResponseDecorator(httpResponse);
|
||||
}
|
||||
|
||||
asyncContext.addListener(ERROR_LISTENER);
|
||||
|
||||
HandlerResultSubscriber subscriber = new HandlerResultSubscriber(asyncContext);
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.reactivestreams.Subscription;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -67,6 +68,10 @@ public class UndertowHttpHandlerAdapter implements io.undertow.server.HttpHandle
|
||||
ServerHttpRequest request = new UndertowServerHttpRequest(exchange, getDataBufferFactory());
|
||||
ServerHttpResponse response = new UndertowServerHttpResponse(exchange, getDataBufferFactory());
|
||||
|
||||
if (HttpMethod.HEAD.equals(request.getMethod())) {
|
||||
response = new HttpHeadResponseDecorator(response);
|
||||
}
|
||||
|
||||
HandlerResultSubscriber resultSubscriber = new HandlerResultSubscriber(exchange);
|
||||
this.httpHandler.handle(request, response).subscribe(resultSubscriber);
|
||||
}
|
||||
|
||||
@@ -80,32 +80,6 @@ public class UndertowServerHttpResponse extends AbstractListenerServerHttpRespon
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(File file, long position, long count) {
|
||||
return doCommit(() -> {
|
||||
FileChannel source = null;
|
||||
try {
|
||||
source = FileChannel.open(file.toPath(), StandardOpenOption.READ);
|
||||
StreamSinkChannel destination = getUndertowExchange().getResponseChannel();
|
||||
Channels.transferBlocking(destination, source, position, count);
|
||||
return Mono.empty();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
finally {
|
||||
if (source != null) {
|
||||
try {
|
||||
source.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyHeaders() {
|
||||
for (Map.Entry<String, List<String>> entry : getHeaders().entrySet()) {
|
||||
@@ -135,6 +109,33 @@ public class UndertowServerHttpResponse extends AbstractListenerServerHttpRespon
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(File file, long position, long count) {
|
||||
return doCommit(() -> {
|
||||
FileChannel source = null;
|
||||
try {
|
||||
source = FileChannel.open(file.toPath(), StandardOpenOption.READ);
|
||||
StreamSinkChannel destination = getUndertowExchange().getResponseChannel();
|
||||
Channels.transferBlocking(destination, source, position, count);
|
||||
return Mono.empty();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
finally {
|
||||
if (source != null) {
|
||||
try {
|
||||
source.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Processor<? super Publisher<? extends DataBuffer>, Void> createBodyFlushProcessor() {
|
||||
return new ResponseBodyFlushProcessor();
|
||||
|
||||
Reference in New Issue
Block a user