Removes RSocket support temporarily.

Fixes gh-1366

Moved to https://github.com/spring-cloud-incubator/spring-cloud-rsocket
This commit is contained in:
Spencer Gibb
2019-10-21 15:12:52 -04:00
parent 6560a9ce65
commit 7a24912c1d
87 changed files with 0 additions and 9447 deletions

10
pom.xml
View File

@@ -129,7 +129,6 @@
<module>spring-cloud-gateway-mvc</module>
<module>spring-cloud-gateway-webflux</module>
<module>spring-cloud-gateway-core</module>
<module>spring-cloud-gateway-rsocket</module>
<module>spring-cloud-starter-gateway</module>
<module>spring-cloud-gateway-sample</module>
<module>docs</module>
@@ -179,15 +178,6 @@
<enabled>false</enabled>
</snapshots>
</repository>
<!-- RSocket Snapshots -->
<repository>
<id>jfrog-snapshots</id>
<name>JFRog Snapshots</name>
<url>https://oss.jfrog.org/artifactory/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>

View File

@@ -18,7 +18,6 @@
<description>Spring Cloud Gateway Dependencies</description>
<properties>
<roaringbitmap.version>0.8.9</roaringbitmap.version>
</properties>
<dependencyManagement>
@@ -38,36 +37,11 @@
<artifactId>spring-cloud-gateway-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-broker</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>RoaringBitmap</artifactId>
<version>${roaringbitmap.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2018-2019 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket</artifactId>
<name>Spring Cloud Gateway RSocket</name>
<description>Spring Cloud Gateway RSocket</description>
<packaging>pom</packaging>
<modules>
<module>spring-cloud-gateway-rsocket-common</module>
<module>spring-cloud-gateway-rsocket-client</module>
<module>spring-cloud-gateway-rsocket-broker</module>
</modules>
</project>

View File

@@ -1,168 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2018-2019 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-broker</artifactId>
<name>Spring Cloud Gateway RSocket Broker</name>
<description>Spring Cloud Gateway RSocket Broker</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.rsocket</groupId>
<artifactId>rsocket-core</artifactId>
</dependency>
<dependency>
<groupId>io.rsocket</groupId>
<artifactId>rsocket-micrometer</artifactId>
</dependency>
<dependency>
<groupId>io.rsocket</groupId>
<artifactId>rsocket-transport-netty</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>RoaringBitmap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-common</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>java8plus</id>
<activation>
<jdk>[1.8,2.0)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,129 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import java.math.BigInteger;
import java.util.List;
import io.rsocket.RSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.cluster.ClusterService;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.stereotype.Controller;
@Controller
public class BrokerActuator {
private static final Log log = LogFactory.getLog(BrokerActuator.class);
/**
* Path for BrokerInfo actuator endpoint.
*/
public static final String BROKER_INFO_PATH = "actuator.gateway.brokerinfo";
/**
* Path for RouteJoin actuator endpoint.
*/
public static final String ROUTE_JOIN_PATH = "actuator.gateway.routejoin";
/**
* Path for RouteJoin actuator endpoint.
*/
public static final String ROUTE_REMOVE_PATH = "actuator.gateway.routeremove";
private final BrokerProperties properties;
private final ClusterService clusterService;
private final RoutingTable routingTable;
public BrokerActuator(BrokerProperties properties, ClusterService clusterService,
RoutingTable routingTable) {
this.properties = properties;
this.clusterService = clusterService;
this.routingTable = routingTable;
}
@MessageMapping("hello")
public Mono<String> hello(String name) {
return Mono.just("Hello " + name);
}
@MessageMapping(BROKER_INFO_PATH)
public BigInteger brokerInfo(BrokerInfo brokerInfo) {
log.info("BrokerInfo: " + brokerInfo);
clusterService.registerIncoming(brokerInfo);
return properties.getRouteId();
}
@MessageMapping(ROUTE_JOIN_PATH)
@SuppressWarnings("Duplicates")
public RouteJoin routeJoin(RouteJoin routeJoin) {
log.info("RouteJoin: " + routeJoin);
TagsMetadata findBrokerQuery = TagsMetadata.builder()
.routeId(routeJoin.getBrokerId().toString()).build();
List<Tuple2<String, RSocket>> rSockets = routingTable
.findRSockets(findBrokerQuery);
if (rSockets.size() != 1) {
// should only be one broker
if (log.isDebugEnabled()) {
log.debug("Expected 1 RSocket for broker: " + routeJoin.getBrokerId()
+ ", found " + rSockets.size());
}
return null;
}
RSocket brokerRSocket = rSockets.iterator().next().getT2();
TagsMetadata.Builder tags = TagsMetadata.builder();
// TODO: other tags.
// routeJoin.getTags().forEach(tags::with);
tags.routeId(routeJoin.getRouteId().toString())
.serviceName(routeJoin.getServiceName());
TagsMetadata tagsMetadata = tags.build();
routingTable.register(tagsMetadata, brokerRSocket);
brokerRSocket.onClose().doOnSuccess(v -> {
if (log.isDebugEnabled()) {
log.debug("Broker closed, deregistering " + tagsMetadata);
}
routingTable.deregister(tagsMetadata);
}).doOnError(t -> {
if (log.isErrorEnabled()) {
log.error("Error received on broker, deregistering " + tagsMetadata, t);
}
routingTable.deregister(tagsMetadata);
}).subscribe();
// TODO: keep track of disposable?
return routeJoin;
}
@MessageMapping(ROUTE_REMOVE_PATH)
public boolean routeRemove(RouteRemove routeRemove) {
log.info("RouteRemove: " + routeRemove);
return routingTable.deregister(TagsMetadata.builder()
.routeId(routeRemove.getRouteId().toString()).build());
}
}

View File

@@ -1,120 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.rsocket.AbstractRSocket;
import io.rsocket.ConnectionSetupPayload;
import io.rsocket.Payload;
import io.rsocket.SocketAcceptor;
import io.rsocket.frame.SetupFrameFlyweight;
import io.rsocket.util.DefaultPayload;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import static io.rsocket.metadata.WellKnownMimeType.APPLICATION_CBOR;
import static io.rsocket.metadata.WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA;
public class BrokerActuatorHandlerRegistration implements SmartLifecycle {
private final AtomicBoolean running = new AtomicBoolean();
private final RoutingTable routingTable;
private final RSocketMessageHandler messageHandler;
private final BrokerProperties properties;
public BrokerActuatorHandlerRegistration(RoutingTable routingTable,
RSocketMessageHandler messageHandler, BrokerProperties properties) {
this.routingTable = routingTable;
this.messageHandler = messageHandler;
this.properties = properties;
}
@Override
public void start() {
if (this.running.compareAndSet(false, true)) {
ConnectionSetupPayload connectionSetupPayload = getConnectionSetupPayload();
SocketAcceptor responder = this.messageHandler.responder();
responder.accept(connectionSetupPayload, new AbstractRSocket() {
}).subscribe(rSocket -> {
TagsMetadata tagsMetadata = TagsMetadata.builder()
.routeId(properties.getRouteId().toString())
.serviceName(properties.getServiceName())
// TODO: move to well known implementation key
.with("proxy", Boolean.FALSE.toString()).build();
routingTable.register(tagsMetadata, rSocket);
});
}
}
private ConnectionSetupPayload getConnectionSetupPayload() {
DataBufferFactory dataBufferFactory = messageHandler.getRSocketStrategies()
.dataBufferFactory();
NettyDataBufferFactory ndbf = (NettyDataBufferFactory) dataBufferFactory;
ByteBufAllocator byteBufAllocator = ndbf.getByteBufAllocator();
Payload setupPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER,
Unpooled.EMPTY_BUFFER);
ByteBuf setup = SetupFrameFlyweight.encode(byteBufAllocator, false, 1, 1,
MESSAGE_RSOCKET_COMPOSITE_METADATA.getString(),
// TODO: configurable?
APPLICATION_CBOR.getString(), setupPayload);
return ConnectionSetupPayload.create(setup);
}
@Override
public void stop() {
if (this.running.compareAndSet(true, false)) {
System.out.println();
}
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
if (callback != null) {
callback.run();
}
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isRunning() {
return this.running.get();
}
}

View File

@@ -1,138 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import java.math.BigInteger;
import java.util.Map;
import java.util.Objects;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
public final class BrokerInfo {
private final BigInteger brokerId;
private final long timestamp;
private final Map<Key, String> tags;
private BrokerInfo(BigInteger brokerId, long timestamp, Map<Key, String> tags) {
this.brokerId = brokerId;
this.timestamp = timestamp;
this.tags = tags;
}
public BigInteger getBrokerId() {
return this.brokerId;
}
public long getTimestamp() {
return this.timestamp;
}
public Map<Key, String> getTags() {
return this.tags;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BrokerInfo that = (BrokerInfo) o;
return this.timestamp == that.timestamp
&& Objects.equals(this.brokerId, that.brokerId)
&& Objects.equals(this.tags, that.tags);
}
@Override
public int hashCode() {
return Objects.hash(this.brokerId, this.timestamp, this.tags);
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("id", brokerId)
.append("timestamp", timestamp)
.append("tags", getTags())
.toString();
// @formatter:on
}
public static Builder of(BigInteger brokerId) {
return new Builder(brokerId);
}
public static Builder of(Long brokerId) {
return of(BigInteger.valueOf(brokerId));
}
public static final class Builder {
private final BigInteger brokerId;
private long timestamp = System.currentTimeMillis();
private final TagsMetadata.Builder tagsBuilder = TagsMetadata.builder();
private Builder(BigInteger brokerId) {
Assert.notNull(brokerId, "brokerId may not be null");
this.brokerId = brokerId;
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder with(String key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(WellKnownKey key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(Key key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(TagsMetadata tagsMetadata) {
tagsBuilder.with(tagsMetadata);
return this;
}
public BrokerInfo build() {
Assert.isTrue(timestamp > 0, "timestamp must be > 0");
return new BrokerInfo(brokerId, timestamp, tagsBuilder.build().getTags());
}
}
}

View File

@@ -1,175 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import java.math.BigInteger;
import java.util.Map;
import java.util.Objects;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
public final class RouteJoin {
private final BigInteger brokerId;
private final BigInteger routeId;
private final long timestamp;
private final String serviceName;
private final Map<Key, String> tags;
public RouteJoin(BigInteger brokerId, BigInteger routeId, long timestamp,
String serviceName, Map<Key, String> tags) {
this.brokerId = brokerId;
this.routeId = routeId;
this.timestamp = timestamp;
this.serviceName = serviceName;
this.tags = tags;
}
public BigInteger getBrokerId() {
return this.brokerId;
}
public BigInteger getRouteId() {
return this.routeId;
}
public long getTimestamp() {
return this.timestamp;
}
public String getServiceName() {
return this.serviceName;
}
public Map<Key, String> getTags() {
return this.tags;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RouteJoin routeJoin = (RouteJoin) o;
return this.timestamp == routeJoin.timestamp
&& Objects.equals(this.brokerId, routeJoin.brokerId)
&& Objects.equals(this.routeId, routeJoin.routeId)
&& Objects.equals(this.serviceName, routeJoin.serviceName)
&& Objects.equals(this.tags, routeJoin.tags);
}
@Override
public int hashCode() {
return Objects.hash(this.brokerId, this.routeId, this.timestamp, this.serviceName,
this.tags);
}
@Override
public String toString() {
return new ToStringCreator(this).append("brokerId", brokerId)
.append("routeId", routeId).append("timestamp", timestamp)
.append("serviceName", serviceName).append("tags", tags).toString();
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private BigInteger brokerId;
private BigInteger routeId;
private long timestamp = System.currentTimeMillis();
private String serviceName;
private TagsMetadata.Builder tagsBuilder = TagsMetadata.builder();
public Builder brokerId(BigInteger brokerId) {
this.brokerId = brokerId;
return this;
}
public Builder brokerId(long brokerId) {
return brokerId(BigInteger.valueOf(brokerId));
}
public Builder routeId(BigInteger routeId) {
this.routeId = routeId;
return this;
}
public Builder routeId(long routeId) {
return routeId(BigInteger.valueOf(routeId));
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder serviceName(String serviceName) {
this.serviceName = serviceName;
return this;
}
public Builder with(String key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(WellKnownKey key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(Key key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(TagsMetadata tagsMetadata) {
tagsBuilder.with(tagsMetadata);
return this;
}
public RouteJoin build() {
Assert.notNull(brokerId, "brokerId may not be null");
Assert.notNull(routeId, "brokerId may not be null");
Assert.notNull(serviceName, "brokerId may not be null");
Assert.isTrue(timestamp > 0, "timestamp must be > 0");
return new RouteJoin(brokerId, routeId, timestamp, serviceName,
tagsBuilder.build().getTags());
}
}
}

View File

@@ -1,121 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import java.math.BigInteger;
import java.util.Objects;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
public final class RouteRemove {
private final BigInteger brokerId;
private final BigInteger routeId;
private final long timestamp;
public RouteRemove(BigInteger brokerId, BigInteger routeId, long timestamp) {
this.brokerId = brokerId;
this.routeId = routeId;
this.timestamp = timestamp;
}
public BigInteger getBrokerId() {
return this.brokerId;
}
public BigInteger getRouteId() {
return this.routeId;
}
public long getTimestamp() {
return this.timestamp;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RouteRemove routeJoin = (RouteRemove) o;
return this.timestamp == routeJoin.timestamp
&& Objects.equals(this.brokerId, routeJoin.brokerId)
&& Objects.equals(this.routeId, routeJoin.routeId);
}
@Override
public int hashCode() {
return Objects.hash(this.brokerId, this.routeId, this.timestamp);
}
@Override
public String toString() {
return new ToStringCreator(this).append("brokerId", brokerId)
.append("routeId", routeId).append("timestamp", timestamp).toString();
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private BigInteger brokerId;
private BigInteger routeId;
private long timestamp = System.currentTimeMillis();
public Builder brokerId(BigInteger brokerId) {
this.brokerId = brokerId;
return this;
}
public Builder brokerId(long brokerId) {
return brokerId(BigInteger.valueOf(brokerId));
}
public Builder routeId(BigInteger routeId) {
this.routeId = routeId;
return this;
}
public Builder routeId(long routeId) {
return routeId(BigInteger.valueOf(routeId));
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public RouteRemove build() {
Assert.notNull(brokerId, "brokerId may not be null");
Assert.notNull(routeId, "brokerId may not be null");
Assert.isTrue(timestamp > 0, "timestamp must be > 0");
return new RouteRemove(brokerId, routeId, timestamp);
}
}
}

View File

@@ -1,115 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.autoconfigure;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker;
import org.springframework.core.style.ToStringCreator;
@ConfigurationProperties("spring.cloud.gateway.rsocket")
public class BrokerProperties {
/**
* Enable Gateway RSocket.
*/
private boolean enabled = true;
private String id = "gateway"; // TODO: + UUID?
private BigInteger routeId;
private String serviceName = "gateway";
private List<Broker> brokers = new ArrayList<>();
/**
* Tag names and values to be supplied to Micrometer Interceptor.
*/
private List<String> micrometerTags = new ArrayList<>();
public BrokerProperties() {
micrometerTags.add("component");
micrometerTags.add("gateway");
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getMicrometerTags() {
return micrometerTags;
}
public void setMicrometerTags(List<String> micrometerTags) {
this.micrometerTags = micrometerTags;
}
public BigInteger getRouteId() {
return this.routeId;
}
public void setRouteId(BigInteger routeId) {
this.routeId = routeId;
}
public String getServiceName() {
return this.serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public List<Broker> getBrokers() {
return this.brokers;
}
public void setBrokers(List<Broker> brokers) {
this.brokers = brokers;
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("enabled", enabled)
.append("id", id)
.append("micrometerTags", micrometerTags)
.append("routeId", routeId)
.append("serviceName", serviceName)
.append("brokers", brokers)
.toString();
// @formatter:on
}
}

View File

@@ -1,196 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.autoconfigure;
import java.math.BigInteger;
import java.util.List;
import java.util.function.Supplier;
import io.micrometer.core.instrument.MeterRegistry;
import io.rsocket.RSocket;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
import org.springframework.boot.rsocket.server.RSocketServerFactory;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerActuatorHandlerRegistration;
import org.springframework.cloud.gateway.rsocket.cluster.ClusterJoinListener;
import org.springframework.cloud.gateway.rsocket.cluster.ClusterService;
import org.springframework.cloud.gateway.rsocket.cluster.RouteJoinListener;
import org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration;
import org.springframework.cloud.gateway.rsocket.core.GatewayRSocketFactory;
import org.springframework.cloud.gateway.rsocket.core.GatewayServerRSocketFactoryProcessor;
import org.springframework.cloud.gateway.rsocket.core.PendingRequestRSocketFactory;
import org.springframework.cloud.gateway.rsocket.route.Routes;
import org.springframework.cloud.gateway.rsocket.routing.LoadBalancerFactory;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTableRoutes;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTableSocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicate;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicateFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import static org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration.ID_GENERATOR_BEAN_NAME;
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled",
matchIfMissing = true)
@EnableConfigurationProperties
@ConditionalOnClass(RSocket.class)
@AutoConfigureBefore(RSocketServerAutoConfiguration.class)
@AutoConfigureAfter(GatewayRSocketCommonAutoConfiguration.class)
public class GatewayRSocketAutoConfiguration {
@Bean
public RoutingTable routingTable() {
return new RoutingTable();
}
// TODO: CompositeRoutes
@Bean
public RoutingTableRoutes registryRoutes(RoutingTable routingTable) {
return new RoutingTableRoutes(routingTable);
}
@Bean
public RoutingTableSocketAcceptorFilter registrySocketAcceptorFilter(
RoutingTable routingTable) {
return new RoutingTableSocketAcceptorFilter(routingTable);
}
@Bean
public PendingRequestRSocketFactory pendingRequestRSocketFactory(
RoutingTable routingTable, Routes routes,
RSocketStrategies rSocketStrategies) {
return new PendingRequestRSocketFactory(routingTable, routes,
rSocketStrategies.metadataExtractor());
}
@Bean
public LoadBalancerFactory loadBalancerFactory(RoutingTable routingTable) {
return new LoadBalancerFactory(routingTable);
}
@Bean
public GatewayRSocketFactory gatewayRSocketFactory(RoutingTable routingTable,
Routes routes, PendingRequestRSocketFactory pendingFactory,
LoadBalancerFactory loadBalancerFactory, MeterRegistry meterRegistry,
BrokerProperties properties, RSocketStrategies rSocketStrategies) {
return new GatewayRSocketFactory(routingTable, routes, pendingFactory,
loadBalancerFactory, meterRegistry, properties,
rSocketStrategies.metadataExtractor());
}
@Bean
public BrokerProperties brokerProperties(Environment env,
@Qualifier(ID_GENERATOR_BEAN_NAME) Supplier<BigInteger> idGenerator) {
BrokerProperties properties = new BrokerProperties();
// set default from env
if (env.containsProperty("spring.application.name")) {
properties.setId(env.getProperty("spring.application.name"));
}
properties.setRouteId(idGenerator.get());
return properties;
}
@Bean
public SocketAcceptorPredicateFilter socketAcceptorPredicateFilter(
List<SocketAcceptorPredicate> predicates) {
return new SocketAcceptorPredicateFilter(predicates);
}
@Bean
public GatewaySocketAcceptor socketAcceptor(GatewayRSocketFactory rsocketFactory,
List<SocketAcceptorFilter> filters, MeterRegistry meterRegistry,
BrokerProperties properties, RSocketStrategies rSocketStrategies) {
return new GatewaySocketAcceptor(rsocketFactory, filters, meterRegistry,
properties, rSocketStrategies.metadataExtractor());
}
@Bean
public GatewayServerRSocketFactoryProcessor gatewayServerRSocketFactoryProcessor(
BrokerProperties properties, MeterRegistry meterRegistry) {
return new GatewayServerRSocketFactoryProcessor(properties, meterRegistry);
}
@Bean
public RSocketServerBootstrap gatewayRSocketServerBootstrap(
RSocketServerFactory rSocketServerFactory,
GatewaySocketAcceptor gatewaySocketAcceptor) {
return new RSocketServerBootstrap(rSocketServerFactory, gatewaySocketAcceptor);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.gateway.rsocket.broker.actuator.enabled",
matchIfMissing = true)
public BrokerActuatorHandlerRegistration brokerActuatorHandlerRegistration(
RoutingTable routingTable, RSocketMessageHandler messageHandler,
BrokerProperties properties) {
return new BrokerActuatorHandlerRegistration(routingTable, messageHandler,
properties);
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.gateway.rsocket.broker.actuator.enabled",
matchIfMissing = true)
public BrokerActuator brokerActuator(BrokerProperties properties,
ClusterService clusterService, RoutingTable routingTable) {
return new BrokerActuator(properties, clusterService, routingTable);
}
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.cluster.enabled",
matchIfMissing = true)
protected static class ClusterConfiguration {
@Bean
public ClusterService clusterService() {
return new ClusterService();
}
@Bean
public ClusterJoinListener clusterJoinListener(ClusterService clusterService,
BrokerProperties properties, RSocketStrategies strategies,
GatewayRSocketFactory gatewayRSocketFactory) {
return new ClusterJoinListener(clusterService, properties, strategies,
gatewayRSocketFactory);
}
@Bean
public RouteJoinListener routeJoinListener(ClusterService clusterService,
RoutingTable routingTable, BrokerProperties properties) {
return new RouteJoinListener(clusterService, routingTable, properties);
}
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.autoconfigure;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
public class GatewayRSocketEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env,
SpringApplication application) {
Boolean enabled = env.getProperty("spring.cloud.gateway.rsocket.enabled",
Boolean.class, true);
if (enabled && !env.containsProperty("spring.rsocket.server.port")) {
Map<String, Object> map = Collections
.singletonMap("spring.rsocket.server.port", 7002);
env.getPropertySources()
.addLast(new MapPropertySource("Default Gateway RSocket Port", map));
}
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.cluster;
import java.math.BigInteger;
import io.rsocket.SocketAcceptor;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerInfo;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.core.GatewayRSocketFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
public class ClusterJoinListener implements ApplicationListener<ApplicationReadyEvent> {
private final ClusterService clusterService;
private final BrokerProperties properties;
private final RSocketStrategies strategies;
private final GatewayRSocketFactory gatewayRSocketFactory;
public ClusterJoinListener(ClusterService clusterService, BrokerProperties properties,
RSocketStrategies strategies, GatewayRSocketFactory gatewayRSocketFactory) {
this.clusterService = clusterService;
this.properties = properties;
this.strategies = strategies;
this.gatewayRSocketFactory = gatewayRSocketFactory;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
for (Broker broker : properties.getBrokers()) {
RouteSetup routeSetup = RouteSetup
.of(properties.getRouteId(), properties.getServiceName()).build();
// TODO: micrometer
RSocketRequester.builder().rsocketStrategies(strategies)
.setupMetadata(routeSetup, RouteSetup.ROUTE_SETUP_MIME_TYPE)
.rsocketFactory(rsocketFactory -> rsocketFactory
.acceptor(brokerSocketAcceptor()))
// TODO: other types
.connectTcp(broker.getHost(), broker.getPort())
.flatMap(this::callBrokerInfo).subscribe(this::registerOutgoing);
}
}
/**
* For incoming requests to this broker node, the RSocketRequester needs an acceptor
* that is able to hand out GatewayRSocket instances. So here is a very simple one
* that just constructs tags metadata and creates a GatewayRSocket.
* @return A SocketAcceptor that creates a GatewayRSocket.
*/
SocketAcceptor brokerSocketAcceptor() {
return (setup, sendingSocket) -> {
TagsMetadata.Builder builder = TagsMetadata.builder();
// TODO: other tags.
builder.serviceName(properties.getServiceName())
.routeId(properties.getRouteId().toString());
return Mono.just(gatewayRSocketFactory.create(builder.build()));
};
}
Mono<Tuple2<BigInteger, RSocketRequester>> callBrokerInfo(
RSocketRequester requester) {
Forwarding forwarding = Forwarding.of(properties.getRouteId())
.serviceName("gateway").disableProxy().build();
return requester.route(BrokerActuator.BROKER_INFO_PATH)
.metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE)
.data(BrokerInfo.of(properties.getRouteId()).build())
.retrieveMono(BigInteger.class)
.map(brokerId -> Tuples.of(brokerId, requester));
}
boolean registerOutgoing(Tuple2<BigInteger, RSocketRequester> tuple) {
return clusterService.registerOutgoing(tuple.getT1().toString(), tuple.getT2());
}
}

View File

@@ -1,116 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.cluster;
import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerInfo;
import org.springframework.cloud.gateway.rsocket.actuate.RouteJoin;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.core.style.ToStringCreator;
import org.springframework.messaging.rsocket.RSocketRequester;
public class ClusterService {
final Map<String, BrokerEntry> incomingBrokers = new ConcurrentHashMap<>();
final Map<String, RSocketRequester> outgoingBrokers = new ConcurrentHashMap<>();
public boolean registerIncoming(BrokerInfo brokerInfo) {
String brokerId = brokerInfo.getBrokerId().toString();
// TODO: validate that there is a corresponding route in RoutingTable
if (incomingBrokers.containsKey(brokerId)) {
BrokerEntry brokerEntry = incomingBrokers.get(brokerId);
if (brokerEntry.timestamp < brokerInfo.getTimestamp()) {
incomingBrokers.put(brokerId, new BrokerEntry(brokerInfo.getBrokerId(),
brokerInfo.getTags(), brokerInfo.getTimestamp()));
return true;
}
}
else {
incomingBrokers.put(brokerId, new BrokerEntry(brokerInfo.getBrokerId(),
brokerInfo.getTags(), brokerInfo.getTimestamp()));
return true;
}
return false;
}
public boolean registerOutgoing(String routeId, RSocketRequester requester) {
// TODO: BrokerClient instead of RSocketRequester?
// TODO: validation
outgoingBrokers.put(routeId, requester);
return true;
}
public boolean send(RouteJoin routeJoin) {
outgoingBrokers.values().forEach(requester -> {
Forwarding forwarding = Forwarding.of(routeJoin.getRouteId())
.serviceName("gateway").disableProxy().build();
requester.route(BrokerActuator.ROUTE_JOIN_PATH)
.metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE).data(routeJoin)
.retrieveMono(RouteJoin.class)
.subscribe(res -> System.out.println("RouteJoin: " + res));
});
return true;
}
static class BrokerEntry {
private final BigInteger brokerId;
private final Map<TagsMetadata.Key, String> tags;
private final Long timestamp;
BrokerEntry(BigInteger brokerId, Map<TagsMetadata.Key, String> tags,
Long timestamp) {
this.brokerId = brokerId;
this.tags = tags;
this.timestamp = timestamp;
}
public BigInteger getBrokerId() {
return this.brokerId;
}
public Map<TagsMetadata.Key, String> getTags() {
return this.tags;
}
public Long getTimestamp() {
return this.timestamp;
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("brokerId", brokerId)
.append("tags", tags)
.append("timestamp", timestamp)
.toString();
// @formatter:on
}
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.cluster;
import java.math.BigInteger;
import java.util.function.Consumer;
import org.springframework.cloud.gateway.rsocket.actuate.RouteJoin;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RegisteredEvent;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME;
public class RouteJoinListener implements Consumer<RegisteredEvent> {
private final ClusterService clusterService;
private final RoutingTable routingTable;
private final BrokerProperties properties;
public RouteJoinListener(ClusterService clusterService, RoutingTable routingTable,
BrokerProperties properties) {
this.clusterService = clusterService;
this.routingTable = routingTable;
this.properties = properties;
routingTable.addListener(this);
}
@Override
public void accept(RegisteredEvent registeredEvent) {
BigInteger brokerId = properties.getRouteId();
TagsMetadata tagsMetadata = registeredEvent.getRoutingMetadata();
String serviceName = tagsMetadata.get(SERVICE_NAME);
// Do not send RouteJoin requests for self
if (!brokerId.toString().equals(tagsMetadata.getRouteId()) &&
// or for other gateways yet
!"gateway".equals(serviceName)) {
BigInteger routeId = new BigInteger(tagsMetadata.getRouteId());
RouteJoin routeJoin = RouteJoin.builder().brokerId(brokerId).routeId(routeId)
.serviceName(serviceName).with(tagsMetadata).build();
clusterService.send(routeJoin);
}
}
}

View File

@@ -1,106 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.core;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.rsocket.AbstractRSocket;
import io.rsocket.Payload;
import io.rsocket.ResponderRSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Convience class to hold and calculate exchange and metrics related information.
*/
public abstract class AbstractGatewayRSocket extends AbstractRSocket
implements ResponderRSocket {
private static final Log log = LogFactory.getLog(AbstractGatewayRSocket.class);
protected final MeterRegistry meterRegistry;
private final BrokerProperties properties;
private final MetadataExtractor metadataExtractor;
private final TagsMetadata metadata;
AbstractGatewayRSocket(MeterRegistry meterRegistry, BrokerProperties properties,
MetadataExtractor metadataExtractor, TagsMetadata metadata) {
this.meterRegistry = meterRegistry;
this.properties = properties;
this.metadataExtractor = metadataExtractor;
this.metadata = metadata;
}
protected GatewayExchange createExchange(GatewayExchange.Type type, Payload payload) {
GatewayExchange exchange = GatewayExchange.fromPayload(type, payload,
metadataExtractor);
Tags tags = getTags(exchange);
exchange.setTags(tags);
return exchange;
}
protected Tags getTags(GatewayExchange exchange) {
// TODO: add tags to exchange
String requesterName = "FIXME"; // FIXME: this.metadata.get(SERVICE_NAME);
String requesterId = "FIXME"; // FIXME: this.metadata.getRouteId();
String responderName = "FIXME"; // FIXME: exchange.getRoutingMetadata().getName();
Assert.hasText(responderName, "responderName must not be empty");
Assert.hasText(requesterId, "requesterId must not be empty");
Assert.hasText(requesterName, "requesterName must not be empty");
// responder.id happens in a callback, later
return Tags.of("requester.name", requesterName, "responder.name", responderName,
"requester.id", requesterId, "gateway.id", this.properties.getId());
}
protected void count(GatewayExchange exchange, String suffix) {
count(exchange, suffix, Tags.empty());
}
protected void count(GatewayExchange exchange, Tags additionalTags) {
count(exchange, null, additionalTags);
}
protected void count(GatewayExchange exchange, String suffix, Tags additionalTags) {
Tags tags = exchange.getTags().and(additionalTags);
String name = getMetricName(exchange, suffix);
this.meterRegistry.counter(name, tags).increment();
}
protected String getMetricName(GatewayExchange exchange) {
return getMetricName(exchange, null);
}
protected String getMetricName(GatewayExchange exchange, String suffix) {
StringBuilder name = new StringBuilder("forward.");
name.append(exchange.getType().getKey());
if (StringUtils.hasLength(suffix)) {
name.append(".");
name.append(suffix);
}
return name.toString();
}
}

View File

@@ -1,139 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.util.Map;
import io.micrometer.core.instrument.Tags;
import io.rsocket.Payload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.Metadata;
import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange;
import org.springframework.messaging.rsocket.MetadataExtractor;
/**
* Exchange object used in GatewayFilterChain started by GatewayRSocket.
*/
public class GatewayExchange extends AbstractRSocketExchange {
private static final Log log = LogFactory.getLog(GatewayExchange.class);
/**
* Key for the route object in attributes.
*/
public static final String ROUTE_ATTR = "__route_attr_";
public enum Type {
/**
* RSocket fire and forget request type.
*/
FIRE_AND_FORGET("request.fnf"),
/**
* RSocket request channel request type.
*/
REQUEST_CHANNEL("request.channel"),
/**
* RSocket request response request type.
*/
REQUEST_RESPONSE("request.response"),
/**
* RSocket request stream request type.
*/
REQUEST_STREAM("request.stream");
private String key;
Type(String key) {
this.key = key;
}
String getKey() {
return this.key;
}
}
private final Type type;
private final Forwarding routingMetadata;
private Tags tags = Tags.empty();
public static GatewayExchange fromPayload(Type type, Payload payload,
MetadataExtractor metadataExtractor) {
if (payload == null || !payload.hasMetadata()) {
return null;
}
// TODO: deal with payload mimetype
Map<String, Object> metadataMap = metadataExtractor.extract(payload,
Metadata.COMPOSITE_MIME_TYPE);
GatewayExchange exchange = new GatewayExchange(type,
getForwardingMetadata(metadataMap));
// TODO: custm metadata extractors
// Adds routing metadata to exchange
if (metadataMap.containsKey("route")) {
exchange.getAttributes().put("route-metadata", metadataMap.get("route"));
}
return exchange;
}
private static Forwarding getForwardingMetadata(Map<String, Object> metadataMap) {
if (metadataMap.containsKey("forwarding")) {
Forwarding metadata = (Forwarding) metadataMap.get("forwarding");
if (log.isDebugEnabled()) {
log.debug("found routing metadata " + metadata);
}
return metadata;
}
return null;
}
public GatewayExchange(Type type, Forwarding routingMetadata) {
this.type = type;
this.routingMetadata = routingMetadata;
}
public Type getType() {
return type;
}
public Forwarding getRoutingMetadata() {
return routingMetadata;
}
public Tags getTags() {
return this.tags;
}
public void setTags(Tags tags) {
this.tags = tags;
}
}

View File

@@ -1,24 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter;
public interface GatewayFilter
extends RSocketFilter<GatewayExchange, GatewayFilterChain> {
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success;
public class GatewayFilterChain
extends AbstractFilterChain<GatewayFilter, GatewayExchange, GatewayFilterChain> {
/**
* Public constructor with the list of filters and the target handler to use.
* @param filters the filters ahead of the handler
*/
private GatewayFilterChain(List<GatewayFilter> filters) {
super(filters);
}
protected GatewayFilterChain(List<GatewayFilter> allFilters,
GatewayFilter currentFilter, GatewayFilterChain next) {
super(allFilters, currentFilter, next);
}
@Override
protected GatewayFilterChain create(List<GatewayFilter> allFilters,
GatewayFilter currentFilter, GatewayFilterChain next) {
return new GatewayFilterChain(allFilters, currentFilter, next);
}
public static Mono<Success> executeFilterChain(List<GatewayFilter> filters,
GatewayExchange exchange) {
return new GatewayFilterChain(filters).filter(exchange);
}
}

View File

@@ -1,23 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate;
public interface GatewayPredicate extends AsyncPredicate<GatewayExchange> {
}

View File

@@ -1,225 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.ResponderRSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import org.springframework.cloud.gateway.rsocket.route.Route;
import org.springframework.cloud.gateway.rsocket.route.Routes;
import org.springframework.cloud.gateway.rsocket.routing.LoadBalancerFactory;
import org.springframework.messaging.rsocket.MetadataExtractor;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.ROUTE_ATTR;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.FIRE_AND_FORGET;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.REQUEST_CHANNEL;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.REQUEST_RESPONSE;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.REQUEST_STREAM;
import static org.springframework.cloud.gateway.rsocket.core.GatewayFilterChain.executeFilterChain;
/**
* Acts as a proxy to other registered sockets. Creates a GatewayExchange and attempts to
* locate a Route. If a Route is found, it is added to the exchange and the filter chains
* is executed againts the Route's filters. If the filter chain is successful, an attempt
* to locate a target RSocket via the Registry is executed. If not found a pending RSocket
* is returned.
*/
public class GatewayRSocket extends AbstractGatewayRSocket {
private static final Log log = LogFactory.getLog(GatewayRSocket.class);
private final Routes routes;
private final PendingRequestRSocketFactory pendingFactory;
private final LoadBalancerFactory loadBalancerFactory;
GatewayRSocket(Routes routes, PendingRequestRSocketFactory pendingFactory,
LoadBalancerFactory loadBalancerFactory, MeterRegistry meterRegistry,
BrokerProperties properties, MetadataExtractor metadataExtractor,
TagsMetadata metadata) {
super(meterRegistry, properties, metadataExtractor, metadata);
this.routes = routes;
this.pendingFactory = pendingFactory;
this.loadBalancerFactory = loadBalancerFactory;
}
protected PendingRequestRSocketFactory getPendingFactory() {
return this.pendingFactory;
}
@Override
public Mono<Void> fireAndForget(Payload payload) {
GatewayExchange exchange = createExchange(FIRE_AND_FORGET, payload);
return findRSocketOrCreatePending(exchange).flatMap(rSockets -> {
retain(payload, rSockets);
List<Mono<Void>> results = rSockets.stream()
.map(rSocket -> rSocket.fireAndForget(payload))
.collect(Collectors.toList());
// TODO: this looks weird
return Flux.merge(results).then();
}).doOnError(t -> count(exchange, "error")).doFinally(s -> count(exchange, ""));
}
@Override
public Flux<Payload> requestChannel(Payload payload, Publisher<Payload> payloads) {
GatewayExchange exchange = createExchange(REQUEST_CHANNEL, payload);
Tags responderTags = Tags.of("source", "responder");
return findRSocketOrCreatePending(exchange).flatMapMany(rSockets -> {
Tags requesterTags = Tags.of("source", "requester");
Flux<Payload> flux = Flux.from(payloads).doOnNext(p -> {
retain(p, rSockets);
count(exchange, "payload", requesterTags);
}).doOnError(t -> count(exchange, "error", requesterTags))
.doFinally(s -> count(exchange, requesterTags)).publish()
.refCount(rSockets.size());
List<Flux<Payload>> payloadList = rSockets.stream().map(rSocket -> {
if (rSocket instanceof ResponderRSocket) {
ResponderRSocket socket = (ResponderRSocket) rSocket;
return socket.requestChannel(payload, flux);
}
else {
return rSocket.requestChannel(flux);
}
}).collect(Collectors.toList());
return Flux.merge(payloadList).log(
GatewayRSocket.class.getName() + ".request-channel", Level.FINEST);
}).doOnNext(s -> count(exchange, "payload", responderTags))
.doOnError(t -> count(exchange, "error", responderTags))
.doFinally(s -> count(exchange, responderTags));
}
@Override
public Mono<Payload> requestResponse(Payload payload) {
AtomicReference<Timer.Sample> timer = new AtomicReference<>();
GatewayExchange exchange = createExchange(REQUEST_RESPONSE, payload);
return findRSocketOrCreatePending(exchange).flatMap(rSockets -> {
retain(payload, rSockets);
List<Mono<Payload>> results = rSockets.stream()
.map(rSocket -> rSocket.requestResponse(payload))
.collect(Collectors.toList());
return Flux.merge(results).next();
// TODO: does this cancel the others?
}).doOnSubscribe(s -> timer.set(Timer.start(meterRegistry)))
.doOnError(t -> count(exchange, "error"))
.doFinally(s -> timer.get().stop(meterRegistry
.timer(getMetricName(exchange), exchange.getTags())));
}
@Override
public Flux<Payload> requestStream(Payload payload) {
GatewayExchange exchange = createExchange(REQUEST_STREAM, payload);
return findRSocketOrCreatePending(exchange).flatMapMany(rSockets -> {
retain(payload, rSockets);
List<Flux<Payload>> results = rSockets.stream()
.map(rSocket -> rSocket.requestStream(payload))
.collect(Collectors.toList());
return Flux.merge(results);
})
// S N E F
.doOnNext(s -> count(exchange, "payload"))
.doOnError(t -> count(exchange, "error"))
.doFinally(s -> count(exchange, Tags.empty()));
}
private void retain(Payload payload, List<RSocket> rSockets) {
if (rSockets.size() > 1) {
payload.retain(rSockets.size() - 1);
}
}
/**
* First locate Route. If found, put route in exchange and execute filter chain. If
* successful, locate target RSocket. If not found, create a pending RSocket.
* @param exchange GatewayExchange.
* @return Target RSocket or empty.
*/
private Mono<List<RSocket>> findRSocketOrCreatePending(GatewayExchange exchange) {
return this.routes.findRoute(exchange)
.log(GatewayRSocket.class.getName() + ".find route", Level.FINEST)
.flatMap(route -> {
// put route in exchange for later use
exchange.getAttributes().put(ROUTE_ATTR, route);
return findRSocketOrCreatePending(exchange, route);
}).switchIfEmpty(createPending(exchange));
// TODO: deal with connecting to cluster?
}
private Mono<List<RSocket>> findRSocketOrCreatePending(GatewayExchange exchange,
Route route) {
return executeFilterChain(route.getFilters(), exchange)
.log(GatewayRSocket.class.getName() + ".after filter chain", Level.FINEST)
.flatMapMany(success -> {
Map<Key, String> tags = exchange.getRoutingMetadata().getTags();
// TODO: use frame flag
if (tags.containsKey(new Key("multicast"))) {
List<Tuple2<String, RSocket>> rsockets = loadBalancerFactory
.find(exchange.getRoutingMetadata());
return Flux.fromIterable(rsockets);
}
return loadBalancerFactory.choose(exchange.getRoutingMetadata())
.flatMapMany(
tuple -> Flux.just((Tuple2<String, RSocket>) tuple));
}).map(tuple -> {
// TODO: this is routeId, should it be service name?
// Tags tags = exchange.getTags().and("responder.id", tuple.getT1());
// exchange.setTags(tags);
return tuple.getT2();
}).cast(RSocket.class).map(rSocket -> {
if (log.isDebugEnabled()) {
log.debug("Found RSocket: " + rSocket);
}
return rSocket;
}).collectList()
.log(GatewayRSocket.class.getName() + ".find rsocket", Level.FINEST);
}
protected Mono<List<RSocket>> createPending(GatewayExchange exchange) {
if (log.isDebugEnabled()) {
log.debug("Unable to find destination RSocket for "
+ exchange.getRoutingMetadata());
}
// if a route can't be found or registered RSocket, create pending
return pendingFactory.create(exchange).cast(RSocket.class)
.map(Collections::singletonList);
}
}

View File

@@ -1,100 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.core;
import io.micrometer.core.instrument.MeterRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.route.Routes;
import org.springframework.cloud.gateway.rsocket.routing.LoadBalancerFactory;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.util.Assert;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME;
public class GatewayRSocketFactory {
private static final Log log = LogFactory.getLog(GatewayRSocket.class);
private final RoutingTable routingTable;
private final Routes routes;
private final PendingRequestRSocketFactory pendingFactory;
private final LoadBalancerFactory loadBalancerFactory;
private final MeterRegistry meterRegistry;
private final BrokerProperties properties;
private final MetadataExtractor metadataExtractor;
public GatewayRSocketFactory(RoutingTable routingTable, Routes routes,
PendingRequestRSocketFactory pendingFactory,
LoadBalancerFactory loadBalancerFactory, MeterRegistry meterRegistry,
BrokerProperties properties, MetadataExtractor metadataExtractor) {
this.routingTable = routingTable;
this.routes = routes;
this.pendingFactory = pendingFactory;
this.loadBalancerFactory = loadBalancerFactory;
this.meterRegistry = meterRegistry;
this.properties = properties;
this.metadataExtractor = metadataExtractor;
}
@SuppressWarnings("Duplicates")
public GatewayRSocket create(TagsMetadata metadata) {
Assert.hasText(metadata.get(ROUTE_ID), "metadata must contain " + ROUTE_ID);
Assert.hasText(metadata.get(SERVICE_NAME),
"metadata must contain " + SERVICE_NAME);
GatewayRSocket gatewayRSocket = new GatewayRSocket(this.routes,
this.pendingFactory, this.loadBalancerFactory, this.meterRegistry,
this.properties, this.metadataExtractor, metadata);
gatewayRSocket.onClose().doOnSuccess(v -> {
if (log.isDebugEnabled()) {
log.debug("Closed, deregistering " + metadata);
}
routingTable.deregister(metadata);
}).doOnError(t -> {
if (log.isErrorEnabled()) {
log.error("Error received, deregistering " + metadata, t);
}
routingTable.deregister(metadata);
}).doOnNext(v -> {
if (log.isTraceEnabled()) {
log.trace("OnClose doOnNext");
}
}).doOnTerminate(() -> {
if (log.isTraceEnabled()) {
log.trace("OnClose doOnTerminate");
}
}).doFinally(st -> {
if (log.isTraceEnabled()) {
log.trace("OnClose doFinally");
}
}).subscribe();
return gatewayRSocket;
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.rsocket.RSocketFactory.ServerRSocketFactory;
import io.rsocket.micrometer.MicrometerDuplexConnectionInterceptor;
import io.rsocket.plugins.RSocketInterceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.rsocket.server.ServerRSocketFactoryProcessor;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.util.Assert;
public class GatewayServerRSocketFactoryProcessor
implements ServerRSocketFactoryProcessor {
private static final Log log = LogFactory
.getLog(GatewayServerRSocketFactoryProcessor.class);
private static final RSocketInterceptor[] EMPTY_INTERCEPTORS = new RSocketInterceptor[0];
private final BrokerProperties properties;
private final List<RSocketInterceptor> serverInterceptors;
private final MeterRegistry meterRegistry;
public GatewayServerRSocketFactoryProcessor(BrokerProperties properties,
MeterRegistry meterRegistry) {
this(properties, meterRegistry, EMPTY_INTERCEPTORS);
}
public GatewayServerRSocketFactoryProcessor(BrokerProperties properties,
MeterRegistry meterRegistry, RSocketInterceptor... interceptors) {
Assert.notNull(properties, "properties may not be null");
Assert.notNull(meterRegistry, "meterRegistry may not be null");
Assert.notNull(interceptors, "interceptors may not be null");
this.properties = properties;
this.meterRegistry = meterRegistry;
this.serverInterceptors = Arrays.asList(interceptors);
}
@Override
public ServerRSocketFactory process(ServerRSocketFactory factory) {
serverInterceptors.forEach(factory::addResponderPlugin);
List<String> micrometerTags = properties.getMicrometerTags();
Tag[] tags = Tags.of(micrometerTags.toArray(new String[] {}))
.and("gateway.id", properties.getId()).stream()
.collect(Collectors.toList()).toArray(new Tag[] {});
return factory
// TODO: add as bean like serverInterceptors above
.addConnectionPlugin(
new MicrometerDuplexConnectionInterceptor(meterRegistry, tags))
.errorConsumer(throwable -> {
if (log.isDebugEnabled()) {
log.debug("Error with connection", throwable);
}
}); // TODO: add configurable errorConsumer
}
}

View File

@@ -1,158 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import io.rsocket.AbstractRSocket;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.ResponderRSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.util.function.Tuple2;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success;
import org.springframework.cloud.gateway.rsocket.route.Route;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RegisteredEvent;
import org.springframework.messaging.rsocket.MetadataExtractor;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.ROUTE_ATTR;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.REQUEST_STREAM;
import static org.springframework.cloud.gateway.rsocket.core.GatewayFilterChain.executeFilterChain;
public class PendingRequestRSocket extends AbstractRSocket
implements ResponderRSocket, Consumer<RegisteredEvent> {
private static final Log log = LogFactory.getLog(PendingRequestRSocket.class);
// TODO: if this were just routeId & route wasn't an exchange attr would be simpler.
private final Function<RegisteredEvent, Mono<Route>> routeFinder;
private final MetadataExtractor metadataExtractor;
private final Consumer<TagsMetadata> metadataCallback;
private final MonoProcessor<RSocket> rSocketProcessor;
private Disposable subscriptionDisposable;
private Route route;
public PendingRequestRSocket(MetadataExtractor metadataExtractor,
Function<RegisteredEvent, Mono<Route>> routeFinder,
Consumer<TagsMetadata> metadataCallback) {
this(metadataExtractor, routeFinder, metadataCallback, MonoProcessor.create());
}
/* for testing */ PendingRequestRSocket(MetadataExtractor metadataExtractor,
Function<RegisteredEvent, Mono<Route>> routeFinder,
Consumer<TagsMetadata> metadataCallback,
MonoProcessor<RSocket> rSocketProcessor) {
this.routeFinder = routeFinder;
this.metadataExtractor = metadataExtractor;
this.metadataCallback = metadataCallback;
this.rSocketProcessor = rSocketProcessor;
}
/**
* Find route (if needed) using pendingExchange. If found, see if the route target
* matches the registered service. If it matches, send registered RSocket to
* processor. Then execute normal filter chain. If filter chain is successful, execute
* request.
* @param registeredEvent the RegisteredEvent
*/
@Override
public void accept(RegisteredEvent registeredEvent) {
this.routeFinder.apply(registeredEvent).subscribe(route -> {
this.route = route;
this.metadataCallback.accept(registeredEvent.getRoutingMetadata());
this.rSocketProcessor.onNext(registeredEvent.getRSocket());
this.rSocketProcessor.onComplete();
if (this.subscriptionDisposable != null) {
this.subscriptionDisposable.dispose();
}
});
}
@Override
public Mono<Void> fireAndForget(Payload payload) {
return processor("pending-request-faf", payload)
.flatMap(tuple -> tuple.getT1().fireAndForget(payload));
}
@Override
public Mono<Payload> requestResponse(Payload payload) {
return processor("pending-request-rr", payload)
.flatMap(tuple -> tuple.getT1().requestResponse(payload));
}
@Override
public Flux<Payload> requestStream(Payload payload) {
return processor("pending-request-rs", payload)
.flatMapMany(tuple -> tuple.getT1().requestStream(payload));
}
@Override
public Flux<Payload> requestChannel(Payload payload, Publisher<Payload> payloads) {
return processor("pending-request-rc", payload).flatMapMany(tuple -> {
RSocket rSocket = tuple.getT1();
if (rSocket instanceof ResponderRSocket) {
ResponderRSocket socket = (ResponderRSocket) rSocket;
return socket.requestChannel(payload, payloads);
}
return rSocket.requestChannel(payloads);
});
}
/**
* After processor receives onNext signal, get route from exchange attrs, create a new
* exchange from payload. Copy exchange attrs. Execute filter chain, if successful,
* execute request.
* @param logCategory log category
* @param payload payload.
* @return
*/
protected Mono<Tuple2<RSocket, Success>> processor(String logCategory,
Payload payload) {
return rSocketProcessor
.log(PendingRequestRSocket.class.getName() + "." + logCategory,
Level.FINEST)
.flatMap(rSocket -> {
GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM,
payload, metadataExtractor);
exchange.getAttributes().put(ROUTE_ATTR, route);
// exchange.getAttributes().putAll(pendingExchange.getAttributes());
return Mono.just(rSocket)
.zipWith(executeFilterChain(route.getFilters(), exchange));
});
}
public void setSubscriptionDisposable(Disposable subscriptionDisposable) {
this.subscriptionDisposable = subscriptionDisposable;
}
}

View File

@@ -1,105 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.core;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import io.micrometer.core.instrument.Tags;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.route.Route;
import org.springframework.cloud.gateway.rsocket.route.Routes;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.messaging.rsocket.MetadataExtractor;
public class PendingRequestRSocketFactory {
private static final Log log = LogFactory.getLog(PendingRequestRSocket.class);
private final RoutingTable routingTable;
private final Routes routes;
private final MetadataExtractor metadataExtractor;
public PendingRequestRSocketFactory(RoutingTable routingTable, Routes routes,
MetadataExtractor metadataExtractor) {
this.routingTable = routingTable;
this.routes = routes;
this.metadataExtractor = metadataExtractor;
}
public Mono<PendingRequestRSocket> create(GatewayExchange exchange) {
if (log.isDebugEnabled()) {
log.debug("creating pending RSocket for " + exchange.getRoutingMetadata());
}
PendingRequestRSocket pending = constructPendingRSocket(exchange);
Disposable disposable = this.routingTable.addListener(pending);
pending.setSubscriptionDisposable(disposable);
return Mono.just(pending);
}
protected PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) {
Function<RoutingTable.RegisteredEvent, Mono<Route>> routeFinder = registeredEvent -> getRouteMono(
registeredEvent, exchange);
Consumer<TagsMetadata> tagsMetadataConsumer = tagsMetadata -> {
Tags tags = exchange.getTags().and("responder.id", tagsMetadata.getRouteId());
exchange.setTags(tags);
};
return new PendingRequestRSocket(metadataExtractor, routeFinder,
tagsMetadataConsumer);
}
/**
* Finds routes using exchange of original request that created pending RSocket.
* @param registeredEvent newly registered event
* @param exchange from original request
* @return route if route matches
*/
protected Mono<Route> getRouteMono(RoutingTable.RegisteredEvent registeredEvent,
GatewayExchange exchange) {
return this.routes.findRoute(exchange)
.log(PendingRequestRSocket.class.getName() + ".find route pending",
Level.FINEST)
// TODO: can this be replaced with filter?
.flatMap(
route -> matchRoute(route, registeredEvent.getRoutingMetadata()));
}
/**
* Matches route found using original exchange with routeIds from recently registered
* routes.
* @param route route found using original exchange.
* @param tagsMetadata tags from recent registration.
* @return
*/
private Mono<Route> matchRoute(Route route, TagsMetadata tagsMetadata) {
Set<String> routeIds = this.routingTable.findRouteIds(tagsMetadata);
if (routeIds.contains(route.getId())) {
return Mono.just(route);
}
return Mono.empty();
}
}

View File

@@ -1,117 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.filter;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success;
import org.springframework.lang.Nullable;
/**
* Default implementation of {@link FilterChain}.
*
* <p>
* Each instance of this class represents one link in the chain. The public constructor
* {@link #AbstractFilterChain(List)} initializes the full chain and represents its first
* link.
*
* <p>
* This class is immutable and thread-safe. It can be created once and re-used to handle
* request concurrently.
*
* Copied from org.springframework.web.server.handler.AbstractFilterChain
*
* @since 5.0
*/
public abstract class AbstractFilterChain<F extends RSocketFilter, E extends RSocketExchange, FC extends AbstractFilterChain>
implements FilterChain<E> {
private final Log log = LogFactory.getLog(getClass());
protected final List<F> allFilters;
@Nullable
protected final F currentFilter;
@Nullable
protected final FC next;
/**
* Public constructor with the list of filters and the target handler to use.
* @param filters the filters ahead of the handler
*/
@SuppressWarnings("unchecked")
protected AbstractFilterChain(List<F> filters) {
this.allFilters = Collections.unmodifiableList(filters);
FC chain = initChain(filters);
this.currentFilter = (F) chain.currentFilter;
this.next = (FC) chain.next;
}
private FC initChain(List<F> filters) {
FC chain = create(filters, null, null);
ListIterator<? extends F> iterator = filters.listIterator(filters.size());
while (iterator.hasPrevious()) {
chain = create(filters, iterator.previous(), chain);
}
return chain;
}
/**
* Private constructor to represent one link in the chain.
*/
protected AbstractFilterChain(List<F> allFilters, @Nullable F currentFilter,
@Nullable FC next) {
this.allFilters = allFilters;
this.currentFilter = currentFilter;
this.next = next;
}
/**
* Private constructor to represent one link in the chain.
*/
protected abstract FC create(List<F> allFilters, @Nullable F currentFilter,
@Nullable FC next);
public List<F> getFilters() {
return this.allFilters;
}
@Override
@SuppressWarnings("unchecked")
public Mono<Success> filter(E exchange) {
return Mono.defer(() -> this.currentFilter != null && this.next != null
? this.currentFilter.filter(exchange, this.next) : getMonoSuccess());
}
private Mono<Success> getMonoSuccess() {
if (log.isDebugEnabled()) {
log.debug("filter chain completed with success");
}
return MONO_SUCCESS;
}
private static final Mono<Success> MONO_SUCCESS = Mono.just(Success.INSTANCE);
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.filter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public abstract class AbstractRSocketExchange implements RSocketExchange {
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
@Override
public Map<String, Object> getAttributes() {
return this.attributes;
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.filter;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success;
/**
* Contract to allow a {@link RSocketFilter} to delegate to the next in the chain.
*
* @author Spencer Gibb
*/
public interface FilterChain<E extends RSocketExchange> {
/**
* Delegate to the next {@code WebFilter} in the chain.
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
Mono<Success> filter(E exchange);
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.filter;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
public interface RSocketExchange {
/**
* Return a mutable map of request attributes for the current exchange.
* @return current attributes.
*/
Map<String, Object> getAttributes();
/**
* Return the request attribute value if present.
* @param name the attribute name
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
@Nullable
default <T> T getAttribute(String name) {
return (T) getAttributes().get(name);
}
/**
* Return the request attribute value or if not present raise an
* {@link IllegalArgumentException}.
* @param name the attribute name
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
default <T> T getRequiredAttribute(String name) {
T value = getAttribute(name);
Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
return value;
}
/**
* Return the request attribute value, or a default, fallback value.
* @param name the attribute name
* @param defaultValue a default value to return instead
* @param <T> the attribute type
* @return the attribute value
*/
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
return (T) getAttributes().getOrDefault(name, defaultValue);
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.filter;
import reactor.core.publisher.Mono;
/**
* Contract for interception-style, chained processing of Web requests that may be used to
* implement cross-cutting, application-agnostic requirements such as security, timeouts,
* and others.
*
* Copied from WebFilter
*
* @author Spencer Gibb
*/
public interface RSocketFilter<E extends RSocketExchange, FC extends FilterChain<E>> {
/**
* Enum to signal successful end of chain reached without the end being empty, i.e.
* Mono&lt;Void&gt; via Mono.empty(). This is because at the end of the chain an
* actual value needs to be returned. We can map success, but not empty.
*/
enum Success {
INSTANCE
} // should never have more than one value
/**
* Process the Web request and (optionally) delegate to the next {@code RSocketFilter}
* through the given {@link FilterChain}.
* @param exchange the current RSocket exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Success>} to indicate when request processing is complete.
*/
Mono<Success> filter(E exchange, FC chain);
}

View File

@@ -1,249 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.metrics;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.ResponderRSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;
import org.springframework.util.Assert;
import static reactor.core.publisher.SignalType.CANCEL;
import static reactor.core.publisher.SignalType.ON_COMPLETE;
import static reactor.core.publisher.SignalType.ON_ERROR;
public class MicrometerResponderRSocket implements ResponderRSocket {
private static final Log log = LogFactory.getLog(MicrometerResponderRSocket.class);
private final RSocket delegate;
private final InteractionCounters metadataPush;
private final InteractionCounters requestChannel;
private final InteractionCounters requestFireAndForget;
private final InteractionTimers requestResponse;
private final InteractionCounters requestStream;
/**
* Creates a new {@link RSocket}.
* @param delegate the {@link RSocket} to delegate to
* @param meterRegistry the {@link MeterRegistry} to use
* @param tags additional tags to attach to {@link Meter}s
* @throws IllegalArgumentException if {@code delegate} or {@code meterRegistry} is
* {@code null}
*/
public MicrometerResponderRSocket(RSocket delegate, MeterRegistry meterRegistry,
Tag... tags) {
Assert.notNull(delegate, "delegate must not be null");
Assert.notNull(meterRegistry, "meterRegistry must not be null");
this.delegate = delegate;
this.metadataPush = new InteractionCounters(meterRegistry, "metadata.push", tags);
this.requestChannel = new InteractionCounters(meterRegistry, "request.channel",
tags);
this.requestFireAndForget = new InteractionCounters(meterRegistry, "request.fnf",
tags);
this.requestResponse = new InteractionTimers(meterRegistry, "request.response",
tags);
this.requestStream = new InteractionCounters(meterRegistry, "request.stream",
tags);
}
@Override
public void dispose() {
delegate.dispose();
}
@Override
public Mono<Void> fireAndForget(Payload payload) {
return delegate.fireAndForget(payload).doFinally(requestFireAndForget);
}
@Override
public Mono<Void> metadataPush(Payload payload) {
return delegate.metadataPush(payload).doFinally(metadataPush);
}
@Override
public Mono<Void> onClose() {
return delegate.onClose();
}
@Override
public Flux<Payload> requestChannel(Publisher<Payload> payloads) {
return delegate.requestChannel(payloads).doFinally(requestChannel);
}
@Override
public Mono<Payload> requestResponse(Payload payload) {
return Mono.defer(() -> {
Timer.Sample sample = requestResponse.start();
return delegate.requestResponse(payload)
.doFinally(signalType -> requestResponse.accept(sample, signalType));
});
}
@Override
public Flux<Payload> requestStream(Payload payload) {
return delegate.requestStream(payload).doFinally(requestStream);
}
@Override
public Flux<Payload> requestChannel(Payload payload, Publisher<Payload> payloads) {
if (delegate instanceof ResponderRSocket) {
ResponderRSocket rSocket = (ResponderRSocket) delegate;
return rSocket.requestChannel(payload, payloads).doFinally(requestChannel);
}
return delegate.requestChannel(payloads).doFinally(requestChannel);
}
private static final class InteractionCounters implements Consumer<SignalType> {
private final Counter cancel;
private final Counter onComplete;
private final Counter onError;
private InteractionCounters(MeterRegistry meterRegistry, String interactionModel,
Tag... tags) {
this.cancel = counter(meterRegistry, interactionModel, CANCEL, tags);
this.onComplete = counter(meterRegistry, interactionModel, ON_COMPLETE, tags);
this.onError = counter(meterRegistry, interactionModel, ON_ERROR, tags);
}
@Override
public void accept(SignalType signalType) {
switch (signalType) {
case CANCEL:
if (this.cancel != null) {
this.cancel.increment();
}
break;
case ON_COMPLETE:
if (this.onComplete != null) {
this.onComplete.increment();
}
break;
case ON_ERROR:
if (this.onError != null) {
this.onError.increment();
}
break;
}
}
private Counter counter(MeterRegistry meterRegistry, String interactionModel,
SignalType signalType, Tag... tags) {
Tags withType = Tags.of(tags).and("signal.type", signalType.name());
try {
return meterRegistry.counter("rsocket." + interactionModel, withType);
}
catch (Exception e) {
if (log.isTraceEnabled()) {
log.trace("Error creating counter with tags: " + withType, e);
}
return null;
}
}
}
private static final class InteractionTimers
implements BiConsumer<Timer.Sample, SignalType> {
private final Timer cancel;
private final MeterRegistry meterRegistry;
private final Timer onComplete;
private final Timer onError;
private InteractionTimers(MeterRegistry meterRegistry, String interactionModel,
Tag... tags) {
this.meterRegistry = meterRegistry;
this.cancel = timer(meterRegistry, interactionModel, CANCEL, tags);
this.onComplete = timer(meterRegistry, interactionModel, ON_COMPLETE, tags);
this.onError = timer(meterRegistry, interactionModel, ON_ERROR, tags);
}
@Override
public void accept(Timer.Sample sample, SignalType signalType) {
switch (signalType) {
case CANCEL:
if (this.cancel != null) {
sample.stop(this.cancel);
}
break;
case ON_COMPLETE:
if (this.onComplete != null) {
sample.stop(this.onComplete);
}
break;
case ON_ERROR:
if (this.onError != null) {
sample.stop(this.onError);
}
break;
}
}
Timer.Sample start() {
return Timer.start(meterRegistry);
}
private static Timer timer(MeterRegistry meterRegistry, String interactionModel,
SignalType signalType, Tag... tags) {
Tags withType = Tags.of(tags).and("signal.type", signalType.name());
try {
return meterRegistry.timer("rsocket." + interactionModel, withType);
}
catch (Exception e) {
if (log.isTraceEnabled()) {
log.trace("Error creating timer with tags: " + withType, e);
}
return null;
}
}
}
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.metrics;
import java.util.Objects;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.rsocket.RSocket;
import io.rsocket.plugins.RSocketInterceptor;
public class MicrometerResponderRSocketInterceptor implements RSocketInterceptor {
private final MeterRegistry meterRegistry;
private final Tag[] tags;
/**
* Creates a new {@link RSocketInterceptor}.
* @param meterRegistry the {@link MeterRegistry} to use to create {@link Meter}s.
* @param tags the additional tags to attach to each {@link Meter}
* @throws NullPointerException if {@code meterRegistry} is {@code null}
*/
public MicrometerResponderRSocketInterceptor(MeterRegistry meterRegistry,
Tag... tags) {
this.meterRegistry = Objects.requireNonNull(meterRegistry,
"meterRegistry must not be null");
this.tags = tags;
}
@Override
public MicrometerResponderRSocket apply(RSocket delegate) {
Objects.requireNonNull(delegate, "delegate must not be null");
return new MicrometerResponderRSocket(delegate, meterRegistry, tags);
}
}

View File

@@ -1,197 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.route;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.core.GatewayExchange;
import org.springframework.cloud.gateway.rsocket.core.GatewayFilter;
import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* @author Spencer Gibb
*/
public class DefaultRoute implements Route {
private final String id;
private final RouteSetup targetMetadata;
private final int order;
private final AsyncPredicate<GatewayExchange> predicate;
private final List<GatewayFilter> gatewayFilters;
public static Builder builder() {
return new Builder();
}
private DefaultRoute(String id, RouteSetup targetMetadata, int order,
AsyncPredicate<GatewayExchange> predicate,
List<GatewayFilter> gatewayFilters) {
this.id = id;
this.targetMetadata = targetMetadata;
this.order = order;
this.predicate = predicate;
this.gatewayFilters = gatewayFilters;
}
public String getId() {
return this.id;
}
public int getOrder() {
return order;
}
public AsyncPredicate<GatewayExchange> getPredicate() {
return this.predicate;
}
public List<GatewayFilter> getFilters() {
return Collections.unmodifiableList(this.gatewayFilters);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Route route = (Route) o;
return Objects.equals(id, route.getId())
&& Objects.equals(order, route.getOrder())
&& Objects.equals(predicate, route.getPredicate())
&& Objects.equals(gatewayFilters, route.getFilters());
}
@Override
public int hashCode() {
return Objects.hash(id, targetMetadata, predicate, gatewayFilters);
}
@Override
public String toString() {
return new ToStringCreator(this).append("id", id)
.append("targetMetadata", targetMetadata).append("order", order)
.append("predicate", predicate).append("gatewayFilters", gatewayFilters)
.toString();
}
public static class Builder {
protected String id;
protected RouteSetup routingMetadata;
protected int order = 0;
protected AsyncPredicate<GatewayExchange> predicate;
protected List<GatewayFilter> gatewayFilters = new ArrayList<>();
protected Builder() {
}
public Builder id(String id) {
this.id = id;
return this;
}
public String getId() {
return id;
}
public Builder order(int order) {
this.order = order;
return this;
}
public AsyncPredicate<GatewayExchange> getPredicate() {
return this.predicate;
}
public Builder routingMetadata(RouteSetup routingMetadata) {
this.routingMetadata = routingMetadata;
return this;
}
public Builder setFilters(List<GatewayFilter> gatewayFilters) {
this.gatewayFilters = gatewayFilters;
return this;
}
public Builder filter(GatewayFilter gatewayFilter) {
this.gatewayFilters.add(gatewayFilter);
return this;
}
public Builder filters(Collection<GatewayFilter> gatewayFilters) {
this.gatewayFilters.addAll(gatewayFilters);
return this;
}
public Builder filters(GatewayFilter... gatewayFilters) {
return filters(Arrays.asList(gatewayFilters));
}
public Builder predicate(AsyncPredicate<GatewayExchange> predicate) {
this.predicate = predicate;
return this;
}
public Builder and(AsyncPredicate<GatewayExchange> predicate) {
Assert.notNull(this.predicate, "can not call and() on null predicate");
this.predicate = this.predicate.and(predicate);
return this;
}
public Builder or(AsyncPredicate<GatewayExchange> predicate) {
Assert.notNull(this.predicate, "can not call or() on null predicate");
this.predicate = this.predicate.or(predicate);
return this;
}
public Builder negate() {
Assert.notNull(this.predicate, "can not call negate() on null predicate");
this.predicate = this.predicate.negate();
return this;
}
public Route build() {
Assert.notNull(this.id, "id can not be null");
Assert.notNull(this.routingMetadata, "targetMetadata can not be null");
Assert.notNull(this.predicate, "predicate can not be null");
return new DefaultRoute(this.id, this.routingMetadata, this.order, predicate,
this.gatewayFilters);
}
}
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.route;
import java.util.List;
import org.springframework.cloud.gateway.rsocket.core.GatewayExchange;
import org.springframework.cloud.gateway.rsocket.core.GatewayFilter;
import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate;
import org.springframework.core.Ordered;
/**
* @author Spencer Gibb
*/
public interface Route extends Ordered {
String getId();
default int getOrder() {
return 0;
}
AsyncPredicate<GatewayExchange> getPredicate();
List<GatewayFilter> getFilters();
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.route;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.core.GatewayExchange;
/**
* @author Spencer Gibb
*/
public interface Routes {
/** log. */
Log log = LogFactory.getLog(Routes.class);
Flux<Route> getRoutes();
default Mono<Route> findRoute(GatewayExchange exchange) {
return getRoutes()
// individually filter routes so that filterWhen error delaying is not a
// problem
.concatMap(route -> Mono.just(route).filterWhen(r -> {
// add the current route we are testing
// TODO: exchange attributes
// exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR,
// r.getId());
return r.getPredicate().apply(exchange);
})
// instead of immediately stopping main flux due to error, log and
// swallow it
.doOnError(e -> log.error(
"Error applying predicate for route: " + route.getId(),
e))
.onErrorResume(e -> Mono.empty()))
.next().map(route -> {
if (log.isDebugEnabled()) {
log.debug("Route matched: " + route.getId());
}
return route;
});
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.routing;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import io.rsocket.RSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
public class LoadBalancerFactory {
private static final Log log = LogFactory.getLog(LoadBalancerFactory.class);
private final RoutingTable routingTable;
public LoadBalancerFactory(RoutingTable routingTable) {
this.routingTable = routingTable;
}
public List<Tuple2<String, RSocket>> find(TagsMetadata tagsMetadata) {
List<Tuple2<String, RSocket>> rSockets = this.routingTable
.findRSockets(tagsMetadata);
return rSockets;
}
// TODO: potentially GatewayExchange or return a new Result Object?
public Mono<Tuple2<String, RSocket>> choose(TagsMetadata tagsMetadata) {
List<Tuple2<String, RSocket>> rSockets = this.routingTable
.findRSockets(tagsMetadata);
// TODO: change loadbalancer impl based on tags
// TODO: cache loadbalancers based on tags
return new RoundRobinLoadBalancer(tagsMetadata).apply(rSockets);
}
// TODO: Flux<RSocket> as input?
// TODO: reuse commons load balancer?
public interface LoadBalancer extends
Function<List<Tuple2<String, RSocket>>, Mono<Tuple2<String, RSocket>>> {
}
public static class RoundRobinLoadBalancer implements LoadBalancer {
private final TagsMetadata tagsMetadata;
private final AtomicInteger position;
public RoundRobinLoadBalancer(TagsMetadata tagsMetadata) {
this(tagsMetadata, new Random().nextInt(1000));
}
public RoundRobinLoadBalancer(TagsMetadata tagsMetadata, int seedPosition) {
this.tagsMetadata = tagsMetadata;
this.position = new AtomicInteger(seedPosition);
}
@Override
public Mono<Tuple2<String, RSocket>> apply(
List<Tuple2<String, RSocket>> rSockets) {
if (rSockets.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for: " + this.tagsMetadata);
}
return Mono.empty();
}
// TODO: enforce order?
int pos = Math.abs(this.position.incrementAndGet());
Tuple2<String, RSocket> tuple = rSockets.get(pos % rSockets.size());
return Mono.just(tuple);
}
}
}

View File

@@ -1,333 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.routing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import io.rsocket.RSocket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.roaringbitmap.IntConsumer;
import org.roaringbitmap.RoaringBitmap;
import reactor.core.Disposable;
import reactor.core.publisher.DirectProcessor;
import reactor.core.publisher.FluxSink;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* The RoutingTable handles all RSocket connections that have been made that have
* associated RouteSetup metadata. RSocket connections can then be found based on
* Forwarding metadata. When a new RSocket is registered, a RegisteredEvent is pushed onto
* a DirectProcessor that is acting as an event bus for registered Consumers.
*/
public class RoutingTable {
private static final Log log = LogFactory.getLog(RoutingTable.class);
AtomicInteger internalRouteId = new AtomicInteger();
final Map<Integer, String> internalRouteIdToRouteId = new ConcurrentHashMap<>();
final Map<TagKey, RoaringBitmap> tagsToBitmaps = new ConcurrentHashMap<>();
final Map<String, RouteEntry> routeEntries = new ConcurrentHashMap<>();
private final DirectProcessor<RegisteredEvent> registeredEvents = DirectProcessor
.create();
private final FluxSink<RegisteredEvent> registeredEventsSink = registeredEvents
.sink(FluxSink.OverflowStrategy.DROP);
public RoutingTable() {
}
// TODO: Mono<Void>?
public void register(TagsMetadata tagsMetadata, RSocket rsocket) {
register(new RouteEntry(rsocket, tagsMetadata));
}
private void register(RouteEntry routeEntry) {
if (log.isInfoEnabled()) {
log.info("Registering RSocket: " + routeEntry.tagsMetadata);
}
// TODO: only register new route if timestamp is newer
String routeId = routeEntry.getRouteId();
if (routeEntries.containsKey(routeId)) {
throw new IllegalStateException("Route Id already registered: " + routeId);
}
int internalId = internalRouteId.incrementAndGet();
internalRouteIdToRouteId.put(internalId, routeId);
routeEntries.put(routeId, routeEntry);
routeEntry.getTags().forEach((key, value) -> {
// TODO: deal with string keys?
RoaringBitmap bitmap = tagsToBitmaps.computeIfAbsent(new TagKey(key, value),
k -> new RoaringBitmap());
bitmap.add(internalId);
});
registeredEventsSink.next(new RegisteredEvent(routeEntry));
}
public boolean deregister(TagsMetadata metadata) {
Assert.notNull(metadata, "metadata may not be null");
String routeId = metadata.getRouteId();
if (!StringUtils.hasText(routeId)) {
if (log.isDebugEnabled()) {
log.debug("Unable to deregister, no RouteId: " + metadata);
}
return false;
}
if (log.isInfoEnabled()) {
log.info("Deregistering RSocket: " + metadata);
}
TagsMetadata findByRouteId = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, routeId).build();
RoaringBitmap found = find(findByRouteId);
if (found.isEmpty() || found.getLongCardinality() > 1) {
if (log.isWarnEnabled()) {
log.warn("Unable to deregister " + metadata + ", found: "
+ found.getLongCardinality());
}
return false;
}
int internalId = found.first();
internalRouteIdToRouteId.remove(internalId);
routeEntries.remove(routeId);
metadata.getTags().forEach((key, value) -> {
// TODO: deal with string keys?
TagKey tagKey = new TagKey(key, value);
if (tagsToBitmaps.containsKey(tagKey)) {
RoaringBitmap bitmap = tagsToBitmaps.get(tagKey);
bitmap.remove(internalId);
}
});
// TODO: deregistered event
return true;
}
/**
* Finds routeIds of matching routes.
* @param tagsMetadata tags to match.
* @return all matching routeIds or empty list.
*/
public Set<String> findRouteIds(TagsMetadata tagsMetadata) {
RoaringBitmap found = find(tagsMetadata);
if (found.isEmpty()) {
return Collections.emptySet();
}
HashSet<String> routeIds = new HashSet<>();
found.forEach((IntConsumer) internalId -> {
String routeId = internalRouteIdToRouteId.get(internalId);
routeIds.add(routeId);
});
return routeIds;
}
/**
* Finds tuples of routeIds and RSockets of matching routes.
* @param tagsMetadata tags to match.
* @return all matching routeId and RSocket tuples or empty list.
*/
public List<Tuple2<String, RSocket>> findRSockets(TagsMetadata tagsMetadata) {
RoaringBitmap found = find(tagsMetadata);
if (found.isEmpty()) {
return Collections.emptyList();
}
ArrayList<Tuple2<String, RSocket>> rSockets = new ArrayList<>();
found.forEach((IntConsumer) internalId -> {
String routeId = internalRouteIdToRouteId.get(internalId);
RouteEntry routeEntry = routeEntries.get(routeId);
RSocket rSocket = routeEntry.getRSocket();
rSockets.add(Tuples.of(routeId, rSocket));
});
return rSockets;
}
/**
* Finds internal ids of routes.
* @param tagsMetadata tags to match
* @return bitmap of all internal ids of routes.
*/
RoaringBitmap find(TagsMetadata tagsMetadata) {
RoaringBitmap found = new RoaringBitmap();
AtomicBoolean first = new AtomicBoolean(true);
tagsMetadata.getTags().forEach((key, value) -> {
TagKey tagKey = new TagKey(key, value);
if (tagsToBitmaps.containsKey(tagKey)) {
RoaringBitmap search = tagsToBitmaps.get(tagKey);
if (first.get()) {
// initiliaze found bitmap with current search
found.or(search);
first.compareAndSet(true, false);
}
else {
found.and(search);
}
}
});
return found;
}
public Disposable addListener(Consumer<RegisteredEvent> consumer) {
return this.registeredEvents.subscribe(consumer);
}
public static class RegisteredEvent {
private final RouteEntry routeEntry;
public RegisteredEvent(RouteEntry routeEntry) {
Assert.notNull(routeEntry, "routeEntry may not be null");
this.routeEntry = routeEntry;
}
public TagsMetadata getRoutingMetadata() {
return this.routeEntry.getTagsMetadata();
}
public RSocket getRSocket() {
return this.routeEntry.getRSocket();
}
}
static class RouteEntry {
private final RSocket rSocket;
private final TagsMetadata tagsMetadata;
private final Long timestamp;
RouteEntry(RSocket rSocket, TagsMetadata tagsMetadata) {
this(rSocket, tagsMetadata, System.currentTimeMillis());
}
RouteEntry(RSocket rSocket, TagsMetadata tagsMetadata, Long timestamp) {
Assert.notNull(tagsMetadata, "tagsMetadata may not be null");
Assert.notNull(rSocket, "RSocket may not be null");
this.rSocket = rSocket;
this.tagsMetadata = tagsMetadata;
this.timestamp = timestamp;
}
public RSocket getRSocket() {
return this.rSocket;
}
public TagsMetadata getTagsMetadata() {
return this.tagsMetadata;
}
public Long getTimestamp() {
return this.timestamp;
}
public String getRouteId() {
return this.tagsMetadata.getRouteId();
}
public Map<TagsMetadata.Key, String> getTags() {
return this.getTagsMetadata().getTags();
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("rSocket", rSocket)
.append("tagsMetadata", tagsMetadata)
.toString();
// @formatter:on
}
}
static class TagKey {
final TagsMetadata.Key key;
final String value;
TagKey(TagsMetadata.Key key, String value) {
// TODO: Assert non null
this.key = key;
this.value = value.toLowerCase();
}
public TagsMetadata.Key getKey() {
return this.key;
}
public String getValue() {
return this.value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TagKey that = (TagKey) o;
return Objects.equals(this.key, that.key)
&& Objects.equals(this.value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(this.key, this.value);
}
@Override
public String toString() {
return new ToStringCreator(this).append("key", key).append("value", value)
.toString();
}
}
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.routing;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.core.GatewayExchange;
import org.springframework.cloud.gateway.rsocket.core.GatewayFilter;
import org.springframework.cloud.gateway.rsocket.route.Route;
import org.springframework.cloud.gateway.rsocket.route.Routes;
import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate;
import org.springframework.core.style.ToStringCreator;
/**
* View of RoutingTable as Route objects.
*/
public class RoutingTableRoutes
implements Routes, Consumer<RoutingTable.RegisteredEvent> {
private static final Log log = LogFactory.getLog(RoutingTableRoutes.class);
private Map<String, Route> routes = new ConcurrentHashMap<>();
private final RoutingTable routingTable;
public RoutingTableRoutes(RoutingTable routingTable) {
this.routingTable = routingTable;
this.routingTable.addListener(this);
}
@Override
public Flux<Route> getRoutes() {
// TODO: sorting?
// TODO: caching
Collection<Route> routeCollection = routes.values();
if (log.isDebugEnabled()) {
log.debug("Found routes: " + routeCollection);
}
return Flux.fromIterable(routeCollection);
}
@Override
public void accept(RoutingTable.RegisteredEvent registeredEvent) {
TagsMetadata routingMetadata = registeredEvent.getRoutingMetadata();
String routeId = routingMetadata.getRouteId();
routes.computeIfAbsent(routeId, key -> createRoute(routeId));
}
private Route createRoute(String routeId) {
AsyncPredicate<GatewayExchange> predicate = new RoutIdPredicate(routingTable,
routeId);
RegistryRoute route = new RegistryRoute(routeId, predicate);
if (log.isDebugEnabled()) {
log.debug("Created Route for registered service " + route);
}
return route;
}
static class RoutIdPredicate implements AsyncPredicate<GatewayExchange> {
private final RoutingTable routingTable;
private final String routeId;
RoutIdPredicate(RoutingTable routingTable, String routeId) {
this.routingTable = routingTable;
this.routeId = routeId;
}
@Override
public Publisher<Boolean> apply(GatewayExchange exchange) {
// TODO: standard predicates
// TODO: allow customized predicates
Set<String> routeIds = routingTable
.findRouteIds(exchange.getRoutingMetadata());
return Mono.just(routeIds.contains(routeId));
}
@Override
public String toString() {
return String.format("[RoutIdPredicate %s]", routeId);
}
}
static class RegistryRoute implements Route {
final String id;
final AsyncPredicate<GatewayExchange> predicate;
RegistryRoute(String id, AsyncPredicate<GatewayExchange> predicate) {
this.id = id;
this.predicate = predicate;
}
@Override
public String getId() {
return this.id;
}
@Override
public AsyncPredicate<GatewayExchange> getPredicate() {
return this.predicate;
}
@Override
public List<GatewayFilter> getFilters() {
return Collections.emptyList();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RegistryRoute that = (RegistryRoute) o;
return Objects.equals(this.id, that.id)
&& Objects.equals(this.predicate, that.predicate);
}
@Override
public int hashCode() {
return Objects.hash(this.id, this.predicate);
}
@Override
public String toString() {
return new ToStringCreator(this).append("id", id)
.append("predicate", predicate).toString();
}
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.routing;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorExchange;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilterChain;
import org.springframework.core.Ordered;
/**
* Filter that registers the SendingSocket.
*/
public class RoutingTableSocketAcceptorFilter implements SocketAcceptorFilter, Ordered {
private final RoutingTable routingTable;
public RoutingTableSocketAcceptorFilter(RoutingTable routingTable) {
this.routingTable = routingTable;
}
@Override
public Mono<Success> filter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
if (exchange.getMetadata() != null) {
// TODO: needed? &&
// StringUtils.hasLength(exchange.getMetadata().getServiceName())) {
this.routingTable.register(exchange.getMetadata().getEnrichedTagsMetadata(),
exchange.getSendingSocket());
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 1000;
}
}

View File

@@ -1,126 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Collectors;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.rsocket.ConnectionSetupPayload;
import io.rsocket.RSocket;
import io.rsocket.SocketAcceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.core.GatewayRSocketFactory;
import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocket;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.util.MimeType;
public class GatewaySocketAcceptor implements SocketAcceptor {
private static final Log log = LogFactory.getLog(GatewaySocketAcceptor.class);
private final SocketAcceptorFilterChain filterChain;
private final GatewayRSocketFactory rSocketFactory;
private final MeterRegistry meterRegistry;
private final BrokerProperties properties;
private final MetadataExtractor metadataExtractor;
public GatewaySocketAcceptor(GatewayRSocketFactory rSocketFactory,
List<SocketAcceptorFilter> filters, MeterRegistry meterRegistry,
BrokerProperties properties, MetadataExtractor metadataExtractor) {
this.rSocketFactory = rSocketFactory;
this.filterChain = new SocketAcceptorFilterChain(filters);
this.meterRegistry = meterRegistry;
this.properties = properties;
this.metadataExtractor = metadataExtractor;
}
@Override
@SuppressWarnings("Duplicates")
public Mono<RSocket> accept(ConnectionSetupPayload setup, RSocket sendingSocket) {
if (log.isTraceEnabled()) {
log.trace("accept()");
}
// decorate GatewayRSocket with metrics
// current gateway id, type requester, service name (from metadata), service id
Tags requesterTags = Tags.of("gateway.id", properties.getId(), "type",
"requester");
Tags metadataTags;
SocketAcceptorExchange exchange;
Map<String, Object> metadataMap = null;
try {
metadataMap = this.metadataExtractor.extract(setup,
MimeType.valueOf(setup.metadataMimeType()));
}
catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Error extracting metadata", e);
}
return Mono.error(e);
}
if (metadataMap.containsKey("routesetup")) {
RouteSetup metadata = (RouteSetup) metadataMap.get("routesetup");
metadataTags = Tags.of("service.name", metadata.getServiceName())
.and("service.id", metadata.getId().toString());
// enrich exchange to have metadata
exchange = new SocketAcceptorExchange(setup,
decorate(sendingSocket, requesterTags.and(metadataTags)), metadata);
}
else {
metadataTags = Tags.of("service.name", "UNKNOWN").and("service.id",
"UNKNOWN");
exchange = new SocketAcceptorExchange(setup,
decorate(sendingSocket, requesterTags));
}
Tags responderTags = Tags
.of("gateway.id", properties.getId(), "type", "responder")
.and(metadataTags);
// decorate with metrics gateway id, type responder, service name, service id
// (instance id)
return this.filterChain.filter(exchange).log(
GatewaySocketAcceptor.class.getName() + ".socket acceptor filter chain",
Level.FINEST).map(success -> {
TagsMetadata tags = exchange.getMetadata().getEnrichedTagsMetadata();
return decorate(this.rSocketFactory.create(tags), responderTags);
});
}
private RSocket decorate(RSocket rSocket, Tags tags) {
Tag[] tagArray = tags.stream().collect(Collectors.toList()).toArray(new Tag[] {});
return new MicrometerResponderRSocket(rSocket, meterRegistry, tagArray);
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import java.math.BigInteger;
import io.rsocket.ConnectionSetupPayload;
import io.rsocket.RSocket;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange;
public class SocketAcceptorExchange extends AbstractRSocketExchange {
private final ConnectionSetupPayload setup;
private final RSocket sendingSocket;
private final RouteSetup metadata;
public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) {
this(setup, sendingSocket, RouteSetup.of((BigInteger) null, null).build());
}
public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket,
RouteSetup metadata) {
this.setup = setup;
this.sendingSocket = sendingSocket;
this.metadata = metadata;
}
public ConnectionSetupPayload getSetup() {
return setup;
}
public RSocket getSendingSocket() {
return sendingSocket;
}
public RouteSetup getMetadata() {
return metadata;
}
}

View File

@@ -1,24 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter;
public interface SocketAcceptorFilter
extends RSocketFilter<SocketAcceptorExchange, SocketAcceptorFilterChain> {
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import java.util.List;
import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain;
public class SocketAcceptorFilterChain extends
AbstractFilterChain<SocketAcceptorFilter, SocketAcceptorExchange, SocketAcceptorFilterChain> {
/**
* Public constructor with the list of filters and the target handler to use.
* @param filters the filters ahead of the handler
*/
public SocketAcceptorFilterChain(List<SocketAcceptorFilter> filters) {
super(filters);
}
public SocketAcceptorFilterChain(List<SocketAcceptorFilter> allFilters,
SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) {
super(allFilters, currentFilter, next);
}
@Override
protected SocketAcceptorFilterChain create(List<SocketAcceptorFilter> allFilters,
SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) {
return new SocketAcceptorFilterChain(allFilters, currentFilter, next);
}
}

View File

@@ -1,23 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate;
public interface SocketAcceptorPredicate extends AsyncPredicate<SocketAcceptorExchange> {
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
public class SocketAcceptorPredicateFilter implements SocketAcceptorFilter, Ordered {
private final AsyncPredicate<SocketAcceptorExchange> predicate;
// TODO: change from List to Flux?
public SocketAcceptorPredicateFilter(List<SocketAcceptorPredicate> predicates) {
Assert.notNull(predicates, "predicates may not be null");
if (predicates.isEmpty()) {
predicate = exchange -> Mono.just(true);
}
else {
AsyncPredicate<SocketAcceptorExchange> combined = predicates.get(0);
for (SocketAcceptorPredicate p : predicates.subList(1, predicates.size())) {
combined = combined.and(p);
}
predicate = combined;
}
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE + 10000;
}
@Override
public Mono<Success> filter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
return Mono.from(predicate.apply(exchange)).flatMap(test -> {
if (test) {
return chain.filter(exchange);
}
return Mono.empty();
});
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2013-2018 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.cloud.gateway.rsocket.support;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.util.Assert;
/**
* @author Ben Hale
*/
public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
default AsyncPredicate<T> and(AsyncPredicate<? super T> other) {
Assert.notNull(other, "other must not be null");
return t -> Flux.zip(apply(t), other.apply(t))
.map(tuple -> tuple.getT1() && tuple.getT2());
}
default AsyncPredicate<T> negate() {
return t -> Mono.from(apply(t)).map(b -> !b);
}
default AsyncPredicate<T> or(AsyncPredicate<? super T> other) {
Assert.notNull(other, "other must not be null");
return t -> Flux.zip(apply(t), other.apply(t))
.map(tuple -> tuple.getT1() || tuple.getT2());
}
}

View File

@@ -1,7 +0,0 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketAutoConfiguration
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketEnvironmentPostProcessor

View File

@@ -1,194 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import java.math.BigInteger;
import java.util.Random;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.cloud.gateway.rsocket.cluster.ClusterService;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator.BROKER_INFO_PATH;
import static org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator.ROUTE_JOIN_PATH;
import static org.springframework.cloud.gateway.rsocket.actuate.BrokerActuator.ROUTE_REMOVE_PATH;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
properties = { "spring.cloud.gateway.rsocket.cluster.enabled=false",
"spring.cloud.gateway.rsocket.broker.actuator.enabled=true" })
public class BrokerActuatorIntegrationTests {
private final Random random = new Random();
@Autowired
private RSocketRequester.Builder requesterBuilder;
@Autowired
private RSocketMessageHandler messageHandler;
@MockBean
private ClusterService clusterService;
// @LocalServerPort
private static int port;
@BeforeClass
public static void init() {
Hooks.onOperatorDebug();
port = SocketUtils.findAvailableTcpPort();
System.setProperty("spring.rsocket.server.port", String.valueOf(port));
}
@AfterClass
public static void after() {
System.clearProperty("spring.rsocket.server.port");
}
@Test
public void brokerInfoWorks() {
long brokerId = random.nextLong();
BrokerInfo data = BrokerInfo.of(brokerId).build();
Mono<BigInteger> result = callActuator(brokerId, BigInteger.class, data,
BROKER_INFO_PATH);
StepVerifier.create(result).consumeNextWith(
res -> assertThat(res).isNotNull().isEqualTo(BigInteger.valueOf(1234L)))
.verifyComplete();
// TODO: assert server side calls worked
}
@Test
@Ignore // TODO: move to integration tests module
public void routeJoinRemoveWorks() {
long brokerId = random.nextLong();
long routeId = random.nextLong();
RouteJoin data = RouteJoin.builder().brokerId(brokerId).routeId(routeId)
.serviceName("testServiceName").build();
RSocketRequester requester = getRequester(brokerId);
Mono<RouteJoin> result = callActuator(requester, brokerId, RouteJoin.class, data,
ROUTE_JOIN_PATH);
StepVerifier.create(result)
.consumeNextWith(res -> assertThat(res).isNotNull().isEqualTo(data))
.verifyComplete();
// TODO: assert server side calls worked
routeRemoveWorks(requester, routeId);
}
public void routeRemoveWorks(RSocketRequester requester, long routeId) {
long brokerId = random.nextLong();
RouteRemove data = RouteRemove.builder().brokerId(brokerId).routeId(routeId)
.build();
Mono<Boolean> result = callActuator(requester, brokerId, Boolean.class, data,
ROUTE_REMOVE_PATH);
StepVerifier.create(result).consumeNextWith(res -> assertThat(res).isTrue())
.verifyComplete();
// TODO: assert server side calls worked
result = callActuator(brokerId, Boolean.class, data, ROUTE_REMOVE_PATH);
StepVerifier.create(result).consumeNextWith(res -> assertThat(res).isTrue())
.verifyComplete();
}
@Test
@Ignore // TODO: move to integration tests module
public void routeJoinCloseDeregisters() {
long brokerId = random.nextLong();
long routeId = random.nextLong();
RouteJoin data = RouteJoin.builder().brokerId(brokerId).routeId(routeId)
.serviceName("testServiceName").build();
RSocketRequester requester = getRequester(brokerId);
Mono<RouteJoin> result = callActuator(requester, brokerId, RouteJoin.class, data,
ROUTE_JOIN_PATH);
result.block();
StepVerifier.create(result)
.consumeNextWith(res -> assertThat(res).isNotNull().isEqualTo(data))
.verifyComplete();
requester.rsocket().dispose();
// TODO: assert server side calls worked
}
private <T, D> Mono<T> callActuator(long brokerId, Class<T> type, D data,
String path) {
RSocketRequester requester = getRequester(brokerId);
return callActuator(requester, brokerId, type, data, path);
}
private <T, D> Mono<T> callActuator(RSocketRequester requester, long brokerId,
Class<T> type, D data, String path) {
Forwarding forwarding = Forwarding.of(brokerId).serviceName("gateway")
.disableProxy().build();
return requester.route(path).metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE)
.data(data).retrieveMono(type);
}
private RSocketRequester getRequester(long brokerId) {
RouteSetup routeSetup = RouteSetup.of(brokerId, "gateway")
.with("proxy", Boolean.FALSE.toString()).build();
// mimic rsocket client autoconfig
return requesterBuilder
.setupMetadata(routeSetup, RouteSetup.ROUTE_SETUP_MIME_TYPE)
.rsocketFactory(rsocketFactory -> rsocketFactory
.acceptor(messageHandler.responder()))
.connectTcp("localhost", port).block();
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class Config {
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.actuate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
properties = { "spring.rsocket.server.port=0",
"spring.cloud.gateway.rsocket.route-id=55",
"spring.cloud.gateway.rsocket.service-name=gateway" })
public class BrokerActuatorRegistrarTests {
@Autowired
private BrokerActuatorHandlerRegistration registrar;
@Test
public void test() {
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class Config {
}
}

View File

@@ -1,84 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.autoconfigure;
import io.rsocket.SocketAcceptor;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration;
import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
import org.springframework.boot.rsocket.server.RSocketServer;
import org.springframework.boot.rsocket.server.RSocketServerFactory;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration;
import org.springframework.cloud.gateway.rsocket.core.GatewayServerRSocketFactoryProcessor;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTableRoutes;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTableSocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicate;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicateFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GatewayRSocketAutoConfigurationTests {
@Test
public void gatewayRSocketConfigured() {
new ReactiveWebApplicationContextRunner().withUserConfiguration(MyConfig.class)
.withSystemProperties("spring.cloud.gateway.rsocket.route-id=11")
.withConfiguration(
AutoConfigurations.of(RSocketStrategiesAutoConfiguration.class,
RSocketMessagingAutoConfiguration.class,
GatewayRSocketCommonAutoConfiguration.class,
GatewayRSocketAutoConfiguration.class,
CompositeMeterRegistryAutoConfiguration.class,
MetricsAutoConfiguration.class))
.run(context -> assertThat(context).hasSingleBean(RoutingTable.class)
.hasSingleBean(RoutingTableRoutes.class)
.hasSingleBean(RoutingTableSocketAcceptorFilter.class)
.hasSingleBean(GatewayServerRSocketFactoryProcessor.class)
.hasSingleBean(BrokerProperties.class)
.hasSingleBean(GatewaySocketAcceptor.class)
.hasSingleBean(SocketAcceptorPredicateFilter.class)
.hasSingleBean(RSocketServerBootstrap.class)
.doesNotHaveBean(SocketAcceptorPredicate.class));
}
@Configuration
protected static class MyConfig {
@Bean
RSocketServerFactory rSocketServerFactory() {
RSocketServerFactory serverFactory = mock(RSocketServerFactory.class);
when(serverFactory.create(any(SocketAcceptor.class)))
.thenReturn(mock(RSocketServer.class));
return serverFactory;
}
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.cluster;
import org.junit.Test;
import org.springframework.cloud.gateway.rsocket.actuate.BrokerInfo;
import static org.assertj.core.api.Assertions.assertThat;
public class ClusterServiceTests {
@Test
public void registerIncomingWorks() {
ClusterService routingTable = new ClusterService();
BrokerInfo brokerInfo = BrokerInfo.of(1L).timestamp(100L).build();
boolean result = routingTable.registerIncoming(brokerInfo);
String brokerId = brokerInfo.getBrokerId().toString();
assertThat(result).isTrue();
assertThat(routingTable.incomingBrokers).containsKey(brokerId);
brokerInfo = BrokerInfo.of(1L).timestamp(10L).build();
result = routingTable.registerIncoming(brokerInfo);
assertThat(result).isFalse();
assertThat(routingTable.incomingBrokers.get(brokerId)).isNotNull()
.extracting(ClusterService.BrokerEntry::getTimestamp).isEqualTo(100L);
}
}

View File

@@ -1,95 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.time.Duration;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import reactor.core.publisher.Hooks;
import reactor.test.StepVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.rsocket.RSocketProperties;
import org.springframework.boot.rsocket.context.RSocketServerBootstrap;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.gateway.rsocket.test.PingPongApp;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = PingPongApp.class,
properties = { "ping.take=10", "ping.subscribe=false" },
webEnvironment = WebEnvironment.RANDOM_PORT)
public class GatewayRSocketIntegrationTests {
private static int port;
@Autowired
private PingPongApp.Ping ping;
@Autowired
private PingPongApp.Pong pong;
@Autowired
private RSocketProperties properties;
@Autowired
private PingPongApp.MySocketAcceptorFilter mySocketAcceptorFilter;
@Autowired
private RSocketServerBootstrap server;
@BeforeClass
public static void init() {
Hooks.onOperatorDebug();
port = SocketUtils.findAvailableTcpPort();
System.setProperty("spring.rsocket.server.port", String.valueOf(port));
}
@AfterClass
public static void after() {
System.clearProperty("spring.rsocket.server.port");
}
@Test
public void contextLoads() {
// @formatter:off
StepVerifier.create(ping.getPongFlux())
.expectSubscription()
.then(() -> server.stop())
.thenConsumeWhile(s -> true)
.expectComplete()
.verify(Duration.ofSeconds(20));
// @formatter:on
assertThat(ping.getPongsReceived()).isGreaterThan(0);
assertThat(pong.getPingsReceived()).isGreaterThan(0);
Object server = properties.getServer();
Object port = ReflectionTestUtils.invokeGetterMethod(server, "port");
assertThat(port).isNotEqualTo(7002);
assertThat(mySocketAcceptorFilter.invoked()).isTrue();
assertThat(this.server.isRunning()).isFalse();
}
}

View File

@@ -1,321 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.core;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.util.DefaultPayload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.test.StepVerifier;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import org.springframework.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.Metadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder;
import org.springframework.cloud.gateway.rsocket.route.DefaultRoute;
import org.springframework.cloud.gateway.rsocket.route.Route;
import org.springframework.cloud.gateway.rsocket.route.Routes;
import org.springframework.cloud.gateway.rsocket.routing.LoadBalancerFactory;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.messaging.rsocket.DefaultMetadataExtractor;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.messaging.rsocket.PayloadUtils;
import org.springframework.messaging.rsocket.RSocketStrategies;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding.FORWARDING_MIME_TYPE;
/**
* @author Spencer Gibb
*/
public class GatewayRSocketTests {
private static Log logger = LogFactory.getLog(GatewayRSocketTests.class);
private RoutingTable routingTable;
private Payload incomingPayload;
private final RSocketStrategies rSocketStrategies = RSocketStrategies.builder()
.decoder(new Forwarding.Decoder()).encoder(new Forwarding.Encoder()).build();
private DefaultMetadataExtractor metadataExtractor = new DefaultMetadataExtractor(
rSocketStrategies.decoders());
// TODO: add tests for metrics and other request types
@Before
public void init() {
routingTable = mock(RoutingTable.class);
this.metadataExtractor.metadataToExtract(FORWARDING_MIME_TYPE, Forwarding.class,
"forwarding");
MetadataEncoder encoder = new MetadataEncoder(Metadata.COMPOSITE_MIME_TYPE,
this.rSocketStrategies);
Forwarding metadata = Forwarding.of(1).with(WellKnownKey.SERVICE_NAME, "mock")
.build();
DataBuffer dataBuffer = encoder.metadata(metadata, FORWARDING_MIME_TYPE).encode();
DataBuffer data = MetadataEncoder.emptyDataBuffer(rSocketStrategies);
incomingPayload = PayloadUtils.createPayload(data, dataBuffer);
RSocket rSocket = mock(RSocket.class);
Tuple2<String, RSocket> tuple = Tuples.of("1111", rSocket);
when(routingTable.findRSockets(any(TagsMetadata.class)))
.thenReturn(Collections.singletonList(tuple));
when(rSocket.requestResponse(any(Payload.class)))
.thenReturn(Mono.just(DefaultPayload.create("response")));
}
@Test
public void multipleFilters() {
TestFilter filter1 = new TestFilter();
TestFilter filter2 = new TestFilter();
TestFilter filter3 = new TestFilter();
Payload payload = new TestGatewayRSocket(routingTable,
new TestRoutes(filter1, filter2, filter3), metadataExtractor)
.requestResponse(incomingPayload).block(Duration.ZERO);
assertThat(filter1.invoked()).isTrue();
assertThat(filter2.invoked()).isTrue();
assertThat(filter3.invoked()).isTrue();
assertThat(payload).isNotNull();
}
@Test
public void zeroFilters() {
Payload payload = new TestGatewayRSocket(routingTable, new TestRoutes(),
metadataExtractor).requestResponse(incomingPayload).block(Duration.ZERO);
assertThat(payload).isNotNull();
}
@Test
public void shortcircuitFilter() {
TestFilter filter1 = new TestFilter();
ShortcircuitingFilter filter2 = new ShortcircuitingFilter();
TestFilter filter3 = new TestFilter();
TestGatewayRSocket gatewayRSocket = new TestGatewayRSocket(routingTable,
new TestRoutes(filter1, filter2, filter3), metadataExtractor);
Mono<Payload> response = gatewayRSocket.requestResponse(incomingPayload);
// a false filter will create a pending rsocket that blocks forever
// this tweaks the rsocket to complete.
gatewayRSocket.getProcessor().onNext(null);
StepVerifier.withVirtualTime(() -> response).expectSubscription()
.verifyComplete();
assertThat(filter1.invoked()).isTrue();
assertThat(filter2.invoked()).isTrue();
assertThat(filter3.invoked()).isFalse();
}
@Test
public void asyncFilter() {
AsyncFilter filter = new AsyncFilter();
Payload payload = new TestGatewayRSocket(routingTable, new TestRoutes(filter),
metadataExtractor).requestResponse(incomingPayload)
.block(Duration.ofSeconds(5));
assertThat(filter.invoked()).isTrue();
assertThat(payload).isNotNull();
}
// TODO: add exception handlers?
@Test(expected = IllegalStateException.class)
public void handleErrorFromFilter() {
ExceptionFilter filter = new ExceptionFilter();
new TestGatewayRSocket(routingTable, new TestRoutes(filter), metadataExtractor)
.requestResponse(incomingPayload).block(Duration.ofSeconds(5));
// assertNull(socket);
}
private static RouteSetup getMetadata() {
return RouteSetup.of(1L, "service").build();
}
private static class TestGatewayRSocket extends GatewayRSocket {
TestGatewayRSocket(RoutingTable routingTable, Routes routes,
MetadataExtractor metadataExtractor) {
super(routes, new TestPendingFactory(routingTable, routes, metadataExtractor),
new LoadBalancerFactory(routingTable), new SimpleMeterRegistry(),
new BrokerProperties(), metadataExtractor, getMetadata());
}
private MonoProcessor<RSocket> getProcessor() {
TestPendingFactory factory = (TestPendingFactory) super.getPendingFactory();
return factory.processor;
}
}
private static class TestPendingFactory extends PendingRequestRSocketFactory {
private final MonoProcessor<RSocket> processor = MonoProcessor.create();
private final MetadataExtractor metadataExtractor;
TestPendingFactory(RoutingTable routingTable, Routes routes,
MetadataExtractor metadataExtractor) {
super(routingTable, routes, metadataExtractor);
this.metadataExtractor = metadataExtractor;
}
@Override
protected PendingRequestRSocket constructPendingRSocket(
GatewayExchange exchange) {
Function<RoutingTable.RegisteredEvent, Mono<Route>> routeFinder = registeredEvent -> getRouteMono(
registeredEvent, exchange);
return new PendingRequestRSocket(metadataExtractor, routeFinder,
tagsMetadata -> {
Tags tags = exchange.getTags().and("responder.id",
tagsMetadata.getRouteId());
exchange.setTags(tags);
}, processor);
}
}
private static class TestRoutes implements Routes {
private final Route route;
private List<GatewayFilter> filters;
TestRoutes() {
this(Collections.emptyList());
}
TestRoutes(GatewayFilter... filters) {
this(Arrays.asList(filters));
}
TestRoutes(List<GatewayFilter> filters) {
this.filters = filters;
route = DefaultRoute.builder().id("route1")
.routingMetadata(RouteSetup.of(1L, "mock").build())
.predicate(exchange -> Mono.just(true)).filters(filters).build();
}
@Override
public Flux<Route> getRoutes() {
return Flux.just(route);
}
}
private static class TestFilter implements GatewayFilter {
private volatile boolean invoked;
public boolean invoked() {
return this.invoked;
}
@Override
public Mono<Success> filter(GatewayExchange exchange, GatewayFilterChain chain) {
this.invoked = true;
return doFilter(exchange, chain);
}
public Mono<Success> doFilter(GatewayExchange exchange,
GatewayFilterChain chain) {
return chain.filter(exchange);
}
}
private static class ShortcircuitingFilter extends TestFilter {
@Override
public Mono<Success> doFilter(GatewayExchange exchange,
GatewayFilterChain chain) {
return Mono.empty();
}
}
private static class AsyncFilter extends TestFilter {
@Override
public Mono<Success> doFilter(GatewayExchange exchange,
GatewayFilterChain chain) {
return doAsyncWork().flatMap(asyncResult -> {
logger.debug("Async result: " + asyncResult);
return chain.filter(exchange);
});
}
private Mono<String> doAsyncWork() {
return Mono.delay(Duration.ofMillis(100L)).map(l -> "123");
}
}
private static class ExceptionFilter implements GatewayFilter {
@Override
public Mono<Success> filter(GatewayExchange exchange, GatewayFilterChain chain) {
return Mono.error(new IllegalStateException("boo"));
}
}
/*
* private static class TestExceptionHandler implements WebExceptionHandler {
*
* private Throwable ex;
*
* @Override public Mono<Void> handle(GatewayExchange exchange, Throwable ex) {
* this.ex = ex; return Mono.error(ex); } }
*/
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.routing;
import java.util.HashSet;
import io.rsocket.RSocket;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.core.GatewayExchange;
import org.springframework.cloud.gateway.rsocket.route.Route;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RegisteredEvent;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable.RouteEntry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.rsocket.core.GatewayExchange.Type.REQUEST_RESPONSE;
public class RoutingTableRoutesTests {
@Test
public void routesAreBuilt() {
RoutingTable routingTable = mock(RoutingTable.class);
RoutingTableRoutes routes = new RoutingTableRoutes(routingTable);
HashSet<String> routeIds = new HashSet<>();
routeIds.add("2");
when(routingTable.findRouteIds(any(TagsMetadata.class))).thenReturn(routeIds);
addRoute(routes, "1");
addRoute(routes, "2");
addRoute(routes, "3");
Forwarding forwarding = Forwarding.of(1L).routeId("2").build();
Mono<Route> routeMono = routes
.findRoute(new GatewayExchange(REQUEST_RESPONSE, forwarding));
StepVerifier.create(routeMono).consumeNextWith(route -> {
assertThat(route).isNotNull().extracting(Route::getId).isEqualTo("2");
}).verifyComplete();
}
void addRoute(RoutingTableRoutes routes, String routeId) {
TagsMetadata tagsMetadata = TagsMetadata.builder().routeId(routeId).build();
RSocket rsocket = mock(RSocket.class);
routes.accept(new RegisteredEvent(new RouteEntry(rsocket, tagsMetadata)));
}
}

View File

@@ -1,168 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.routing;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import io.rsocket.AbstractRSocket;
import io.rsocket.RSocket;
import org.junit.Test;
import org.roaringbitmap.RoaringBitmap;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.core.style.ToStringCreator;
import static org.assertj.core.api.Assertions.assertThat;
public class RoutingTableTests {
@Test
public void testIndexesCreatedAndSearchWorks() {
RoutingTable routingTable = new RoutingTable();
// @formatter:off
TagsMetadata setupTags1 = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, "1111")
.with(WellKnownKey.SERVICE_NAME, "serviceA")
.with(WellKnownKey.CLUSTER_NAME, "clusterA")
.build();
TagsMetadata setupTags2 = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, "2222")
.with(WellKnownKey.SERVICE_NAME, "serviceA")
.with(WellKnownKey.CLUSTER_NAME, "clusterB")
.build();
TagsMetadata setupTags3 = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, "3333")
.with(WellKnownKey.SERVICE_NAME, "serviceB")
.with(WellKnownKey.REGION, "region1")
.build();
TagsMetadata setupTags4 = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, "4444")
.with(WellKnownKey.SERVICE_NAME, "serviceB")
.with(WellKnownKey.CLUSTER_NAME, "clusterB")
.with(WellKnownKey.ZONE, "zone1")
.build();
TagsMetadata setupTags5 = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, "5555")
.with(WellKnownKey.SERVICE_NAME, "serviceA")
.with(WellKnownKey.CLUSTER_NAME, "clusterB")
.build();
// @formatter:on
AtomicInteger internalRouteId = routingTable.internalRouteId;
RSocket rSocket1 = assertRegister(routingTable, setupTags1,
internalRouteId.get() + 1);
internalRouteId.set(99);
RSocket rSocket2 = assertRegister(routingTable, setupTags2,
internalRouteId.get() + 1);
internalRouteId.set(999);
RSocket rSocket3 = assertRegister(routingTable, setupTags3,
internalRouteId.get() + 1);
internalRouteId.set(9999);
RSocket rSocket4 = assertRegister(routingTable, setupTags4,
internalRouteId.get() + 1);
internalRouteId.set(99999);
RSocket rSocket5 = assertRegister(routingTable, setupTags5,
internalRouteId.get() + 1);
// @formatter:off
TagsMetadata searchTags1 = TagsMetadata.builder()
.with(WellKnownKey.SERVICE_NAME, "serviceA")
.with(WellKnownKey.CLUSTER_NAME, "clusterB")
.build();
// @formatter:on
List<Tuple2<String, RSocket>> results1 = routingTable.findRSockets(searchTags1);
assertThat(results1).containsOnly(Tuples.of("2222", rSocket2),
Tuples.of("5555", rSocket5));
// @formatter:off
TagsMetadata searchTags2 = TagsMetadata.builder()
.with(WellKnownKey.ROUTE_ID, "3333")
.build();
// @formatter:on
List<Tuple2<String, RSocket>> results2 = routingTable.findRSockets(searchTags2);
assertThat(results2).containsOnly(Tuples.of("3333", rSocket3));
// @formatter:off
TagsMetadata searchTags3 = TagsMetadata.builder()
.with(WellKnownKey.ZONE, "zone1")
.with(WellKnownKey.SERVICE_NAME, "serviceB")
.with(WellKnownKey.CLUSTER_NAME, "clusterB")
.build();
// @formatter:on
List<Tuple2<String, RSocket>> results3 = routingTable.findRSockets(searchTags3);
assertThat(results3).containsOnly(Tuples.of("4444", rSocket4));
assertDeregister(routingTable, setupTags1);
assertDeregister(routingTable, setupTags2);
assertDeregister(routingTable, setupTags3);
assertDeregister(routingTable, setupTags4);
assertDeregister(routingTable, setupTags5);
assertThat(routingTable.deregister(setupTags5)).isFalse();
}
void assertDeregister(RoutingTable routingTable, TagsMetadata tagsMetadata) {
boolean result = routingTable.deregister(tagsMetadata);
assertThat(result).isTrue();
String routeId = tagsMetadata.getRouteId();
assertThat(routingTable.internalRouteIdToRouteId).doesNotContainValue(routeId);
assertThat(routingTable.routeEntries).doesNotContainKey(routeId);
}
private RSocket assertRegister(RoutingTable routingTable, TagsMetadata tagsMetadata,
int internalId) {
String routeId = tagsMetadata.getRouteId();
RSocket rsocket = new TestRSocket(routeId);
routingTable.register(tagsMetadata, rsocket);
assertThat(routingTable.internalRouteId).hasValue(internalId);
assertThat(routingTable.internalRouteIdToRouteId).containsEntry(internalId,
routeId);
assertThat(routingTable.routeEntries).containsKey(routeId);
tagsMetadata.getTags().forEach((key, value) -> {
RoutingTable.TagKey tagKey = new RoutingTable.TagKey(key, value);
assertThat(routingTable.tagsToBitmaps).containsKey(tagKey);
RoaringBitmap bitmap = routingTable.tagsToBitmaps.get(tagKey);
assertThat(bitmap.contains(internalId));
});
return rsocket;
}
static class TestRSocket extends AbstractRSocket {
final String routeId;
TestRSocket(String routeId) {
this.routeId = routeId;
}
@Override
public String toString() {
return new ToStringCreator(this).append("routeId", routeId).toString();
}
}
}

View File

@@ -1,247 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.rsocket.ConnectionSetupPayload;
import io.rsocket.Payload;
import io.rsocket.RSocket;
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.cloud.gateway.rsocket.autoconfigure.BrokerProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.Metadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder;
import org.springframework.cloud.gateway.rsocket.core.GatewayRSocket;
import org.springframework.cloud.gateway.rsocket.core.GatewayRSocketFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.messaging.rsocket.DefaultMetadataExtractor;
import org.springframework.messaging.rsocket.PayloadUtils;
import org.springframework.messaging.rsocket.RSocketStrategies;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup.ROUTE_SETUP_MIME_TYPE;
/**
* @author Spencer Gibb
*/
public class GatewaySocketAcceptorTests {
private static Log logger = LogFactory.getLog(GatewaySocketAcceptorTests.class);
private GatewayRSocketFactory factory;
private ConnectionSetupPayload setupPayload;
private RSocket sendingSocket;
private MeterRegistry meterRegistry;
private BrokerProperties properties = new BrokerProperties();
private final RSocketStrategies rSocketStrategies = RSocketStrategies.builder()
.decoder(new RouteSetup.Decoder()).encoder(new RouteSetup.Encoder()).build();
private DefaultMetadataExtractor metadataExtractor = new DefaultMetadataExtractor(
rSocketStrategies.decoders());
@Before
public void init() {
this.factory = mock(GatewayRSocketFactory.class);
this.setupPayload = mock(ConnectionSetupPayload.class);
this.sendingSocket = mock(RSocket.class);
this.meterRegistry = new SimpleMeterRegistry();
this.metadataExtractor.metadataToExtract(ROUTE_SETUP_MIME_TYPE, RouteSetup.class,
"routesetup");
when(this.factory.create(any(TagsMetadata.class)))
.thenReturn(mock(GatewayRSocket.class));
when(this.setupPayload.metadataMimeType())
.thenReturn(Metadata.COMPOSITE_MIME_TYPE.toString());
when(this.setupPayload.hasMetadata()).thenReturn(true);
MetadataEncoder encoder = new MetadataEncoder(Metadata.COMPOSITE_MIME_TYPE,
this.rSocketStrategies);
encoder.metadata(RouteSetup.of(1L, "myservice").build(), ROUTE_SETUP_MIME_TYPE);
DataBuffer dataBuffer = encoder.encode();
DataBuffer data = MetadataEncoder.emptyDataBuffer(rSocketStrategies);
Payload payload = PayloadUtils.createPayload(data, dataBuffer);
when(setupPayload.metadata()).thenReturn(payload.metadata());
}
// TODO: test metrics
@Test
public void multipleFilters() {
TestFilter filter1 = new TestFilter();
TestFilter filter2 = new TestFilter();
TestFilter filter3 = new TestFilter();
RSocket socket = new GatewaySocketAcceptor(this.factory,
Arrays.asList(filter1, filter2, filter3), this.meterRegistry,
this.properties, this.metadataExtractor)
.accept(this.setupPayload, this.sendingSocket)
.block(Duration.ZERO);
assertThat(filter1.invoked()).isTrue();
assertThat(filter2.invoked()).isTrue();
assertThat(filter3.invoked()).isTrue();
assertThat(socket).isNotNull();
}
@Test
public void zeroFilters() {
RSocket socket = new GatewaySocketAcceptor(this.factory, Collections.emptyList(),
this.meterRegistry, this.properties, this.metadataExtractor)
.accept(this.setupPayload, this.sendingSocket)
.block(Duration.ZERO);
assertThat(socket).isNotNull();
}
@Test
public void shortcircuitFilter() {
TestFilter filter1 = new TestFilter();
ShortcircuitingFilter filter2 = new ShortcircuitingFilter();
TestFilter filter3 = new TestFilter();
RSocket socket = new GatewaySocketAcceptor(this.factory,
Arrays.asList(filter1, filter2, filter3), this.meterRegistry,
this.properties, this.metadataExtractor)
.accept(this.setupPayload, this.sendingSocket)
.block(Duration.ZERO);
assertThat(filter1.invoked()).isTrue();
assertThat(filter2.invoked()).isTrue();
assertThat(filter3.invoked()).isFalse();
assertThat(socket).isNull();
}
@Test
public void asyncFilter() {
AsyncFilter filter = new AsyncFilter();
RSocket socket = new GatewaySocketAcceptor(this.factory, singletonList(filter),
this.meterRegistry, this.properties, this.metadataExtractor)
.accept(this.setupPayload, this.sendingSocket)
.block(Duration.ofSeconds(5));
assertThat(filter.invoked()).isTrue();
assertThat(socket).isNotNull();
}
// TODO: add exception handlers?
@Test(expected = IllegalStateException.class)
public void handleErrorFromFilter() {
ExceptionFilter filter = new ExceptionFilter();
new GatewaySocketAcceptor(this.factory, singletonList(filter), this.meterRegistry,
this.properties, this.metadataExtractor)
.accept(this.setupPayload, this.sendingSocket)
.block(Duration.ofSeconds(5));
}
private static class TestFilter implements SocketAcceptorFilter {
private volatile boolean invoked;
public boolean invoked() {
return this.invoked;
}
@Override
public Mono<Success> filter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
this.invoked = true;
return doFilter(exchange, chain);
}
public Mono<Success> doFilter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
return chain.filter(exchange);
}
}
private static class ShortcircuitingFilter extends TestFilter {
@Override
public Mono<Success> doFilter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
return Mono.empty();
}
}
private static class AsyncFilter extends TestFilter {
@Override
public Mono<Success> doFilter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
return doAsyncWork().flatMap(asyncResult -> {
logger.debug("Async result: " + asyncResult);
return chain.filter(exchange);
});
}
private Mono<String> doAsyncWork() {
return Mono.delay(Duration.ofMillis(100L)).map(l -> "123");
}
}
private static class ExceptionFilter implements SocketAcceptorFilter {
@Override
public Mono<Success> filter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
return Mono.error(new IllegalStateException("boo"));
}
}
/*
* private static class TestExceptionHandler implements WebExceptionHandler {
*
* private Throwable ex;
*
* @Override public Mono<Void> handle(SocketAcceptorExchange exchange, Throwable ex) {
* this.ex = ex; return Mono.error(ex); } }
*/
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.socketacceptor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.rsocket.ConnectionSetupPayload;
import io.rsocket.RSocket;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
public class SocketAcceptorPredicateFilterTests {
@Test
public void noPredicateWorks() {
Mono<Success> result = runFilter(Collections.emptyList());
StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete();
}
@Test
public void singleTruePredicateWorks() {
TestPredicate predicate = new TestPredicate(true);
Mono<Success> result = runFilter(predicate);
StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete();
assertThat(predicate.invoked()).isTrue();
}
@Test
public void singleFalsePredicateWorks() {
TestPredicate predicate = new TestPredicate(false);
Mono<Success> result = runFilter(predicate);
StepVerifier.create(result).verifyComplete();
assertThat(predicate.invoked()).isTrue();
}
@Test
public void multipleFalsePredicateWorks() {
TestPredicate predicate = new TestPredicate(false);
TestPredicate predicate2 = new TestPredicate(false);
Mono<Success> result = runFilter(predicate, predicate2);
StepVerifier.create(result).verifyComplete();
assertThat(predicate.invoked()).isTrue();
assertThat(predicate2.invoked()).isTrue(); // Async predicates don't short circuit
}
@Test
public void multiplePredicatesNoSuccessWorks() {
TestPredicate truePredicate = new TestPredicate(true);
TestPredicate falsePredicate = new TestPredicate(false);
Mono<Success> result = runFilter(truePredicate, falsePredicate);
StepVerifier.create(result).verifyComplete();
assertThat(truePredicate.invoked()).isTrue();
assertThat(falsePredicate.invoked()).isTrue();
}
@Test
public void multiplePredicatesSuccessWorks() {
TestPredicate truePredicate = new TestPredicate(true);
TestPredicate truePredicate2 = new TestPredicate(true);
Mono<Success> result = runFilter(truePredicate, truePredicate2);
StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete();
assertThat(truePredicate.invoked()).isTrue();
assertThat(truePredicate2.invoked()).isTrue();
}
private Mono<Success> runFilter(SocketAcceptorPredicate predicate) {
return runFilter(Collections.singletonList(predicate));
}
private Mono<Success> runFilter(SocketAcceptorPredicate... predicates) {
return runFilter(Arrays.asList(predicates));
}
private Mono<Success> runFilter(List<SocketAcceptorPredicate> predicates) {
SocketAcceptorPredicateFilter filter = new SocketAcceptorPredicateFilter(
predicates);
SocketAcceptorExchange exchange = new SocketAcceptorExchange(
mock(ConnectionSetupPayload.class), mock(RSocket.class));
SocketAcceptorFilterChain filterChain = new SocketAcceptorFilterChain(
Collections.singletonList(filter));
return filter.filter(exchange, filterChain);
}
private class TestPredicate implements SocketAcceptorPredicate {
private boolean invoked = false;
private final Mono<Boolean> test;
TestPredicate(boolean value) {
test = Mono.just(value);
}
@Override
public Publisher<Boolean> apply(SocketAcceptorExchange exchange) {
invoked = true;
return test;
}
public boolean invoked() {
return invoked;
}
}
}

View File

@@ -1,336 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.test;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.rsocket.Payload;
import io.rsocket.RSocket;
import io.rsocket.RSocketFactory;
import io.rsocket.frame.decoder.PayloadDecoder;
import io.rsocket.micrometer.MicrometerRSocketInterceptor;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.util.DefaultPayload;
import io.rsocket.util.RSocketProxy;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Hooks;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder;
import org.springframework.cloud.gateway.rsocket.core.GatewayExchange;
import org.springframework.cloud.gateway.rsocket.core.GatewayFilter;
import org.springframework.cloud.gateway.rsocket.core.GatewayFilterChain;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorExchange;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilterChain;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.messaging.rsocket.RSocketStrategies;
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
import static org.springframework.cloud.gateway.rsocket.common.metadata.Metadata.COMPOSITE_MIME_TYPE;
import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup.ROUTE_SETUP_MIME_TYPE;
@SpringBootApplication
public class PingPongApp {
@Bean
public Ping ping1() {
return new Ping(1L);
}
@Bean
@ConditionalOnProperty("ping.two.enabled")
public Ping ping2() {
return new Ping(2L);
}
@Bean
public Pong pong() {
return new Pong();
}
@Bean
public MySocketAcceptorFilter mySocketAcceptorFilter() {
return new MySocketAcceptorFilter();
}
public static void main(String[] args) {
Hooks.onOperatorDebug();
SpringApplication.run(PingPongApp.class, args);
}
static String reply(String in) {
if (in.length() > 4) {
in = in.substring(0, 4);
}
switch (in.toLowerCase()) {
case "ping":
return "pong";
case "pong":
return "ping";
default:
throw new IllegalArgumentException("Value must be ping or pong, not " + in);
}
}
static ByteBuf getRouteSetupMetadata(RSocketStrategies strategies, String name,
long id) {
RouteSetup routeSetup = RouteSetup.of(id, name)
.with("current-time", String.valueOf(System.currentTimeMillis())).build();
LinkedHashMap<TagsMetadata.Key, String> tags = new LinkedHashMap<>();
tags.put(new TagsMetadata.Key(WellKnownKey.TIME_ZONE),
System.currentTimeMillis() + "");
DataBuffer dataBuffer = new MetadataEncoder(COMPOSITE_MIME_TYPE, strategies)
.metadata(routeSetup, ROUTE_SETUP_MIME_TYPE).encode();
return TagsMetadata.asByteBuf(dataBuffer);
}
static ByteBuf getForwardingMetadata(RSocketStrategies strategies, String name,
long id) {
Forwarding metadata = Forwarding.of(id).serviceName(name).build();
DataBuffer dataBuffer = new MetadataEncoder(COMPOSITE_MIME_TYPE, strategies)
.metadata(metadata, Forwarding.FORWARDING_MIME_TYPE).encode();
return TagsMetadata.asByteBuf(dataBuffer);
}
@Slf4j
public static class Ping
implements Ordered, ApplicationListener<ApplicationReadyEvent> {
@Autowired
private MeterRegistry meterRegistry;
@Autowired
private RSocketStrategies strategies;
private final Long id;
private final AtomicInteger pongsReceived = new AtomicInteger();
private Flux<String> pongFlux;
public Ping(Long id) {
this.id = id;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
log.info("Starting Ping" + id);
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
Integer take = env.getProperty("ping.take", Integer.class, null);
Integer gatewayPort = env.getProperty("spring.rsocket.server.port",
Integer.class, 7002);
log.debug("ping.take: " + take);
MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(
meterRegistry, Tag.of("component", "ping"));
ByteBuf metadata = getRouteSetupMetadata(strategies, "ping", id);
Payload setupPayload = DefaultPayload.create(EMPTY_BUFFER, metadata);
pongFlux = RSocketFactory.connect().frameDecoder(PayloadDecoder.ZERO_COPY)
.metadataMimeType(COMPOSITE_MIME_TYPE.toString())
.setupPayload(setupPayload).addRequesterPlugin(interceptor)
.transport(TcpClientTransport.create(gatewayPort)) // proxy
.start().log("startPing" + id)
.flatMapMany(socket -> doPing(take, socket)).cast(String.class)
.doOnSubscribe(o -> {
if (log.isDebugEnabled()) {
log.debug("ping doOnSubscribe");
}
});
boolean subscribe = env.getProperty("ping.subscribe", Boolean.class, true);
if (subscribe) {
pongFlux.subscribe();
}
}
Publisher<? extends String> doPing(Integer take, RSocket socket) {
Flux<String> pong = socket
.requestChannel(Flux.interval(Duration.ofSeconds(1)).map(i -> {
ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT,
"ping" + id);
ByteBuf routingMetadata = getForwardingMetadata(strategies,
"pong", id);
log.debug("Sending ping" + id);
return DefaultPayload.create(data, routingMetadata);
// onBackpressure is needed in case pong is not available yet
}).log("doPing")
.onBackpressureDrop(payload -> log
.debug("Dropped payload " + payload.getDataUtf8())))
.map(Payload::getDataUtf8).doOnNext(str -> {
int received = pongsReceived.incrementAndGet();
log.info("received " + str + "(" + received + ") in Ping" + id);
}).doFinally(signal -> socket.dispose());
if (take != null) {
return pong.take(take);
}
return pong;
}
public Flux<String> getPongFlux() {
return pongFlux;
}
public int getPongsReceived() {
return pongsReceived.get();
}
}
@Slf4j
public static class Pong
implements Ordered, ApplicationListener<ApplicationReadyEvent> {
@Autowired
private MeterRegistry meterRegistry;
@Autowired
private RSocketStrategies strategies;
private final AtomicInteger pingsReceived = new AtomicInteger();
@Override
public int getOrder() {
return 1;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
Integer pongDelay = env.getProperty("pong.delay", Integer.class, 5000);
try {
Thread.sleep(pongDelay);
}
catch (InterruptedException e) {
e.printStackTrace();
}
log.info("Starting Pong");
Integer gatewayPort = env.getProperty("spring.rsocket.server.port",
Integer.class, 7002);
MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(
meterRegistry, Tag.of("component", "pong"));
ByteBuf announcementMetadata = getRouteSetupMetadata(strategies, "pong", 3L);
RSocketFactory.connect().metadataMimeType(COMPOSITE_MIME_TYPE.toString())
.setupPayload(
DefaultPayload.create(EMPTY_BUFFER, announcementMetadata))
.addRequesterPlugin(interceptor).acceptor(this::accept)
.transport(TcpClientTransport.create(gatewayPort)) // proxy
.start().block();
}
@SuppressWarnings("Duplicates")
RSocket accept(RSocket rSocket) {
RSocket pong = new RSocketProxy(rSocket) {
@Override
public Flux<Payload> requestChannel(Publisher<Payload> payloads) {
return Flux.from(payloads).map(Payload::getDataUtf8).doOnNext(str -> {
int received = pingsReceived.incrementAndGet();
log.info("received " + str + "(" + received + ") in Pong");
}).map(PingPongApp::reply).map(reply -> {
ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT,
reply);
ByteBuf routingMetadata = getForwardingMetadata(strategies,
"ping", 1L);
return DefaultPayload.create(data, routingMetadata);
});
}
};
return pong;
}
public int getPingsReceived() {
return pingsReceived.get();
}
}
@Slf4j
public static class MyGatewayFilter implements GatewayFilter {
private AtomicBoolean invoked = new AtomicBoolean(false);
@Override
public Mono<Success> filter(GatewayExchange exchange, GatewayFilterChain chain) {
log.info("in custom gateway filter");
invoked.compareAndSet(false, true);
return chain.filter(exchange);
}
public boolean invoked() {
return invoked.get();
}
}
@Slf4j
public static class MySocketAcceptorFilter implements SocketAcceptorFilter, Ordered {
private AtomicBoolean invoked = new AtomicBoolean(false);
@Override
public Mono<Success> filter(SocketAcceptorExchange exchange,
SocketAcceptorFilterChain chain) {
log.info("in custom socket acceptor filter");
invoked.compareAndSet(false, true);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
public boolean invoked() {
return invoked.get();
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2018-2019 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.cloud.gateway.rsocket.test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTable;
import org.springframework.cloud.gateway.rsocket.routing.RoutingTableSocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter;
import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicateFilter;
import org.springframework.core.OrderComparator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
public class SocketAcceptorFilterOrderTests {
@Test
public void predicateFilterAfterRegistryFilter() {
SocketAcceptorFilter predicateFilter = new SocketAcceptorPredicateFilter(
Collections.emptyList());
SocketAcceptorFilter registryFilter = new RoutingTableSocketAcceptorFilter(
mock(RoutingTable.class));
List<SocketAcceptorFilter> filters = Arrays.asList(predicateFilter,
registryFilter);
OrderComparator.sort(filters);
assertThat(filters).containsExactly(registryFilter, predicateFilter);
}
}

View File

@@ -1,17 +0,0 @@
logging:
level:
# org.springframework.cloud.gateway.rsocket: DEBUG
org.springframework.cloud.gateway.rsocket: TRACE
org.springframework.messaging.handler.invocation.reactive: TRACE
management:
endpoints:
web:
exposure:
include: '*'
spring:
cloud:
gateway:
rsocket:
route-id: 1234

View File

@@ -1,145 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2018-2019 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-client</artifactId>
<name>Spring Cloud Gateway RSocket Client</name>
<description>Spring Cloud Gateway RSocket Client</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>java8plus</id>
<activation>
<jdk>[1.8,2.0)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,76 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.util.function.Consumer;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.messaging.rsocket.RSocketRequester;
public class BrokerClient {
private final ClientProperties properties;
private final RSocketRequester.Builder builder;
public BrokerClient(ClientProperties properties, RSocketRequester.Builder builder) {
this.properties = properties;
this.builder = builder;
}
public ClientProperties getProperties() {
return this.properties;
}
public RSocketRequester.Builder getRSocketRequesterBuilder() {
return this.builder;
}
public Mono<RSocketRequester> connect() {
return connect(builder);
}
public Mono<RSocketRequester> connect(RSocketRequester.Builder requesterBuilder) {
Broker broker = properties.getBroker();
switch (broker.getConnectionType()) {
case WEBSOCKET:
return requesterBuilder.connectWebSocket(broker.getWsUri());
}
return requesterBuilder.connectTcp(broker.getHost(), broker.getPort());
}
public Consumer<RSocketRequester.MetadataSpec<?>> forwarding(String destServiceName) {
return spec -> {
Forwarding forwarding = Forwarding.of(properties.getRouteId())
.serviceName(destServiceName).build();
spec.metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE);
};
}
public Consumer<RSocketRequester.MetadataSpec<?>> forwarding(
Consumer<Forwarding.Builder> builderConsumer) {
return spec -> {
Forwarding.Builder builder = Forwarding.of(properties.getRouteId());
builderConsumer.accept(builder);
spec.metadata(builder.build(), Forwarding.FORWARDING_MIME_TYPE);
};
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.util.function.Consumer;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.messaging.rsocket.RSocketRequester;
/**
* Automatically subscribes to {@link BrokerClient}. On subscribe it publishes a
* {@link PayloadApplicationEvent} with a generic type of {@link RSocketRequester}.
*/
public class BrokerClientConnectionListener
implements ApplicationListener<ApplicationReadyEvent>, Ordered {
private final BrokerClient brokerClient;
private final ApplicationEventPublisher publisher;
public BrokerClientConnectionListener(BrokerClient brokerClient,
ApplicationEventPublisher publisher) {
this.brokerClient = brokerClient;
this.publisher = publisher;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// TODO: is there a better event the just RSocketRequester?
// TODO: save Disposable?
this.brokerClient.connect().subscribe(publishEvent());
}
private Consumer<RSocketRequester> publishEvent() {
return requester -> publisher.publishEvent(new RSocketRequesterEvent<>(
BrokerClientConnectionListener.this, requester));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // TODO: configurable
}
private static final class RSocketRequesterEvent<T extends RSocketRequester>
extends PayloadApplicationEvent<T> {
private RSocketRequesterEvent(Object source, T payload) {
super(source, payload);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forClass(RSocketRequester.class));
}
}
}

View File

@@ -1,180 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.cloud.gateway.rsocket.common.autoconfigure.Broker;
import org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("spring.cloud.gateway.rsocket.client")
@Validated
public class ClientProperties {
@NotNull
private BigInteger routeId;
@NotEmpty
private String serviceName;
private Map<TagKey, String> tags = new LinkedHashMap<>();
@Valid
@NestedConfigurationProperty
private Broker broker = new Broker();
private Map<String, Map<TagKey, String>> forwarding = new LinkedHashMap<>();
public BigInteger getRouteId() {
return this.routeId;
}
public void setRouteId(BigInteger routeId) {
this.routeId = routeId;
}
public String getServiceName() {
return this.serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public Map<TagKey, String> getTags() {
return tags;
}
public Broker getBroker() {
return this.broker;
}
public void setBroker(Broker broker) {
this.broker = broker;
}
public Map<String, Map<TagKey, String>> getForwarding() {
return forwarding;
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("routeId", routeId)
.append("serviceName", serviceName)
.append("tags", tags)
.append("broker", broker)
.append("forwarding", forwarding)
.toString();
// @formatter:on
}
public static class TagKey {
private WellKnownKey wellKnownKey;
private String customKey;
public TagKey() {
System.out.println("here");
}
public TagKey(String text) {
if (!StringUtils.isEmpty(text)) {
try {
wellKnownKey = WellKnownKey.valueOf(text.toUpperCase());
}
catch (IllegalArgumentException e) {
// NOT a valid well know key
customKey = text;
}
}
}
public static TagKey of(WellKnownKey key) {
TagKey tagKey = new TagKey();
tagKey.setWellKnownKey(key);
return tagKey;
}
public static TagKey of(String key) {
return new TagKey(key);
}
public WellKnownKey getWellKnownKey() {
return wellKnownKey;
}
public void setWellKnownKey(WellKnownKey wellKnownKey) {
this.wellKnownKey = wellKnownKey;
}
public String getCustomKey() {
return customKey;
}
public void setCustomKey(String customKey) {
this.customKey = customKey;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TagKey tag = (TagKey) o;
return wellKnownKey == tag.wellKnownKey
&& Objects.equals(customKey, tag.customKey);
}
@Override
public int hashCode() {
return Objects.hash(wellKnownKey, customKey);
}
@Override
public String toString() {
StringJoiner joiner = new StringJoiner(", ", "[", "]");
if (wellKnownKey != null) {
joiner.add(wellKnownKey.name());
}
if (customKey != null) {
joiner.add("'" + customKey + "'");
}
return joiner.toString();
}
}
}

View File

@@ -1,185 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.math.BigInteger;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.rsocket.RSocket;
import org.springframework.cloud.gateway.rsocket.client.ClientProperties.TagKey;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.lang.Nullable;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.ObjectUtils;
import org.springframework.util.RouteMatcher;
final class ClientRSocketRequester implements RSocketRequester {
/** For route variable replacement. */
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
private final RSocketRequester delegate;
private final ClientProperties properties;
private final RouteMatcher routeMatcher;
ClientRSocketRequester(RSocketRequester delegate, ClientProperties properties,
RouteMatcher routeMatcher) {
this.delegate = delegate;
this.properties = properties;
this.routeMatcher = routeMatcher;
}
@Override
public RSocket rsocket() {
return delegate.rsocket();
}
@Override
public MimeType dataMimeType() {
return delegate.dataMimeType();
}
@Override
public MimeType metadataMimeType() {
return delegate.metadataMimeType();
}
@Override
public RequestSpec route(String route, Object... routeVars) {
RequestSpec requestSpec = delegate.route(route, routeVars);
// needs to be expanded with routeVars
RouteMatcher.Route parsed = routeMatcher.parseRoute(expand(route, routeVars));
properties.getForwarding().entrySet().stream()
.filter(entry -> routeMatcher.match(entry.getKey(), parsed)).findFirst()
.ifPresent(entry -> {
Forwarding.Builder forwarding = forwarding(routeMatcher, parsed,
properties.getRouteId(), entry.getKey(), entry.getValue());
requestSpec.metadata(forwarding.build(),
Forwarding.FORWARDING_MIME_TYPE);
});
return requestSpec;
}
/* for testing */ static Forwarding.Builder forwarding(RouteMatcher routeMatcher,
RouteMatcher.Route route, BigInteger originRouteId, String routeKey,
Map<TagKey, String> tags) {
Map<String, String> extracted = routeMatcher.matchAndExtract(routeKey, route);
Forwarding.Builder forwarding = Forwarding.of(originRouteId);
tags.forEach((tagKey, value) -> {
if (tagKey.getWellKnownKey() != null) {
forwarding.with(tagKey.getWellKnownKey(), expand(value, extracted));
}
else if (tagKey.getCustomKey() != null) {
forwarding.with(tagKey.getCustomKey(), expand(value, extracted));
}
});
return forwarding;
}
@Override
public RequestSpec metadata(Object metadata, MimeType mimeType) {
return delegate.metadata(metadata, mimeType);
}
/* for testing */ static String expand(String route, Object... routeVars) {
if (ObjectUtils.isEmpty(routeVars)) {
return route;
}
StringBuffer sb = new StringBuffer();
int index = 0;
Matcher matcher = NAMES_PATTERN.matcher(route);
while (matcher.find()) {
Assert.isTrue(index < routeVars.length,
() -> "No value for variable '" + matcher.group(1) + "'");
String value = routeVars[index].toString();
value = value.contains(".") ? value.replaceAll("\\.", "%2E") : value;
matcher.appendReplacement(sb, value);
index++;
}
return sb.toString();
}
/* for testing */ static String expand(String template, Map<String, ?> vars) {
if (template == null) {
return null;
}
if (template.indexOf('{') == -1) {
return template;
}
if (template.indexOf(':') != -1) {
template = sanitizeSource(template);
}
if (ObjectUtils.isEmpty(vars)) {
return template;
}
StringBuffer sb = new StringBuffer();
Matcher matcher = NAMES_PATTERN.matcher(template);
while (matcher.find()) {
String match = matcher.group(1);
String varName = getVariableName(match);
Object varValue = vars.get(varName);
String formatted = getVariableValueAsString(varValue);
matcher.appendReplacement(sb, formatted);
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* Remove nested "{}" such as in URI vars with regular expressions.
*/
private static String sanitizeSource(String source) {
int level = 0;
StringBuilder sb = new StringBuilder();
for (char c : source.toCharArray()) {
if (c == '{') {
level++;
}
if (c == '}') {
level--;
}
if (level > 1 || (level == 1 && c == '}')) {
continue;
}
sb.append(c);
}
return sb.toString();
}
private static String getVariableName(String match) {
int colonIdx = match.indexOf(':');
return (colonIdx != -1 ? match.substring(0, colonIdx) : match);
}
private static String getVariableValueAsString(@Nullable Object variableValue) {
return (variableValue != null ? variableValue.toString() : "");
}
}

View File

@@ -1,112 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.net.URI;
import java.util.function.Consumer;
import io.rsocket.transport.ClientTransport;
import io.rsocket.transport.netty.client.TcpClientTransport;
import io.rsocket.transport.netty.client.WebsocketClientTransport;
import reactor.core.publisher.Mono;
import org.springframework.messaging.rsocket.ClientRSocketFactoryConfigurer;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.util.MimeType;
import org.springframework.util.RouteMatcher;
final class ClientRSocketRequesterBuilder implements RSocketRequester.Builder {
private final RSocketRequester.Builder delegate;
private final ClientProperties properties;
private final RouteMatcher routeMatcher;
ClientRSocketRequesterBuilder(RSocketRequester.Builder delegate,
ClientProperties properties, RouteMatcher routeMatcher) {
this.delegate = delegate;
this.properties = properties;
this.routeMatcher = routeMatcher;
}
@Override
public RSocketRequester.Builder dataMimeType(MimeType mimeType) {
return delegate.dataMimeType(mimeType);
}
@Override
public RSocketRequester.Builder metadataMimeType(MimeType mimeType) {
return delegate.metadataMimeType(mimeType);
}
@Override
public RSocketRequester.Builder setupData(Object data) {
return delegate.setupData(data);
}
@Override
public RSocketRequester.Builder setupRoute(String route, Object... routeVars) {
return delegate.setupRoute(route, routeVars);
}
@Override
public RSocketRequester.Builder setupMetadata(Object value, MimeType mimeType) {
return delegate.setupMetadata(value, mimeType);
}
@Override
public RSocketRequester.Builder rsocketStrategies(RSocketStrategies strategies) {
return delegate.rsocketStrategies(strategies);
}
@Override
public RSocketRequester.Builder rsocketStrategies(
Consumer<RSocketStrategies.Builder> configurer) {
return delegate.rsocketStrategies(configurer);
}
@Override
public RSocketRequester.Builder rsocketFactory(
ClientRSocketFactoryConfigurer configurer) {
return delegate.rsocketFactory(configurer);
}
@Override
public RSocketRequester.Builder apply(Consumer<RSocketRequester.Builder> configurer) {
return delegate.apply(configurer);
}
@Override
public Mono<RSocketRequester> connectTcp(String host, int port) {
return connect(TcpClientTransport.create(host, port));
}
@Override
public Mono<RSocketRequester> connectWebSocket(URI uri) {
return connect(WebsocketClientTransport.create(uri));
}
@Override
public Mono<RSocketRequester> connect(ClientTransport transport) {
return delegate.connect(transport)
.map(requester -> new ClientRSocketRequester(requester, properties,
routeMatcher));
}
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.math.BigInteger;
import java.util.function.Supplier;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.rsocket.RSocket;
import io.rsocket.micrometer.MicrometerRSocketInterceptor;
import io.rsocket.plugins.RSocketInterceptor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration;
import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.rsocket.ClientRSocketFactoryConfigurer;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
import static org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration.ID_GENERATOR_BEAN_NAME;
/**
* @author Spencer Gibb
*/
@Configuration
// TODO: add this property to config metadata
@ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled",
matchIfMissing = true)
@EnableConfigurationProperties
@ConditionalOnClass({ RSocket.class, RSocketRequester.class })
@AutoConfigureAfter(RSocketStrategiesAutoConfiguration.class)
@AutoConfigureBefore(RSocketRequesterAutoConfiguration.class)
public class GatewayRSocketClientAutoConfiguration {
private final RSocketMessageHandler messageHandler;
public GatewayRSocketClientAutoConfiguration(RSocketMessageHandler handler) {
messageHandler = handler;
}
@Bean
@Scope("prototype") // TODO: I don't think prototype works here
@ConditionalOnMissingBean
public RSocketRequester.Builder gatewayRSocketRequesterBuilder(
RSocketStrategies strategies, ClientProperties properties,
MeterRegistry meterRegistry) {
RouteSetup.Builder routeSetup = RouteSetup.of(properties.getRouteId(),
properties.getServiceName());
properties.getTags().forEach((key, value) -> {
if (key.getWellKnownKey() != null) {
routeSetup.with(key.getWellKnownKey(), value);
}
else if (key.getCustomKey() != null) {
routeSetup.with(key.getCustomKey(), value);
}
});
MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(
meterRegistry, Tag.of("servicename", properties.getServiceName()));
RSocketRequester.Builder builder = RSocketRequester.builder()
.setupMetadata(routeSetup.build(), RouteSetup.ROUTE_SETUP_MIME_TYPE)
.rsocketStrategies(strategies).rsocketFactory(configurer(interceptor));
return new ClientRSocketRequesterBuilder(builder, properties,
strategies.routeMatcher());
}
private ClientRSocketFactoryConfigurer configurer(RSocketInterceptor interceptor) {
return rsocketFactory -> rsocketFactory.addRequesterPlugin(interceptor)
.acceptor(messageHandler.responder());
}
@Bean
public BrokerClient brokerClient(RSocketRequester.Builder builder,
ClientProperties properties) {
return new BrokerClient(properties, builder);
}
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.client.auto-connect",
matchIfMissing = true)
public BrokerClientConnectionListener brokerClientConnectionListener(
BrokerClient client, ApplicationEventPublisher publisher) {
return new BrokerClientConnectionListener(client, publisher);
}
@Bean
public ClientProperties clientProperties(
@Qualifier(ID_GENERATOR_BEAN_NAME) Supplier<BigInteger> idGenerator) {
ClientProperties clientProperties = new ClientProperties();
clientProperties.setRouteId(idGenerator.get());
return clientProperties;
}
}

View File

@@ -1,3 +0,0 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.rsocket.client.GatewayRSocketClientAutoConfiguration

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.rsocket.client.ClientProperties.TagKey;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT,
properties = "spring.cloud.gateway.rsocket.client.auto-connect=false")
public class ClientPropertiesTests {
@Autowired
ClientProperties properties;
@Test
public void clientProperties() {
assertThat(properties).isNotNull();
assertThat(properties.getRouteId()).isEqualTo(11L);
assertThat(properties.getServiceName()).isEqualTo("test_requester");
assertThat(properties.getTags()).containsEntry(TagKey.of("INSTANCE_NAME"),
"test_requester1");
assertThat(properties.getForwarding()).containsKey("test_responder-rc");
Map<TagKey, String> map = properties.getForwarding().get("test_responder-rc");
assertThat(map).contains(entry(TagKey.of("SERVICE_NAME"), "test_responder"),
entry(TagKey.of("custom-tag"), "custom-value"));
assertThat(properties.getBroker()).isNotNull().extracting("host", "port")
.containsExactly("localhost", 7002);
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class Config {
}
}

View File

@@ -1,108 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.client;
import java.math.BigInteger;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.gateway.rsocket.client.ClientProperties.TagKey;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.RouteMatcher;
import org.springframework.util.SimpleRouteMatcher;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.rsocket.client.ClientRSocketRequester.expand;
import static org.springframework.cloud.gateway.rsocket.client.ClientRSocketRequester.forwarding;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME;
public class ClientRSocketRequesterTests {
@Test
public void forwardingWorks() {
RouteMatcher routeMatcher = new SimpleRouteMatcher(new AntPathMatcher("."));
RouteMatcher.Route route = routeMatcher.parseRoute("myroute.foo1.bar1");
LinkedHashMap<TagKey, String> tags = new LinkedHashMap<>();
tags.put(TagKey.of(SERVICE_NAME), "{foo}");
tags.put(TagKey.of(ROUTE_ID), "22");
tags.put(TagKey.of("mycustomkey"), "{foo}-{bar}");
Forwarding fwd = (Forwarding) forwarding(routeMatcher, route,
new BigInteger("11"), "myroute.{foo}.{bar}", tags).build();
assertThat(fwd).isNotNull();
assertThat(fwd.getEnrichedTagsMetadata().getTags()).isNotEmpty()
.containsEntry(new Key(SERVICE_NAME), "foo1")
.containsEntry(new Key(ROUTE_ID), "22")
.containsEntry(new Key("mycustomkey"), "foo1-bar1");
}
@Test
public void expandArrayVars() {
String result = expand("myroute.{foo}.{bar}", "foo1", "bar1");
assertThat(result).isEqualTo("myroute.foo1.bar1");
}
@Test
public void expandMapVars() {
HashMap<String, Object> map = new HashMap<>();
map.put("value", "a+b");
map.put("city", "Z\u00fcrich");
String result = expand("/hotel list/{city} specials/{value}", map);
assertThat(result).isEqualTo("/hotel list/Z\u00fcrich specials/a+b");
}
@Test
public void expandPartially() {
HashMap<String, Object> map = new HashMap<>();
map.put("city", "Z\u00fcrich");
String result = expand("/hotel list/{city} specials/{value}", map);
assertThat(result).isEqualTo("/hotel list/Zürich specials/");
}
@Test
public void expandSimple() {
HashMap<String, Object> map = new HashMap<>();
map.put("foo", "1 2");
map.put("bar", "3 4");
String result = expand("/{foo} {bar}", map);
assertThat(result).isEqualTo("/1 2 3 4");
}
@Test // SPR-13311
public void expandWithRegexVar() {
String template = "/myurl/{name:[a-z]{1,5}}/show";
Map<String, String> map = Collections.singletonMap("name", "test");
String result = expand(template, map);
assertThat(result).isEqualTo("/myurl/test/show");
}
@Test // SPR-17630
public void expandWithMismatchedCurlyBraces() {
String result = expand("/myurl/{{{{", Collections.emptyMap());
assertThat(result).isEqualTo("/myurl/{{{{");
}
}

View File

@@ -1,12 +0,0 @@
spring.cloud.gateway.rsocket.client:
route-id: 11
service-name: test_requester
tags:
INSTANCE_NAME: test_requester1
forwarding:
test_responder-rc:
service_name: test_responder
custom-tag: custom-value
broker:
host: localhost
port: 7002

View File

@@ -1,161 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2018-2019 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-rsocket-common</artifactId>
<name>Spring Cloud Gateway RSocket Common</name>
<description>Spring Cloud Gateway RSocket Common</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-rsocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.rsocket</groupId>
<artifactId>rsocket-core</artifactId>
</dependency>
<dependency>
<groupId>io.rsocket</groupId>
<artifactId>rsocket-micrometer</artifactId>
</dependency>
<dependency>
<groupId>io.rsocket</groupId>
<artifactId>rsocket-transport-netty</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>RoaringBitmap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<!-- Replacing default-testCompile as it is treated specially by maven -->
<execution>
<id>default-testCompile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>java-test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>java8plus</id>
<activation>
<jdk>[1.8,2.0)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.autoconfigure;
import java.net.URI;
import javax.validation.constraints.NotNull;
import org.springframework.core.style.ToStringCreator;
public class Broker {
public enum ConnectionType {
/** TCP RSocket connection. */
TCP,
/** WEBSOCKET RSocket connection. */
WEBSOCKET
}
// FIXME: validate based on connectionType
private String host;
private int port;
@NotNull
private ConnectionType connectionType = ConnectionType.TCP;
private URI wsUri;
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public ConnectionType getConnectionType() {
return this.connectionType;
}
public void setConnectionType(ConnectionType connectionType) {
this.connectionType = connectionType;
}
public URI getWsUri() {
return this.wsUri;
}
public void setWsUri(URI wsUri) {
this.wsUri = wsUri;
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("host", host)
.append("port", port)
.append("wsUri", wsUri)
.append("connectionType", connectionType)
.toString();
// @formatter:on
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.autoconfigure;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.function.Supplier;
import io.rsocket.RSocket;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled",
matchIfMissing = true)
@EnableConfigurationProperties
@ConditionalOnClass(RSocket.class)
@AutoConfigureBefore(RSocketStrategiesAutoConfiguration.class)
public class GatewayRSocketCommonAutoConfiguration {
/**
* Name of id generator bean.
*/
public static final String ID_GENERATOR_BEAN_NAME = "gatewayRSocketIdGenerator";
private final SecureRandom secureRandom = new SecureRandom();
@Bean
public RSocketStrategiesCustomizer gatewayRSocketStrategiesCustomizer() {
return strategies -> {
strategies.decoder(new Forwarding.Decoder(), new RouteSetup.Decoder())
.encoder(new Forwarding.Encoder(), new RouteSetup.Encoder());
};
}
@Bean(name = ID_GENERATOR_BEAN_NAME)
@ConditionalOnMissingBean(name = ID_GENERATOR_BEAN_NAME)
public Supplier<BigInteger> gatewayRSocketIdGenerator() {
return () -> {
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
return new BigInteger(bytes);
};
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.autoconfigure;
import io.rsocket.RSocket;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding;
import org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.rsocket.DefaultMetadataExtractor;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.messaging.rsocket.RSocketStrategies;
import static org.springframework.cloud.gateway.rsocket.common.metadata.Forwarding.FORWARDING_MIME_TYPE;
import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetup.ROUTE_SETUP_MIME_TYPE;
/**
* @author Spencer Gibb
*/
@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.rsocket.enabled",
matchIfMissing = true)
@EnableConfigurationProperties
@ConditionalOnClass(RSocket.class)
@AutoConfigureAfter({ GatewayRSocketCommonAutoConfiguration.class })
public class GatewayRSocketCommonMetadataAutoConfiguration implements InitializingBean {
private final ApplicationContext context;
public GatewayRSocketCommonMetadataAutoConfiguration(ApplicationContext context) {
this.context = context;
}
@Override
public void afterPropertiesSet() {
RSocketStrategies rSocketStrategies = this.context
.getBean(RSocketStrategies.class);
MetadataExtractor metadataExtractor = rSocketStrategies.metadataExtractor();
// TODO: see if possible to make easier in framework.
if (metadataExtractor instanceof DefaultMetadataExtractor) {
DefaultMetadataExtractor extractor = (DefaultMetadataExtractor) metadataExtractor;
extractor.metadataToExtract(FORWARDING_MIME_TYPE, Forwarding.class,
Forwarding.METADATA_KEY);
extractor.metadataToExtract(ROUTE_SETUP_MIME_TYPE, RouteSetup.class,
RouteSetup.METADATA_KEY);
}
}
}

View File

@@ -1,220 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.AbstractDecoder;
import org.springframework.core.codec.AbstractEncoder;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME;
// TODO: currently an ENVELOPE frame in RSocket extension, also discarding metadata
public final class Forwarding extends TagsMetadata {
/**
* Forwarding metadata key.
*/
public static final String METADATA_KEY = "forwarding";
/**
* Forwarding subtype.
*/
public static final String FORWARDING = "x.rsocket.forwarding.v0";
/**
* Forwarding mimetype.
*/
public static final MimeType FORWARDING_MIME_TYPE = new MimeType("message",
FORWARDING);
private final BigInteger originRouteId;
private Forwarding(BigInteger originRouteId, Map<TagsMetadata.Key, String> tags) {
super(tags);
this.originRouteId = originRouteId;
}
public static Builder of(long originRouteId) {
return of(BigInteger.valueOf(originRouteId));
}
public static Builder of(BigInteger originRouteId) {
return new Builder(originRouteId);
}
public BigInteger getOriginRouteId() {
return this.originRouteId;
}
public ByteBuf encode() {
return encode(this);
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("originRouteId", originRouteId)
.append("tags", getTags())
.toString();
// @formatter:on
}
static ByteBuf encode(Forwarding forwarding) {
return encode(ByteBufAllocator.DEFAULT, forwarding);
}
static ByteBuf encode(ByteBufAllocator allocator, Forwarding forwarding) {
Assert.notNull(forwarding, "forwarding may not be null");
Assert.notNull(allocator, "allocator may not be null");
ByteBuf byteBuf = allocator.buffer();
encodeBigInteger(byteBuf, forwarding.originRouteId);
encode(byteBuf, forwarding.getTags());
return byteBuf;
}
static Forwarding decodeForwarding(ByteBuf byteBuf) {
AtomicInteger offset = new AtomicInteger(0);
BigInteger originRouteId = decodeBigInteger(byteBuf, offset);
TagsMetadata tagsMetadata = decode(offset, byteBuf);
Forwarding forwarding = new Forwarding(originRouteId, tagsMetadata.getTags());
return forwarding;
}
public static class Encoder extends AbstractEncoder<Forwarding> {
public Encoder() {
super(Forwarding.FORWARDING_MIME_TYPE);
}
@Override
public Flux<DataBuffer> encode(Publisher<? extends Forwarding> inputStream,
DataBufferFactory bufferFactory, ResolvableType elementType,
MimeType mimeType, Map<String, Object> hints) {
throw new UnsupportedOperationException("stream encoding not supported.");
}
@Override
public DataBuffer encodeValue(Forwarding value, DataBufferFactory bufferFactory,
ResolvableType valueType, MimeType mimeType, Map<String, Object> hints) {
NettyDataBufferFactory factory = (NettyDataBufferFactory) bufferFactory;
ByteBuf encoded = Forwarding.encode(factory.getByteBufAllocator(), value);
return factory.wrap(encoded);
}
}
public static class Decoder extends AbstractDecoder<Forwarding> {
public Decoder() {
super(Forwarding.FORWARDING_MIME_TYPE);
}
@Override
public Flux<Forwarding> decode(Publisher<DataBuffer> inputStream,
ResolvableType elementType, MimeType mimeType,
Map<String, Object> hints) {
throw new UnsupportedOperationException("stream decoding not supported.");
}
@Override
public Forwarding decode(DataBuffer buffer, ResolvableType targetType,
MimeType mimeType, Map<String, Object> hints) throws DecodingException {
ByteBuf byteBuf = TagsMetadata.asByteBuf(buffer);
return Forwarding.decodeForwarding(byteBuf);
}
}
public final static class Builder {
private final BigInteger originRouteId;
private final TagsMetadata.Builder tagsBuilder = TagsMetadata.builder();
private Builder(BigInteger originRouteId) {
// Assert.notNull(id, "id may not be null");
this.originRouteId = originRouteId;
}
public Builder with(String key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(WellKnownKey key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(Key key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(TagsMetadata tagsMetadata) {
tagsBuilder.with(tagsMetadata);
return this;
}
public Builder routeId(String routeId) {
tagsBuilder.with(ROUTE_ID, routeId);
return this;
}
public Builder serviceName(String serviceName) {
tagsBuilder.with(SERVICE_NAME, serviceName);
return this;
}
public Builder disableProxy() {
// TODO: move to well known implementation key
tagsBuilder.with("proxy", Boolean.FALSE.toString());
return this;
}
public Forwarding build() {
return new Forwarding(originRouteId, tagsBuilder.build().getTags());
}
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import io.rsocket.metadata.WellKnownMimeType;
import org.springframework.util.MimeType;
public abstract class Metadata {
/**
* Composite Metadata MimeType.
*/
public static final MimeType COMPOSITE_MIME_TYPE = MimeType
.valueOf(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.toString());
}

View File

@@ -1,228 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.math.BigInteger;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.AbstractDecoder;
import org.springframework.core.codec.AbstractEncoder;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
public final class RouteSetup extends TagsMetadata {
/**
* Forwarding metadata key.
*/
public static final String METADATA_KEY = "routesetup";
/**
* Route Setup subtype.
*/
public static final String ROUTE_SETUP = "x.rsocket.routesetup.v0";
/**
* Route Setup mime type.
*/
public static final MimeType ROUTE_SETUP_MIME_TYPE = new MimeType("message",
ROUTE_SETUP);
private final BigInteger id;
private final String serviceName;
private RouteSetup(BigInteger id, String serviceName, Map<Key, String> tags) {
super(tags);
this.id = id;
this.serviceName = serviceName;
}
public BigInteger getId() {
return this.id;
}
public String getServiceName() {
return this.serviceName;
}
public ByteBuf encode() {
return encode(this);
}
@Override
public TagsMetadata getEnrichedTagsMetadata() {
// @formatter:off
TagsMetadata tagsMetadata = TagsMetadata.builder(this)
.with(WellKnownKey.SERVICE_NAME, getServiceName())
.with(WellKnownKey.ROUTE_ID, id == null ? null : getId().toString())
.build();
// @formatter:on
return tagsMetadata;
}
@Override
public String toString() {
// @formatter:off
return new ToStringCreator(this)
.append("id", id)
.append("serviceName", serviceName)
.append("tags", getTags())
.toString();
// @formatter:on
}
public static Builder of(BigInteger id, String serviceName) {
return new Builder(id, serviceName);
}
public static Builder of(Long id, String serviceName) {
return of(BigInteger.valueOf(id), serviceName);
}
static ByteBuf encode(RouteSetup routeSetup) {
return encode(ByteBufAllocator.DEFAULT, routeSetup);
}
static ByteBuf encode(ByteBufAllocator allocator, RouteSetup routeSetup) {
Assert.notNull(routeSetup, "routeSetup may not be null");
Assert.notNull(allocator, "allocator may not be null");
ByteBuf byteBuf = allocator.buffer();
encodeBigInteger(byteBuf, routeSetup.id);
encodeString(byteBuf, routeSetup.getServiceName());
encode(byteBuf, routeSetup.getTags());
return byteBuf;
}
static RouteSetup decodeRouteSetup(ByteBuf byteBuf) {
AtomicInteger offset = new AtomicInteger(0);
BigInteger id = decodeBigInteger(byteBuf, offset);
String serviceName = decodeString(byteBuf, offset);
TagsMetadata tagsMetadata = decode(offset, byteBuf);
RouteSetup routeSetup = new RouteSetup(id, serviceName, tagsMetadata.getTags());
return routeSetup;
}
public static class Encoder extends AbstractEncoder<RouteSetup> {
public Encoder() {
super(ROUTE_SETUP_MIME_TYPE);
}
@Override
public Flux<DataBuffer> encode(Publisher<? extends RouteSetup> inputStream,
DataBufferFactory bufferFactory, ResolvableType elementType,
MimeType mimeType, Map<String, Object> hints) {
throw new UnsupportedOperationException("stream encoding not supported.");
}
@Override
public DataBuffer encodeValue(RouteSetup value, DataBufferFactory bufferFactory,
ResolvableType valueType, MimeType mimeType, Map<String, Object> hints) {
NettyDataBufferFactory factory = (NettyDataBufferFactory) bufferFactory;
ByteBuf encoded = RouteSetup.encode(factory.getByteBufAllocator(), value);
return factory.wrap(encoded);
}
}
public static class Decoder extends AbstractDecoder<RouteSetup> {
public Decoder() {
super(ROUTE_SETUP_MIME_TYPE);
}
@Override
public Flux<RouteSetup> decode(Publisher<DataBuffer> inputStream,
ResolvableType elementType, MimeType mimeType,
Map<String, Object> hints) {
throw new UnsupportedOperationException("stream decoding not supported.");
}
@Override
public RouteSetup decode(DataBuffer buffer, ResolvableType targetType,
MimeType mimeType, Map<String, Object> hints) throws DecodingException {
ByteBuf byteBuf = TagsMetadata.asByteBuf(buffer);
return RouteSetup.decodeRouteSetup(byteBuf);
}
}
public static final class Builder {
private final BigInteger id;
private final String serviceName;
private final TagsMetadata.Builder tagsBuilder = TagsMetadata.builder();
private Builder(BigInteger id, String serviceName) {
// Assert.notNull(id, "id may not be null");
// Assert.hasText(serviceName, "serviceName may not be empty");
this.id = id;
this.serviceName = serviceName;
}
public Builder with(String key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(WellKnownKey key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(Key key, String value) {
tagsBuilder.with(key, value);
return this;
}
public Builder with(TagsMetadata tagsMetadata) {
tagsBuilder.with(tagsMetadata);
return this;
}
public RouteSetup build() {
return new RouteSetup(id, serviceName, tagsBuilder.build().getTags());
}
}
}

View File

@@ -1,356 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.rsocket.util.NumberUtils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.util.Assert;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME;
public class TagsMetadata {
private static final Key ROUTE_ID_KEY = new Key(ROUTE_ID);
private static final int WELL_KNOWN_TAG = 0x80;
private static final int HAS_MORE_TAGS = 0x80;
private static final int MAX_TAG_LENGTH = 0x7F;
private final Map<Key, String> tags;
protected TagsMetadata(Map<Key, String> tags) {
this.tags = tags;
}
public static ByteBuf asByteBuf(DataBuffer buffer) {
return buffer instanceof NettyDataBuffer
? ((NettyDataBuffer) buffer).getNativeBuffer()
: Unpooled.wrappedBuffer(buffer.asByteBuffer());
}
public Map<Key, String> getTags() {
return this.tags;
}
public String getRouteId() {
return this.tags.get(ROUTE_ID_KEY);
}
public String get(WellKnownKey key) {
return this.tags.get(new Key(key));
}
public String put(Key key, String value) {
return this.tags.put(key, value);
}
/**
* Allows subclasses to enrich tags before use.
* @return by default, this.
*/
public TagsMetadata getEnrichedTagsMetadata() {
return this;
}
@Override
public String toString() {
return "TagsMetadata" + tags;
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(TagsMetadata existing) {
Builder builder = new Builder();
return builder.with(existing);
}
protected static ByteBuf encode(TagsMetadata metadata) {
return encode(ByteBufAllocator.DEFAULT, metadata.tags);
}
protected static ByteBuf encode(ByteBufAllocator allocator, Map<Key, String> tags) {
Assert.notNull(tags, "tags may not be null");
Assert.notNull(allocator, "allocator may not be null");
ByteBuf byteBuf = allocator.buffer();
return encode(byteBuf, tags);
}
protected static ByteBuf encode(ByteBuf byteBuf, Map<Key, String> tags) {
Assert.notNull(byteBuf, "byteBuf may not be null");
Iterator<Map.Entry<Key, String>> it = tags.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Key, String> entry = it.next();
Key key = entry.getKey();
if (key.wellKnownKey != null) {
byte id = key.wellKnownKey.getIdentifier();
int keyLength = WELL_KNOWN_TAG | id;
byteBuf.writeByte(keyLength);
}
else {
String keyString = key.key;
if (keyString == null) {
continue;
}
int keyLength = ByteBufUtil.utf8Bytes(keyString);
if (keyLength == 0 || keyLength > MAX_TAG_LENGTH) {
continue;
}
byteBuf.writeByte(keyLength);
ByteBufUtil.reserveAndWriteUtf8(byteBuf, keyString, keyLength);
}
boolean hasMoreTags = it.hasNext();
String value = entry.getValue();
int valueLength = ByteBufUtil.utf8Bytes(value);
if (valueLength == 0 || valueLength > MAX_TAG_LENGTH) {
continue;
}
int valueByte;
if (hasMoreTags) {
valueByte = HAS_MORE_TAGS | valueLength;
}
else {
valueByte = valueLength;
}
byteBuf.writeByte(valueByte);
ByteBufUtil.reserveAndWriteUtf8(byteBuf, value, valueLength);
}
return byteBuf;
}
protected static void encodeBigInteger(ByteBuf byteBuf, BigInteger bigInteger) {
byte[] idBytes = bigInteger.toByteArray();
// truncate or pad to 16 bytes or 128 bits
// byte[] normalizedBytes = Arrays.copyOf(idBytes, 16);
byte[] normalizedBytes = new byte[16];
// right shift
int destPos = normalizedBytes.length - idBytes.length;
System.arraycopy(idBytes, 0, normalizedBytes, destPos, idBytes.length);
byteBuf.writeBytes(normalizedBytes);
}
protected static void encodeString(ByteBuf byteBuf, String s) {
int length = NumberUtils.requireUnsignedByte(ByteBufUtil.utf8Bytes(s));
byteBuf.writeByte(length);
ByteBufUtil.reserveAndWriteUtf8(byteBuf, s, length);
}
protected static TagsMetadata decode(ByteBuf byteBuf) {
AtomicInteger offset = new AtomicInteger(0);
return decode(offset, byteBuf);
}
protected static TagsMetadata decode(AtomicInteger offset, ByteBuf byteBuf) {
Builder builder = TagsMetadata.builder();
// this means we've reached the end of the buffer
if (offset.get() >= byteBuf.writerIndex()) {
return builder.build();
}
boolean hasMoreTags = true;
while (hasMoreTags) {
int keyByte = byteBuf.getByte(offset.get());
offset.addAndGet(Byte.BYTES);
boolean isWellKnownTag = (keyByte & WELL_KNOWN_TAG) == WELL_KNOWN_TAG;
int keyLength = keyByte & MAX_TAG_LENGTH;
Key key;
if (isWellKnownTag) {
WellKnownKey wellKnownKey = WellKnownKey.fromIdentifier(keyLength);
key = new Key(wellKnownKey, null);
}
else {
String keyString = byteBuf.toString(offset.get(), keyLength,
StandardCharsets.UTF_8);
offset.addAndGet(keyLength);
key = new Key(null, keyString);
}
int valueByte = byteBuf.getByte(offset.get());
offset.addAndGet(Byte.BYTES);
hasMoreTags = (valueByte & HAS_MORE_TAGS) == HAS_MORE_TAGS;
int valueLength = valueByte & MAX_TAG_LENGTH;
String value = byteBuf.toString(offset.get(), valueLength,
StandardCharsets.UTF_8);
offset.addAndGet(valueLength);
builder.with(key, value);
}
return builder.build();
}
protected static BigInteger decodeBigInteger(ByteBuf byteBuf, AtomicInteger offset) {
byte[] idBytes = new byte[16];
byteBuf.getBytes(offset.get(), idBytes, 0, 16);
offset.getAndAdd(16);
return new BigInteger(idBytes);
}
protected static long decodeLong(ByteBuf byteBuf, AtomicInteger offset) {
return byteBuf.getLong(offset.getAndAdd(8));
}
protected static String decodeString(ByteBuf byteBuf, AtomicInteger offset) {
int length = byteBuf.getByte(offset.get());
int index = offset.addAndGet(Byte.BYTES);
String s = byteBuf.toString(index, length, StandardCharsets.UTF_8);
offset.addAndGet(length);
return s;
}
public static class Builder {
private final TagsMetadata metadata;
public Builder() {
this.metadata = new TagsMetadata(new LinkedHashMap<>());
}
public Builder with(String key, String value) {
Assert.notNull(key, "key may not be null");
return with(new Key(key), value);
}
public Builder with(WellKnownKey key, String value) {
Assert.notNull(key, "key may not be null");
return with(new Key(key), value);
}
public Builder with(Key key, String value) {
Assert.notNull(key, "key may not be null");
this.metadata.put(key, value);
return this;
}
public Builder routeId(String routeId) {
Assert.notNull(routeId, "routeId may not be null");
return with(ROUTE_ID, routeId);
}
public Builder serviceName(String serviceName) {
Assert.notNull(serviceName, "serviceName may not be null");
return with(SERVICE_NAME, serviceName);
}
public Builder with(TagsMetadata tagsMetadata) {
this.metadata.getTags().putAll(tagsMetadata.getTags());
return this;
}
public TagsMetadata build() {
return this.metadata;
}
public ByteBuf encode() {
return TagsMetadata.encode(build());
}
}
public static class Key {
private final WellKnownKey wellKnownKey;
private final String key;
public Key(WellKnownKey wellKnownKey) {
this(wellKnownKey, null);
}
public Key(String key) {
this(null, key);
}
public Key(WellKnownKey wellKnownKey, String key) {
this.wellKnownKey = wellKnownKey;
this.key = key;
}
public WellKnownKey getWellKnownKey() {
return this.wellKnownKey;
}
public String getKey() {
return this.key;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Key key1 = (Key) o;
return this.wellKnownKey == key1.wellKnownKey
&& Objects.equals(this.key, key1.key);
}
@Override
public int hashCode() {
return Objects.hash(this.wellKnownKey, this.key);
}
@Override
public String toString() {
StringJoiner joiner = new StringJoiner(", ", "[", "]");
if (wellKnownKey != null) {
joiner.add(wellKnownKey.toString());
joiner.add(String.format("0x%02x", wellKnownKey.getIdentifier()));
}
if (key != null) {
joiner.add("'" + key + "'");
}
return joiner.toString();
}
}
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public enum WellKnownKey {
// CHECKSTYLE:OFF
// @formatter:off
UNPARSEABLE_KEY("UNPARSEABLE_KEY_DO_NOT_USE", (byte) -2),
UNKNOWN_RESERVED_KEY("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1),
NO_TAG("NO_TAG_DO_NOT_USE", (byte) 0x00),
SERVICE_NAME("io.rsocket.routing.ServiceName", (byte) 0x01),
ROUTE_ID("io.rsocket.routing.RouteId", (byte) 0x02),
INSTANCE_NAME("io.rsocket.routing.InstanceName", (byte) 0x03),
CLUSTER_NAME("io.rsocket.routing.ClusterName", (byte) 0x04),
PROVIDER("io.rsocket.routing.Provider", (byte) 0x05),
REGION("io.rsocket.routing.Region", (byte) 0x06),
ZONE("io.rsocket.routing.Zone", (byte) 0x07),
DEVICE("io.rsocket.routing.Device", (byte) 0x08),
OS("io.rsocket.routing.OS", (byte) 0x09),
USER_NAME("io.rsocket.routing.UserName", (byte) 0x0A),
USER_ID("io.rsocket.routing.UserId", (byte) 0x0B),
MAJOR_VERSION("io.rsocket.routing.MajorVersion", (byte) 0x0C),
MINOR_VERSION("io.rsocket.routing.MinorVersion", (byte) 0x0D),
PATCH_VERSION("io.rsocket.routing.PatchVersion", (byte) 0x0E),
VERSION("io.rsocket.routing.Version", (byte) 0x0F),
ENVIRONMENT("io.rsocket.routing.Environment", (byte) 0x10),
TESTC_ELL("io.rsocket.routing.TestCell", (byte) 0x11),
DNS("io.rsocket.routing.DNS", (byte) 0x12),
IPV4("io.rsocket.routing.IPv4", (byte) 0x13),
IPV6("io.rsocket.routing.IPv6", (byte) 0x14),
COUNTRY("io.rsocket.routing.Country", (byte) 0x15),
TIME_ZONE("io.rsocket.routing.TimeZone", (byte) 0x1A),
SHARD_KEY("io.rsocket.routing.ShardKey", (byte) 0x1B),
SHARD_METHOD("io.rsocket.routing.ShardMethod", (byte) 0x1C),
STICKY_ROUTE_KEY("io.rsocket.routing.StickyRouteKey", (byte) 0x1D),
LB_METHOD("io.rsocket.routing.LBMethod", (byte) 0x1E),
BROKER_EXTENSION("Broker Implementation Extension Key", (byte) 0x1E),
WELL_KNOWN_EXTENSION("Well Known Extension Key", (byte) 0x1E);
// @formatter:on
// CHECKSTYLE:ON
static final WellKnownKey[] TYPES_BY_ID;
static final Map<String, WellKnownKey> TYPES_BY_STRING;
static {
// precompute an array of all valid mime ids,
// filling the blanks with the RESERVED enum
TYPES_BY_ID = new WellKnownKey[128]; // 0-127 inclusive
Arrays.fill(TYPES_BY_ID, UNKNOWN_RESERVED_KEY);
// also prepare a Map of the types by key string
TYPES_BY_STRING = new HashMap<>(128);
for (WellKnownKey value : values()) {
if (value.getIdentifier() >= 0) {
TYPES_BY_ID[value.getIdentifier()] = value;
TYPES_BY_STRING.put(value.getString(), value);
}
}
}
private final byte identifier;
private final String str;
WellKnownKey(String str, byte identifier) {
this.str = str;
this.identifier = identifier;
}
public static WellKnownKey fromIdentifier(int id) {
if (id < 0x00 || id > 0x7F) {
return UNPARSEABLE_KEY;
}
return TYPES_BY_ID[id];
}
public static WellKnownKey fromMimeType(String mimeType) {
if (mimeType == null) {
throw new IllegalArgumentException("type must be non-null");
}
// force UNPARSEABLE if by chance UNKNOWN_RESERVED_MIME_TYPE's text has been used
if (mimeType.equals(UNKNOWN_RESERVED_KEY.str)) {
return UNPARSEABLE_KEY;
}
return TYPES_BY_STRING.getOrDefault(mimeType, UNPARSEABLE_KEY);
}
/**
* @return the byte identifier of the mime type, guaranteed to be positive or zero.
*/
public byte getIdentifier() {
return identifier;
}
/**
* @return the mime type represented as a {@link String}, which is made of US_ASCII
* compatible characters only
*/
public String getString() {
return str;
}
/** @see #getString() */
@Override
public String toString() {
return str;
}
}

View File

@@ -1,4 +0,0 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonAutoConfiguration,\
org.springframework.cloud.gateway.rsocket.common.autoconfigure.GatewayRSocketCommonMetadataAutoConfiguration

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.util.Map;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.rsocket.Payload;
import io.rsocket.util.DefaultPayload;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.rsocket.server.port=0", webEnvironment = RANDOM_PORT)
public class ForwardingIntegrationTests extends ForwardingTests {
@Autowired
private RSocketStrategies strategies;
@Override
protected ByteBuf encode(Forwarding forwarding) {
DataBuffer dataBuffer = new MetadataEncoder(Metadata.COMPOSITE_MIME_TYPE,
strategies).metadata(forwarding, Forwarding.FORWARDING_MIME_TYPE)
.encode();
return TagsMetadata.asByteBuf(dataBuffer);
}
@Override
protected Forwarding decode(ByteBuf byteBuf) {
MetadataExtractor metadataExtractor = strategies.metadataExtractor();
Payload payload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, byteBuf);
Map<String, Object> metadata = metadataExtractor.extract(payload,
Metadata.COMPOSITE_MIME_TYPE);
assertThat(metadata).containsKey(Forwarding.METADATA_KEY);
return (Forwarding) metadata.get(Forwarding.METADATA_KEY);
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class Config {
}
}

View File

@@ -1,75 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import io.netty.buffer.ByteBuf;
import org.junit.Test;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetupTests.MAX_BIGINT;
import static org.springframework.cloud.gateway.rsocket.common.metadata.RouteSetupTests.TWO_BYTE_BIGINT;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.REGION;
public class ForwardingTests {
@Test
public void encodeAndDecodeWorksMaxBigint() {
ByteBuf byteBuf = createForwarding(MAX_BIGINT);
assertForwarding(byteBuf, MAX_BIGINT);
}
@Test
public void encodeAndDecodeWorksMinBigint() {
ByteBuf byteBuf = createForwarding(BigInteger.ONE);
assertForwarding(byteBuf, BigInteger.ONE);
}
@Test
public void encodeAndDecodeWorksTwoBytes() {
ByteBuf byteBuf = createForwarding(TWO_BYTE_BIGINT);
assertForwarding(byteBuf, TWO_BYTE_BIGINT);
}
protected ByteBuf createForwarding(BigInteger originRouteId) {
LinkedHashMap<Key, String> tags = new LinkedHashMap<>();
Forwarding forwarding = Forwarding.of(originRouteId).with(REGION, "us-east-1")
.build();
return encode(forwarding);
}
protected ByteBuf encode(Forwarding forwarding) {
return forwarding.encode();
}
protected void assertForwarding(ByteBuf byteBuf, BigInteger originRouteId) {
Forwarding forwarding = decode(byteBuf);
assertThat(forwarding).isNotNull();
assertThat(forwarding.getOriginRouteId()).isEqualTo(originRouteId);
assertThat(forwarding.getTags()).hasSize(1).containsOnlyKeys(new Key(REGION))
.containsValues("us-east-1");
}
protected Forwarding decode(ByteBuf byteBuf) {
return Forwarding.decodeForwarding(byteBuf);
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.util.Map;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.rsocket.Payload;
import io.rsocket.util.DefaultPayload;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.rsocket.common.test.MetadataEncoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.rsocket.server.port=0", webEnvironment = RANDOM_PORT)
public class RouteSetupIntegrationTests extends RouteSetupTests {
@Autowired
private RSocketStrategies strategies;
@Override
protected ByteBuf encode(RouteSetup routeSetup) {
DataBuffer dataBuffer = new MetadataEncoder(Metadata.COMPOSITE_MIME_TYPE,
strategies).metadata(routeSetup, RouteSetup.ROUTE_SETUP_MIME_TYPE)
.encode();
return TagsMetadata.asByteBuf(dataBuffer);
}
@Override
protected RouteSetup decode(ByteBuf byteBuf) {
MetadataExtractor metadataExtractor = strategies.metadataExtractor();
Payload payload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, byteBuf);
Map<String, Object> metadata = metadataExtractor.extract(payload,
Metadata.COMPOSITE_MIME_TYPE);
assertThat(metadata).containsKey(RouteSetup.METADATA_KEY);
return (RouteSetup) metadata.get(RouteSetup.METADATA_KEY);
}
@SpringBootConfiguration
@EnableAutoConfiguration
static class Config {
}
}

View File

@@ -1,111 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import java.math.BigInteger;
import io.netty.buffer.ByteBuf;
import org.junit.Test;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.REGION;
public class RouteSetupTests {
static final BigInteger MAX_BIGINT = new BigInteger(
"170141183460469231731687303715884105727");
static final BigInteger TWO_BYTE_BIGINT = new BigInteger("128");
@Test
public void bigIntegerTest() {
byte[] bytes = MAX_BIGINT.toByteArray();
System.out.println("max bytes: " + bytes.length);
bytes = BigInteger.ONE.toByteArray();
System.out.println("min bytes: " + bytes.length);
BigInteger bigInteger = TWO_BYTE_BIGINT;
bytes = bigInteger.toByteArray();
System.out.println("16 bytes: " + bytes.length);
}
@Test
public void encodeAndDecodeWorksMaxBigint() {
ByteBuf byteBuf = createRouteSetup(MAX_BIGINT);
assertRouteSetup(byteBuf, MAX_BIGINT);
}
@Test
public void encodeAndDecodeWorksMinBigint() {
ByteBuf byteBuf = createRouteSetup(BigInteger.ONE);
assertRouteSetup(byteBuf, BigInteger.ONE);
}
@Test
public void encodeAndDecodeWorksTwoBytes() {
ByteBuf byteBuf = createRouteSetup(TWO_BYTE_BIGINT);
assertRouteSetup(byteBuf, TWO_BYTE_BIGINT);
}
@Test
public void encodeAndDecodeWorksEmptyTags() {
ByteBuf byteBuf = createRouteSetup(TWO_BYTE_BIGINT, false);
assertRouteSetup(byteBuf, TWO_BYTE_BIGINT, false);
}
protected ByteBuf createRouteSetup(BigInteger id) {
return createRouteSetup(id, true);
}
protected ByteBuf createRouteSetup(BigInteger id, boolean addTags) {
RouteSetup.Builder routeSetup = RouteSetup.of(id, "myservice11111111");
if (addTags) {
routeSetup.with(REGION, "us-east-1");
}
return encode(routeSetup.build());
}
protected ByteBuf encode(RouteSetup routeSetup) {
return routeSetup.encode();
}
protected void assertRouteSetup(ByteBuf byteBuf, BigInteger routeId) {
assertRouteSetup(byteBuf, routeId, true);
}
protected void assertRouteSetup(ByteBuf byteBuf, BigInteger routeId,
boolean addTags) {
RouteSetup routeSetup = decode(byteBuf);
assertThat(routeSetup).isNotNull();
assertThat(routeSetup.getId()).isEqualTo(routeId);
assertThat(routeSetup.getServiceName()).isEqualTo("myservice11111111");
if (addTags) {
assertThat(routeSetup.getTags()).hasSize(1).containsOnlyKeys(new Key(REGION))
.containsValues("us-east-1");
}
else {
assertThat(routeSetup.getTags()).isEmpty();
}
}
protected RouteSetup decode(ByteBuf byteBuf) {
return RouteSetup.decodeRouteSetup(byteBuf);
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.metadata;
import io.netty.buffer.ByteBuf;
import org.junit.Test;
import org.springframework.cloud.gateway.rsocket.common.metadata.TagsMetadata.Key;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.ROUTE_ID;
import static org.springframework.cloud.gateway.rsocket.common.metadata.WellKnownKey.SERVICE_NAME;
public class TagsMetadataTests {
@Test
public void encodeAndDecodeWorksAllWellKnowKeys() {
ByteBuf byteBuf = TagsMetadata.builder().with(ROUTE_ID, "routeId1111111")
.with(SERVICE_NAME, "serviceName2222222").encode();
TagsMetadata metadata = TagsMetadata.decode(byteBuf);
assertThat(metadata).isNotNull();
assertThat(metadata.getTags()).hasSize(2)
.containsOnlyKeys(new Key(ROUTE_ID), new Key(SERVICE_NAME))
.containsValues("routeId1111111", "serviceName2222222");
}
@Test
public void encodeAndDecodeWorksAllStringKeys() {
ByteBuf byteBuf = TagsMetadata.builder().with("mykey111111111", "myval1111111")
.with("mykey2222222222", "myval2222222").encode();
TagsMetadata metadata = TagsMetadata.decode(byteBuf);
assertThat(metadata).isNotNull();
assertThat(metadata.getTags()).hasSize(2)
.containsOnlyKeys(new Key("mykey111111111"), new Key("mykey2222222222"))
.containsValues("myval1111111", "myval2222222");
}
@Test
public void encodeAndDecodeWorksMixedKeys() {
ByteBuf byteBuf = TagsMetadata.builder().with(ROUTE_ID, "routeId1111111")
.with("mykey2222222222", "myval2222222").encode();
TagsMetadata metadata = TagsMetadata.decode(byteBuf);
assertThat(metadata).isNotNull();
assertThat(metadata.getTags()).hasSize(2)
.containsOnlyKeys(new Key(ROUTE_ID), new Key("mykey2222222222"))
.containsValues("routeId1111111", "myval2222222");
}
}

View File

@@ -1,256 +0,0 @@
/*
* Copyright 2013-2019 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.cloud.gateway.rsocket.common.test;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.rsocket.metadata.CompositeMetadataFlyweight;
import io.rsocket.metadata.TaggingMetadataFlyweight;
import io.rsocket.metadata.WellKnownMimeType;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Encoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.lang.Nullable;
import org.springframework.messaging.rsocket.MetadataExtractor;
import org.springframework.messaging.rsocket.PayloadUtils;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.messaging.rsocket.RSocketStrategies;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeType;
import org.springframework.util.ObjectUtils;
/**
* Helps to collect metadata values and mime types, and encode them. TODO: remove if
* framework class is made public.
*
* {@link MetadataExtractor} original.
*
* @author Rossen Stoyanchev
*/
public class MetadataEncoder {
/** For route variable replacement. */
private static final Pattern VARS_PATTERN = Pattern.compile("\\{([^/]+?)}");
private final MimeType metadataMimeType;
private final RSocketStrategies strategies;
private final boolean isComposite;
private final ByteBufAllocator allocator;
@Nullable
private String route;
private final Map<Object, MimeType> metadata = new LinkedHashMap<>(4);
public MetadataEncoder(MimeType metadataMimeType, RSocketStrategies strategies) {
Assert.notNull(metadataMimeType, "'metadataMimeType' is required");
Assert.notNull(strategies, "RSocketStrategies is required");
this.metadataMimeType = metadataMimeType;
this.strategies = strategies;
this.isComposite = this.metadataMimeType.toString()
.equals(WellKnownMimeType.MESSAGE_RSOCKET_COMPOSITE_METADATA.getString());
this.allocator = bufferFactory() instanceof NettyDataBufferFactory
? ((NettyDataBufferFactory) bufferFactory()).getByteBufAllocator()
: ByteBufAllocator.DEFAULT;
}
private DataBufferFactory bufferFactory() {
return this.strategies.dataBufferFactory();
}
/**
* Set the route to a remote handler as described in
* {@link RSocketRequester#route(String, Object...)}.
*/
public MetadataEncoder route(String route, Object... routeVars) {
this.route = expand(route, routeVars);
assertMetadataEntryCount();
return this;
}
private static String expand(String route, Object... routeVars) {
if (ObjectUtils.isEmpty(routeVars)) {
return route;
}
StringBuffer sb = new StringBuffer();
int index = 0;
Matcher matcher = VARS_PATTERN.matcher(route);
while (matcher.find()) {
Assert.isTrue(index < routeVars.length,
() -> "No value for variable '" + matcher.group(1) + "'");
String value = routeVars[index].toString();
value = value.contains(".") ? value.replaceAll("\\.", "%2E") : value;
matcher.appendReplacement(sb, value);
index++;
}
return sb.toString();
}
private void assertMetadataEntryCount() {
if (!this.isComposite) {
int count = this.route != null ? this.metadata.size() + 1
: this.metadata.size();
Assert.isTrue(count < 2,
"Composite metadata required for multiple metadata entries.");
}
}
/**
* Add a metadata entry. If called more than once or in addition to route, composite
* metadata must be in use.
*/
public MetadataEncoder metadata(Object metadata, @Nullable MimeType mimeType) {
if (this.isComposite) {
Assert.notNull(mimeType,
"MimeType is required for composite metadata entries.");
}
else if (mimeType == null) {
mimeType = this.metadataMimeType;
}
else if (!this.metadataMimeType.equals(mimeType)) {
throw new IllegalArgumentException("Mime type is optional (may be null) "
+ "but was provided and does not match the connection metadata mime type.");
}
this.metadata.put(metadata, mimeType);
assertMetadataEntryCount();
return this;
}
/**
* Add route and/or metadata, both optional.
*/
public MetadataEncoder metadataAndOrRoute(@Nullable Map<Object, MimeType> metadata,
@Nullable String route, @Nullable Object[] vars) {
if (route != null) {
this.route = expand(route, vars != null ? vars : new Object[0]);
}
if (!CollectionUtils.isEmpty(metadata)) {
for (Map.Entry<Object, MimeType> entry : metadata.entrySet()) {
metadata(entry.getKey(), entry.getValue());
}
}
assertMetadataEntryCount();
return this;
}
/**
* Encode the collected metadata entries to a {@code DataBuffer}.
* @see PayloadUtils#createPayload(DataBuffer, DataBuffer)
*/
public DataBuffer encode() {
if (this.isComposite) {
CompositeByteBuf composite = this.allocator.compositeBuffer();
try {
if (this.route != null) {
CompositeMetadataFlyweight.encodeAndAddMetadata(composite,
this.allocator, WellKnownMimeType.MESSAGE_RSOCKET_ROUTING,
encodeRoute());
}
this.metadata.forEach((value, mimeType) -> {
ByteBuf metadata = (value instanceof ByteBuf ? (ByteBuf) value
: asByteBuf(encodeEntry(value, mimeType)));
CompositeMetadataFlyweight.encodeAndAddMetadata(composite,
this.allocator, mimeType.toString(), metadata);
});
return asDataBuffer(composite);
}
catch (Throwable ex) {
composite.release();
throw ex;
}
}
else if (this.route != null) {
Assert.isTrue(this.metadata.isEmpty(),
"Composite metadata required for route and other entries");
String routingMimeType = WellKnownMimeType.MESSAGE_RSOCKET_ROUTING
.getString();
return this.metadataMimeType.toString().equals(routingMimeType)
? asDataBuffer(encodeRoute())
: encodeEntry(this.route, this.metadataMimeType);
}
else {
Assert.isTrue(this.metadata.size() == 1,
"Composite metadata required for multiple entries");
Map.Entry<Object, MimeType> entry = this.metadata.entrySet().iterator()
.next();
if (!this.metadataMimeType.equals(entry.getValue())) {
throw new IllegalArgumentException(
"Connection configured for metadata mime type " + "'"
+ this.metadataMimeType + "', but actual is `"
+ this.metadata + "`");
}
return encodeEntry(entry.getKey(), entry.getValue());
}
}
private ByteBuf encodeRoute() {
return TaggingMetadataFlyweight.createRoutingMetadata(this.allocator,
Collections.singletonList(this.route)).getContent();
}
@SuppressWarnings("unchecked")
private <T> DataBuffer encodeEntry(Object metadata, MimeType mimeType) {
if (metadata instanceof ByteBuf) {
return asDataBuffer((ByteBuf) metadata);
}
ResolvableType type = ResolvableType.forInstance(metadata);
Encoder<T> encoder = this.strategies.encoder(type, mimeType);
Assert.notNull(encoder, () -> "No encoder for metadata " + metadata
+ ", mimeType '" + mimeType + "'");
return encoder.encodeValue((T) metadata, bufferFactory(), type, mimeType,
Collections.emptyMap());
}
private DataBuffer asDataBuffer(ByteBuf byteBuf) {
if (bufferFactory() instanceof NettyDataBufferFactory) {
return ((NettyDataBufferFactory) bufferFactory()).wrap(byteBuf);
}
else {
DataBuffer buffer = bufferFactory().wrap(byteBuf.nioBuffer());
byteBuf.release();
return buffer;
}
}
public static DataBuffer emptyDataBuffer(RSocketStrategies rSocketStrategies) {
return rSocketStrategies.dataBufferFactory().wrap(new byte[0]);
}
static ByteBuf asByteBuf(DataBuffer buffer) {
return buffer instanceof NettyDataBuffer
? ((NettyDataBuffer) buffer).getNativeBuffer()
: Unpooled.wrappedBuffer(buffer.asByteBuffer());
}
}