diff --git a/spring-web-reactive/src/main/java/org/springframework/http/ResponseCookie.java b/spring-web-reactive/src/main/java/org/springframework/http/ResponseCookie.java
index 13d9adbc45c..0254d5a4358 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/ResponseCookie.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/ResponseCookie.java
@@ -150,6 +150,12 @@ public final class ResponseCookie extends HttpCookie {
return this;
}
+ @Override
+ public ResponseCookieBuilder maxAge(long maxAgeSeconds) {
+ this.maxAge = maxAgeSeconds >= 0 ? Duration.ofSeconds(maxAgeSeconds) : Duration.ofSeconds(-1);
+ return this;
+ }
+
@Override
public ResponseCookieBuilder domain(String domain) {
this.domain = domain;
@@ -163,14 +169,14 @@ public final class ResponseCookie extends HttpCookie {
}
@Override
- public ResponseCookieBuilder secure() {
- this.secure = true;
+ public ResponseCookieBuilder secure(boolean secure) {
+ this.secure = secure;
return this;
}
@Override
- public ResponseCookieBuilder httpOnly() {
- this.httpOnly = true;
+ public ResponseCookieBuilder httpOnly(boolean httpOnly) {
+ this.httpOnly = httpOnly;
return this;
}
@@ -197,6 +203,11 @@ public final class ResponseCookie extends HttpCookie {
*/
ResponseCookieBuilder maxAge(Duration maxAge);
+ /**
+ * Set the cookie "Max-Age" attribute in seconds.
+ */
+ ResponseCookieBuilder maxAge(long maxAgeSeconds);
+
/**
* Set the cookie "Path" attribute.
*/
@@ -210,13 +221,13 @@ public final class ResponseCookie extends HttpCookie {
/**
* Add the "Secure" attribute to the cookie.
*/
- ResponseCookieBuilder secure();
+ ResponseCookieBuilder secure(boolean secure);
/**
* Add the "HttpOnly" attribute to the cookie.
* @see http://www.owasp.org/index.php/HTTPOnly
*/
- ResponseCookieBuilder httpOnly();
+ ResponseCookieBuilder httpOnly(boolean httpOnly);
/**
* Create the HttpCookie.
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java
index 850eea9e36e..8018698e769 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java
@@ -23,8 +23,12 @@ import java.util.function.Supplier;
import reactor.core.publisher.Mono;
+import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
/**
* Base class for {@link ClientHttpRequest} implementations.
@@ -36,6 +40,8 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private final HttpHeaders headers;
+ private final MultiValueMap cookies;
+
private AtomicReference state = new AtomicReference<>(State.NEW);
private final List>> beforeCommitActions = new ArrayList<>(4);
@@ -47,6 +53,7 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
else {
this.headers = httpHeaders;
}
+ this.cookies = new LinkedMultiValueMap<>();
}
@Override
@@ -57,6 +64,14 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
return this.headers;
}
+ @Override
+ public MultiValueMap getCookies() {
+ if (State.COMITTED.equals(this.state.get())) {
+ return CollectionUtils.unmodifiableMultiValueMap(this.cookies);
+ }
+ return this.cookies;
+ }
+
protected Mono applyBeforeCommit() {
Mono mono = Mono.empty();
if (this.state.compareAndSet(State.NEW, State.COMMITTING)) {
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java
index 32c38940aef..3e7098a0434 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java
@@ -20,8 +20,10 @@ import java.net.URI;
import reactor.core.publisher.Mono;
+import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.http.ReactiveHttpOutputMessage;
+import org.springframework.util.MultiValueMap;
/**
* Represents a reactive client-side HTTP request.
@@ -41,6 +43,11 @@ public interface ClientHttpRequest extends ReactiveHttpOutputMessage {
*/
URI getURI();
+ /**
+ * Return a mutable map of request cookies to send to the server.
+ */
+ MultiValueMap getCookies();
+
/**
* Execute this request, resulting in a reactive stream of a single
* {@link org.springframework.http.client.ClientHttpResponse}.
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java
index 34cad69c105..502102b2279 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java
@@ -18,6 +18,8 @@ package org.springframework.http.client.reactive;
import org.springframework.http.HttpStatus;
import org.springframework.http.ReactiveHttpInputMessage;
+import org.springframework.http.ResponseCookie;
+import org.springframework.util.MultiValueMap;
/**
* Represents a reactive client-side HTTP response.
@@ -31,4 +33,9 @@ public interface ClientHttpResponse extends ReactiveHttpInputMessage {
*/
HttpStatus getStatusCode();
+ /**
+ * Return a read-only map of response cookies received from the server.
+ */
+ MultiValueMap getCookies();
+
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
index 81ec72a3d41..5ff05bc20d1 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
@@ -17,16 +17,19 @@
package org.springframework.http.client.reactive;
import java.net.URI;
+import java.util.Collection;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.io.buffer.Buffer;
import reactor.io.netty.http.HttpClient;
+import reactor.io.netty.http.model.Cookie;
import reactor.io.netty.http.model.Method;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferAllocator;
+import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -96,13 +99,13 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
channel.headers().removeTransferEncodingChunked();
}
return applyBeforeCommit()
- .after(() ->
- {
- getHeaders().entrySet().stream()
- .forEach(e -> channel.headers().set(e.getKey(), e.getValue()));
- return Mono.empty();
- }
- )
+ .after(() -> {
+ getHeaders().entrySet().stream().forEach(e ->
+ channel.headers().set(e.getKey(), e.getValue()));
+ getCookies().values().stream().flatMap(Collection::stream).forEach(cookie ->
+ channel.addCookie(cookie.getName(), new ReactorCookie(cookie)));
+ return Mono.empty();
+ })
.after(() -> {
if (body != null) {
return channel.writeBufferWith(body);
@@ -115,5 +118,29 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
.map(httpChannel -> new ReactorClientHttpResponse(httpChannel, allocator));
}
+
+ /**
+ * At present Reactor does not provide a {@link Cookie} implementation.
+ */
+ private final static class ReactorCookie extends Cookie {
+
+ private final HttpCookie httpCookie;
+
+
+ public ReactorCookie(HttpCookie httpCookie) {
+ this.httpCookie = httpCookie;
+ }
+
+ @Override
+ public String name() {
+ return this.httpCookie.getName();
+ }
+
+ @Override
+ public String value() {
+ return this.httpCookie.getValue();
+ }
+ }
+
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java
index 937d26d07b5..df2d58aded3 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java
@@ -17,6 +17,7 @@
package org.springframework.http.client.reactive;
import java.nio.ByteBuffer;
+import java.util.Collection;
import reactor.core.publisher.Flux;
import reactor.io.buffer.Buffer;
@@ -26,6 +27,10 @@ import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferAllocator;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseCookie;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
/**
* {@link ClientHttpResponse} implementation for the Reactor Net HTTP client
@@ -62,10 +67,27 @@ public class ReactorClientHttpResponse implements ClientHttpResponse {
return HttpStatus.valueOf(this.channel.responseStatus().getCode());
}
+ @Override
+ public MultiValueMap getCookies() {
+ MultiValueMap result = new LinkedMultiValueMap<>();
+ this.channel.cookies().values().stream().flatMap(Collection::stream)
+ .forEach(cookie -> {
+ ResponseCookie responseCookie = ResponseCookie.from(cookie.name(), cookie.value())
+ .domain(cookie.domain())
+ .path(cookie.path())
+ .maxAge(cookie.maxAge())
+ .secure(cookie.secure())
+ .httpOnly(cookie.httpOnly())
+ .build();
+ result.add(cookie.name(), responseCookie);
+ });
+ return CollectionUtils.unmodifiableMultiValueMap(result);
+ }
+
@Override
public String toString() {
return "ReactorClientHttpResponse{" +
- "request=" + this.channel.method() + " " + this.channel.uri().toString() + "," +
+ "request=" + this.channel.method() + " " + this.channel.uri() + "," +
"status=" + getStatusCode() +
'}';
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpRequest.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpRequest.java
index 74c894e7fa8..7ab96b37c66 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpRequest.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpRequest.java
@@ -17,10 +17,12 @@
package org.springframework.http.client.reactive;
import java.net.URI;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.reactivex.netty.protocol.http.client.HttpClient;
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import org.reactivestreams.Publisher;
@@ -31,6 +33,7 @@ import rx.Observable;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferAllocator;
+import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -102,6 +105,11 @@ public class RxNettyClientHttpRequest extends AbstractClientHttpRequest {
req = req.addHeader(entry.getKey(), value);
}
}
+ for (Map.Entry> entry : getCookies().entrySet()) {
+ for (HttpCookie cookie : entry.getValue()) {
+ req.addCookie(new DefaultCookie(cookie.getName(), cookie.getValue()));
+ }
+ }
return req;
})
.map(req -> {
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpResponse.java
index a6a2efe3b2f..5edd02ef9e9 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpResponse.java
@@ -16,6 +16,8 @@
package org.springframework.http.client.reactive;
+import java.util.Collection;
+
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import reactor.core.converter.RxJava1ObservableConverter;
@@ -25,7 +27,11 @@ import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferAllocator;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseCookie;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
/**
* {@link ClientHttpResponse} implementation for the RxNetty HTTP client
@@ -38,8 +44,11 @@ public class RxNettyClientHttpResponse implements ClientHttpResponse {
private final HttpHeaders headers;
+ private final MultiValueMap cookies;
+
private final NettyDataBufferAllocator allocator;
+
public RxNettyClientHttpResponse(HttpClientResponse response,
NettyDataBufferAllocator allocator) {
Assert.notNull("'request', request must not be null");
@@ -48,8 +57,26 @@ public class RxNettyClientHttpResponse implements ClientHttpResponse {
this.response = response;
this.headers = new HttpHeaders();
this.response.headerIterator().forEachRemaining(e -> this.headers.set(e.getKey(), e.getValue()));
+ this.cookies = initCookies(response);
+ }
+
+ private static MultiValueMap initCookies(HttpClientResponse response) {
+ MultiValueMap result = new LinkedMultiValueMap<>();
+ response.getCookies().values().stream().flatMap(Collection::stream)
+ .forEach(cookie -> {
+ ResponseCookie responseCookie = ResponseCookie.from(cookie.name(), cookie.value())
+ .domain(cookie.domain())
+ .path(cookie.path())
+ .maxAge(cookie.maxAge())
+ .secure(cookie.isSecure())
+ .httpOnly(cookie.isHttpOnly())
+ .build();
+ result.add(cookie.name(), responseCookie);
+ });
+ return CollectionUtils.unmodifiableMultiValueMap(result);
}
+
@Override
public HttpStatus getStatusCode() {
return HttpStatus.valueOf(this.response.getStatus().code());
@@ -64,4 +91,10 @@ public class RxNettyClientHttpResponse implements ClientHttpResponse {
public HttpHeaders getHeaders() {
return this.headers;
}
+
+ @Override
+ public MultiValueMap getCookies() {
+ return this.cookies;
+ }
+
}
diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
index 18ba0f172b7..4e641499f55 100644
--- a/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
+++ b/spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
@@ -37,7 +37,7 @@ public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
void setStatusCode(HttpStatus status);
/**
- * Return a mutable map with cookies to be sent to the client.
+ * Return a mutable map with the cookies to send to the server.
*/
MultiValueMap getCookies();
diff --git a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java
index db9e79a3c04..d39d60a45cb 100644
--- a/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java
+++ b/spring-web-reactive/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java
@@ -103,7 +103,7 @@ public class CookieIntegrationTests extends AbstractHttpHandlerIntegrationTests
this.requestCookies.size(); // Cause lazy loading
response.getCookies().add("SID", ResponseCookie.from("SID", "31d4d96e407aad42")
- .path("/").secure().httpOnly().build());
+ .path("/").secure(true).httpOnly(true).build());
response.getCookies().add("lang", ResponseCookie.from("lang", "en-US")
.domain("example.com").path("/").build());