Set 304 status on ServerResponse when ETag/LastModified match
This commit checks the Etag/LastModified headers on the incoming request, and sets a 304 Not Modified status with no body when they match, by delegating to ServerWebExchange.checkNotModified. Issue: SPR-16348
This commit is contained in:
parent
c211e3998b
commit
c53c8bfc5a
|
@ -224,10 +224,8 @@ class DefaultEntityResponseBuilder<T> implements EntityResponse.Builder<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
|
protected Mono<Void> writeToInternal(ServerWebExchange exchange, Context context) {
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
return inserter().insert(exchange.getResponse(), new BodyInserter.Context() {
|
||||||
writeStatusAndHeaders(response);
|
|
||||||
return inserter().insert(response, new BodyInserter.Context() {
|
|
||||||
@Override
|
@Override
|
||||||
public List<HttpMessageWriter<?>> messageWriters() {
|
public List<HttpMessageWriter<?>> messageWriters() {
|
||||||
return context.messageWriters();
|
return context.messageWriters();
|
||||||
|
|
|
@ -35,7 +35,6 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseCookie;
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
@ -184,9 +183,7 @@ class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
|
protected Mono<Void> writeToInternal(ServerWebExchange exchange, Context context) {
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
|
||||||
writeStatusAndHeaders(response);
|
|
||||||
MediaType responseContentType = exchange.getResponse().getHeaders().getContentType();
|
MediaType responseContentType = exchange.getResponse().getHeaders().getContentType();
|
||||||
Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
|
Locale locale = LocaleContextHolder.getLocale(exchange.getLocaleContext());
|
||||||
Stream<ViewResolver> viewResolverStream = context.viewResolvers().stream();
|
Stream<ViewResolver> viewResolverStream = context.viewResolvers().stream();
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
package org.springframework.web.reactive.function.server;
|
package org.springframework.web.reactive.function.server;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -278,6 +280,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
|
|
||||||
static abstract class AbstractServerResponse implements ServerResponse {
|
static abstract class AbstractServerResponse implements ServerResponse {
|
||||||
|
|
||||||
|
private static final Set<HttpMethod> SAFE_METHODS = EnumSet.of(HttpMethod.GET, HttpMethod.HEAD);
|
||||||
|
|
||||||
final int statusCode;
|
final int statusCode;
|
||||||
|
|
||||||
private final HttpHeaders headers;
|
private final HttpHeaders headers;
|
||||||
|
@ -319,7 +323,21 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
return this.cookies;
|
return this.cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeStatusAndHeaders(ServerHttpResponse response) {
|
@Override
|
||||||
|
public final Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
|
||||||
|
writeStatusAndHeaders(exchange.getResponse());
|
||||||
|
|
||||||
|
Instant lastModified = Instant.ofEpochMilli(headers().getLastModified());
|
||||||
|
HttpMethod httpMethod = exchange.getRequest().getMethod();
|
||||||
|
if (SAFE_METHODS.contains(httpMethod) && exchange.checkNotModified(headers().getETag(), lastModified)) {
|
||||||
|
return exchange.getResponse().setComplete();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return writeToInternal(exchange, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeStatusAndHeaders(ServerHttpResponse response) {
|
||||||
if (response instanceof AbstractServerHttpResponse) {
|
if (response instanceof AbstractServerHttpResponse) {
|
||||||
((AbstractServerHttpResponse) response).setStatusCodeValue(this.statusCode);
|
((AbstractServerHttpResponse) response).setStatusCodeValue(this.statusCode);
|
||||||
}
|
}
|
||||||
|
@ -335,6 +353,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
copy(this.cookies, response.getCookies());
|
copy(this.cookies, response.getCookies());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Mono<Void> writeToInternal(ServerWebExchange exchange, Context context);
|
||||||
|
|
||||||
private static <K,V> void copy(MultiValueMap<K,V> src, MultiValueMap<K,V> dst) {
|
private static <K,V> void copy(MultiValueMap<K,V> src, MultiValueMap<K,V> dst) {
|
||||||
if (!src.isEmpty()) {
|
if (!src.isEmpty()) {
|
||||||
src.entrySet().stream()
|
src.entrySet().stream()
|
||||||
|
@ -358,8 +378,7 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
|
protected Mono<Void> writeToInternal(ServerWebExchange exchange, Context context) {
|
||||||
writeStatusAndHeaders(exchange.getResponse());
|
|
||||||
return this.writeFunction.apply(exchange, context);
|
return this.writeFunction.apply(exchange, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,10 +400,8 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
|
protected Mono<Void> writeToInternal(ServerWebExchange exchange, Context context) {
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
return this.inserter.insert(exchange.getResponse(), new BodyInserter.Context() {
|
||||||
writeStatusAndHeaders(response);
|
|
||||||
return this.inserter.insert(response, new BodyInserter.Context() {
|
|
||||||
@Override
|
@Override
|
||||||
public List<HttpMessageWriter<?>> messageWriters() {
|
public List<HttpMessageWriter<?>> messageWriters() {
|
||||||
return context.messageWriters();
|
return context.messageWriters();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +18,8 @@ package org.springframework.web.reactive.function.server;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -44,6 +46,7 @@ import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||||
import org.springframework.http.codec.HttpMessageWriter;
|
import org.springframework.http.codec.HttpMessageWriter;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
@ -232,4 +235,52 @@ public class DefaultEntityResponseBuilderTests {
|
||||||
assertNotNull(exchange.getResponse().getBody());
|
assertNotNull(exchange.getResponse().getBody());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notModifiedEtag() {
|
||||||
|
String etag = "\"foo\"";
|
||||||
|
EntityResponse<String> responseMono = EntityResponse.fromObject("bar")
|
||||||
|
.eTag(etag)
|
||||||
|
.build()
|
||||||
|
.block();
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com")
|
||||||
|
.header(HttpHeaders.IF_NONE_MATCH, etag)
|
||||||
|
.build();
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
responseMono.writeTo(exchange, DefaultServerResponseBuilderTests.EMPTY_CONTEXT);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.expectError(IllegalStateException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notModifiedLastModified() {
|
||||||
|
ZonedDateTime now = ZonedDateTime.now();
|
||||||
|
ZonedDateTime oneMinuteBeforeNow = now.minus(1, ChronoUnit.MINUTES);
|
||||||
|
|
||||||
|
EntityResponse<String> responseMono = EntityResponse.fromObject("bar")
|
||||||
|
.lastModified(oneMinuteBeforeNow)
|
||||||
|
.build()
|
||||||
|
.block();
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com")
|
||||||
|
.header(HttpHeaders.IF_MODIFIED_SINCE,
|
||||||
|
DateTimeFormatter.RFC_1123_DATE_TIME.format(now))
|
||||||
|
.build();
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
responseMono.writeTo(exchange, DefaultServerResponseBuilderTests.EMPTY_CONTEXT);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.expectError(IllegalStateException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.function.server;
|
package org.springframework.web.reactive.function.server;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -28,9 +31,11 @@ import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseCookie;
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
@ -40,7 +45,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
|
||||||
import org.springframework.web.reactive.result.view.ViewResolverSupport;
|
import org.springframework.web.reactive.result.view.ViewResolverSupport;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,4 +186,52 @@ public class DefaultRenderingResponseTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notModifiedEtag() {
|
||||||
|
String etag = "\"foo\"";
|
||||||
|
RenderingResponse responseMono = RenderingResponse.create("bar")
|
||||||
|
.header(HttpHeaders.ETAG, etag)
|
||||||
|
.build()
|
||||||
|
.block();
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com")
|
||||||
|
.header(HttpHeaders.IF_NONE_MATCH, etag)
|
||||||
|
.build();
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
responseMono.writeTo(exchange, DefaultServerResponseBuilderTests.EMPTY_CONTEXT);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.expectError(IllegalStateException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notModifiedLastModified() {
|
||||||
|
ZonedDateTime now = ZonedDateTime.now();
|
||||||
|
ZonedDateTime oneMinuteBeforeNow = now.minus(1, ChronoUnit.MINUTES);
|
||||||
|
|
||||||
|
RenderingResponse responseMono = RenderingResponse.create("bar")
|
||||||
|
.header(HttpHeaders.LAST_MODIFIED, DateTimeFormatter.RFC_1123_DATE_TIME.format(oneMinuteBeforeNow))
|
||||||
|
.build()
|
||||||
|
.block();
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com")
|
||||||
|
.header(HttpHeaders.IF_MODIFIED_SINCE,
|
||||||
|
DateTimeFormatter.RFC_1123_DATE_TIME.format(now))
|
||||||
|
.build();
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
responseMono.writeTo(exchange, DefaultServerResponseBuilderTests.EMPTY_CONTEXT);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.expectError(IllegalStateException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +18,8 @@ package org.springframework.web.reactive.function.server;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -33,19 +35,33 @@ import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseCookie;
|
import org.springframework.http.ResponseCookie;
|
||||||
|
import org.springframework.http.codec.HttpMessageWriter;
|
||||||
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
|
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.reactive.result.view.ViewResolver;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
*/
|
*/
|
||||||
public class DefaultServerResponseBuilderTests {
|
public class DefaultServerResponseBuilderTests {
|
||||||
|
|
||||||
|
static final ServerResponse.Context EMPTY_CONTEXT = new ServerResponse.Context() {
|
||||||
|
@Override
|
||||||
|
public List<HttpMessageWriter<?>> messageWriters() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ViewResolver> viewResolvers() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void from() throws Exception {
|
public void from() throws Exception {
|
||||||
ServerResponse other = ServerResponse.ok().header("foo", "bar").build().block();
|
ServerResponse other = ServerResponse.ok().header("foo", "bar").build().block();
|
||||||
|
@ -287,13 +303,12 @@ public class DefaultServerResponseBuilderTests {
|
||||||
.header("MyKey", "MyValue")
|
.header("MyKey", "MyValue")
|
||||||
.cookie(cookie).build();
|
.cookie(cookie).build();
|
||||||
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com").build();
|
||||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
when(exchange.getResponse()).thenReturn(response);
|
|
||||||
ServerResponse.Context context = mock(ServerResponse.Context.class);
|
|
||||||
|
|
||||||
result.flatMap(res -> res.writeTo(exchange, context)).block();
|
result.flatMap(res -> res.writeTo(exchange, EMPTY_CONTEXT)).block();
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
assertEquals(HttpStatus.CREATED, response.getStatusCode());
|
assertEquals(HttpStatus.CREATED, response.getStatusCode());
|
||||||
assertEquals("MyValue", response.getHeaders().getFirst("MyKey"));
|
assertEquals("MyValue", response.getHeaders().getFirst("MyKey"));
|
||||||
assertEquals("value", response.getCookies().getFirst("name").getValue());
|
assertEquals("value", response.getCookies().getFirst("name").getValue());
|
||||||
|
@ -305,13 +320,12 @@ public class DefaultServerResponseBuilderTests {
|
||||||
Mono<Void> mono = Mono.empty();
|
Mono<Void> mono = Mono.empty();
|
||||||
Mono<ServerResponse> result = ServerResponse.ok().build(mono);
|
Mono<ServerResponse> result = ServerResponse.ok().build(mono);
|
||||||
|
|
||||||
ServerWebExchange exchange = mock(ServerWebExchange.class);
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com").build();
|
||||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
when(exchange.getResponse()).thenReturn(response);
|
|
||||||
ServerResponse.Context context = mock(ServerResponse.Context.class);
|
|
||||||
|
|
||||||
result.flatMap(res -> res.writeTo(exchange, context)).block();
|
result.flatMap(res -> res.writeTo(exchange, EMPTY_CONTEXT)).block();
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
StepVerifier.create(response.getBody()).expectComplete().verify();
|
StepVerifier.create(response.getBody()).expectComplete().verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,5 +336,52 @@ public class DefaultServerResponseBuilderTests {
|
||||||
ServerResponse.ok().syncBody(mono);
|
ServerResponse.ok().syncBody(mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notModifiedEtag() {
|
||||||
|
String etag = "\"foo\"";
|
||||||
|
ServerResponse responseMono = ServerResponse.ok()
|
||||||
|
.eTag(etag)
|
||||||
|
.syncBody("bar")
|
||||||
|
.block();
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com")
|
||||||
|
.header(HttpHeaders.IF_NONE_MATCH, etag)
|
||||||
|
.build();
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
responseMono.writeTo(exchange, EMPTY_CONTEXT);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.expectError(IllegalStateException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void notModifiedLastModified() {
|
||||||
|
ZonedDateTime now = ZonedDateTime.now();
|
||||||
|
ZonedDateTime oneMinuteBeforeNow = now.minus(1, ChronoUnit.MINUTES);
|
||||||
|
|
||||||
|
ServerResponse responseMono = ServerResponse.ok()
|
||||||
|
.lastModified(oneMinuteBeforeNow)
|
||||||
|
.syncBody("bar")
|
||||||
|
.block();
|
||||||
|
|
||||||
|
MockServerHttpRequest request = MockServerHttpRequest.get("http://example.com")
|
||||||
|
.header(HttpHeaders.IF_MODIFIED_SINCE,
|
||||||
|
DateTimeFormatter.RFC_1123_DATE_TIME.format(now))
|
||||||
|
.build();
|
||||||
|
MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
||||||
|
|
||||||
|
responseMono.writeTo(exchange, EMPTY_CONTEXT);
|
||||||
|
|
||||||
|
MockServerHttpResponse response = exchange.getResponse();
|
||||||
|
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
|
||||||
|
StepVerifier.create(response.getBody())
|
||||||
|
.expectError(IllegalStateException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue