diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteFlushProcessor.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteFlushProcessor.java index 329cd3dbca..81c8098d32 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteFlushProcessor.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteFlushProcessor.java @@ -246,6 +246,17 @@ public abstract class AbstractListenerWriteFlushProcessor implements Processo super.onSubscribe(processor, subscription); } } + + @Override + public void onComplete(AbstractListenerWriteFlushProcessor processor) { + // This can happen on (very early) completion notification from container.. + if (processor.changeState(this, COMPLETED)) { + processor.resultPublisher.publishComplete(); + } + else { + processor.state.get().onComplete(processor); + } + } }, REQUESTED { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java index 509076d02b..4f05d5e1e0 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractListenerWriteProcessor.java @@ -322,6 +322,12 @@ public abstract class AbstractListenerWriteProcessor implements Processor void onComplete(AbstractListenerWriteProcessor processor) { + // This can happen on (very early) completion notification from container.. + processor.changeStateToComplete(this); + } }, REQUESTED { diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java index a94afe08f9..d5378f058e 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/AbstractServerHttpResponse.java @@ -259,8 +259,13 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse { protected abstract void applyStatusCode(); /** - * Apply header changes from {@link #getHeaders()} to the underlying response. - * This method is called once only. + * Invoked when the response is getting committed allowing sub-classes to + * make apply header values to the underlying response. + *

Note that most sub-classes use an {@link HttpHeaders} instance that + * wraps an adapter to the native response headers such that changes are + * propagated to the underlying response on the go. That means this callback + * is typically not used other than for specialized updates such as setting + * the contentType or characterEncoding fields in a Servlet response. */ protected abstract void applyHeaders(); diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java index fe50441d46..e0c538bc47 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/JettyHttpHandlerAdapter.java @@ -102,8 +102,15 @@ public class JettyHttpHandlerAdapter extends ServletHttpHandlerAdapter { @Override protected void applyHeaders() { - MediaType contentType = getHeaders().getContentType(); HttpServletResponse response = getNativeResponse(); + MediaType contentType = null; + try { + contentType = getHeaders().getContentType(); + } + catch (Exception ex) { + String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + response.setContentType(rawContentType); + } if (response.getContentType() == null && contentType != null) { response.setContentType(contentType.toString()); } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index 3668d4ebaf..f28b404c50 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -119,7 +119,14 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { this.response.addHeader(headerName, headerValue); } }); - MediaType contentType = getHeaders().getContentType(); + MediaType contentType = null; + try { + contentType = getHeaders().getContentType(); + } + catch (Exception ex) { + String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + this.response.setContentType(rawContentType); + } if (this.response.getContentType() == null && contentType != null) { this.response.setContentType(contentType.toString()); } @@ -127,6 +134,10 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse { if (this.response.getCharacterEncoding() == null && charset != null) { this.response.setCharacterEncoding(charset.name()); } + long contentLength = getHeaders().getContentLength(); + if (contentLength != -1) { + this.response.setContentLengthLong(contentLength); + } } @Override diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java index ec91d17400..055869ed0b 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/TomcatHttpHandlerAdapter.java @@ -205,7 +205,14 @@ public class TomcatHttpHandlerAdapter extends ServletHttpHandlerAdapter { @Override protected void applyHeaders() { HttpServletResponse response = getNativeResponse(); - MediaType contentType = getHeaders().getContentType(); + MediaType contentType = null; + try { + contentType = getHeaders().getContentType(); + } + catch (Exception ex) { + String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + response.setContentType(rawContentType); + } if (response.getContentType() == null && contentType != null) { response.setContentType(contentType.toString()); } diff --git a/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java b/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java index d88a0e3fb4..a775b8b504 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/OncePerRequestFilter.java @@ -248,7 +248,7 @@ public abstract class OncePerRequestFilter extends GenericFilterBean { protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - doFilter(request, response, filterChain); + filterChain.doFilter(request, response); } } diff --git a/spring-web/src/test/java/org/springframework/web/filter/OncePerRequestFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/OncePerRequestFilterTests.java index efa78b2b06..7f1b70da68 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/OncePerRequestFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/OncePerRequestFilterTests.java @@ -168,9 +168,10 @@ public class OncePerRequestFilterTests { @Override protected void doFilterNestedErrorDispatch(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) { + FilterChain filterChain) throws ServletException, IOException { this.didFilterNestedErrorDispatch = true; + super.doFilterNestedErrorDispatch(request, response, filterChain); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java index 645fd02f9c..f688756b22 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/JettyRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -33,7 +33,9 @@ import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.server.reactive.AbstractServerHttpRequest; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.reactive.socket.HandshakeInfo; @@ -145,8 +147,8 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); - HttpServletRequest servletRequest = getHttpServletRequest(request); - HttpServletResponse servletResponse = getHttpServletResponse(response); + HttpServletRequest servletRequest = getNativeRequest(request); + HttpServletResponse servletResponse = getNativeResponse(response); HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); DataBufferFactory factory = response.bufferFactory(); @@ -174,14 +176,30 @@ public class JettyRequestUpgradeStrategy implements RequestUpgradeStrategy, Life return Mono.empty(); } - private HttpServletRequest getHttpServletRequest(ServerHttpRequest request) { - Assert.isInstanceOf(AbstractServerHttpRequest.class, request); - return ((AbstractServerHttpRequest) request).getNativeRequest(); + private static HttpServletRequest getNativeRequest(ServerHttpRequest request) { + if (request instanceof AbstractServerHttpRequest) { + return ((AbstractServerHttpRequest) request).getNativeRequest(); + } + else if (request instanceof ServerHttpRequestDecorator) { + return getNativeRequest(((ServerHttpRequestDecorator) request).getDelegate()); + } + else { + throw new IllegalArgumentException( + "Couldn't find HttpServletRequest in " + request.getClass().getName()); + } } - private HttpServletResponse getHttpServletResponse(ServerHttpResponse response) { - Assert.isInstanceOf(AbstractServerHttpResponse.class, response); - return ((AbstractServerHttpResponse) response).getNativeResponse(); + private static HttpServletResponse getNativeResponse(ServerHttpResponse response) { + if (response instanceof AbstractServerHttpResponse) { + return ((AbstractServerHttpResponse) response).getNativeResponse(); + } + else if (response instanceof ServerHttpResponseDecorator) { + return getNativeResponse(((ServerHttpResponseDecorator) response).getDelegate()); + } + else { + throw new IllegalArgumentException( + "Couldn't find HttpServletResponse in " + response.getClass().getName()); + } } private void startLazily(HttpServletRequest request) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java index 91962dfdf6..85edc74852 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/ReactorNettyRequestUpgradeStrategy.java @@ -25,6 +25,7 @@ import reactor.netty.http.server.HttpServerResponse; import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.lang.Nullable; import org.springframework.web.reactive.socket.HandshakeInfo; import org.springframework.web.reactive.socket.WebSocketHandler; @@ -73,7 +74,7 @@ public class ReactorNettyRequestUpgradeStrategy implements RequestUpgradeStrateg @Nullable String subProtocol, Supplier handshakeInfoFactory) { ServerHttpResponse response = exchange.getResponse(); - HttpServerResponse reactorResponse = ((AbstractServerHttpResponse) response).getNativeResponse(); + HttpServerResponse reactorResponse = getNativeResponse(response); HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); NettyDataBufferFactory bufferFactory = (NettyDataBufferFactory) response.bufferFactory(); @@ -87,4 +88,17 @@ public class ReactorNettyRequestUpgradeStrategy implements RequestUpgradeStrateg }); } + private static HttpServerResponse getNativeResponse(ServerHttpResponse response) { + if (response instanceof AbstractServerHttpResponse) { + return ((AbstractServerHttpResponse) response).getNativeResponse(); + } + else if (response instanceof ServerHttpResponseDecorator) { + return getNativeResponse(((ServerHttpResponseDecorator) response).getDelegate()); + } + else { + throw new IllegalArgumentException( + "Couldn't find native response in " + response.getClass().getName()); + } + } + } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java index 7273ec3f2f..fb4ef36dd0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/TomcatRequestUpgradeStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -33,7 +33,9 @@ import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.server.reactive.AbstractServerHttpRequest; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.reactive.socket.HandshakeInfo; @@ -131,8 +133,8 @@ public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); - HttpServletRequest servletRequest = getHttpServletRequest(request); - HttpServletResponse servletResponse = getHttpServletResponse(response); + HttpServletRequest servletRequest = getNativeRequest(request); + HttpServletResponse servletResponse = getNativeResponse(response); HandshakeInfo handshakeInfo = handshakeInfoFactory.get(); DataBufferFactory bufferFactory = response.bufferFactory(); @@ -156,14 +158,30 @@ public class TomcatRequestUpgradeStrategy implements RequestUpgradeStrategy { return Mono.empty(); } - private HttpServletRequest getHttpServletRequest(ServerHttpRequest request) { - Assert.isInstanceOf(AbstractServerHttpRequest.class, request, "ServletServerHttpRequest required"); - return ((AbstractServerHttpRequest) request).getNativeRequest(); + private static HttpServletRequest getNativeRequest(ServerHttpRequest request) { + if (request instanceof AbstractServerHttpRequest) { + return ((AbstractServerHttpRequest) request).getNativeRequest(); + } + else if (request instanceof ServerHttpRequestDecorator) { + return getNativeRequest(((ServerHttpRequestDecorator) request).getDelegate()); + } + else { + throw new IllegalArgumentException( + "Couldn't find HttpServletRequest in " + request.getClass().getName()); + } } - private HttpServletResponse getHttpServletResponse(ServerHttpResponse response) { - Assert.isInstanceOf(AbstractServerHttpResponse.class, response, "ServletServerHttpResponse required"); - return ((AbstractServerHttpResponse) response).getNativeResponse(); + private static HttpServletResponse getNativeResponse(ServerHttpResponse response) { + if (response instanceof AbstractServerHttpResponse) { + return ((AbstractServerHttpResponse) response).getNativeResponse(); + } + else if (response instanceof ServerHttpResponseDecorator) { + return getNativeResponse(((ServerHttpResponseDecorator) response).getDelegate()); + } + else { + throw new IllegalArgumentException( + "Couldn't find HttpServletResponse in " + response.getClass().getName()); + } } private WsServerContainer getContainer(HttpServletRequest request) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java index 70228291f6..254241c662 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/upgrade/UndertowRequestUpgradeStrategy.java @@ -33,6 +33,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.server.reactive.AbstractServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.web.reactive.socket.HandshakeInfo; @@ -55,9 +56,7 @@ public class UndertowRequestUpgradeStrategy implements RequestUpgradeStrategy { public Mono upgrade(ServerWebExchange exchange, WebSocketHandler handler, @Nullable String subProtocol, Supplier handshakeInfoFactory) { - ServerHttpRequest request = exchange.getRequest(); - Assert.isInstanceOf(AbstractServerHttpRequest.class, request); - HttpServerExchange httpExchange = ((AbstractServerHttpRequest) request).getNativeRequest(); + HttpServerExchange httpExchange = getNativeRequest(exchange.getRequest()); Set protocols = (subProtocol != null ? Collections.singleton(subProtocol) : Collections.emptySet()); Hybi13Handshake handshake = new Hybi13Handshake(protocols, false); @@ -77,6 +76,19 @@ public class UndertowRequestUpgradeStrategy implements RequestUpgradeStrategy { return Mono.empty(); } + private static HttpServerExchange getNativeRequest(ServerHttpRequest request) { + if (request instanceof AbstractServerHttpRequest) { + return ((AbstractServerHttpRequest) request).getNativeRequest(); + } + else if (request instanceof ServerHttpRequestDecorator) { + return getNativeRequest(((ServerHttpRequestDecorator) request).getDelegate()); + } + else { + throw new IllegalArgumentException( + "Couldn't find HttpServerExchange in " + request.getClass().getName()); + } + } + private class DefaultCallback implements WebSocketConnectionCallback {