Removes RSocket support temporarily.
Fixes gh-1366 Moved to https://github.com/spring-cloud-incubator/spring-cloud-rsocket
This commit is contained in:
10
pom.xml
10
pom.xml
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Void> 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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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); } }
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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); } }
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() : "");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.gateway.rsocket.client.GatewayRSocketClientAutoConfiguration
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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/{{{{");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user