Add support for RSocket interface client
See gh-24456
This commit is contained in:
@@ -28,17 +28,14 @@ import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import io.rsocket.Payload;
|
||||
import io.rsocket.RSocket;
|
||||
import io.rsocket.metadata.WellKnownMimeType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester.RequestSpec;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester.RetrieveSpec;
|
||||
import org.springframework.util.MimeType;
|
||||
@@ -246,73 +243,4 @@ public class DefaultRSocketRequesterTests {
|
||||
return PayloadUtils.createPayload(DefaultDataBufferFactory.sharedInstance.wrap(bytes));
|
||||
}
|
||||
|
||||
|
||||
private static class TestRSocket implements RSocket {
|
||||
|
||||
private Mono<Payload> payloadMonoToReturn = Mono.empty();
|
||||
private Flux<Payload> payloadFluxToReturn = Flux.empty();
|
||||
|
||||
@Nullable private volatile String savedMethodName;
|
||||
@Nullable private volatile Payload savedPayload;
|
||||
@Nullable private volatile Flux<Payload> savedPayloadFlux;
|
||||
|
||||
void setPayloadMonoToReturn(Mono<Payload> payloadMonoToReturn) {
|
||||
this.payloadMonoToReturn = payloadMonoToReturn;
|
||||
}
|
||||
|
||||
void setPayloadFluxToReturn(Flux<Payload> payloadFluxToReturn) {
|
||||
this.payloadFluxToReturn = payloadFluxToReturn;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getSavedMethodName() {
|
||||
return this.savedMethodName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Payload getSavedPayload() {
|
||||
return this.savedPayload;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Flux<Payload> getSavedPayloadFlux() {
|
||||
return this.savedPayloadFlux;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.savedMethodName = null;
|
||||
this.savedPayload = null;
|
||||
this.savedPayloadFlux = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> fireAndForget(Payload payload) {
|
||||
this.savedMethodName = "fireAndForget";
|
||||
this.savedPayload = payload;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Payload> requestResponse(Payload payload) {
|
||||
this.savedMethodName = "requestResponse";
|
||||
this.savedPayload = payload;
|
||||
return this.payloadMonoToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Payload> requestStream(Payload payload) {
|
||||
this.savedMethodName = "requestStream";
|
||||
this.savedPayload = payload;
|
||||
return this.payloadFluxToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Payload> requestChannel(Publisher<Payload> publisher) {
|
||||
this.savedMethodName = "requestChannel";
|
||||
this.savedPayloadFlux = Flux.from(publisher);
|
||||
return this.payloadFluxToReturn;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket;
|
||||
|
||||
import io.rsocket.Payload;
|
||||
import io.rsocket.RSocket;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link RSocket} that saves the name of the invoked method and the input payload(s).
|
||||
*/
|
||||
public class TestRSocket implements RSocket {
|
||||
|
||||
private Mono<Payload> payloadMonoToReturn = Mono.empty();
|
||||
|
||||
private Flux<Payload> payloadFluxToReturn = Flux.empty();
|
||||
|
||||
@Nullable private volatile String savedMethodName;
|
||||
|
||||
@Nullable private volatile Payload savedPayload;
|
||||
|
||||
@Nullable private volatile Flux<Payload> savedPayloadFlux;
|
||||
|
||||
|
||||
public void setPayloadMonoToReturn(Mono<Payload> payloadMonoToReturn) {
|
||||
this.payloadMonoToReturn = payloadMonoToReturn;
|
||||
}
|
||||
|
||||
public void setPayloadFluxToReturn(Flux<Payload> payloadFluxToReturn) {
|
||||
this.payloadFluxToReturn = payloadFluxToReturn;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSavedMethodName() {
|
||||
return this.savedMethodName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Payload getSavedPayload() {
|
||||
return this.savedPayload;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Flux<Payload> getSavedPayloadFlux() {
|
||||
return this.savedPayloadFlux;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.savedMethodName = null;
|
||||
this.savedPayload = null;
|
||||
this.savedPayloadFlux = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Mono<Void> fireAndForget(Payload payload) {
|
||||
this.savedMethodName = "fireAndForget";
|
||||
this.savedPayload = payload;
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Payload> requestResponse(Payload payload) {
|
||||
this.savedMethodName = "requestResponse";
|
||||
this.savedPayload = payload;
|
||||
return this.payloadMonoToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Payload> requestStream(Payload payload) {
|
||||
this.savedMethodName = "requestStream";
|
||||
this.savedPayload = payload;
|
||||
return this.payloadFluxToReturn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Payload> requestChannel(Publisher<Payload> publisher) {
|
||||
this.savedMethodName = "requestChannel";
|
||||
this.savedPayloadFlux = Flux.from(publisher);
|
||||
return this.payloadFluxToReturn;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DestinationVariableArgumentResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class DestinationVariableArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
|
||||
|
||||
@Override
|
||||
protected RSocketServiceArgumentResolver initResolver() {
|
||||
return new DestinationVariableArgumentResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
void variable() {
|
||||
String value = "foo";
|
||||
boolean resolved = execute(value, initMethodParameter(Service.class, "execute", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getRouteVariables()).containsExactly(value);
|
||||
}
|
||||
|
||||
@Test
|
||||
void variableList() {
|
||||
List<String> values = Arrays.asList("foo", "bar", "baz");
|
||||
boolean resolved = execute(values, initMethodParameter(Service.class, "execute", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getRouteVariables()).containsExactlyElementsOf(values);
|
||||
}
|
||||
|
||||
@Test
|
||||
void variableArray() {
|
||||
String[] values = new String[] {"foo", "bar", "baz"};
|
||||
boolean resolved = execute(values, initMethodParameter(Service.class, "execute", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getRouteVariables()).containsExactlyElementsOf(Arrays.asList(values));
|
||||
}
|
||||
|
||||
@Test
|
||||
void notRequestBody() {
|
||||
MethodParameter parameter = initMethodParameter(Service.class, "executeNotAnnotated", 0);
|
||||
boolean resolved = execute("value", parameter);
|
||||
|
||||
assertThat(resolved).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreNull() {
|
||||
boolean resolved = execute(null, initMethodParameter(Service.class, "execute", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getPayloadValue()).isNull();
|
||||
assertThat(getRequestValues().getPayload()).isNull();
|
||||
assertThat(getRequestValues().getPayloadElementType()).isNull();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private interface Service {
|
||||
|
||||
void execute(@DestinationVariable String variable);
|
||||
|
||||
void executeList(@DestinationVariable List<String> variables);
|
||||
|
||||
void executeArray(@DestinationVariable String[] variables);
|
||||
|
||||
void executeNotAnnotated(String variable);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MetadataArgumentResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class MetadataArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
|
||||
|
||||
@Override
|
||||
protected RSocketServiceArgumentResolver initResolver() {
|
||||
return new MetadataArgumentResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
void metadata() {
|
||||
MethodParameter param1 = initMethodParameter(Service.class, "execute", 0);
|
||||
MethodParameter param2 = initMethodParameter(Service.class, "execute", 1);
|
||||
MethodParameter param3 = initMethodParameter(Service.class, "execute", 2);
|
||||
MethodParameter param4 = initMethodParameter(Service.class, "execute", 3);
|
||||
|
||||
assertThat(execute("foo", param1)).isTrue();
|
||||
assertThat(execute(MimeTypeUtils.APPLICATION_JSON, param2)).isTrue();
|
||||
|
||||
assertThat(execute("bar", param3)).isTrue();
|
||||
assertThat(execute(MimeTypeUtils.APPLICATION_XML, param4)).isTrue();
|
||||
|
||||
Map<Object, MimeType> expected = new LinkedHashMap<>();
|
||||
expected.put("foo", MimeTypeUtils.APPLICATION_JSON);
|
||||
expected.put("bar", MimeTypeUtils.APPLICATION_XML);
|
||||
|
||||
assertThat(getRequestValues().getMetadata()).containsExactlyEntriesOf(expected);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private interface Service {
|
||||
|
||||
void execute(String metadata1, MimeType mimeType1, String metadata2, MimeType mimeType2);
|
||||
|
||||
void executeNotAnnotated(String foo, String bar);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PayloadArgumentResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class PayloadArgumentResolverTests extends RSocketServiceArgumentResolverTestSupport {
|
||||
|
||||
@Override
|
||||
protected RSocketServiceArgumentResolver initResolver() {
|
||||
return new PayloadArgumentResolver(ReactiveAdapterRegistry.getSharedInstance(), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringPayload() {
|
||||
String payload = "payloadValue";
|
||||
boolean resolved = execute(payload, initMethodParameter(Service.class, "execute", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getPayloadValue()).isEqualTo(payload);
|
||||
assertThat(getRequestValues().getPayload()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void monoPayload() {
|
||||
Mono<String> payloadMono = Mono.just("payloadValue");
|
||||
boolean resolved = execute(payloadMono, initMethodParameter(Service.class, "executeMono", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getPayloadValue()).isNull();
|
||||
assertThat(getRequestValues().getPayload()).isSameAs(payloadMono);
|
||||
assertThat(getRequestValues().getPayloadElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void singlePayload() {
|
||||
boolean resolved = execute(Single.just("bodyValue"), initMethodParameter(Service.class, "executeSingle", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getPayloadValue()).isNull();
|
||||
assertThat(getRequestValues().getPayloadElementType()).isEqualTo(new ParameterizedTypeReference<String>() {});
|
||||
|
||||
Publisher<?> payload = getRequestValues().getPayload();
|
||||
assertThat(payload).isNotNull();
|
||||
assertThat(((Mono<String>) payload).block()).isEqualTo("bodyValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
void monoVoid() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> execute(Mono.empty(), initMethodParameter(Service.class, "executeMonoVoid", 0)))
|
||||
.withMessage("Async type for @Payload should produce value(s)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void completable() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> execute(Completable.complete(), initMethodParameter(Service.class, "executeCompletable", 0)))
|
||||
.withMessage("Async type for @Payload should produce value(s)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void notRequestBody() {
|
||||
MethodParameter parameter = initMethodParameter(Service.class, "executeNotAnnotated", 0);
|
||||
boolean resolved = execute("value", parameter);
|
||||
|
||||
assertThat(resolved).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreNull() {
|
||||
boolean resolved = execute(null, initMethodParameter(Service.class, "execute", 0));
|
||||
|
||||
assertThat(resolved).isTrue();
|
||||
assertThat(getRequestValues().getPayloadValue()).isNull();
|
||||
assertThat(getRequestValues().getPayload()).isNull();
|
||||
assertThat(getRequestValues().getPayloadElementType()).isNull();
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private interface Service {
|
||||
|
||||
void execute(@Payload String body);
|
||||
|
||||
void executeMono(@Payload Mono<String> body);
|
||||
|
||||
void executeSingle(@Payload Single<String> body);
|
||||
|
||||
void executeMonoVoid(@Payload Mono<Void> body);
|
||||
|
||||
void executeCompletable(@Payload Completable body);
|
||||
|
||||
void executeNotAnnotated(String body);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RSocketRequestValues}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RSocketRequestValuesTests {
|
||||
|
||||
@Test
|
||||
void route() {
|
||||
String myRoute = "myRoute";
|
||||
RSocketRequestValues values = RSocketRequestValues.builder(myRoute).build();
|
||||
assertThat(values.getRoute()).isEqualTo(myRoute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void routeOverride() {
|
||||
RSocketRequestValues values = RSocketRequestValues.builder("route1").setRoute("route2").build();
|
||||
|
||||
assertThat(values.getRoute()).isEqualTo("route2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void payloadValue() {
|
||||
String payload = "myValue";
|
||||
RSocketRequestValues values = RSocketRequestValues.builder(null).setPayloadValue(payload).build();
|
||||
|
||||
assertThat(values.getPayloadValue()).isEqualTo(payload);
|
||||
assertThat(values.getPayload()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void payloadPublisher() {
|
||||
Mono<String> payloadMono = Mono.just( "myValue");
|
||||
RSocketRequestValues values = RSocketRequestValues.builder(null)
|
||||
.setPayload(payloadMono, new ParameterizedTypeReference<>() { })
|
||||
.build();
|
||||
|
||||
assertThat(values.getPayloadValue()).isNull();
|
||||
assertThat(values.getPayload()).isSameAs(payloadMono);
|
||||
}
|
||||
|
||||
@Test
|
||||
void metadata() {
|
||||
RSocketRequestValues values = RSocketRequestValues.builder(null)
|
||||
.addMetadata("myMetadata1").addMimeType(MimeTypeUtils.TEXT_PLAIN)
|
||||
.addMetadata("myMetadata2").addMimeType(MimeTypeUtils.TEXT_HTML)
|
||||
.build();
|
||||
|
||||
assertThat(values.getMetadata())
|
||||
.hasSize(2)
|
||||
.containsEntry("myMetadata1", MimeTypeUtils.TEXT_PLAIN)
|
||||
.containsEntry("myMetadata2", MimeTypeUtils.TEXT_HTML);
|
||||
}
|
||||
|
||||
@Test
|
||||
void metadataInvalidEntry() {
|
||||
// MimeType without metadata
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RSocketRequestValues.builder(null).addMimeType(MimeTypeUtils.TEXT_PLAIN));
|
||||
|
||||
// Metadata without MimeType
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RSocketRequestValues.builder(null)
|
||||
.addMetadata("metadata1")
|
||||
.addMetadata("metadata2"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Base class for {@link RSocketServiceArgumentResolver} test fixtures.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public abstract class RSocketServiceArgumentResolverTestSupport {
|
||||
|
||||
@Nullable
|
||||
private RSocketServiceArgumentResolver resolver;
|
||||
|
||||
private final RSocketRequestValues.Builder requestValuesBuilder = RSocketRequestValues.builder(null);
|
||||
|
||||
@Nullable
|
||||
private RSocketRequestValues requestValues;
|
||||
|
||||
|
||||
protected RSocketServiceArgumentResolverTestSupport() {
|
||||
this.resolver = initResolver();
|
||||
}
|
||||
|
||||
protected abstract RSocketServiceArgumentResolver initResolver();
|
||||
|
||||
protected static MethodParameter initMethodParameter(Class<?> serviceClass, String methodName, int index) {
|
||||
Method method = ClassUtils.getMethod(serviceClass, methodName, (Class<?>[]) null);
|
||||
return new MethodParameter(method, index);
|
||||
}
|
||||
|
||||
protected boolean execute(Object payload, MethodParameter parameter) {
|
||||
return this.resolver.resolve(payload, parameter, this.requestValuesBuilder);
|
||||
}
|
||||
|
||||
protected RSocketRequestValues getRequestValues() {
|
||||
this.requestValues = (this.requestValues != null ? this.requestValues : this.requestValuesBuilder.build());
|
||||
return this.requestValues;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.rsocket.SocketAcceptor;
|
||||
import io.rsocket.core.RSocketServer;
|
||||
import io.rsocket.metadata.WellKnownMimeType;
|
||||
import io.rsocket.transport.netty.server.CloseableChannel;
|
||||
import io.rsocket.transport.netty.server.TcpServerTransport;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
import org.springframework.messaging.rsocket.RSocketStrategies;
|
||||
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* Integration tests with RSocket Service client.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RSocketServiceIntegrationTests {
|
||||
|
||||
private static CloseableChannel server;
|
||||
|
||||
private static RSocketRequester requester;
|
||||
|
||||
private static Service serviceProxy;
|
||||
|
||||
|
||||
@BeforeAll
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void setupOnce() throws Exception {
|
||||
|
||||
MimeType metadataMimeType = MimeTypeUtils.parseMimeType(
|
||||
WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
|
||||
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ServerConfig.class);
|
||||
RSocketMessageHandler messageHandler = context.getBean(RSocketMessageHandler.class);
|
||||
SocketAcceptor responder = messageHandler.responder();
|
||||
|
||||
server = RSocketServer.create(responder)
|
||||
.bind(TcpServerTransport.create("localhost", 7000))
|
||||
.block();
|
||||
|
||||
requester = RSocketRequester.builder()
|
||||
.metadataMimeType(metadataMimeType)
|
||||
.rsocketStrategies(context.getBean(RSocketStrategies.class))
|
||||
.tcp("localhost", 7000);
|
||||
|
||||
RSocketServiceProxyFactory proxyFactory = new RSocketServiceProxyFactory(requester);
|
||||
proxyFactory.afterPropertiesSet();
|
||||
|
||||
serviceProxy = proxyFactory.createClient(Service.class);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDownOnce() {
|
||||
requester.rsocketClient().dispose();
|
||||
server.dispose();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void echoAsync() {
|
||||
Flux<String> result = Flux.range(1, 3).concatMap(i -> serviceProxy.echoAsync("Hello " + i));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext("Hello 1 async").expectNext("Hello 2 async").expectNext("Hello 3 async")
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void echoStream() {
|
||||
Flux<String> result = serviceProxy.echoStream("Hello");
|
||||
|
||||
StepVerifier.create(result)
|
||||
.expectNext("Hello 0").expectNextCount(6).expectNext("Hello 7")
|
||||
.thenCancel()
|
||||
.verify(Duration.ofSeconds(5));
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
interface Service {
|
||||
|
||||
@RSocketExchange("echo-async")
|
||||
Mono<String> echoAsync(String payload);
|
||||
|
||||
@RSocketExchange("echo-stream")
|
||||
Flux<String> echoStream(String payload);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
static class ServerController {
|
||||
|
||||
@MessageMapping("echo-async")
|
||||
Mono<String> echoAsync(String payload) {
|
||||
return Mono.delay(Duration.ofMillis(10)).map(aLong -> payload + " async");
|
||||
}
|
||||
|
||||
@MessageMapping("echo-stream")
|
||||
Flux<String> echoStream(String payload) {
|
||||
return Flux.interval(Duration.ofMillis(10)).map(aLong -> payload + " " + aLong);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class ServerConfig {
|
||||
|
||||
@Bean
|
||||
public ServerController controller() {
|
||||
return new ServerController();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RSocketMessageHandler messageHandler(RSocketStrategies rsocketStrategies) {
|
||||
RSocketMessageHandler handler = new RSocketMessageHandler();
|
||||
handler.setRSocketStrategies(rsocketStrategies);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RSocketStrategies rsocketStrategies() {
|
||||
return RSocketStrategies.create();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.messaging.rsocket.service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import io.rsocket.util.DefaultPayload;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.rsocket.RSocketRequester;
|
||||
import org.springframework.messaging.rsocket.RSocketStrategies;
|
||||
import org.springframework.messaging.rsocket.TestRSocket;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.util.MimeTypeUtils.TEXT_PLAIN;
|
||||
|
||||
/**
|
||||
* Tests for {@link RSocketServiceMethod} that create an interface client with
|
||||
* an {@link RSocketRequester} delegating to a {@link TestRSocket}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class RSocketServiceMethodTests {
|
||||
|
||||
private TestRSocket rsocket;
|
||||
|
||||
private RSocketServiceProxyFactory proxyFactory;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
this.rsocket = new TestRSocket();
|
||||
RSocketRequester requester = RSocketRequester.wrap(this.rsocket, TEXT_PLAIN, TEXT_PLAIN, RSocketStrategies.create());
|
||||
this.proxyFactory = new RSocketServiceProxyFactory(requester);
|
||||
this.proxyFactory.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void fireAndForget() {
|
||||
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
|
||||
|
||||
String payload = "p1";
|
||||
service.fireAndForget(Mono.just(payload)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("fireAndForget");
|
||||
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("ff");
|
||||
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo(payload);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestResponse() {
|
||||
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
|
||||
|
||||
String payload1 = "p1";
|
||||
String payload2 = "p2";
|
||||
this.rsocket.setPayloadMonoToReturn(
|
||||
Mono.just(DefaultPayload.create(payload2)));
|
||||
|
||||
String response = service.requestResponse(Mono.just(payload1)).block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(response).isEqualTo(payload2);
|
||||
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestResponse");
|
||||
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("rr");
|
||||
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo(payload1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestStream() {
|
||||
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
|
||||
|
||||
String payload1 = "p1";
|
||||
String payload2 = "p2";
|
||||
String payload3 = "p3";
|
||||
this.rsocket.setPayloadFluxToReturn(
|
||||
Flux.just(DefaultPayload.create(payload2), DefaultPayload.create(payload3)));
|
||||
|
||||
List<String> response = service.requestStream(Mono.just(payload1))
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(response).containsExactly(payload2, payload3);
|
||||
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestStream");
|
||||
assertThat(this.rsocket.getSavedPayload().getMetadataUtf8()).isEqualTo("rs");
|
||||
assertThat(this.rsocket.getSavedPayload().getDataUtf8()).isEqualTo(payload1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requestChannel() {
|
||||
ReactorService service = this.proxyFactory.createClient(ReactorService.class);
|
||||
|
||||
String payload1 = "p1";
|
||||
String payload2 = "p2";
|
||||
String payload3 = "p3";
|
||||
String payload4 = "p4";
|
||||
this.rsocket.setPayloadFluxToReturn(
|
||||
Flux.just(DefaultPayload.create(payload3), DefaultPayload.create(payload4)));
|
||||
|
||||
List<String> response = service.requestChannel(Flux.just(payload1, payload2))
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(response).containsExactly(payload3, payload4);
|
||||
assertThat(this.rsocket.getSavedMethodName()).isEqualTo("requestChannel");
|
||||
|
||||
List<String> savedPayloads = this.rsocket.getSavedPayloadFlux()
|
||||
.map(io.rsocket.Payload::getDataUtf8)
|
||||
.collectList()
|
||||
.block(Duration.ofSeconds(5));
|
||||
|
||||
assertThat(savedPayloads).containsExactly("p1", "p2");
|
||||
}
|
||||
|
||||
|
||||
private interface ReactorService {
|
||||
|
||||
@RSocketExchange("ff")
|
||||
Mono<Void> fireAndForget(@Payload Mono<String> input);
|
||||
|
||||
@RSocketExchange("rr")
|
||||
Mono<String> requestResponse(@Payload Mono<String> input);
|
||||
|
||||
@RSocketExchange("rs")
|
||||
Flux<String> requestStream(@Payload Mono<String> input);
|
||||
|
||||
@RSocketExchange("rc")
|
||||
Flux<String> requestChannel(@Payload Flux<String> input);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user