Move spring-web-reactive classes to spring-web

This commit is contained in:
Rossen Stoyanchev
2016-07-13 23:13:29 -04:00
parent 2e8326220b
commit 5d1b542698
152 changed files with 217 additions and 169 deletions

View File

@@ -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 + "\']";
}
}

View File

@@ -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())
);
}
}

View File

@@ -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) {
}
}

View File

@@ -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 {
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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());
}
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,2 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "namespace")
package org.springframework.http.codec.xml.jaxb;

View File

@@ -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();
}
}

View File

@@ -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())));
}
}
}

View File

@@ -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() {
}
};
}

View File

@@ -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();
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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 {
}
};
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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());

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}