Browse Source

Add cookies to ClientHttpRequest/Response

pull/1111/head
Rossen Stoyanchev 10 years ago
parent
commit
72dbe9012e
  1. 23
      spring-web-reactive/src/main/java/org/springframework/http/ResponseCookie.java
  2. 15
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/AbstractClientHttpRequest.java
  3. 7
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpRequest.java
  4. 7
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ClientHttpResponse.java
  5. 41
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java
  6. 24
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java
  7. 8
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpRequest.java
  8. 33
      spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpResponse.java
  9. 2
      spring-web-reactive/src/main/java/org/springframework/http/server/reactive/ServerHttpResponse.java
  10. 2
      spring-web-reactive/src/test/java/org/springframework/http/server/reactive/CookieIntegrationTests.java

23
spring-web-reactive/src/main/java/org/springframework/http/ResponseCookie.java

@ -150,6 +150,12 @@ public final class ResponseCookie extends HttpCookie {
return this; return this;
} }
@Override
public ResponseCookieBuilder maxAge(long maxAgeSeconds) {
this.maxAge = maxAgeSeconds >= 0 ? Duration.ofSeconds(maxAgeSeconds) : Duration.ofSeconds(-1);
return this;
}
@Override @Override
public ResponseCookieBuilder domain(String domain) { public ResponseCookieBuilder domain(String domain) {
this.domain = domain; this.domain = domain;
@ -163,14 +169,14 @@ public final class ResponseCookie extends HttpCookie {
} }
@Override @Override
public ResponseCookieBuilder secure() { public ResponseCookieBuilder secure(boolean secure) {
this.secure = true; this.secure = secure;
return this; return this;
} }
@Override @Override
public ResponseCookieBuilder httpOnly() { public ResponseCookieBuilder httpOnly(boolean httpOnly) {
this.httpOnly = true; this.httpOnly = httpOnly;
return this; return this;
} }
@ -197,6 +203,11 @@ public final class ResponseCookie extends HttpCookie {
*/ */
ResponseCookieBuilder maxAge(Duration maxAge); ResponseCookieBuilder maxAge(Duration maxAge);
/**
* Set the cookie "Max-Age" attribute in seconds.
*/
ResponseCookieBuilder maxAge(long maxAgeSeconds);
/** /**
* Set the cookie "Path" attribute. * Set the cookie "Path" attribute.
*/ */
@ -210,13 +221,13 @@ public final class ResponseCookie extends HttpCookie {
/** /**
* Add the "Secure" attribute to the cookie. * Add the "Secure" attribute to the cookie.
*/ */
ResponseCookieBuilder secure(); ResponseCookieBuilder secure(boolean secure);
/** /**
* Add the "HttpOnly" attribute to the cookie. * Add the "HttpOnly" attribute to the cookie.
* @see <a href="http://www.owasp.org/index.php/HTTPOnly">http://www.owasp.org/index.php/HTTPOnly</a> * @see <a href="http://www.owasp.org/index.php/HTTPOnly">http://www.owasp.org/index.php/HTTPOnly</a>
*/ */
ResponseCookieBuilder httpOnly(); ResponseCookieBuilder httpOnly(boolean httpOnly);
/** /**
* Create the HttpCookie. * Create the HttpCookie.

15
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 reactor.core.publisher.Mono;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert; 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. * Base class for {@link ClientHttpRequest} implementations.
@ -36,6 +40,8 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private final HttpHeaders headers; private final HttpHeaders headers;
private final MultiValueMap<String, HttpCookie> cookies;
private AtomicReference<State> state = new AtomicReference<>(State.NEW); private AtomicReference<State> state = new AtomicReference<>(State.NEW);
private final List<Supplier<? extends Mono<Void>>> beforeCommitActions = new ArrayList<>(4); private final List<Supplier<? extends Mono<Void>>> beforeCommitActions = new ArrayList<>(4);
@ -47,6 +53,7 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
else { else {
this.headers = httpHeaders; this.headers = httpHeaders;
} }
this.cookies = new LinkedMultiValueMap<>();
} }
@Override @Override
@ -57,6 +64,14 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
return this.headers; return this.headers;
} }
@Override
public MultiValueMap<String, HttpCookie> getCookies() {
if (State.COMITTED.equals(this.state.get())) {
return CollectionUtils.unmodifiableMultiValueMap(this.cookies);
}
return this.cookies;
}
protected Mono<Void> applyBeforeCommit() { protected Mono<Void> applyBeforeCommit() {
Mono<Void> mono = Mono.empty(); Mono<Void> mono = Mono.empty();
if (this.state.compareAndSet(State.NEW, State.COMMITTING)) { if (this.state.compareAndSet(State.NEW, State.COMMITTING)) {

7
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 reactor.core.publisher.Mono;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.ReactiveHttpOutputMessage; import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.util.MultiValueMap;
/** /**
* Represents a reactive client-side HTTP request. * Represents a reactive client-side HTTP request.
@ -41,6 +43,11 @@ public interface ClientHttpRequest extends ReactiveHttpOutputMessage {
*/ */
URI getURI(); URI getURI();
/**
* Return a mutable map of request cookies to send to the server.
*/
MultiValueMap<String, HttpCookie> getCookies();
/** /**
* Execute this request, resulting in a reactive stream of a single * Execute this request, resulting in a reactive stream of a single
* {@link org.springframework.http.client.ClientHttpResponse}. * {@link org.springframework.http.client.ClientHttpResponse}.

7
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.HttpStatus;
import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.ResponseCookie;
import org.springframework.util.MultiValueMap;
/** /**
* Represents a reactive client-side HTTP response. * Represents a reactive client-side HTTP response.
@ -31,4 +33,9 @@ public interface ClientHttpResponse extends ReactiveHttpInputMessage {
*/ */
HttpStatus getStatusCode(); HttpStatus getStatusCode();
/**
* Return a read-only map of response cookies received from the server.
*/
MultiValueMap<String, ResponseCookie> getCookies();
} }

41
spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java

@ -17,16 +17,19 @@
package org.springframework.http.client.reactive; package org.springframework.http.client.reactive;
import java.net.URI; import java.net.URI;
import java.util.Collection;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.io.buffer.Buffer; import reactor.io.buffer.Buffer;
import reactor.io.netty.http.HttpClient; import reactor.io.netty.http.HttpClient;
import reactor.io.netty.http.model.Cookie;
import reactor.io.netty.http.model.Method; import reactor.io.netty.http.model.Method;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferAllocator; import org.springframework.core.io.buffer.DataBufferAllocator;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -96,13 +99,13 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
channel.headers().removeTransferEncodingChunked(); channel.headers().removeTransferEncodingChunked();
} }
return applyBeforeCommit() return applyBeforeCommit()
.after(() -> .after(() -> {
{ getHeaders().entrySet().stream().forEach(e ->
getHeaders().entrySet().stream() channel.headers().set(e.getKey(), e.getValue()));
.forEach(e -> channel.headers().set(e.getKey(), e.getValue())); getCookies().values().stream().flatMap(Collection::stream).forEach(cookie ->
return Mono.empty(); channel.addCookie(cookie.getName(), new ReactorCookie(cookie)));
} return Mono.empty();
) })
.after(() -> { .after(() -> {
if (body != null) { if (body != null) {
return channel.writeBufferWith(body); return channel.writeBufferWith(body);
@ -115,5 +118,29 @@ public class ReactorClientHttpRequest extends AbstractClientHttpRequest {
.map(httpChannel -> new ReactorClientHttpResponse(httpChannel, allocator)); .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();
}
}
} }

24
spring-web-reactive/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpResponse.java

@ -17,6 +17,7 @@
package org.springframework.http.client.reactive; package org.springframework.http.client.reactive;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collection;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.io.buffer.Buffer; 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.core.io.buffer.DataBufferAllocator;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; 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 * {@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()); return HttpStatus.valueOf(this.channel.responseStatus().getCode());
} }
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
MultiValueMap<String, ResponseCookie> 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 @Override
public String toString() { public String toString() {
return "ReactorClientHttpResponse{" + return "ReactorClientHttpResponse{" +
"request=" + this.channel.method() + " " + this.channel.uri().toString() + "," + "request=" + this.channel.method() + " " + this.channel.uri() + "," +
"status=" + getStatusCode() + "status=" + getStatusCode() +
'}'; '}';
} }

8
spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpRequest.java

@ -17,10 +17,12 @@
package org.springframework.http.client.reactive; package org.springframework.http.client.reactive;
import java.net.URI; import java.net.URI;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.netty.buffer.ByteBuf; 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.HttpClient;
import io.reactivex.netty.protocol.http.client.HttpClientRequest; import io.reactivex.netty.protocol.http.client.HttpClientRequest;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
@ -31,6 +33,7 @@ import rx.Observable;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferAllocator; import org.springframework.core.io.buffer.NettyDataBufferAllocator;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -102,6 +105,11 @@ public class RxNettyClientHttpRequest extends AbstractClientHttpRequest {
req = req.addHeader(entry.getKey(), value); req = req.addHeader(entry.getKey(), value);
} }
} }
for (Map.Entry<String, List<HttpCookie>> entry : getCookies().entrySet()) {
for (HttpCookie cookie : entry.getValue()) {
req.addCookie(new DefaultCookie(cookie.getName(), cookie.getValue()));
}
}
return req; return req;
}) })
.map(req -> { .map(req -> {

33
spring-web-reactive/src/main/java/org/springframework/http/client/reactive/RxNettyClientHttpResponse.java

@ -16,6 +16,8 @@
package org.springframework.http.client.reactive; package org.springframework.http.client.reactive;
import java.util.Collection;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.client.HttpClientResponse; import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import reactor.core.converter.RxJava1ObservableConverter; 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.core.io.buffer.NettyDataBufferAllocator;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.util.Assert; 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 * {@link ClientHttpResponse} implementation for the RxNetty HTTP client
@ -38,8 +44,11 @@ public class RxNettyClientHttpResponse implements ClientHttpResponse {
private final HttpHeaders headers; private final HttpHeaders headers;
private final MultiValueMap<String, ResponseCookie> cookies;
private final NettyDataBufferAllocator allocator; private final NettyDataBufferAllocator allocator;
public RxNettyClientHttpResponse(HttpClientResponse<ByteBuf> response, public RxNettyClientHttpResponse(HttpClientResponse<ByteBuf> response,
NettyDataBufferAllocator allocator) { NettyDataBufferAllocator allocator) {
Assert.notNull("'request', request must not be null"); Assert.notNull("'request', request must not be null");
@ -48,8 +57,26 @@ public class RxNettyClientHttpResponse implements ClientHttpResponse {
this.response = response; this.response = response;
this.headers = new HttpHeaders(); this.headers = new HttpHeaders();
this.response.headerIterator().forEachRemaining(e -> this.headers.set(e.getKey(), e.getValue())); this.response.headerIterator().forEachRemaining(e -> this.headers.set(e.getKey(), e.getValue()));
this.cookies = initCookies(response);
}
private static MultiValueMap<String, ResponseCookie> initCookies(HttpClientResponse<ByteBuf> response) {
MultiValueMap<String, ResponseCookie> 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 @Override
public HttpStatus getStatusCode() { public HttpStatus getStatusCode() {
return HttpStatus.valueOf(this.response.getStatus().code()); return HttpStatus.valueOf(this.response.getStatus().code());
@ -64,4 +91,10 @@ public class RxNettyClientHttpResponse implements ClientHttpResponse {
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return this.headers; return this.headers;
} }
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return this.cookies;
}
} }

2
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); 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<String, ResponseCookie> getCookies(); MultiValueMap<String, ResponseCookie> getCookies();

2
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 this.requestCookies.size(); // Cause lazy loading
response.getCookies().add("SID", ResponseCookie.from("SID", "31d4d96e407aad42") 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") response.getCookies().add("lang", ResponseCookie.from("lang", "en-US")
.domain("example.com").path("/").build()); .domain("example.com").path("/").build());

Loading…
Cancel
Save