Move spring-web-reactive classes to spring-web
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec;
|
||||
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
@XmlRootElement
|
||||
public class Pojo {
|
||||
|
||||
private String foo;
|
||||
|
||||
private String bar;
|
||||
|
||||
public Pojo() {
|
||||
}
|
||||
|
||||
public Pojo(String foo, String bar) {
|
||||
this.foo = foo;
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof Pojo) {
|
||||
Pojo other = (Pojo) o;
|
||||
return this.foo.equals(other.foo) && this.bar.equals(other.bar);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * foo.hashCode() + bar.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pojo[foo='" + this.foo + "\'" + ", bar='" + this.bar + "\']";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.FlushingDataBuffer;
|
||||
import org.springframework.http.codec.json.JacksonJsonEncoder;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class SseEventEncoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
@Test
|
||||
public void nullMimeType() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
assertTrue(encoder.canEncode(ResolvableType.forClass(Object.class), null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedMimeType() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
assertFalse(encoder.canEncode(ResolvableType.forClass(Object.class), new MimeType("foo", "bar")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportedMimeType() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
assertTrue(encoder.canEncode(ResolvableType.forClass(Object.class), new MimeType("text", "event-stream")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeServerSentEvent() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
SseEvent event = new SseEvent();
|
||||
event.setId("c42");
|
||||
event.setName("foo");
|
||||
event.setComment("bla\nbla bla\nbla bla bla");
|
||||
event.setReconnectTime(123L);
|
||||
Mono<SseEvent> source = Mono.just(event);
|
||||
Flux<DataBuffer> output = encoder.encode(source, this.bufferFactory,
|
||||
ResolvableType.forClass(SseEvent.class), new MimeType("text", "event-stream"));
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertValuesWith(
|
||||
stringConsumer(
|
||||
"id:c42\n" +
|
||||
"event:foo\n" +
|
||||
"retry:123\n" +
|
||||
":bla\n:bla bla\n:bla bla bla\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeString() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
Flux<String> source = Flux.just("foo", "bar");
|
||||
Flux<DataBuffer> output = encoder.encode(source, this.bufferFactory,
|
||||
ResolvableType.forClass(String.class), new MimeType("text", "event-stream"));
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertValuesWith(
|
||||
stringConsumer("data:foo\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass()),
|
||||
stringConsumer("data:bar\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeMultilineString() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
Flux<String> source = Flux.just("foo\nbar", "foo\nbaz");
|
||||
Flux<DataBuffer> output = encoder.encode(source, this.bufferFactory,
|
||||
ResolvableType.forClass(String.class), new MimeType("text", "event-stream"));
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertValuesWith(
|
||||
stringConsumer("data:foo\ndata:bar\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass()),
|
||||
stringConsumer("data:foo\ndata:baz\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodePojo() {
|
||||
SseEventEncoder encoder = new SseEventEncoder(Arrays.asList(new JacksonJsonEncoder()));
|
||||
Flux<Pojo> source = Flux.just(new Pojo("foofoo", "barbar"), new Pojo("foofoofoo", "barbarbar"));
|
||||
Flux<DataBuffer> output = encoder.encode(source, this.bufferFactory,
|
||||
ResolvableType.forClass(Pojo.class), new MimeType("text", "event-stream"));
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertValuesWith(
|
||||
stringConsumer("data:"),
|
||||
stringConsumer("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}"),
|
||||
stringConsumer("\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass()),
|
||||
stringConsumer("data:"),
|
||||
stringConsumer("{\"foo\":\"foofoofoo\",\"bar\":\"barbarbar\"}"),
|
||||
stringConsumer("\n"),
|
||||
stringConsumer("\n"),
|
||||
b -> assertEquals(FlushingDataBuffer.class, b.getClass())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.json;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.Pojo;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JacksonJsonDecoder}.
|
||||
* @author Sebastien Deleuze
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class JacksonJsonDecoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
@Test
|
||||
public void canDecode() {
|
||||
JacksonJsonDecoder decoder = new JacksonJsonDecoder();
|
||||
|
||||
assertTrue(decoder.canDecode(null, MediaType.APPLICATION_JSON));
|
||||
assertFalse(decoder.canDecode(null, MediaType.APPLICATION_XML));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodePojo() {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}"));
|
||||
ResolvableType elementType = ResolvableType.forClass(Pojo.class);
|
||||
Flux<Object> flux = new JacksonJsonDecoder().decode(source, elementType, null);
|
||||
|
||||
TestSubscriber.subscribe(flux).assertNoError().assertComplete().
|
||||
assertValues(new Pojo("foofoo", "barbar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeToList() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(
|
||||
"[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"));
|
||||
|
||||
Method method = getClass().getDeclaredMethod("handle", List.class);
|
||||
ResolvableType elementType = ResolvableType.forMethodParameter(method, 0);
|
||||
Mono<Object> mono = new JacksonJsonDecoder().decodeToMono(source, elementType, null);
|
||||
|
||||
TestSubscriber.subscribe(mono).assertNoError().assertComplete().
|
||||
assertValues(Arrays.asList(new Pojo("f1", "b1"), new Pojo("f2", "b2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeToFlux() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(
|
||||
"[{\"bar\":\"b1\",\"foo\":\"f1\"},{\"bar\":\"b2\",\"foo\":\"f2\"}]"));
|
||||
|
||||
ResolvableType elementType = ResolvableType.forClass(Pojo.class);
|
||||
Flux<Object> flux = new JacksonJsonDecoder().decode(source, elementType, null);
|
||||
|
||||
TestSubscriber.subscribe(flux).assertNoError().assertComplete().
|
||||
assertValues(new Pojo("f1", "b1"), new Pojo("f2", "b2"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
void handle(List<Pojo> list) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.json;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.Pojo;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class JacksonJsonEncoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
private JacksonJsonEncoder encoder;
|
||||
|
||||
|
||||
@Before
|
||||
public void createEncoder() {
|
||||
this.encoder = new JacksonJsonEncoder();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void canEncode() {
|
||||
assertTrue(this.encoder.canEncode(null, MediaType.APPLICATION_JSON));
|
||||
assertFalse(this.encoder.canEncode(null, MediaType.APPLICATION_XML));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encode() {
|
||||
Flux<Pojo> source = Flux.just(
|
||||
new Pojo("foo", "bar"),
|
||||
new Pojo("foofoo", "barbar"),
|
||||
new Pojo("foofoofoo", "barbarbar")
|
||||
);
|
||||
ResolvableType type = ResolvableType.forClass(Pojo.class);
|
||||
Flux<DataBuffer> output = this.encoder.encode(source, this.bufferFactory, type, null);
|
||||
|
||||
TestSubscriber.subscribe(output)
|
||||
.assertComplete()
|
||||
.assertNoError()
|
||||
.assertValuesWith(
|
||||
stringConsumer("["),
|
||||
stringConsumer("{\"foo\":\"foo\",\"bar\":\"bar\"}"),
|
||||
stringConsumer(","),
|
||||
stringConsumer("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}"),
|
||||
stringConsumer(","),
|
||||
stringConsumer("{\"foo\":\"foofoofoo\",\"bar\":\"barbarbar\"}"),
|
||||
stringConsumer("]")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeWithType() {
|
||||
Flux<ParentClass> source = Flux.just(new Foo(), new Bar());
|
||||
ResolvableType type = ResolvableType.forClass(ParentClass.class);
|
||||
Flux<DataBuffer> output = this.encoder.encode(source, this.bufferFactory, type, null);
|
||||
|
||||
TestSubscriber.subscribe(output)
|
||||
.assertComplete()
|
||||
.assertNoError()
|
||||
.assertValuesWith(stringConsumer("["),
|
||||
stringConsumer("{\"type\":\"foo\"}"),
|
||||
stringConsumer(","),
|
||||
stringConsumer("{\"type\":\"bar\"}"),
|
||||
stringConsumer("]"));
|
||||
}
|
||||
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||
private static class ParentClass {
|
||||
}
|
||||
|
||||
@JsonTypeName("foo")
|
||||
private static class Foo extends ParentClass {
|
||||
}
|
||||
|
||||
@JsonTypeName("bar")
|
||||
private static class Bar extends ParentClass {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.json;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class JsonObjectDecoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void decodeSingleChunkToJsonObject() {
|
||||
JsonObjectDecoder decoder = new JsonObjectDecoder();
|
||||
Flux<DataBuffer> source =
|
||||
Flux.just(stringBuffer("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}"));
|
||||
Flux<String> output =
|
||||
decoder.decode(source, null, null).map(JsonObjectDecoderTests::toString);
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertValues("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeMultipleChunksToJsonObject() throws InterruptedException {
|
||||
JsonObjectDecoder decoder = new JsonObjectDecoder();
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer("{\"foo\": \"foofoo\""),
|
||||
stringBuffer(", \"bar\": \"barbar\"}"));
|
||||
Flux<String> output =
|
||||
decoder.decode(source, null, null).map(JsonObjectDecoderTests::toString);
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertValues("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeSingleChunkToArray() throws InterruptedException {
|
||||
JsonObjectDecoder decoder = new JsonObjectDecoder();
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(
|
||||
"[{\"foo\": \"foofoo\", \"bar\": \"barbar\"},{\"foo\": \"foofoofoo\", \"bar\": \"barbarbar\"}]"));
|
||||
Flux<String> output =
|
||||
decoder.decode(source, null, null).map(JsonObjectDecoderTests::toString);
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertValues("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}",
|
||||
"{\"foo\": \"foofoofoo\", \"bar\": \"barbarbar\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeMultipleChunksToArray() throws InterruptedException {
|
||||
JsonObjectDecoder decoder = new JsonObjectDecoder();
|
||||
Flux<DataBuffer> source =
|
||||
Flux.just(stringBuffer("[{\"foo\": \"foofoo\", \"bar\""), stringBuffer(
|
||||
": \"barbar\"},{\"foo\": \"foofoofoo\", \"bar\": \"barbarbar\"}]"));
|
||||
Flux<String> output =
|
||||
decoder.decode(source, null, null).map(JsonObjectDecoderTests::toString);
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertValues("{\"foo\": \"foofoo\", \"bar\": \"barbar\"}",
|
||||
"{\"foo\": \"foofoofoo\", \"bar\": \"barbarbar\"}");
|
||||
}
|
||||
|
||||
private static String toString(DataBuffer buffer) {
|
||||
byte[] b = new byte[buffer.readableByteCount()];
|
||||
buffer.read(b);
|
||||
return new String(b, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml;
|
||||
|
||||
import java.util.List;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.Pojo;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElement;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElementWithName;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElementWithNameAndNamespace;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlType;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeWithName;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeWithNameAndNamespace;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class Jaxb2DecoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
private static final String POJO_ROOT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||
"<pojo>" +
|
||||
"<foo>foofoo</foo>" +
|
||||
"<bar>barbar</bar>" +
|
||||
"</pojo>";
|
||||
|
||||
private static final String POJO_CHILD =
|
||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||
"<root>" +
|
||||
"<pojo>" +
|
||||
"<foo>foo</foo>" +
|
||||
"<bar>bar</bar>" +
|
||||
"</pojo>" +
|
||||
"<pojo>" +
|
||||
"<foo>foofoo</foo>" +
|
||||
"<bar>barbar</bar>" +
|
||||
"</pojo>" +
|
||||
"<root/>";
|
||||
|
||||
private final Jaxb2Decoder decoder = new Jaxb2Decoder();
|
||||
|
||||
private final XmlEventDecoder xmlEventDecoder = new XmlEventDecoder();
|
||||
|
||||
|
||||
@Test
|
||||
public void canDecode() {
|
||||
assertTrue(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
|
||||
MediaType.APPLICATION_XML));
|
||||
assertTrue(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
|
||||
MediaType.TEXT_XML));
|
||||
assertFalse(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
|
||||
MediaType.APPLICATION_JSON));
|
||||
|
||||
assertTrue(this.decoder.canDecode(ResolvableType.forClass(TypePojo.class),
|
||||
MediaType.APPLICATION_XML));
|
||||
|
||||
assertFalse(this.decoder.canDecode(ResolvableType.forClass(getClass()),
|
||||
MediaType.APPLICATION_XML));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitOneBranches() {
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder
|
||||
.decode(Flux.just(stringBuffer(POJO_ROOT)), null, null);
|
||||
Flux<List<XMLEvent>> result = this.decoder.split(xmlEvents, new QName("pojo"));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValuesWith(events -> {
|
||||
assertEquals(8, events.size());
|
||||
assertStartElement(events.get(0), "pojo");
|
||||
assertStartElement(events.get(1), "foo");
|
||||
assertCharacters(events.get(2), "foofoo");
|
||||
assertEndElement(events.get(3), "foo");
|
||||
assertStartElement(events.get(4), "bar");
|
||||
assertCharacters(events.get(5), "barbar");
|
||||
assertEndElement(events.get(6), "bar");
|
||||
assertEndElement(events.get(7), "pojo");
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void splitMultipleBranches() {
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder
|
||||
.decode(Flux.just(stringBuffer(POJO_CHILD)), null, null);
|
||||
Flux<List<XMLEvent>> result = this.decoder.split(xmlEvents, new QName("pojo"));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValuesWith(events -> {
|
||||
assertEquals(8, events.size());
|
||||
assertStartElement(events.get(0), "pojo");
|
||||
assertStartElement(events.get(1), "foo");
|
||||
assertCharacters(events.get(2), "foo");
|
||||
assertEndElement(events.get(3), "foo");
|
||||
assertStartElement(events.get(4), "bar");
|
||||
assertCharacters(events.get(5), "bar");
|
||||
assertEndElement(events.get(6), "bar");
|
||||
assertEndElement(events.get(7), "pojo");
|
||||
}, events -> {
|
||||
assertEquals(8, events.size());
|
||||
assertStartElement(events.get(0), "pojo");
|
||||
assertStartElement(events.get(1), "foo");
|
||||
assertCharacters(events.get(2), "foofoo");
|
||||
assertEndElement(events.get(3), "foo");
|
||||
assertStartElement(events.get(4), "bar");
|
||||
assertCharacters(events.get(5), "barbar");
|
||||
assertEndElement(events.get(6), "bar");
|
||||
assertEndElement(events.get(7), "pojo");
|
||||
});
|
||||
}
|
||||
|
||||
private static void assertStartElement(XMLEvent event, String expectedLocalName) {
|
||||
assertTrue(event.isStartElement());
|
||||
assertEquals(expectedLocalName, event.asStartElement().getName().getLocalPart());
|
||||
}
|
||||
|
||||
private static void assertEndElement(XMLEvent event, String expectedLocalName) {
|
||||
assertTrue(event.isEndElement());
|
||||
assertEquals(expectedLocalName, event.asEndElement().getName().getLocalPart());
|
||||
}
|
||||
|
||||
private static void assertCharacters(XMLEvent event, String expectedData) {
|
||||
assertTrue(event.isCharacters());
|
||||
assertEquals(expectedData, event.asCharacters().getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeSingleXmlRootElement() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_ROOT));
|
||||
Flux<Object> output =
|
||||
this.decoder.decode(source, ResolvableType.forClass(Pojo.class), null);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValues(new Pojo("foofoo", "barbar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeSingleXmlTypeElement() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_ROOT));
|
||||
Flux<Object> output = this.decoder
|
||||
.decode(source, ResolvableType.forClass(TypePojo.class), null);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValues(new TypePojo("foofoo", "barbar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeMultipleXmlRootElement() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_CHILD));
|
||||
Flux<Object> output =
|
||||
this.decoder.decode(source, ResolvableType.forClass(Pojo.class), null);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValues(new Pojo("foo", "bar"), new Pojo("foofoo", "barbar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeMultipleXmlTypeElement() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_CHILD));
|
||||
Flux<Object> output = this.decoder
|
||||
.decode(source, ResolvableType.forClass(TypePojo.class), null);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValues(new TypePojo("foo", "bar"), new TypePojo("foofoo", "barbar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toExpectedQName() {
|
||||
assertEquals(new QName("pojo"), this.decoder.toQName(Pojo.class));
|
||||
assertEquals(new QName("pojo"), this.decoder.toQName(TypePojo.class));
|
||||
|
||||
assertEquals(new QName("namespace", "name"),
|
||||
this.decoder.toQName(XmlRootElementWithNameAndNamespace.class));
|
||||
assertEquals(new QName("namespace", "name"),
|
||||
this.decoder.toQName(XmlRootElementWithName.class));
|
||||
assertEquals(new QName("namespace", "xmlRootElement"),
|
||||
this.decoder.toQName(XmlRootElement.class));
|
||||
|
||||
assertEquals(new QName("namespace", "name"),
|
||||
this.decoder.toQName(XmlTypeWithNameAndNamespace.class));
|
||||
assertEquals(new QName("namespace", "name"),
|
||||
this.decoder.toQName(XmlTypeWithName.class));
|
||||
assertEquals(new QName("namespace", "xmlType"),
|
||||
this.decoder.toQName(XmlType.class));
|
||||
|
||||
}
|
||||
|
||||
@javax.xml.bind.annotation.XmlType(name = "pojo")
|
||||
public static class TypePojo {
|
||||
|
||||
private String foo;
|
||||
|
||||
private String bar;
|
||||
|
||||
public TypePojo() {
|
||||
}
|
||||
|
||||
public TypePojo(String foo, String bar) {
|
||||
this.foo = foo;
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof TypePojo) {
|
||||
TypePojo other = (TypePojo) o;
|
||||
return this.foo.equals(other.foo) && this.bar.equals(other.bar);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.foo.hashCode();
|
||||
result = 31 * result + this.bar.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.xml.sax.SAXException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
import org.springframework.core.io.buffer.support.DataBufferUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.Pojo;
|
||||
|
||||
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
||||
import static org.custommonkey.xmlunit.XMLAssert.fail;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class Jaxb2EncoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
private Jaxb2Encoder encoder;
|
||||
|
||||
@Before
|
||||
public void createEncoder() {
|
||||
this.encoder = new Jaxb2Encoder();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canEncode() {
|
||||
assertTrue(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
|
||||
MediaType.APPLICATION_XML));
|
||||
assertTrue(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
|
||||
MediaType.TEXT_XML));
|
||||
assertFalse(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
|
||||
MediaType.APPLICATION_JSON));
|
||||
|
||||
assertTrue(this.encoder.canEncode(
|
||||
ResolvableType.forClass(Jaxb2DecoderTests.TypePojo.class),
|
||||
MediaType.APPLICATION_XML));
|
||||
|
||||
assertFalse(this.encoder.canEncode(ResolvableType.forClass(getClass()),
|
||||
MediaType.APPLICATION_XML));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encode() {
|
||||
Flux<Pojo> source = Flux.just(new Pojo("foofoo", "barbar"), new Pojo("foofoofoo", "barbarbar"));
|
||||
Flux<DataBuffer> output = this.encoder.encode(source, this.bufferFactory,
|
||||
ResolvableType.forClass(Pojo.class),
|
||||
MediaType.APPLICATION_XML);
|
||||
TestSubscriber
|
||||
.subscribe(output)
|
||||
.assertValuesWith(dataBuffer -> {
|
||||
try {
|
||||
String s = DataBufferTestUtils
|
||||
.dumpString(dataBuffer, StandardCharsets.UTF_8);
|
||||
assertXMLEqual("<pojo><bar>barbar</bar><foo>foofoo</foo></pojo>", s);
|
||||
}
|
||||
catch (SAXException | IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml;
|
||||
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class XmlEventDecoderTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
private static final String XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
|
||||
"<pojo>" +
|
||||
"<foo>foofoo</foo>" +
|
||||
"<bar>barbar</bar>" +
|
||||
"</pojo>";
|
||||
|
||||
private XmlEventDecoder decoder = new XmlEventDecoder();
|
||||
|
||||
@Test
|
||||
public void toXMLEventsAalto() {
|
||||
|
||||
Flux<XMLEvent> events =
|
||||
this.decoder.decode(Flux.just(stringBuffer(XML)), null, null);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(events)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValuesWith(e -> assertTrue(e.isStartDocument()),
|
||||
e -> assertStartElement(e, "pojo"),
|
||||
e -> assertStartElement(e, "foo"),
|
||||
e -> assertCharacters(e, "foofoo"),
|
||||
e -> assertEndElement(e, "foo"),
|
||||
e -> assertStartElement(e, "bar"),
|
||||
e -> assertCharacters(e, "barbar"),
|
||||
e -> assertEndElement(e, "bar"),
|
||||
e -> assertEndElement(e, "pojo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toXMLEventsNonAalto() {
|
||||
decoder.useAalto = false;
|
||||
|
||||
Flux<XMLEvent> events =
|
||||
this.decoder.decode(Flux.just(stringBuffer(XML)), null, null);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(events)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValuesWith(e -> assertTrue(e.isStartDocument()),
|
||||
e -> assertStartElement(e, "pojo"),
|
||||
e -> assertStartElement(e, "foo"),
|
||||
e -> assertCharacters(e, "foofoo"),
|
||||
e -> assertEndElement(e, "foo"),
|
||||
e -> assertStartElement(e, "bar"),
|
||||
e -> assertCharacters(e, "barbar"),
|
||||
e -> assertEndElement(e, "bar"), e -> assertEndElement(e, "pojo"),
|
||||
e -> assertTrue(e.isEndDocument()));
|
||||
}
|
||||
|
||||
private static void assertStartElement(XMLEvent event, String expectedLocalName) {
|
||||
assertTrue(event.isStartElement());
|
||||
assertEquals(expectedLocalName, event.asStartElement().getName().getLocalPart());
|
||||
}
|
||||
|
||||
private static void assertEndElement(XMLEvent event, String expectedLocalName) {
|
||||
assertTrue(event + " is no end element", event.isEndElement());
|
||||
assertEquals(expectedLocalName, event.asEndElement().getName().getLocalPart());
|
||||
}
|
||||
|
||||
private static void assertCharacters(XMLEvent event, String expectedData) {
|
||||
assertTrue(event.isCharacters());
|
||||
assertEquals(expectedData, event.asCharacters().getData());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml.jaxb;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@javax.xml.bind.annotation.XmlRootElement
|
||||
public class XmlRootElement {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml.jaxb;
|
||||
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlRootElement(name = "name")
|
||||
public class XmlRootElementWithName {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml.jaxb;
|
||||
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlRootElement(name = "name", namespace = "namespace")
|
||||
public class XmlRootElementWithNameAndNamespace {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml.jaxb;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@javax.xml.bind.annotation.XmlType
|
||||
public class XmlType {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml.jaxb;
|
||||
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlType(name = "name")
|
||||
public class XmlTypeWithName {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.codec.xml.jaxb;
|
||||
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlType(name = "name", namespace = "namespace")
|
||||
public class XmlTypeWithNameAndNamespace {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
@javax.xml.bind.annotation.XmlSchema(namespace = "namespace")
|
||||
package org.springframework.http.codec.xml.jaxb;
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.io.File;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.springframework.http.server.reactive.bootstrap.HttpServer;
|
||||
import org.springframework.http.server.reactive.bootstrap.JettyHttpServer;
|
||||
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
|
||||
import org.springframework.http.server.reactive.bootstrap.RxNettyHttpServer;
|
||||
import org.springframework.http.server.reactive.bootstrap.TomcatHttpServer;
|
||||
import org.springframework.http.server.reactive.bootstrap.UndertowHttpServer;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public abstract class AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
protected int port;
|
||||
|
||||
@Parameterized.Parameter(0)
|
||||
public HttpServer server;
|
||||
|
||||
|
||||
@Parameterized.Parameters(name = "server [{0}]")
|
||||
public static Object[][] arguments() {
|
||||
File base = new File(System.getProperty("java.io.tmpdir"));
|
||||
return new Object[][] {
|
||||
{new JettyHttpServer()},
|
||||
{new RxNettyHttpServer()},
|
||||
{new ReactorHttpServer()},
|
||||
{new TomcatHttpServer(base.getAbsolutePath())},
|
||||
{new UndertowHttpServer()}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.port = SocketUtils.findAvailableTcpPort();
|
||||
this.server.setPort(this.port);
|
||||
this.server.setHandler(createHttpHandler());
|
||||
this.server.afterPropertiesSet();
|
||||
this.server.start();
|
||||
}
|
||||
|
||||
protected abstract HttpHandler createHttpHandler();
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
this.server.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.net.URI;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Temporarily does not extend AbstractHttpHandlerIntegrationTests.
|
||||
*
|
||||
* @author Stephane Maldini
|
||||
*/
|
||||
public class AsyncIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private final Scheduler asyncGroup = Schedulers.parallel();
|
||||
|
||||
private final DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
|
||||
@Override
|
||||
protected AsyncHandler createHttpHandler() {
|
||||
return new AsyncHandler();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void basicTest() throws Exception {
|
||||
URI url = new URI("http://localhost:" + port);
|
||||
ResponseEntity<String> response = new RestTemplate().exchange(RequestEntity.get(url)
|
||||
.build(), String.class);
|
||||
|
||||
assertThat(response.getBody(), Matchers.equalTo("hello"));
|
||||
}
|
||||
|
||||
private class AsyncHandler implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
return response.writeWith(Flux.just("h", "e", "l", "l", "o")
|
||||
.delay(Duration.ofMillis(100))
|
||||
.publishOn(asyncGroup)
|
||||
.collect(dataBufferFactory::allocateBuffer, (buffer, str) -> buffer.write(str.getBytes())));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Signal;
|
||||
import reactor.core.subscriber.SubscriberBarrier;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Stephane Maldini
|
||||
*/
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
public class ChannelSendOperatorTests {
|
||||
|
||||
private OneByOneAsyncWriter writer;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.writer = new OneByOneAsyncWriter();
|
||||
}
|
||||
|
||||
private <T> Mono<Void> sendOperator(Publisher<String> source){
|
||||
return new ChannelSendOperator<>(source, writer::send);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorBeforeFirstItem() throws Exception {
|
||||
IllegalStateException error = new IllegalStateException("boo");
|
||||
Mono<Void> completion = Mono.<String>error(error).as(this::sendOperator);
|
||||
Signal<Void> signal = completion.materialize().block();
|
||||
|
||||
assertNotNull(signal);
|
||||
assertSame("Unexpected signal: " + signal, error, signal.getThrowable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completionBeforeFirstItem() throws Exception {
|
||||
Mono<Void> completion = Flux.<String>empty().as(this::sendOperator);
|
||||
Signal<Void> signal = completion.materialize().block();
|
||||
|
||||
assertNotNull(signal);
|
||||
assertTrue("Unexpected signal: " + signal, signal.isOnComplete());
|
||||
|
||||
assertEquals(0, this.writer.items.size());
|
||||
assertTrue(this.writer.completed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeOneItem() throws Exception {
|
||||
Mono<Void> completion = Flux.just("one").as(this::sendOperator);
|
||||
Signal<Void> signal = completion.materialize().block();
|
||||
|
||||
assertNotNull(signal);
|
||||
assertTrue("Unexpected signal: " + signal, signal.isOnComplete());
|
||||
|
||||
assertEquals(1, this.writer.items.size());
|
||||
assertEquals("one", this.writer.items.get(0));
|
||||
assertTrue(this.writer.completed);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void writeMultipleItems() throws Exception {
|
||||
List<String> items = Arrays.asList("one", "two", "three");
|
||||
Mono<Void> completion = Flux.fromIterable(items).as(this::sendOperator);
|
||||
Signal<Void> signal = completion.materialize().block();
|
||||
|
||||
assertNotNull(signal);
|
||||
assertTrue("Unexpected signal: " + signal, signal.isOnComplete());
|
||||
|
||||
assertEquals(3, this.writer.items.size());
|
||||
assertEquals("one", this.writer.items.get(0));
|
||||
assertEquals("two", this.writer.items.get(1));
|
||||
assertEquals("three", this.writer.items.get(2));
|
||||
assertTrue(this.writer.completed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorAfterMultipleItems() throws Exception {
|
||||
IllegalStateException error = new IllegalStateException("boo");
|
||||
Flux<String> publisher = Flux.generate(() -> 0, (idx , subscriber) -> {
|
||||
int i = ++idx;
|
||||
subscriber.next(String.valueOf(i));
|
||||
if (i == 3) {
|
||||
subscriber.fail(error);
|
||||
}
|
||||
return i;
|
||||
});
|
||||
Mono<Void> completion = publisher.as(this::sendOperator);
|
||||
Signal<Void> signal = completion.materialize().block();
|
||||
|
||||
assertNotNull(signal);
|
||||
assertSame("Unexpected signal: " + signal, error, signal.getThrowable());
|
||||
|
||||
assertEquals(3, this.writer.items.size());
|
||||
assertEquals("1", this.writer.items.get(0));
|
||||
assertEquals("2", this.writer.items.get(1));
|
||||
assertEquals("3", this.writer.items.get(2));
|
||||
assertSame(error, this.writer.error);
|
||||
}
|
||||
|
||||
|
||||
private static class OneByOneAsyncWriter {
|
||||
|
||||
private List<String> items = new ArrayList<>();
|
||||
|
||||
private boolean completed = false;
|
||||
|
||||
private Throwable error;
|
||||
|
||||
|
||||
public Publisher<Void> send(Publisher<String> publisher) {
|
||||
return subscriber -> {
|
||||
Executors.newSingleThreadScheduledExecutor().schedule(() -> publisher.subscribe(new WriteSubscriber(subscriber)),
|
||||
50, TimeUnit.MILLISECONDS);
|
||||
};
|
||||
}
|
||||
|
||||
private class WriteSubscriber extends SubscriberBarrier<String, Void> {
|
||||
|
||||
public WriteSubscriber(Subscriber<? super Void> subscriber) {
|
||||
super(subscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doOnSubscribe(Subscription subscription) {
|
||||
subscription.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doNext(String item) {
|
||||
items.add(item);
|
||||
this.subscription.request(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doError(Throwable ex) {
|
||||
error = ex;
|
||||
this.subscriber.onError(ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doComplete() {
|
||||
completed = true;
|
||||
this.subscriber.onComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final static Subscription NO_OP_SUBSCRIPTION = new Subscription() {
|
||||
|
||||
@Override
|
||||
public void request(long n) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalToIgnoringCase;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class CookieIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private CookieHandler cookieHandler;
|
||||
|
||||
@Override
|
||||
protected HttpHandler createHttpHandler() {
|
||||
this.cookieHandler = new CookieHandler();
|
||||
return this.cookieHandler;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void basicTest() throws Exception {
|
||||
URI url = new URI("http://localhost:" + port);
|
||||
String header = "SID=31d4d96e407aad42; lang=en-US";
|
||||
ResponseEntity<Void> response = new RestTemplate().exchange(
|
||||
RequestEntity.get(url).header("Cookie", header).build(), Void.class);
|
||||
|
||||
Map<String, List<HttpCookie>> requestCookies = this.cookieHandler.requestCookies;
|
||||
assertEquals(2, requestCookies.size());
|
||||
|
||||
List<HttpCookie> list = requestCookies.get("SID");
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("31d4d96e407aad42", list.iterator().next().getValue());
|
||||
|
||||
list = requestCookies.get("lang");
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("en-US", list.iterator().next().getValue());
|
||||
|
||||
List<String> headerValues = response.getHeaders().get("Set-Cookie");
|
||||
assertEquals(2, headerValues.size());
|
||||
|
||||
assertThat(splitCookie(headerValues.get(0)), containsInAnyOrder(equalTo("SID=31d4d96e407aad42"),
|
||||
equalToIgnoringCase("Path=/"), equalToIgnoringCase("Secure"), equalToIgnoringCase("HttpOnly")));
|
||||
|
||||
assertThat(splitCookie(headerValues.get(1)), containsInAnyOrder(equalTo("lang=en-US"),
|
||||
equalToIgnoringCase("Path=/"), equalToIgnoringCase("Domain=example.com")));
|
||||
}
|
||||
|
||||
// No client side HttpCookie support yet
|
||||
private List<String> splitCookie(String value) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String s : value.split(";")){
|
||||
list.add(s.trim());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
private class CookieHandler implements HttpHandler {
|
||||
|
||||
private Map<String, List<HttpCookie>> requestCookies;
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
this.requestCookies = request.getCookies();
|
||||
this.requestCookies.size(); // Cause lazy loading
|
||||
|
||||
response.getCookies().add("SID", ResponseCookie.from("SID", "31d4d96e407aad42")
|
||||
.path("/").secure(true).httpOnly(true).build());
|
||||
response.getCookies().add("lang", ResponseCookie.from("lang", "en-US")
|
||||
.domain("example.com").path("/").build());
|
||||
|
||||
return response.setComplete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.net.URI;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
|
||||
public class EchoHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private static final int REQUEST_SIZE = 4096 * 3;
|
||||
|
||||
private Random rnd = new Random();
|
||||
|
||||
|
||||
@Override
|
||||
protected EchoHandler createHttpHandler() {
|
||||
return new EchoHandler();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void echo() throws Exception {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
byte[] body = randomBytes();
|
||||
RequestEntity<byte[]> request = RequestEntity.post(new URI("http://localhost:" + port)).body(body);
|
||||
ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);
|
||||
|
||||
assertArrayEquals(body, response.getBody());
|
||||
}
|
||||
|
||||
|
||||
private byte[] randomBytes() {
|
||||
byte[] buffer = new byte[REQUEST_SIZE];
|
||||
rnd.nextBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public static class EchoHandler implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
return response.writeWith(request.getBody());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class ErrorHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private ErrorHandler handler = new ErrorHandler();
|
||||
|
||||
@Override
|
||||
protected HttpHandler createHttpHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void response() throws Exception {
|
||||
// TODO: fix Reactor
|
||||
assumeFalse(server instanceof ReactorHttpServer);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER);
|
||||
|
||||
ResponseEntity<String> response = restTemplate
|
||||
.getForEntity(new URI("http://localhost:" + port + "/response"),
|
||||
String.class);
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnValue() throws Exception {
|
||||
// TODO: fix Reactor
|
||||
assumeFalse(server instanceof ReactorHttpServer);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER);
|
||||
|
||||
ResponseEntity<String> response = restTemplate
|
||||
.getForEntity(new URI("http://localhost:" + port + "/returnValue"),
|
||||
String.class);
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
|
||||
}
|
||||
|
||||
private static class ErrorHandler implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
Exception error = new UnsupportedOperationException();
|
||||
String path = request.getURI().getPath();
|
||||
if (path.endsWith("response")) {
|
||||
return response.writeWith(Mono.error(error));
|
||||
}
|
||||
else if (path.endsWith("returnValue")) {
|
||||
return Mono.error(error);
|
||||
}
|
||||
else {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final ResponseErrorHandler NO_OP_ERROR_HANDLER =
|
||||
new ResponseErrorHandler() {
|
||||
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse response) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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 org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.get;
|
||||
import static org.springframework.web.client.reactive.ResponseExtractors.bodyStream;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.FlushingDataBuffer;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.client.reactive.ClientWebRequestBuilders;
|
||||
import org.springframework.web.client.reactive.ResponseExtractors;
|
||||
import org.springframework.web.client.reactive.WebClient;
|
||||
|
||||
/**
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class FlushingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
this.webClient = new WebClient(new ReactorClientHttpConnector());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushing() throws Exception {
|
||||
Mono<String> result = this.webClient
|
||||
.perform(ClientWebRequestBuilders.get("http://localhost:" + port))
|
||||
.extract(ResponseExtractors.bodyStream(String.class))
|
||||
.takeUntil(s -> {
|
||||
return s.endsWith("data1");
|
||||
})
|
||||
.reduce((s1, s2) -> s1 + s2);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.await()
|
||||
.assertValues("data0data1");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected HttpHandler createHttpHandler() {
|
||||
return new FlushingHandler();
|
||||
}
|
||||
|
||||
// Handler that never completes designed to test if flushing is perform correctly when
|
||||
// a FlushingDataBuffer is written
|
||||
private static class FlushingHandler implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
Flux<DataBuffer> responseBody = Flux
|
||||
.intervalMillis(50)
|
||||
.map(l -> {
|
||||
byte[] data = ("data" + l).getBytes();
|
||||
DataBuffer buffer = response.bufferFactory().allocateBuffer(data.length);
|
||||
buffer.write(data);
|
||||
return buffer;
|
||||
})
|
||||
.take(2)
|
||||
.concatWith(Mono.just(FlushingDataBuffer.INSTANCE))
|
||||
.concatWith(Flux.never());
|
||||
return response.writeWith(responseBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.net.URI;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Mock implementation of {@link ServerHttpRequest}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MockServerHttpRequest implements ServerHttpRequest {
|
||||
|
||||
private HttpMethod httpMethod;
|
||||
|
||||
private URI uri;
|
||||
|
||||
private MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
|
||||
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>();
|
||||
|
||||
private Flux<DataBuffer> body;
|
||||
|
||||
|
||||
public MockServerHttpRequest(HttpMethod httpMethod, URI uri) {
|
||||
this.httpMethod = httpMethod;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public MockServerHttpRequest(Publisher<DataBuffer> body, HttpMethod httpMethod,
|
||||
URI uri) {
|
||||
this.body = Flux.from(body);
|
||||
this.httpMethod = httpMethod;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public HttpMethod getMethod() {
|
||||
return this.httpMethod;
|
||||
}
|
||||
|
||||
public void setHttpMethod(HttpMethod httpMethod) {
|
||||
this.httpMethod = httpMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
public void setUri(URI uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, String> getQueryParams() {
|
||||
return this.queryParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, HttpCookie> getCookies() {
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<DataBuffer> getBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
public Mono<Void> writeWith(Publisher<DataBuffer> body) {
|
||||
this.body = Flux.from(body);
|
||||
return this.body.then();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.Supplier;
|
||||
|
||||
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.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Mock implementation of {@link ServerHttpResponse}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MockServerHttpResponse implements ServerHttpResponse {
|
||||
|
||||
private HttpStatus status;
|
||||
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
private MultiValueMap<String, ResponseCookie> cookies = new LinkedMultiValueMap<>();
|
||||
|
||||
private Publisher<DataBuffer> body;
|
||||
|
||||
private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
|
||||
|
||||
@Override
|
||||
public boolean setStatusCode(HttpStatus status) {
|
||||
this.status = status;
|
||||
return true;
|
||||
}
|
||||
|
||||
public HttpStatus getStatusCode() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, ResponseCookie> getCookies() {
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
public Publisher<DataBuffer> getBody() {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> writeWith(Publisher<DataBuffer> body) {
|
||||
this.body = body;
|
||||
return Flux.from(this.body).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCommit(Supplier<? extends Mono<Void>> action) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> setComplete() {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataBufferFactory bufferFactory() {
|
||||
return this.dataBufferFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.net.URI;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
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.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class RandomHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
public static final int REQUEST_SIZE = 4096 * 3;
|
||||
|
||||
public static final int RESPONSE_SIZE = 1024 * 4;
|
||||
|
||||
private final Random rnd = new Random();
|
||||
|
||||
private final RandomHandler handler = new RandomHandler();
|
||||
|
||||
private final DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
|
||||
|
||||
@Override
|
||||
protected RandomHandler createHttpHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void random() throws Throwable {
|
||||
// TODO: fix Reactor support
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
byte[] body = randomBytes();
|
||||
RequestEntity<byte[]> request = RequestEntity.post(new URI("http://localhost:" + port)).body(body);
|
||||
ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);
|
||||
|
||||
assertNotNull(response.getBody());
|
||||
assertEquals(RESPONSE_SIZE,
|
||||
response.getHeaders().getContentLength());
|
||||
assertEquals(RESPONSE_SIZE, response.getBody().length);
|
||||
}
|
||||
|
||||
|
||||
private byte[] randomBytes() {
|
||||
byte[] buffer = new byte[REQUEST_SIZE];
|
||||
rnd.nextBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private class RandomHandler implements HttpHandler {
|
||||
|
||||
public static final int CHUNKS = 16;
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
Mono<Integer> requestSizeMono = request.getBody().
|
||||
reduce(0, (integer, dataBuffer) -> integer +
|
||||
dataBuffer.readableByteCount()).
|
||||
doAfterTerminate((size, throwable) -> {
|
||||
assertNull(throwable);
|
||||
assertEquals(REQUEST_SIZE, (long) size);
|
||||
});
|
||||
|
||||
|
||||
|
||||
response.getHeaders().setContentLength(RESPONSE_SIZE);
|
||||
|
||||
return requestSizeMono.then(response.writeWith(multipleChunks()));
|
||||
}
|
||||
|
||||
private Publisher<DataBuffer> singleChunk() {
|
||||
return Mono.just(randomBuffer(RESPONSE_SIZE));
|
||||
}
|
||||
|
||||
private Publisher<DataBuffer> multipleChunks() {
|
||||
int chunkSize = RESPONSE_SIZE / CHUNKS;
|
||||
return Flux.range(1, CHUNKS).map(integer -> randomBuffer(chunkSize));
|
||||
}
|
||||
|
||||
private DataBuffer randomBuffer(int size) {
|
||||
byte[] bytes = new byte[size];
|
||||
rnd.nextBytes(bytes);
|
||||
DataBuffer buffer = dataBufferFactory.allocateBuffer(size);
|
||||
buffer.write(bytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.Arrays;
|
||||
import java.util.Collections;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractServerHttpRequest}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ServerHttpRequestTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void queryParamsNone() throws Exception {
|
||||
MultiValueMap<String, String> params = createHttpRequest("/path").getQueryParams();
|
||||
assertEquals(0, params.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParams() throws Exception {
|
||||
MultiValueMap<String, String> params = createHttpRequest("/path?a=A&b=B").getQueryParams();
|
||||
assertEquals(2, params.size());
|
||||
assertEquals(Collections.singletonList("A"), params.get("a"));
|
||||
assertEquals(Collections.singletonList("B"), params.get("b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamsWithMulitpleValues() throws Exception {
|
||||
MultiValueMap<String, String> params = createHttpRequest("/path?a=1&a=2").getQueryParams();
|
||||
assertEquals(1, params.size());
|
||||
assertEquals(Arrays.asList("1", "2"), params.get("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamsWithEmptyValue() throws Exception {
|
||||
MultiValueMap<String, String> params = createHttpRequest("/path?a=").getQueryParams();
|
||||
assertEquals(1, params.size());
|
||||
assertEquals(Collections.singletonList(""), params.get("a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParamsWithNoValue() throws Exception {
|
||||
MultiValueMap<String, String> params = createHttpRequest("/path?a").getQueryParams();
|
||||
assertEquals(1, params.size());
|
||||
assertEquals(Collections.singletonList(null), params.get("a"));
|
||||
}
|
||||
|
||||
private ServerHttpRequest createHttpRequest(String path) throws Exception {
|
||||
HttpServletRequest servletRequest = new MockHttpServletRequest("GET", path);
|
||||
return new ServletServerHttpRequest(servletRequest,
|
||||
new DefaultDataBufferFactory(), 1024);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
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.DefaultDataBufferFactory;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
public class ServerHttpResponseTests {
|
||||
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
|
||||
@Test
|
||||
public void writeWith() throws Exception {
|
||||
TestServerHttpResponse response = new TestServerHttpResponse();
|
||||
response.writeWith(Flux.just(wrap("a"), wrap("b"), wrap("c"))).block();
|
||||
|
||||
assertTrue(response.statusCodeWritten);
|
||||
assertTrue(response.headersWritten);
|
||||
assertTrue(response.cookiesWritten);
|
||||
|
||||
assertEquals(3, response.body.size());
|
||||
assertEquals("a", new String(response.body.get(0).asByteBuffer().array(), UTF_8));
|
||||
assertEquals("b", new String(response.body.get(1).asByteBuffer().array(), UTF_8));
|
||||
assertEquals("c", new String(response.body.get(2).asByteBuffer().array(), UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeWithError() throws Exception {
|
||||
TestServerHttpResponse response = new TestServerHttpResponse();
|
||||
IllegalStateException error = new IllegalStateException("boo");
|
||||
response.writeWith(Flux.error(error)).otherwise(ex -> Mono.empty()).block();
|
||||
|
||||
assertFalse(response.statusCodeWritten);
|
||||
assertFalse(response.headersWritten);
|
||||
assertFalse(response.cookiesWritten);
|
||||
assertTrue(response.body.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setComplete() throws Exception {
|
||||
TestServerHttpResponse response = new TestServerHttpResponse();
|
||||
response.setComplete().block();
|
||||
|
||||
assertTrue(response.statusCodeWritten);
|
||||
assertTrue(response.headersWritten);
|
||||
assertTrue(response.cookiesWritten);
|
||||
assertTrue(response.body.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeCommitWithComplete() throws Exception {
|
||||
ResponseCookie cookie = ResponseCookie.from("ID", "123").build();
|
||||
TestServerHttpResponse response = new TestServerHttpResponse();
|
||||
response.beforeCommit(() -> {
|
||||
response.getCookies().add(cookie.getName(), cookie);
|
||||
return Mono.empty();
|
||||
});
|
||||
response.writeWith(Flux.just(wrap("a"), wrap("b"), wrap("c"))).block();
|
||||
|
||||
assertTrue(response.statusCodeWritten);
|
||||
assertTrue(response.headersWritten);
|
||||
assertTrue(response.cookiesWritten);
|
||||
assertSame(cookie, response.getCookies().getFirst("ID"));
|
||||
|
||||
assertEquals(3, response.body.size());
|
||||
assertEquals("a", new String(response.body.get(0).asByteBuffer().array(), UTF_8));
|
||||
assertEquals("b", new String(response.body.get(1).asByteBuffer().array(), UTF_8));
|
||||
assertEquals("c", new String(response.body.get(2).asByteBuffer().array(), UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeCommitActionWithError() throws Exception {
|
||||
TestServerHttpResponse response = new TestServerHttpResponse();
|
||||
IllegalStateException error = new IllegalStateException("boo");
|
||||
response.beforeCommit(() -> Mono.error(error));
|
||||
response.writeWith(Flux.just(wrap("a"), wrap("b"), wrap("c"))).block();
|
||||
|
||||
assertTrue("beforeCommit action errors should be ignored", response.statusCodeWritten);
|
||||
assertTrue("beforeCommit action errors should be ignored", response.headersWritten);
|
||||
assertTrue("beforeCommit action errors should be ignored", response.cookiesWritten);
|
||||
assertNull(response.getCookies().get("ID"));
|
||||
|
||||
assertEquals(3, response.body.size());
|
||||
assertEquals("a", new String(response.body.get(0).asByteBuffer().array(), UTF_8));
|
||||
assertEquals("b", new String(response.body.get(1).asByteBuffer().array(), UTF_8));
|
||||
assertEquals("c", new String(response.body.get(2).asByteBuffer().array(), UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeCommitActionWithSetComplete() throws Exception {
|
||||
ResponseCookie cookie = ResponseCookie.from("ID", "123").build();
|
||||
TestServerHttpResponse response = new TestServerHttpResponse();
|
||||
response.beforeCommit(() -> {
|
||||
response.getCookies().add(cookie.getName(), cookie);
|
||||
return Mono.empty();
|
||||
});
|
||||
response.setComplete().block();
|
||||
|
||||
assertTrue(response.statusCodeWritten);
|
||||
assertTrue(response.headersWritten);
|
||||
assertTrue(response.cookiesWritten);
|
||||
assertTrue(response.body.isEmpty());
|
||||
assertSame(cookie, response.getCookies().getFirst("ID"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private DataBuffer wrap(String a) {
|
||||
return new DefaultDataBufferFactory().wrap(ByteBuffer.wrap(a.getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
|
||||
private static class TestServerHttpResponse extends AbstractServerHttpResponse {
|
||||
|
||||
private boolean statusCodeWritten;
|
||||
|
||||
private boolean headersWritten;
|
||||
|
||||
private boolean cookiesWritten;
|
||||
|
||||
private final List<DataBuffer> body = new ArrayList<>();
|
||||
|
||||
public TestServerHttpResponse() {
|
||||
super(new DefaultDataBufferFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStatusCode() {
|
||||
assertFalse(this.statusCodeWritten);
|
||||
this.statusCodeWritten = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeHeaders() {
|
||||
assertFalse(this.headersWritten);
|
||||
this.headersWritten = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeCookies() {
|
||||
assertFalse(this.cookiesWritten);
|
||||
this.cookiesWritten = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> writeWithInternal(Publisher<DataBuffer> body) {
|
||||
return Flux.from(body).map(b -> {
|
||||
this.body.add(b);
|
||||
return b;
|
||||
}).then();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.io.File;
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.ZeroCopyHttpOutputMessage;
|
||||
import org.springframework.http.server.reactive.bootstrap.ReactorHttpServer;
|
||||
import org.springframework.http.server.reactive.bootstrap.UndertowHttpServer;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class ZeroCopyIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private final ZeroCopyHandler handler = new ZeroCopyHandler();
|
||||
|
||||
@Override
|
||||
protected HttpHandler createHttpHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void zeroCopy() throws Exception {
|
||||
// Zero-copy only does not support servlet
|
||||
assumeTrue(server instanceof ReactorHttpServer ||
|
||||
server instanceof UndertowHttpServer);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
RequestEntity request =
|
||||
RequestEntity.get(new URI("http://localhost:" + port)).build();
|
||||
|
||||
ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);
|
||||
|
||||
Resource logo =
|
||||
new ClassPathResource("spring.png", ZeroCopyIntegrationTests.class);
|
||||
|
||||
assertTrue(response.hasBody());
|
||||
assertEquals(logo.contentLength(), response.getHeaders().getContentLength());
|
||||
assertEquals(logo.contentLength(), response.getBody().length);
|
||||
assertEquals(MediaType.IMAGE_PNG, response.getHeaders().getContentType());
|
||||
|
||||
}
|
||||
|
||||
private static class ZeroCopyHandler implements HttpHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
|
||||
try {
|
||||
ZeroCopyHttpOutputMessage zeroCopyResponse =
|
||||
(ZeroCopyHttpOutputMessage) response;
|
||||
|
||||
Resource logo = new ClassPathResource("spring.png",
|
||||
ZeroCopyIntegrationTests.class);
|
||||
File logoFile = logo.getFile();
|
||||
zeroCopyResponse.getHeaders().setContentType(MediaType.IMAGE_PNG);
|
||||
zeroCopyResponse.getHeaders().setContentLength(logoFile.length());
|
||||
return zeroCopyResponse.writeWith(logoFile, 0, logoFile.length());
|
||||
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.bootstrap;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public interface HttpServer extends InitializingBean, Lifecycle {
|
||||
|
||||
void setHost(String host);
|
||||
|
||||
void setPort(int port);
|
||||
|
||||
void setHandler(HttpHandler handler);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.bootstrap;
|
||||
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class HttpServerSupport {
|
||||
|
||||
private String host = "0.0.0.0";
|
||||
|
||||
private int port = -1;
|
||||
|
||||
private HttpHandler httpHandler;
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
if(this.port == -1) {
|
||||
this.port = SocketUtils.findAvailableTcpPort(8080);
|
||||
}
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setHandler(HttpHandler handler) {
|
||||
this.httpHandler = handler;
|
||||
}
|
||||
|
||||
public HttpHandler getHttpHandler() {
|
||||
return this.httpHandler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.bootstrap;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class JettyHttpServer extends HttpServerSupport implements InitializingBean, HttpServer {
|
||||
|
||||
private Server jettyServer;
|
||||
|
||||
private boolean running;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
this.jettyServer = new Server();
|
||||
|
||||
Assert.notNull(getHttpHandler());
|
||||
ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter();
|
||||
servlet.setHandler(getHttpHandler());
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
|
||||
ServletContextHandler contextHandler = new ServletContextHandler(this.jettyServer, "", false, false);
|
||||
contextHandler.addServlet(servletHolder, "/");
|
||||
|
||||
ServerConnector connector = new ServerConnector(this.jettyServer);
|
||||
connector.setHost(getHost());
|
||||
connector.setPort(getPort());
|
||||
this.jettyServer.addConnector(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!this.running) {
|
||||
try {
|
||||
this.running = true;
|
||||
this.jettyServer.start();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.running) {
|
||||
try {
|
||||
this.running = false;
|
||||
jettyServer.stop();
|
||||
jettyServer.destroy();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved.
|
||||
*
|
||||
* 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.bootstrap;
|
||||
|
||||
import reactor.core.flow.Loopback;
|
||||
import reactor.core.state.Completable;
|
||||
|
||||
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Stephane Maldini
|
||||
*/
|
||||
public class ReactorHttpServer extends HttpServerSupport
|
||||
implements HttpServer, Loopback, Completable {
|
||||
|
||||
private ReactorHttpHandlerAdapter reactorHandler;
|
||||
|
||||
private reactor.io.netty.http.HttpServer reactorServer;
|
||||
|
||||
private boolean running;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
Assert.notNull(getHttpHandler());
|
||||
this.reactorHandler = new ReactorHttpHandlerAdapter(getHttpHandler());
|
||||
this.reactorServer = reactor.io.netty.http.HttpServer.create(getHost(), getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object connectedInput() {
|
||||
return reactorServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object connectedOutput() {
|
||||
return reactorServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return !running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!this.running) {
|
||||
try {
|
||||
this.reactorServer.startAndAwait(reactorHandler);
|
||||
this.running = true;
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.running) {
|
||||
this.reactorServer.shutdown();
|
||||
this.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.bootstrap;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import org.springframework.http.server.reactive.RxNettyHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RxNettyHttpServer extends HttpServerSupport implements HttpServer {
|
||||
|
||||
private RxNettyHttpHandlerAdapter rxNettyHandler;
|
||||
|
||||
private io.reactivex.netty.protocol.http.server.HttpServer<ByteBuf, ByteBuf> rxNettyServer;
|
||||
|
||||
private boolean running;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(getHttpHandler());
|
||||
this.rxNettyHandler = new RxNettyHttpHandlerAdapter(getHttpHandler());
|
||||
|
||||
this.rxNettyServer = io.reactivex.netty.protocol.http.server.HttpServer
|
||||
.newServer(new InetSocketAddress(getHost(), getPort()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!this.running) {
|
||||
this.running = true;
|
||||
this.rxNettyServer.start(this.rxNettyHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.running) {
|
||||
this.running = false;
|
||||
this.rxNettyServer.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.bootstrap;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class TomcatHttpServer extends HttpServerSupport implements InitializingBean, HttpServer {
|
||||
|
||||
private Tomcat tomcatServer;
|
||||
|
||||
private boolean running;
|
||||
|
||||
private String baseDir;
|
||||
|
||||
|
||||
public TomcatHttpServer() {
|
||||
}
|
||||
|
||||
public TomcatHttpServer(String baseDir) {
|
||||
this.baseDir = baseDir;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
this.tomcatServer = new Tomcat();
|
||||
if (this.baseDir != null) {
|
||||
this.tomcatServer.setBaseDir(baseDir);
|
||||
}
|
||||
this.tomcatServer.setHostname(getHost());
|
||||
this.tomcatServer.setPort(getPort());
|
||||
|
||||
Assert.notNull(getHttpHandler());
|
||||
ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter();
|
||||
servlet.setHandler(getHttpHandler());
|
||||
|
||||
File base = new File(System.getProperty("java.io.tmpdir"));
|
||||
Context rootContext = tomcatServer.addContext("", base.getAbsolutePath());
|
||||
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
|
||||
rootContext.addServletMapping("/", "httpHandlerServlet");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!this.running) {
|
||||
try {
|
||||
this.running = true;
|
||||
this.tomcatServer.start();
|
||||
}
|
||||
catch (LifecycleException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.running) {
|
||||
try {
|
||||
this.running = false;
|
||||
this.tomcatServer.stop();
|
||||
this.tomcatServer.destroy();
|
||||
}
|
||||
catch (LifecycleException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.bootstrap;
|
||||
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.server.HttpHandler;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Marek Hawrylczak
|
||||
*/
|
||||
public class UndertowHttpServer extends HttpServerSupport implements HttpServer {
|
||||
|
||||
private Undertow server;
|
||||
|
||||
private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||
|
||||
private boolean running;
|
||||
|
||||
public void setDataBufferFactory(DataBufferFactory dataBufferFactory) {
|
||||
this.dataBufferFactory = dataBufferFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(getHttpHandler());
|
||||
HttpHandler handler =
|
||||
new UndertowHttpHandlerAdapter(getHttpHandler(), dataBufferFactory);
|
||||
this.server = Undertow.builder().addHttpListener(getPort(), getHost())
|
||||
.setHandler(handler).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!this.running) {
|
||||
this.server.start();
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (this.running) {
|
||||
this.server.stop();
|
||||
this.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This package contains temporary interfaces and classes for running embedded servers.
|
||||
* They are expected to be replaced by an upcoming Spring Boot support.
|
||||
*/
|
||||
package org.springframework.http.server.reactive.bootstrap;
|
||||
@@ -18,6 +18,7 @@ package org.springframework.mock.web.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
@@ -65,4 +66,18 @@ public class DelegatingServletInputStream extends ServletInputStream {
|
||||
this.sourceStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener readListener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.mock.web.test;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -71,4 +72,13 @@ public class DelegatingServletOutputStream extends ServletOutputStream {
|
||||
this.targetStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWriteListener(WriteListener writeListener) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpUpgradeHandler;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -1220,4 +1221,9 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -94,6 +94,11 @@ public class MockPart implements Part {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubmittedFileName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return this.contentType;
|
||||
|
||||
@@ -545,6 +545,11 @@ public class MockServletContext implements ServletContext {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVirtualServerName() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Set<String> getDeclaredRoles() {
|
||||
return Collections.unmodifiableSet(this.declaredRoles);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.web.client.reactive;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class DefaultWebRequestBuilderTests {
|
||||
private DefaultClientWebRequestBuilder builder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
builder = new DefaultClientWebRequestBuilder(HttpMethod.GET, "https://example.com/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void apply() {
|
||||
ClientWebRequestPostProcessor postProcessor = mock(ClientWebRequestPostProcessor.class);
|
||||
when(postProcessor.postProcess(any(ClientWebRequest.class))).thenAnswer(new Answer<ClientWebRequest>() {
|
||||
@Override
|
||||
public ClientWebRequest answer(InvocationOnMock invocation) throws Throwable {
|
||||
return (ClientWebRequest) invocation.getArguments()[0];
|
||||
}
|
||||
});
|
||||
|
||||
ClientWebRequest webRequest = builder.apply(postProcessor).build();
|
||||
|
||||
verify(postProcessor).postProcess(webRequest);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void applyNullPostProcessorThrowsIllegalArgumentException() {
|
||||
builder.apply(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.web.client.reactive;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.client.reactive.support.RxJava1ClientWebRequestBuilders.*;
|
||||
import static org.springframework.web.client.reactive.support.RxJava1ResponseExtractors.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
import rx.observers.TestSubscriber;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.http.codec.Pojo;
|
||||
|
||||
/**
|
||||
* {@link WebClient} integration tests with the {@code Obserable} and {@code Single} API.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class RxJava1WebClientIntegrationTests {
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.server = new MockWebServer();
|
||||
this.webClient = new WebClient(new ReactorClientHttpConnector());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetHeaders() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||
|
||||
Single<HttpHeaders> result = this.webClient
|
||||
.perform(get(baseUrl.toString()))
|
||||
.extract(headers());
|
||||
|
||||
TestSubscriber<HttpHeaders> ts = new TestSubscriber<HttpHeaders>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
HttpHeaders httpHeaders = ts.getOnNextEvents().get(0);
|
||||
assertEquals(MediaType.TEXT_PLAIN, httpHeaders.getContentType());
|
||||
assertEquals(13L, httpHeaders.getContentLength());
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPlainTextResponseAsObject() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setBody("Hello Spring!"));
|
||||
|
||||
Single<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.header("X-Test-Header", "testvalue"))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber<String> ts = new TestSubscriber<String>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
String response = ts.getOnNextEvents().get(0);
|
||||
assertEquals("Hello Spring!", response);
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("testvalue", request.getHeader("X-Test-Header"));
|
||||
Assert.assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPlainTextResponse() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||
|
||||
Single<ResponseEntity<String>> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.TEXT_PLAIN))
|
||||
.extract(response(String.class));
|
||||
|
||||
TestSubscriber<ResponseEntity<String>> ts = new TestSubscriber<ResponseEntity<String>>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
ResponseEntity<String> response = ts.getOnNextEvents().get(0);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertEquals(MediaType.TEXT_PLAIN, response.getHeaders().getContentType());
|
||||
assertEquals("Hello Spring!", response.getBody());
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
Assert.assertEquals("text/plain", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsMonoOfString() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/json");
|
||||
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody(content));
|
||||
|
||||
Single<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber<String> ts = new TestSubscriber<String>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
String response = ts.getOnNextEvents().get(0);
|
||||
assertEquals(content, response);
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/json", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsMonoOfPojo() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojo");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));
|
||||
|
||||
Single<Pojo> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(Pojo.class));
|
||||
|
||||
TestSubscriber<Pojo> ts = new TestSubscriber<Pojo>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
Pojo response = ts.getOnNextEvents().get(0);
|
||||
assertEquals("barbar", response.getBar());
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojo", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsFluxOfPojos() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojos");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
|
||||
|
||||
Observable<Pojo> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(bodyStream(Pojo.class));
|
||||
|
||||
TestSubscriber<Pojo> ts = new TestSubscriber<Pojo>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
assertThat(ts.getOnNextEvents().get(0).getBar(), Matchers.is("bar1"));
|
||||
assertThat(ts.getOnNextEvents().get(1).getBar(), Matchers.is("bar2"));
|
||||
ts.assertValueCount(2);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojos", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsResponseOfPojosStream() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojos");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
|
||||
|
||||
Single<ResponseEntity<Observable<Pojo>>> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(responseStream(Pojo.class));
|
||||
|
||||
TestSubscriber<ResponseEntity<Observable<Pojo>>> ts = new TestSubscriber<ResponseEntity<Observable<Pojo>>>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
ResponseEntity<Observable<Pojo>> response = ts.getOnNextEvents().get(0);
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojos", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPostPojoAsJson() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojo/capitalize");
|
||||
this.server.enqueue(new MockResponse()
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
|
||||
|
||||
Pojo spring = new Pojo("foofoo", "barbar");
|
||||
Single<Pojo> result = this.webClient
|
||||
.perform(post(baseUrl.toString())
|
||||
.body(spring)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(Pojo.class));
|
||||
|
||||
TestSubscriber<Pojo> ts = new TestSubscriber<Pojo>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
assertThat(ts.getOnNextEvents().get(0).getBar(), Matchers.is("BARBAR"));
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojo/capitalize", request.getPath());
|
||||
Assert.assertEquals("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}", request.getBody().readUtf8());
|
||||
Assert.assertEquals("chunked", request.getHeader(HttpHeaders.TRANSFER_ENCODING));
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSendCookieHeader() throws Exception {
|
||||
HttpUrl baseUrl = server.url("/test");
|
||||
this.server.enqueue(new MockResponse()
|
||||
.setHeader("Content-Type", "text/plain").setBody("test"));
|
||||
|
||||
Single<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.cookie("testkey", "testvalue"))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber<String> ts = new TestSubscriber<String>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
String response = ts.getOnNextEvents().get(0);
|
||||
assertEquals("test", response);
|
||||
ts.assertValueCount(1);
|
||||
ts.assertCompleted();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/test", request.getPath());
|
||||
Assert.assertEquals("testkey=testvalue", request.getHeader(HttpHeaders.COOKIE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void shouldGetErrorWhen404() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setResponseCode(404));
|
||||
|
||||
Single<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString()))
|
||||
.extract(body(String.class));
|
||||
|
||||
// TODO: error message should be converted to a ClientException
|
||||
TestSubscriber<String> ts = new TestSubscriber<String>();
|
||||
result.subscribe(ts);
|
||||
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
|
||||
|
||||
ts.assertError(WebClientException.class);
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.web.client.reactive;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.web.client.reactive.ClientWebRequestBuilders.*;
|
||||
import static org.springframework.web.client.reactive.ResponseExtractors.*;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.test.TestSubscriber;
|
||||
|
||||
import org.springframework.http.codec.Pojo;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
|
||||
/**
|
||||
* {@link WebClient} integration tests with the {@code Flux} and {@code Mono} API.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class WebClientIntegrationTests {
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private WebClient webClient;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.server = new MockWebServer();
|
||||
this.webClient = new WebClient(new ReactorClientHttpConnector());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetHeaders() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||
|
||||
Mono<HttpHeaders> result = this.webClient
|
||||
.perform(get(baseUrl.toString()))
|
||||
.extract(headers());
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValuesWith(
|
||||
httpHeaders -> {
|
||||
assertEquals(MediaType.TEXT_PLAIN, httpHeaders.getContentType());
|
||||
assertEquals(13L, httpHeaders.getContentLength());
|
||||
})
|
||||
.assertComplete();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPlainTextResponseAsObject() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setBody("Hello Spring!"));
|
||||
|
||||
Mono<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.header("X-Test-Header", "testvalue"))
|
||||
.extract(body(String.class));
|
||||
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValues("Hello Spring!")
|
||||
.assertComplete();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("testvalue", request.getHeader("X-Test-Header"));
|
||||
Assert.assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetPlainTextResponse() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!"));
|
||||
|
||||
Mono<ResponseEntity<String>> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.TEXT_PLAIN))
|
||||
.extract(response(String.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValuesWith((Consumer<ResponseEntity<String>>) response -> {
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertEquals(MediaType.TEXT_PLAIN, response.getHeaders().getContentType());
|
||||
assertEquals("Hello Spring!", response.getBody());
|
||||
});
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
Assert.assertEquals("text/plain", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsMonoOfString() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/json");
|
||||
String content = "{\"bar\":\"barbar\",\"foo\":\"foofoo\"}";
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody(content));
|
||||
|
||||
Mono<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValues(content)
|
||||
.assertComplete();
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/json", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsMonoOfPojo() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojo");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody("{\"bar\":\"barbar\",\"foo\":\"foofoo\"}"));
|
||||
|
||||
Mono<Pojo> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(Pojo.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValuesWith(p -> assertEquals("barbar", p.getBar()))
|
||||
.assertComplete();
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojo", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsFluxOfPojos() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojos");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
|
||||
|
||||
Flux<Pojo> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(bodyStream(Pojo.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValuesWith(
|
||||
p -> assertThat(p.getBar(), Matchers.is("bar1")),
|
||||
p -> assertThat(p.getBar(), Matchers.is("bar2")))
|
||||
.assertValueCount(2)
|
||||
.assertComplete();
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojos", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetJsonAsResponseOfPojosStream() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojos");
|
||||
this.server.enqueue(new MockResponse().setHeader("Content-Type", "application/json")
|
||||
.setBody("[{\"bar\":\"bar1\",\"foo\":\"foo1\"},{\"bar\":\"bar2\",\"foo\":\"foo2\"}]"));
|
||||
|
||||
Mono<ResponseEntity<Flux<Pojo>>> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(responseStream(Pojo.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValuesWith(
|
||||
response -> {
|
||||
assertEquals(200, response.getStatusCode().value());
|
||||
assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType());
|
||||
})
|
||||
.assertComplete();
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojos", request.getPath());
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPostPojoAsJson() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/pojo/capitalize");
|
||||
this.server.enqueue(new MockResponse()
|
||||
.setHeader("Content-Type", "application/json")
|
||||
.setBody("{\"bar\":\"BARBAR\",\"foo\":\"FOOFOO\"}"));
|
||||
|
||||
Pojo spring = new Pojo("foofoo", "barbar");
|
||||
Mono<Pojo> result = this.webClient
|
||||
.perform(post(baseUrl.toString())
|
||||
.body(spring)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.extract(body(Pojo.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValuesWith(p -> assertEquals("BARBAR", p.getBar()))
|
||||
.assertComplete();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/pojo/capitalize", request.getPath());
|
||||
Assert.assertEquals("{\"foo\":\"foofoo\",\"bar\":\"barbar\"}", request.getBody().readUtf8());
|
||||
Assert.assertEquals("chunked", request.getHeader(HttpHeaders.TRANSFER_ENCODING));
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("application/json", request.getHeader(HttpHeaders.CONTENT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSendCookieHeader() throws Exception {
|
||||
HttpUrl baseUrl = server.url("/test");
|
||||
this.server.enqueue(new MockResponse()
|
||||
.setHeader("Content-Type", "text/plain").setBody("test"));
|
||||
|
||||
Mono<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString())
|
||||
.cookie("testkey", "testvalue"))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.awaitAndAssertNextValues("test")
|
||||
.assertComplete();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("/test", request.getPath());
|
||||
Assert.assertEquals("testkey=testvalue", request.getHeader(HttpHeaders.COOKIE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldGetErrorWhen404() throws Exception {
|
||||
|
||||
HttpUrl baseUrl = server.url("/greeting?name=Spring");
|
||||
this.server.enqueue(new MockResponse().setResponseCode(404));
|
||||
|
||||
Mono<String> result = this.webClient
|
||||
.perform(get(baseUrl.toString()))
|
||||
.extract(body(String.class));
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(result)
|
||||
.await()
|
||||
.assertError();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Assert.assertEquals(1, server.getRequestCount());
|
||||
Assert.assertEquals("*/*", request.getHeader(HttpHeaders.ACCEPT));
|
||||
Assert.assertEquals("/greeting?name=Spring", request.getPath());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class StandardServletAsyncWebRequestTests {
|
||||
|
||||
@Test
|
||||
public void startAsyncAfterCompleted() throws Exception {
|
||||
this.asyncRequest.onComplete(new AsyncEvent(null));
|
||||
this.asyncRequest.onComplete(new AsyncEvent(new MockAsyncContext(this.request, this.response)));
|
||||
try {
|
||||
this.asyncRequest.startAsync();
|
||||
fail("expected exception");
|
||||
@@ -116,7 +116,7 @@ public class StandardServletAsyncWebRequestTests {
|
||||
|
||||
@Test
|
||||
public void onTimeoutDefaultBehavior() throws Exception {
|
||||
this.asyncRequest.onTimeout(new AsyncEvent(null));
|
||||
this.asyncRequest.onTimeout(new AsyncEvent(new MockAsyncContext(this.request, this.response)));
|
||||
assertEquals(200, this.response.getStatus());
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public class StandardServletAsyncWebRequestTests {
|
||||
public void onTimeoutHandler() throws Exception {
|
||||
Runnable timeoutHandler = mock(Runnable.class);
|
||||
this.asyncRequest.addTimeoutHandler(timeoutHandler);
|
||||
this.asyncRequest.onTimeout(new AsyncEvent(null));
|
||||
this.asyncRequest.onTimeout(new AsyncEvent(new MockAsyncContext(this.request, this.response)));
|
||||
verify(timeoutHandler).run();
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public class StandardServletAsyncWebRequestTests {
|
||||
this.asyncRequest.addCompletionHandler(handler);
|
||||
|
||||
this.asyncRequest.startAsync();
|
||||
this.asyncRequest.onComplete(new AsyncEvent(null));
|
||||
this.asyncRequest.onComplete(new AsyncEvent(this.request.getAsyncContext()));
|
||||
|
||||
verify(handler).run();
|
||||
assertTrue(this.asyncRequest.isAsyncComplete());
|
||||
@@ -154,7 +154,7 @@ public class StandardServletAsyncWebRequestTests {
|
||||
this.asyncRequest.addCompletionHandler(handler);
|
||||
|
||||
this.asyncRequest.startAsync();
|
||||
this.asyncRequest.onError(new AsyncEvent(null));
|
||||
this.asyncRequest.onError(new AsyncEvent(this.request.getAsyncContext()));
|
||||
|
||||
verify(handler).run();
|
||||
assertTrue(this.asyncRequest.isAsyncComplete());
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.web.server.handler;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.MockWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
||||
public class ExceptionHandlingHttpHandlerTests {
|
||||
|
||||
private MockServerHttpResponse response;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private WebHandler targetHandler;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
URI uri = new URI("http://localhost:8080");
|
||||
WebSessionManager sessionManager = new MockWebSessionManager();
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, uri);
|
||||
this.response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(request, this.response, sessionManager);
|
||||
this.targetHandler = new StubWebHandler(new IllegalStateException("boo"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void handleErrorSignal() throws Exception {
|
||||
WebExceptionHandler exceptionHandler = new BadRequestExceptionHandler();
|
||||
createWebHandler(exceptionHandler).handle(this.exchange).block();
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleErrorSignalWithMultipleHttpErrorHandlers() throws Exception {
|
||||
WebExceptionHandler[] exceptionHandlers = new WebExceptionHandler[] {
|
||||
new UnresolvedExceptionHandler(),
|
||||
new UnresolvedExceptionHandler(),
|
||||
new BadRequestExceptionHandler(),
|
||||
new UnresolvedExceptionHandler()
|
||||
};
|
||||
createWebHandler(exceptionHandlers).handle(this.exchange).block();
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unresolvedException() throws Exception {
|
||||
WebExceptionHandler exceptionHandler = new UnresolvedExceptionHandler();
|
||||
createWebHandler(exceptionHandler).handle(this.exchange).block();
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thrownExceptionBecomesErrorSignal() throws Exception {
|
||||
WebExceptionHandler exceptionHandler = new BadRequestExceptionHandler();
|
||||
createWebHandler(exceptionHandler).handle(this.exchange).block();
|
||||
|
||||
assertEquals(HttpStatus.BAD_REQUEST, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
private WebHandler createWebHandler(WebExceptionHandler... handlers) {
|
||||
return new ExceptionHandlingWebHandler(this.targetHandler, handlers);
|
||||
}
|
||||
|
||||
|
||||
private static class StubWebHandler implements WebHandler {
|
||||
|
||||
private final RuntimeException exception;
|
||||
|
||||
private final boolean raise;
|
||||
|
||||
|
||||
public StubWebHandler(RuntimeException exception) {
|
||||
this(exception, false);
|
||||
}
|
||||
|
||||
public StubWebHandler(RuntimeException exception, boolean raise) {
|
||||
this.exception = exception;
|
||||
this.raise = raise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
if (this.raise) {
|
||||
throw this.exception;
|
||||
}
|
||||
return Mono.error(this.exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BadRequestExceptionHandler implements WebExceptionHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/** Leave the exception unresolved. */
|
||||
private static class UnresolvedExceptionHandler implements WebExceptionHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.web.server.handler;
|
||||
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebExceptionHandler;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class FilteringWebHandlerTests {
|
||||
|
||||
private static Log logger = LogFactory.getLog(FilteringWebHandlerTests.class);
|
||||
|
||||
|
||||
private MockServerHttpRequest request;
|
||||
|
||||
private MockServerHttpResponse response;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, new URI("http://localhost"));
|
||||
this.response = new MockServerHttpResponse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleFilters() throws Exception {
|
||||
StubWebHandler webHandler = new StubWebHandler();
|
||||
TestFilter filter1 = new TestFilter();
|
||||
TestFilter filter2 = new TestFilter();
|
||||
TestFilter filter3 = new TestFilter();
|
||||
HttpHandler httpHandler = createHttpHandler(webHandler, filter1, filter2, filter3);
|
||||
httpHandler.handle(this.request, this.response).block();
|
||||
|
||||
assertTrue(filter1.invoked());
|
||||
assertTrue(filter2.invoked());
|
||||
assertTrue(filter3.invoked());
|
||||
assertTrue(webHandler.invoked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void zeroFilters() throws Exception {
|
||||
StubWebHandler webHandler = new StubWebHandler();
|
||||
HttpHandler httpHandler = createHttpHandler(webHandler);
|
||||
httpHandler.handle(this.request, this.response).block();
|
||||
|
||||
assertTrue(webHandler.invoked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortcircuitFilter() throws Exception {
|
||||
StubWebHandler webHandler = new StubWebHandler();
|
||||
TestFilter filter1 = new TestFilter();
|
||||
ShortcircuitingFilter filter2 = new ShortcircuitingFilter();
|
||||
TestFilter filter3 = new TestFilter();
|
||||
HttpHandler httpHandler = createHttpHandler(webHandler, filter1, filter2, filter3);
|
||||
httpHandler.handle(this.request, this.response).block();
|
||||
|
||||
assertTrue(filter1.invoked());
|
||||
assertTrue(filter2.invoked());
|
||||
assertFalse(filter3.invoked());
|
||||
assertFalse(webHandler.invoked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asyncFilter() throws Exception {
|
||||
StubWebHandler webHandler = new StubWebHandler();
|
||||
AsyncFilter filter = new AsyncFilter();
|
||||
HttpHandler httpHandler = createHttpHandler(webHandler, filter);
|
||||
httpHandler.handle(this.request, this.response).block();
|
||||
|
||||
assertTrue(filter.invoked());
|
||||
assertTrue(webHandler.invoked());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleErrorFromFilter() throws Exception {
|
||||
TestExceptionHandler exceptionHandler = new TestExceptionHandler();
|
||||
HttpHandler handler = WebHttpHandlerBuilder.webHandler(new StubWebHandler())
|
||||
.filters(new ExceptionFilter()).exceptionHandlers(exceptionHandler).build();
|
||||
handler.handle(this.request, this.response).block();
|
||||
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, this.response.getStatusCode());
|
||||
|
||||
Throwable savedException = exceptionHandler.ex;
|
||||
assertNotNull(savedException);
|
||||
assertEquals("boo", savedException.getMessage());
|
||||
}
|
||||
|
||||
private HttpHandler createHttpHandler(StubWebHandler webHandler, WebFilter... filters) {
|
||||
return WebHttpHandlerBuilder.webHandler(webHandler).filters(filters).build();
|
||||
}
|
||||
|
||||
|
||||
private static class TestFilter implements WebFilter {
|
||||
|
||||
private volatile boolean invoked;
|
||||
|
||||
public boolean invoked() {
|
||||
return this.invoked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
this.invoked = true;
|
||||
return doFilter(exchange, chain);
|
||||
}
|
||||
|
||||
public Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ShortcircuitingFilter extends TestFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AsyncFilter extends TestFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return doAsyncWork().then(asyncResult -> {
|
||||
logger.debug("Async result: " + asyncResult);
|
||||
return chain.filter(exchange);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<String> doAsyncWork() {
|
||||
return Mono.just("123");
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExceptionFilter implements WebFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return Mono.error(new IllegalStateException("boo"));
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestExceptionHandler implements WebExceptionHandler {
|
||||
|
||||
private Throwable ex;
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
this.ex = ex;
|
||||
return Mono.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StubWebHandler implements WebHandler {
|
||||
|
||||
private volatile boolean invoked;
|
||||
|
||||
public boolean invoked() {
|
||||
return this.invoked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
logger.trace("StubHandler invoked.");
|
||||
this.invoked = true;
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.web.server.session;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class DefaultWebSessionManagerTests {
|
||||
|
||||
private final DefaultWebSessionManager manager = new DefaultWebSessionManager();
|
||||
|
||||
private final TestWebSessionIdResolver idResolver = new TestWebSessionIdResolver();
|
||||
|
||||
private DefaultServerWebExchange exchange;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.manager.setSessionIdResolver(this.idResolver);
|
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI("/path"));
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, this.manager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getSessionWithoutStarting() throws Exception {
|
||||
this.idResolver.setIdsToResolve(Collections.emptyList());
|
||||
WebSession session = this.manager.getSession(this.exchange).block();
|
||||
session.save();
|
||||
|
||||
assertFalse(session.isStarted());
|
||||
assertFalse(session.isExpired());
|
||||
assertNull(this.idResolver.getSavedId());
|
||||
assertNull(this.manager.getSessionStore().retrieveSession(session.getId()).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startSessionExplicitly() throws Exception {
|
||||
this.idResolver.setIdsToResolve(Collections.emptyList());
|
||||
WebSession session = this.manager.getSession(this.exchange).block();
|
||||
session.start();
|
||||
session.save();
|
||||
|
||||
String id = session.getId();
|
||||
assertNotNull(this.idResolver.getSavedId());
|
||||
assertEquals(id, this.idResolver.getSavedId());
|
||||
assertSame(session, this.manager.getSessionStore().retrieveSession(id).block());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startSessionImplicitly() throws Exception {
|
||||
this.idResolver.setIdsToResolve(Collections.emptyList());
|
||||
WebSession session = this.manager.getSession(this.exchange).block();
|
||||
session.getAttributes().put("foo", "bar");
|
||||
session.save();
|
||||
|
||||
assertNotNull(this.idResolver.getSavedId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingSession() throws Exception {
|
||||
DefaultWebSession existing = new DefaultWebSession("1", Clock.systemDefaultZone());
|
||||
this.manager.getSessionStore().storeSession(existing);
|
||||
this.idResolver.setIdsToResolve(Collections.singletonList("1"));
|
||||
|
||||
WebSession actual = this.manager.getSession(this.exchange).block();
|
||||
assertSame(existing, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existingSessionIsExpired() throws Exception {
|
||||
Clock clock = Clock.systemDefaultZone();
|
||||
DefaultWebSession existing = new DefaultWebSession("1", clock);
|
||||
existing.start();
|
||||
existing.setLastAccessTime(Instant.now(clock).minus(Duration.ofMinutes(31)));
|
||||
this.manager.getSessionStore().storeSession(existing);
|
||||
this.idResolver.setIdsToResolve(Collections.singletonList("1"));
|
||||
|
||||
WebSession actual = this.manager.getSession(this.exchange).block();
|
||||
assertNotSame(existing, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleSessions() throws Exception {
|
||||
DefaultWebSession existing = new DefaultWebSession("3", Clock.systemDefaultZone());
|
||||
this.manager.getSessionStore().storeSession(existing);
|
||||
this.idResolver.setIdsToResolve(Arrays.asList("1", "2", "3"));
|
||||
|
||||
WebSession actual = this.manager.getSession(this.exchange).block();
|
||||
assertSame(existing, actual);
|
||||
}
|
||||
|
||||
|
||||
private static class TestWebSessionIdResolver implements WebSessionIdResolver {
|
||||
|
||||
private List<String> idsToResolve = new ArrayList<>();
|
||||
|
||||
private String id = null;
|
||||
|
||||
|
||||
public void setIdsToResolve(List<String> idsToResolve) {
|
||||
this.idsToResolve = idsToResolve;
|
||||
}
|
||||
|
||||
public String getSavedId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> resolveSessionIds(ServerWebExchange exchange) {
|
||||
return this.idsToResolve;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSessionId(ServerWebExchange exchange, String sessionId) {
|
||||
this.id = sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.web.server.session;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
|
||||
/**
|
||||
* Mock implementation of {@link WebSessionManager}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MockWebSessionManager implements WebSessionManager {
|
||||
|
||||
private final Mono<WebSession> session;
|
||||
|
||||
|
||||
public MockWebSessionManager() {
|
||||
this(Mono.empty());
|
||||
}
|
||||
|
||||
public MockWebSessionManager(WebSession session) {
|
||||
this(Mono.just(session));
|
||||
}
|
||||
|
||||
public MockWebSessionManager(Mono<WebSession> session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession(ServerWebExchange exchange) {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2002-2016 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.web.server.session;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.reactive.AbstractHttpHandlerIntegrationTests;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.server.WebHandler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Integration tests for with a server-side session.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class WebSessionIntegrationTests extends AbstractHttpHandlerIntegrationTests {
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private DefaultWebSessionManager sessionManager;
|
||||
|
||||
private TestWebHandler handler;
|
||||
|
||||
|
||||
@Override
|
||||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
protected URI createUri(String pathAndQuery) throws URISyntaxException {
|
||||
boolean prefix = !StringUtils.hasText(pathAndQuery) || !pathAndQuery.startsWith("/");
|
||||
pathAndQuery = (prefix ? "/" + pathAndQuery : pathAndQuery);
|
||||
return new URI("http://localhost:" + port + pathAndQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpHandler createHttpHandler() {
|
||||
this.sessionManager = new DefaultWebSessionManager();
|
||||
this.handler = new TestWebHandler();
|
||||
return WebHttpHandlerBuilder.webHandler(this.handler).sessionManager(this.sessionManager).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSession() throws Exception {
|
||||
RequestEntity<Void> request = RequestEntity.get(createUri("/")).build();
|
||||
ResponseEntity<Void> response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
String id = extractSessionId(response.getHeaders());
|
||||
assertNotNull(id);
|
||||
assertEquals(1, this.handler.getCount());
|
||||
|
||||
request = RequestEntity.get(createUri("/")).header("Cookie", "SESSION=" + id).build();
|
||||
response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNull(response.getHeaders().get("Set-Cookie"));
|
||||
assertEquals(2, this.handler.getCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expiredSession() throws Exception {
|
||||
|
||||
// First request: no session yet, new session created
|
||||
RequestEntity<Void> request = RequestEntity.get(createUri("/")).build();
|
||||
ResponseEntity<Void> response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
String id = extractSessionId(response.getHeaders());
|
||||
assertNotNull(id);
|
||||
assertEquals(1, this.handler.getCount());
|
||||
|
||||
// Set (server-side) clock back 31 minutes
|
||||
Clock clock = this.sessionManager.getClock();
|
||||
this.sessionManager.setClock(Clock.offset(clock, Duration.ofMinutes(-31)));
|
||||
|
||||
// Second request: lastAccessTime updated (with offset clock)
|
||||
request = RequestEntity.get(createUri("/")).header("Cookie", "SESSION=" + id).build();
|
||||
response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNull(response.getHeaders().get("Set-Cookie"));
|
||||
assertEquals(2, this.handler.getCount());
|
||||
|
||||
// Third request: new session replaces expired session
|
||||
request = RequestEntity.get(createUri("/")).header("Cookie", "SESSION=" + id).build();
|
||||
response = this.restTemplate.exchange(request, Void.class);
|
||||
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
id = extractSessionId(response.getHeaders());
|
||||
assertNotNull("Expected new session id", id);
|
||||
assertEquals("Expected new session attribute", 1, this.handler.getCount());
|
||||
}
|
||||
|
||||
private String extractSessionId(HttpHeaders headers) {
|
||||
List<String> headerValues = headers.get("Set-Cookie");
|
||||
assertNotNull(headerValues);
|
||||
assertEquals(1, headerValues.size());
|
||||
|
||||
List<String> data = new ArrayList<>();
|
||||
for (String s : headerValues.get(0).split(";")){
|
||||
if (s.startsWith("SESSION=")) {
|
||||
return s.substring("SESSION=".length());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class TestWebHandler implements WebHandler {
|
||||
|
||||
private AtomicInteger currentValue = new AtomicInteger();
|
||||
|
||||
|
||||
public int getCount() {
|
||||
return this.currentValue.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange) {
|
||||
return exchange.getSession().map(session -> {
|
||||
Map<String, Object> map = session.getAttributes();
|
||||
int value = (map.get("counter") != null ? (int) map.get("counter") : 0);
|
||||
value++;
|
||||
map.put("counter", value);
|
||||
this.currentValue.set(value);
|
||||
return session;
|
||||
}).then();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user