From 6c2a29c422cf455705901588c3163892b7b15182 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 23 Jan 2019 17:00:39 -0800 Subject: [PATCH 01/67] Initial commit --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 202 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..228c8f0c --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# spring-cloud-gateway-rsocket \ No newline at end of file From d7f83f9ac39096f842a00fa3d1885a8c46fa7d6d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 23 Jan 2019 17:43:39 -0800 Subject: [PATCH 02/67] Adds skeleton project files for Spring Cloud Gateway RSocket --- .gitignore | 25 ++ .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 48337 bytes .mvn/wrapper/maven-wrapper.properties | 1 + README.adoc | 1 + README.md | 1 - mvnw | 267 ++++++++++++++++++ mvnw.cmd | 142 ++++++++++ pom.xml | 89 ++++++ .../GatewayRSocket.java | 33 +++ .../GatewaySocketAcceptor.java | 32 +++ .../GatewayRSocketTests.java | 34 +++ src/test/resources/application.yml | 0 12 files changed, 624 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 README.adoc delete mode 100644 README.md create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java create mode 100644 src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java create mode 100644 src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java create mode 100644 src/test/resources/application.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..82eca336 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01e67997377a393fd672c7dcde9dccbedf0cb1e9 GIT binary patch literal 48337 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 00000000..c70e06c2 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,142 @@ +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" +FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..8b508a0f --- /dev/null +++ b/pom.xml @@ -0,0 +1,89 @@ + + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.2.RELEASE + + + org.springframework.cloud.rsocket + spring-cloud-gateway-rsocket + 0.0.1-SNAPSHOT + spring-cloud-gateway-rsocket + Demo project for Spring Boot + + + 1.8 + 0.11.15 + Californium-SR4 + + + + + + io.projectreactor + reactor-bom + Californium-SR4 + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + io.rsocket + rsocket-core + ${rsocket.version} + + + io.rsocket + rsocket-micrometer + ${rsocket.version} + + + io.rsocket + rsocket-transport-netty + ${rsocket.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + + + + + diff --git a/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java new file mode 100644 index 00000000..24847eef --- /dev/null +++ b/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java @@ -0,0 +1,33 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.rsocket.springcloudgatewayrsocket; + +import io.rsocket.AbstractRSocket; +import io.rsocket.Payload; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; + +public class GatewayRSocket extends AbstractRSocket { + + @Override + public Flux requestChannel(Publisher payloads) { + //TODO: implement requestChannel() + return super.requestChannel(payloads); + } +} + diff --git a/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java new file mode 100644 index 00000000..10b3cdb7 --- /dev/null +++ b/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java @@ -0,0 +1,32 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.rsocket.springcloudgatewayrsocket; + +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import reactor.core.publisher.Mono; + +public class GatewaySocketAcceptor implements SocketAcceptor { + + @Override + public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { + // TODO: implement accept() + return Mono.empty(); + } +} diff --git a/src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java new file mode 100644 index 00000000..c4a099ee --- /dev/null +++ b/src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java @@ -0,0 +1,34 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.rsocket.springcloudgatewayrsocket; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class GatewayRSocketTests { + + @Test + public void contextLoads() { + } + +} + diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 00000000..e69de29b From cb38d1a217975ad0e516f2234faa83a4fdeea9ca Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 24 Jan 2019 11:01:13 -0800 Subject: [PATCH 03/67] Adds test PingPongApp --- pom.xml | 10 + .../cloud/gateway/rsocket/GatewayRSocket.java | 88 +++++++ .../rsocket}/GatewaySocketAcceptor.java | 24 +- .../cloud/gateway/rsocket/Metadata.java | 82 ++++++ .../rsocket/Registry.java} | 32 ++- .../rsocket}/GatewayRSocketTests.java | 2 +- .../gateway/rsocket/test/PingPongApp.java | 237 ++++++++++++++++++ src/test/resources/application.yml | 3 + 8 files changed, 463 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java rename src/main/java/org/springframework/cloud/{rsocket/springcloudgatewayrsocket => gateway/rsocket}/GatewaySocketAcceptor.java (60%) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java rename src/main/java/org/springframework/cloud/{rsocket/springcloudgatewayrsocket/GatewayRSocket.java => gateway/rsocket/Registry.java} (50%) rename src/test/java/org/springframework/cloud/{rsocket/springcloudgatewayrsocket => gateway/rsocket}/GatewayRSocketTests.java (93%) create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java diff --git a/pom.xml b/pom.xml index 8b508a0f..2db731e4 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,16 @@ rsocket-transport-netty ${rsocket.version} + + org.springframework.boot + spring-boot-starter-actuator + test + + + org.projectlombok + lombok + test + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java new file mode 100644 index 00000000..c98abe51 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -0,0 +1,88 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import java.util.List; + +import io.rsocket.AbstractRSocket; +import io.rsocket.Payload; +import io.rsocket.RSocket; +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.util.CollectionUtils; + +public class GatewayRSocket extends AbstractRSocket { + + private static final Log log = LogFactory.getLog(GatewayRSocket.class); + + private final String id; + private final Registry registry; + + public GatewayRSocket(String id, Registry registry) { + this.id = id; + this.registry = registry; + } + + @Override + public Flux requestChannel(Publisher payloads) { + log.debug("Entering requestChannel: " + id); + return Flux.from(payloads) + // .log("gateway rc b4son1") + .switchOnFirst((signal, payloadFlux) -> { + if (signal.hasValue()) { + Payload payload = signal.get(); + + Mono rsocket = discover(payload); + + return rsocket//.log("gateway rc discovered rsocket") + .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); + // .log("gateway rc proxy rc")); + } + return Flux.empty(); + }); + } + + private Mono discover(Payload payload) { + + if (payload == null || !payload.hasMetadata()) { // and metadata is routing + return Mono.empty(); + } + + // TODO: deal with composite metadata + + List tags = Metadata.decodeRouting(payload.sliceMetadata()); + + if (CollectionUtils.isEmpty(tags)) { + return Mono.empty(); + } + + log.debug("discovered service " + tags); + + Mono rsocket = registry.find(tags); + + // TODO: deal with connecting to cluster? + + // if not connected previously, initialize connection + return rsocket; + } +} + diff --git a/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java similarity index 60% rename from src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 10b3cdb7..196d142e 100644 --- a/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -15,18 +15,36 @@ * */ -package org.springframework.cloud.rsocket.springcloudgatewayrsocket; +package org.springframework.cloud.gateway.rsocket; import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import reactor.core.publisher.Mono; +import java.util.UUID; + public class GatewaySocketAcceptor implements SocketAcceptor { + private final Registry registry; + + public GatewaySocketAcceptor(Registry registry) { + this.registry = registry; + } + @Override public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - // TODO: implement accept() - return Mono.empty(); + + String id = null; + if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata + String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); + //TODO: key generation + registry.put(annoucementMetadata, Mono.just(sendingSocket)); + id = annoucementMetadata; + } else { + id = UUID.randomUUID().toString(); + } + + return Mono.just(new GatewayRSocket(id, registry)); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java new file mode 100644 index 00000000..4a3de661 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java @@ -0,0 +1,82 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.rsocket.util.NumberUtils; + +import org.springframework.util.Assert; + +public abstract class Metadata { + + public static final String ROUTING_MIME_TYPE = "message/x.rsocket.routing.v0"; + + private Metadata() {} + + public static ByteBuf encodeAnnouncement(String announcement) { + return encodeAnnouncement(ByteBufAllocator.DEFAULT, announcement); + } + + public static ByteBuf encodeAnnouncement(ByteBufAllocator allocator, String announcement) { + ByteBuf byteBuf = allocator.buffer(); + ByteBufUtil.writeUtf8(byteBuf, announcement); + return byteBuf; + } + + //TODO: either rsocket spec or custom spec + public static String decodeAnnouncement(ByteBuf metadata) { + return metadata.toString(StandardCharsets.UTF_8); + } + + public static ByteBuf encodeRouting(String... tags) { + return encodeRouting(ByteBufAllocator.DEFAULT, tags); + } + + public static ByteBuf encodeRouting(ByteBufAllocator allocator, String... tags) { + Assert.notEmpty(tags, "tags may not be null or empty"); //TODO: is this true? + ByteBuf byteBuf = allocator.buffer(); + + for (String tag : tags) { + int tagLength = NumberUtils.requireUnsignedByte(ByteBufUtil.utf8Bytes(tag)); + byteBuf.writeByte(tagLength); + ByteBufUtil.reserveAndWriteUtf8(byteBuf, tag, tagLength); + } + return byteBuf; + } + + public static List decodeRouting(ByteBuf byteBuf) { + ArrayList tags = new ArrayList<>(); + + int offset = 0; + while (offset < byteBuf.readableBytes()) { //TODO: What is the best conditional here? + int tagLength = byteBuf.getByte(offset); + offset += Byte.BYTES; + String tag = byteBuf.toString(offset, tagLength, StandardCharsets.UTF_8); + tags.add(tag); + offset += tagLength; + } + + return tags; + } +} diff --git a/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java similarity index 50% rename from src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 24847eef..a7e0e4fe 100644 --- a/src/main/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -15,19 +15,29 @@ * */ -package org.springframework.cloud.rsocket.springcloudgatewayrsocket; +package org.springframework.cloud.gateway.rsocket; -import io.rsocket.AbstractRSocket; -import io.rsocket.Payload; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; -public class GatewayRSocket extends AbstractRSocket { +import io.rsocket.RSocket; +import reactor.core.publisher.Mono; - @Override - public Flux requestChannel(Publisher payloads) { - //TODO: implement requestChannel() - return super.requestChannel(payloads); +import org.springframework.util.CollectionUtils; + +public class Registry { + + //TODO: List> + private ConcurrentHashMap> rsockets = new ConcurrentHashMap<>(); + + public void put(String key, Mono rsocket) { + rsockets.put(key, rsocket); + } + + public Mono find(List tags) { + if (CollectionUtils.isEmpty(tags)) { + return Mono.empty(); + } + return rsockets.get(tags.get(0)); } } - diff --git a/src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java similarity index 93% rename from src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java rename to src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java index c4a099ee..c87eb919 100644 --- a/src/test/java/org/springframework/cloud/rsocket/springcloudgatewayrsocket/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.rsocket.springcloudgatewayrsocket; +package org.springframework.cloud.gateway.rsocket; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java new file mode 100644 index 00000000..e1b84da1 --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -0,0 +1,237 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.test; + +import java.time.Duration; + +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.SocketAcceptor; +import io.rsocket.micrometer.MicrometerRSocketInterceptor; +import io.rsocket.transport.netty.client.TcpClientTransport; +import io.rsocket.transport.netty.server.CloseableChannel; +import io.rsocket.transport.netty.server.TcpServerTransport; +import io.rsocket.util.DefaultPayload; +import io.rsocket.util.RSocketProxy; +import lombok.extern.log4j.Log4j2; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +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.context.event.ApplicationReadyEvent; +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.Metadata; +import org.springframework.cloud.gateway.rsocket.Registry; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; + +import static io.netty.buffer.Unpooled.EMPTY_BUFFER; + +@SpringBootApplication +public class PingPongApp { + + @Bean + public Ping ping() { + return new Ping(); + } + + @Bean + public Pong pong() { + return new Pong(); + } + + //TODO: move to auto-configuration + @Bean + public Registry registry() { + return new Registry(); + } + + //TODO: move to auto-configuration + @Bean + public GatewaySocketAcceptor socketAcceptor(Registry registry) { + return new GatewaySocketAcceptor(registry); + } + + @Bean + public GatewayApp gatewayApp(GatewaySocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { + return new GatewayApp(socketAcceptor, meterRegistry); + } + + public static void main(String[] args) { + SpringApplication.run(PingPongApp.class, args); + } + + @Log4j2 + static class GatewayApp implements Ordered, ApplicationListener { + + private final SocketAcceptor socketAcceptor; + private final MeterRegistry meterRegistry; + private MicrometerRSocketInterceptor interceptor; + + public GatewayApp(SocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { + this.socketAcceptor = socketAcceptor; + this.meterRegistry = meterRegistry; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + log.info("Starting Gateway App"); + interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "proxy")); + TcpServerTransport transport = TcpServerTransport.create(7002); + + Mono rsocketServer = RSocketFactory.receive() + .addServerPlugin(interceptor) + .acceptor(this.socketAcceptor) + .transport(transport) + .start(); + + rsocketServer.subscribe(); + } + } + + 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); + } + } + + @Log4j2 + static class Ping implements Ordered, ApplicationListener { + + @Autowired + MeterRegistry meterRegistry; + + private String id = ""; + + public Ping() { + } + + public Ping(String id) { + this.id = id; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + log.info("Starting Ping"+id); + MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); + ByteBuf announcementMetadata = Metadata.encodeAnnouncement("ping"); + RSocketFactory.connect() + .metadataMimeType(Metadata.ROUTING_MIME_TYPE) + .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) + .addClientPlugin(interceptor) + .transport(TcpClientTransport.create(7002)) // proxy + .start() + .flatMapMany(socket -> + socket.requestChannel( + Flux.interval(Duration.ofSeconds(1)) + .map(i -> { + ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "ping" + id); + ByteBuf routingMetadata = Metadata.encodeRouting("pong"); + return DefaultPayload.create(data, routingMetadata); + }) + ).map(Payload::getDataUtf8) + .doOnNext(str -> log.info("received " + str + " in Ping"+id)) + // .take(10) + .doFinally(signal -> socket.dispose()) + ) + .then() + .subscribe(); + } + } + + @Log4j2 + static class Pong implements Ordered, ApplicationListener { + + @Autowired + MeterRegistry meterRegistry; + + @Override + public int getOrder() { + return 1; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + log.info("Starting Pong"); + MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); + ByteBuf announcementMetadata = Metadata.encodeAnnouncement("pong"); + RSocketFactory.connect() + .metadataMimeType(Metadata.ROUTING_MIME_TYPE) + .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) + .addClientPlugin(interceptor) + .acceptor(this::accept) + .transport(TcpClientTransport.create(7002)) // proxy + .start() + .then() + .block(); + } + + @SuppressWarnings("Duplicates") + RSocket accept(RSocket rSocket) { + RSocket pong = new RSocketProxy(rSocket) { + + @Override + public Flux requestChannel(Publisher payloads) { + return Flux.from(payloads) + .map(Payload::getDataUtf8) + .doOnNext(str -> log.info("received " + str + " in Pong")) + .map(PingPongApp::reply) + .map(reply -> { + ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, reply); + ByteBuf routingMetadata = Metadata.encodeRouting("ping"); + return DefaultPayload.create(data, routingMetadata); + }); + } + }; + return pong; + } + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index e69de29b..7fed6c08 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -0,0 +1,3 @@ +logging: + level: + org.springframework.cloud.gateway.rsocket: DEBUG \ No newline at end of file From cdf64c155dc9b545e1bf35d8614b8bef7ae8fcef Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 24 Jan 2019 11:19:05 -0800 Subject: [PATCH 04/67] Removed Mono from Registry --- .../cloud/gateway/rsocket/GatewayRSocket.java | 20 +++++++++++-------- .../rsocket/GatewaySocketAcceptor.java | 2 +- .../cloud/gateway/rsocket/Registry.java | 11 +++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index c98abe51..7565a9a3 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -51,20 +51,21 @@ public class GatewayRSocket extends AbstractRSocket { if (signal.hasValue()) { Payload payload = signal.get(); - Mono rsocket = discover(payload); + RSocket rsocket = discover(payload); - return rsocket//.log("gateway rc discovered rsocket") - .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); - // .log("gateway rc proxy rc")); + if (rsocket == null) { + return Flux.empty(); + } + return rsocket.requestChannel(payloadFlux); } return Flux.empty(); }); } - private Mono discover(Payload payload) { + private RSocket discover(Payload payload) { if (payload == null || !payload.hasMetadata()) { // and metadata is routing - return Mono.empty(); + return null; } // TODO: deal with composite metadata @@ -72,13 +73,16 @@ public class GatewayRSocket extends AbstractRSocket { List tags = Metadata.decodeRouting(payload.sliceMetadata()); if (CollectionUtils.isEmpty(tags)) { - return Mono.empty(); + return null; } log.debug("discovered service " + tags); - Mono rsocket = registry.find(tags); + RSocket rsocket = registry.find(tags); + if (rsocket == null) { + log.debug("Unable to find destination RSocket for " + tags); + } // TODO: deal with connecting to cluster? // if not connected previously, initialize connection diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 196d142e..e1398a5f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -39,7 +39,7 @@ public class GatewaySocketAcceptor implements SocketAcceptor { if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); //TODO: key generation - registry.put(annoucementMetadata, Mono.just(sendingSocket)); + registry.put(annoucementMetadata, sendingSocket); id = annoucementMetadata; } else { id = UUID.randomUUID().toString(); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index a7e0e4fe..7d46b77d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -21,22 +21,21 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import io.rsocket.RSocket; -import reactor.core.publisher.Mono; import org.springframework.util.CollectionUtils; public class Registry { - //TODO: List> - private ConcurrentHashMap> rsockets = new ConcurrentHashMap<>(); + //TODO: List + private ConcurrentHashMap rsockets = new ConcurrentHashMap<>(); - public void put(String key, Mono rsocket) { + public void put(String key, RSocket rsocket) { rsockets.put(key, rsocket); } - public Mono find(List tags) { + public RSocket find(List tags) { if (CollectionUtils.isEmpty(tags)) { - return Mono.empty(); + return null; } return rsockets.get(tags.get(0)); } From cb6d03598f04e1fa10013b364295f359bca1030b Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 24 Jan 2019 12:54:22 -0800 Subject: [PATCH 05/67] Adds support for a single pending request. --- .../cloud/gateway/rsocket/GatewayRSocket.java | 107 +++++++++++++----- .../rsocket/GatewaySocketAcceptor.java | 17 ++- .../cloud/gateway/rsocket/Registry.java | 26 ++++- .../gateway/rsocket/test/PingPongApp.java | 10 +- 4 files changed, 119 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index 7565a9a3..ddf9f075 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; import org.springframework.util.CollectionUtils; @@ -34,36 +35,99 @@ public class GatewayRSocket extends AbstractRSocket { private static final Log log = LogFactory.getLog(GatewayRSocket.class); - private final String id; private final Registry registry; - public GatewayRSocket(String id, Registry registry) { - this.id = id; + public GatewayRSocket(Registry registry) { this.registry = registry; } + @Override + public Mono fireAndForget(Payload payload) { + List metadata = getRoutingMetadata(payload); + RSocket service = findRSocket(metadata); + + if (service != null) { + return service.fireAndForget(payload); + } + + MonoProcessor processor = MonoProcessor.create(); + this.registry.pendingRequest(metadata, processor); + + return processor + .flatMap(rSocket -> rSocket.fireAndForget(payload)); + } + @Override public Flux requestChannel(Publisher payloads) { - log.debug("Entering requestChannel: " + id); return Flux.from(payloads) - // .log("gateway rc b4son1") .switchOnFirst((signal, payloadFlux) -> { - if (signal.hasValue()) { - Payload payload = signal.get(); - - RSocket rsocket = discover(payload); - - if (rsocket == null) { - return Flux.empty(); - } - return rsocket.requestChannel(payloadFlux); + if (!signal.hasValue()) { + return payloadFlux; } - return Flux.empty(); + + Payload payload = signal.get(); + List metadata = getRoutingMetadata(payload); + RSocket service = findRSocket(metadata); + + if (service != null) { + return service.requestChannel(payloadFlux); + } + + MonoProcessor processor = MonoProcessor.create(); + this.registry.pendingRequest(metadata, processor); + + return processor + .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); }); } - private RSocket discover(Payload payload) { + @Override + public Mono requestResponse(Payload payload) { + List metadata = getRoutingMetadata(payload); + RSocket service = findRSocket(metadata); + if (service != null) { + return service.requestResponse(payload); + } + + MonoProcessor processor = MonoProcessor.create(); + this.registry.pendingRequest(metadata, processor); + + return processor + .flatMap(rSocket -> rSocket.requestResponse(payload)); + } + + @Override + public Flux requestStream(Payload payload) { + List metadata = getRoutingMetadata(payload); + RSocket service = findRSocket(metadata); + + if (service != null) { + return service.requestStream(payload); + } + + MonoProcessor processor = MonoProcessor.create(); + this.registry.pendingRequest(metadata, processor); + + return processor + .flatMapMany(rSocket -> rSocket.requestStream(payload)); + } + + private RSocket findRSocket(List tags) { + if (tags == null) return null; + + RSocket rsocket = registry.getRegistered(tags); + + if (rsocket == null) { + log.debug("Unable to getRegistered destination RSocket for " + tags); + } + // TODO: deal with connecting to cluster? + + // if not connected previously, initialize connection + return rsocket; + } + + private List getRoutingMetadata(Payload payload) { if (payload == null || !payload.hasMetadata()) { // and metadata is routing return null; } @@ -77,16 +141,7 @@ public class GatewayRSocket extends AbstractRSocket { } log.debug("discovered service " + tags); - - RSocket rsocket = registry.find(tags); - - if (rsocket == null) { - log.debug("Unable to find destination RSocket for " + tags); - } - // TODO: deal with connecting to cluster? - - // if not connected previously, initialize connection - return rsocket; + return tags; } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index e1398a5f..08039875 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -17,34 +17,31 @@ package org.springframework.cloud.gateway.rsocket; +import java.util.Collections; + import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import reactor.core.publisher.Mono; -import java.util.UUID; - public class GatewaySocketAcceptor implements SocketAcceptor { private final Registry registry; + private final RSocket proxyRSocket; - public GatewaySocketAcceptor(Registry registry) { + public GatewaySocketAcceptor(Registry registry, RSocket proxyRSocket) { this.registry = registry; + this.proxyRSocket = proxyRSocket; } @Override public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - String id = null; if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); - //TODO: key generation - registry.put(annoucementMetadata, sendingSocket); - id = annoucementMetadata; - } else { - id = UUID.randomUUID().toString(); + registry.register(Collections.singletonList(annoucementMetadata), sendingSocket); } - return Mono.just(new GatewayRSocket(id, registry)); + return Mono.just(proxyRSocket); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 7d46b77d..e8f183bc 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -21,22 +21,42 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import io.rsocket.RSocket; +import reactor.core.publisher.MonoProcessor; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +//TODO: name? public class Registry { + //TODO: List> + private final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap<>(); + //TODO: List private ConcurrentHashMap rsockets = new ConcurrentHashMap<>(); - public void put(String key, RSocket rsocket) { - rsockets.put(key, rsocket); + public void register(List tags, RSocket rsocket) { + Assert.notEmpty(tags, "tags may not be empty"); + rsockets.put(tags.get(0), rsocket); } - public RSocket find(List tags) { + public RSocket getRegistered(List tags) { if (CollectionUtils.isEmpty(tags)) { return null; } return rsockets.get(tags.get(0)); } + + public MonoProcessor getPendingRequests(List tags) { + if (CollectionUtils.isEmpty(tags)) { + return null; + } + return this.pendingRequests.get(tags); + } + + public void pendingRequest(List tags, MonoProcessor processor) { + Assert.notEmpty(tags, "tags may not be empty"); + this.pendingRequests.put(tags.get(0), processor); + } + } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index e1b84da1..9b779bab 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -43,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor; import org.springframework.cloud.gateway.rsocket.Metadata; import org.springframework.cloud.gateway.rsocket.Registry; @@ -71,10 +72,15 @@ public class PingPongApp { return new Registry(); } + @Bean + public GatewayRSocket gatewayRSocket(Registry registry) { + return new GatewayRSocket(registry); + } + //TODO: move to auto-configuration @Bean - public GatewaySocketAcceptor socketAcceptor(Registry registry) { - return new GatewaySocketAcceptor(registry); + public GatewaySocketAcceptor socketAcceptor(Registry registry, GatewayRSocket rsocket) { + return new GatewaySocketAcceptor(registry, rsocket); } @Bean From 5f969006eba108c8500e01f0464e82fa5927ac6d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 24 Jan 2019 13:18:17 -0800 Subject: [PATCH 06/67] Adds resume pending requests to SocketAcceptor --- .../cloud/gateway/rsocket/GatewayRSocket.java | 4 ++-- .../cloud/gateway/rsocket/GatewaySocketAcceptor.java | 11 ++++++++++- .../cloud/gateway/rsocket/Registry.java | 12 +++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index ddf9f075..cabe0bd6 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -119,7 +119,7 @@ public class GatewayRSocket extends AbstractRSocket { RSocket rsocket = registry.getRegistered(tags); if (rsocket == null) { - log.debug("Unable to getRegistered destination RSocket for " + tags); + log.debug("Unable to find destination RSocket for " + tags); } // TODO: deal with connecting to cluster? @@ -140,7 +140,7 @@ public class GatewayRSocket extends AbstractRSocket { return null; } - log.debug("discovered service " + tags); + log.debug("found routing metadata " + tags); return tags; } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 08039875..97fab3e5 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -18,11 +18,13 @@ package org.springframework.cloud.gateway.rsocket; import java.util.Collections; +import java.util.List; import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; public class GatewaySocketAcceptor implements SocketAcceptor { @@ -39,7 +41,14 @@ public class GatewaySocketAcceptor implements SocketAcceptor { if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); - registry.register(Collections.singletonList(annoucementMetadata), sendingSocket); + List tags = Collections.singletonList(annoucementMetadata); + registry.register(tags, sendingSocket); + + MonoProcessor processor = this.registry.getPendingRequests(tags); + if (processor != null) { + processor.onNext(sendingSocket); + processor.onComplete(); + } } return Mono.just(proxyRSocket); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index e8f183bc..79011953 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import io.rsocket.RSocket; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.MonoProcessor; import org.springframework.util.Assert; @@ -28,6 +30,7 @@ import org.springframework.util.CollectionUtils; //TODO: name? public class Registry { + private static final Log log = LogFactory.getLog(Registry.class); //TODO: List> private final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap<>(); @@ -37,6 +40,7 @@ public class Registry { public void register(List tags, RSocket rsocket) { Assert.notEmpty(tags, "tags may not be empty"); + log.debug("Registered RSocket: " + tags); rsockets.put(tags.get(0), rsocket); } @@ -51,11 +55,17 @@ public class Registry { if (CollectionUtils.isEmpty(tags)) { return null; } - return this.pendingRequests.get(tags); + MonoProcessor monoProcessor = this.pendingRequests.get(tags.get(0)); + if (monoProcessor != null) { + log.debug("Found pending request: " + tags); + this.pendingRequests.remove(tags.get(0)); + } + return monoProcessor; } public void pendingRequest(List tags, MonoProcessor processor) { Assert.notEmpty(tags, "tags may not be empty"); + log.debug("Adding pending request: " + tags); this.pendingRequests.put(tags.get(0), processor); } From f919229c6d38692f99550ba5cfafde509e58959d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 24 Jan 2019 13:43:12 -0800 Subject: [PATCH 07/67] Adds onBackPressure* to ping to allow out of order startup. --- .../cloud/gateway/rsocket/GatewayRSocket.java | 4 ++++ .../gateway/rsocket/GatewaySocketAcceptor.java | 1 + .../gateway/rsocket/test/PingPongApp.java | 18 +++++++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index cabe0bd6..0f88746f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -54,6 +54,7 @@ public class GatewayRSocket extends AbstractRSocket { this.registry.pendingRequest(metadata, processor); return processor + .log("pending-request") .flatMap(rSocket -> rSocket.fireAndForget(payload)); } @@ -77,6 +78,7 @@ public class GatewayRSocket extends AbstractRSocket { this.registry.pendingRequest(metadata, processor); return processor + .log("pending-request") .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); }); } @@ -94,6 +96,7 @@ public class GatewayRSocket extends AbstractRSocket { this.registry.pendingRequest(metadata, processor); return processor + .log("pending-request") .flatMap(rSocket -> rSocket.requestResponse(payload)); } @@ -110,6 +113,7 @@ public class GatewayRSocket extends AbstractRSocket { this.registry.pendingRequest(metadata, processor); return processor + .log("pending-request") .flatMapMany(rSocket -> rSocket.requestStream(payload)); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 97fab3e5..42bdc080 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -46,6 +46,7 @@ public class GatewaySocketAcceptor implements SocketAcceptor { MonoProcessor processor = this.registry.getPendingRequests(tags); if (processor != null) { + processor.log("resume-pending-request"); processor.onNext(sendingSocket); processor.onComplete(); } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 9b779bab..4e51ec8d 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -106,7 +106,8 @@ public class PingPongApp { @Override public int getOrder() { - return 0; + // return 0; + return HIGHEST_PRECEDENCE; } @Override @@ -156,16 +157,12 @@ public class PingPongApp { @Override public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; + // return Ordered.LOWEST_PRECEDENCE; + return 0; } @Override public void onApplicationEvent(ApplicationReadyEvent event) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } log.info("Starting Ping"+id); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); ByteBuf announcementMetadata = Metadata.encodeAnnouncement("ping"); @@ -183,6 +180,7 @@ public class PingPongApp { ByteBuf routingMetadata = Metadata.encodeRouting("pong"); return DefaultPayload.create(data, routingMetadata); }) + .onBackpressureDrop(payload -> log.debug("Dropped payload " + payload.getDataUtf8())) // this is needed in case pong is not available yet ).map(Payload::getDataUtf8) .doOnNext(str -> log.info("received " + str + " in Ping"+id)) // .take(10) @@ -201,11 +199,17 @@ public class PingPongApp { @Override public int getOrder() { + // return 1; return 1; } @Override public void onApplicationEvent(ApplicationReadyEvent event) { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + e.printStackTrace(); + } log.info("Starting Pong"); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); ByteBuf announcementMetadata = Metadata.encodeAnnouncement("pong"); From b91af60beb44103f1f54e063e6e363b956b660e4 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 24 Jan 2019 15:14:33 -0800 Subject: [PATCH 08/67] Updates Registry to use ConcurrentMultiValueMap --- .../rsocket/ConcurrentMultiValueMap.java | 240 ++++++++++++++++++ .../cloud/gateway/rsocket/GatewayRSocket.java | 9 +- .../rsocket/GatewaySocketAcceptor.java | 14 +- .../cloud/gateway/rsocket/Registry.java | 20 +- 4 files changed, 265 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java b/src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java new file mode 100644 index 00000000..6a0dc0b9 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java @@ -0,0 +1,240 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.lang.Nullable; +import org.springframework.util.MultiValueMap; + +/** + * Simple implementation of {@link MultiValueMap} that wraps a {@link ConcurrentHashMap}, + * storing multiple values in a {@link LinkedList}. + * + *

This Map implementation is generally not thread-safe. It is primarily designed + * for data structures exposed from request objects, for use in a single thread only. + * + * @author Arjen Poutsma + * @author Juergen Hoeller + * @since 3.0 + * @param the key type + * @param the value element type + */ +public class ConcurrentMultiValueMap implements MultiValueMap, Serializable, Cloneable { + + private static final long serialVersionUID = 3801124242820219131L; + + private final Map> targetMap; + + + /** + * Create a new LinkedMultiValueMap that wraps a {@link ConcurrentHashMap}. + */ + public ConcurrentMultiValueMap() { + this.targetMap = new ConcurrentHashMap<>(); + } + + /** + * Create a new LinkedMultiValueMap that wraps a {@link ConcurrentHashMap} + * with the given initial capacity. + * @param initialCapacity the initial capacity + */ + public ConcurrentMultiValueMap(int initialCapacity) { + this.targetMap = new ConcurrentHashMap<>(initialCapacity); + } + + /** + * Copy constructor: Create a new LinkedMultiValueMap with the same mappings as + * the specified Map. Note that this will be a shallow copy; its value-holding + * List entries will get reused and therefore cannot get modified independently. + * @param otherMap the Map whose mappings are to be placed in this Map + * @see #clone() + * @see #deepCopy() + */ + public ConcurrentMultiValueMap(Map> otherMap) { + this.targetMap = new ConcurrentHashMap<>(otherMap); + } + + + // MultiValueMap implementation + + @Override + @Nullable + public V getFirst(K key) { + List values = this.targetMap.get(key); + return (values != null ? values.get(0) : null); + } + + @Override + public void add(K key, @Nullable V value) { + List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + values.add(value); + } + + @Override + public void addAll(K key, List values) { + List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + currentValues.addAll(values); + } + + @Override + public void addAll(MultiValueMap values) { + for (Entry> entry : values.entrySet()) { + addAll(entry.getKey(), entry.getValue()); + } + } + + @Override + public void set(K key, @Nullable V value) { + List values = new LinkedList<>(); + values.add(value); + this.targetMap.put(key, values); + } + + @Override + public void setAll(Map values) { + values.forEach(this::set); + } + + @Override + public Map toSingleValueMap() { + ConcurrentHashMap singleValueMap = new ConcurrentHashMap<>(this.targetMap.size()); + this.targetMap.forEach((key, value) -> singleValueMap.put(key, value.get(0))); + return singleValueMap; + } + + + // Map implementation + + @Override + public int size() { + return this.targetMap.size(); + } + + @Override + public boolean isEmpty() { + return this.targetMap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.targetMap.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.targetMap.containsValue(value); + } + + @Override + @Nullable + public List get(Object key) { + return this.targetMap.get(key); + } + + @Override + @Nullable + public List put(K key, List value) { + return this.targetMap.put(key, value); + } + + @Override + @Nullable + public List remove(Object key) { + return this.targetMap.remove(key); + } + + @Override + public void putAll(Map> map) { + this.targetMap.putAll(map); + } + + @Override + public void clear() { + this.targetMap.clear(); + } + + @Override + public Set keySet() { + return this.targetMap.keySet(); + } + + @Override + public Collection> values() { + return this.targetMap.values(); + } + + @Override + public Set>> entrySet() { + return this.targetMap.entrySet(); + } + + + /** + * Create a deep copy of this Map. + * @return a copy of this Map, including a copy of each value-holding List entry + * (consistently using an independent modifiable {@link LinkedList} for each entry) + * along the lines of {@code MultiValueMap.addAll} semantics + * @since 4.2 + * @see #addAll(MultiValueMap) + * @see #clone() + */ + public ConcurrentMultiValueMap deepCopy() { + ConcurrentMultiValueMap copy = new ConcurrentMultiValueMap<>(this.targetMap.size()); + this.targetMap.forEach((key, value) -> copy.put(key, new LinkedList<>(value))); + return copy; + } + + /** + * Create a regular copy of this Map. + * @return a shallow copy of this Map, reusing this Map's value-holding List entries + * (even if some entries are shared or unmodifiable) along the lines of standard + * {@code Map.put} semantics + * @since 4.2 + * @see #put(Object, List) + * @see #putAll(Map) + * @see ConcurrentMultiValueMap#ConcurrentMultiValueMap(Map) + * @see #deepCopy() + */ + @Override + public ConcurrentMultiValueMap clone() { + return new ConcurrentMultiValueMap<>(this); + } + + @Override + public boolean equals(Object obj) { + return this.targetMap.equals(obj); + } + + @Override + public int hashCode() { + return this.targetMap.hashCode(); + } + + @Override + public String toString() { + return this.targetMap.toString(); + } + +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index 0f88746f..1ded41cf 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -50,6 +50,7 @@ public class GatewayRSocket extends AbstractRSocket { return service.fireAndForget(payload); } + //TODO: handle concurrency issues MonoProcessor processor = MonoProcessor.create(); this.registry.pendingRequest(metadata, processor); @@ -120,13 +121,17 @@ public class GatewayRSocket extends AbstractRSocket { private RSocket findRSocket(List tags) { if (tags == null) return null; - RSocket rsocket = registry.getRegistered(tags); + List rsockets = registry.getRegistered(tags); - if (rsocket == null) { + if (CollectionUtils.isEmpty(rsockets)) { log.debug("Unable to find destination RSocket for " + tags); + return null; } // TODO: deal with connecting to cluster? + // TODO: load balancing + RSocket rsocket = rsockets.get(0); + // if not connected previously, initialize connection return rsocket; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 42bdc080..290ffc7e 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -26,6 +26,8 @@ import io.rsocket.SocketAcceptor; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; +import org.springframework.util.CollectionUtils; + public class GatewaySocketAcceptor implements SocketAcceptor { private final Registry registry; @@ -44,11 +46,13 @@ public class GatewaySocketAcceptor implements SocketAcceptor { List tags = Collections.singletonList(annoucementMetadata); registry.register(tags, sendingSocket); - MonoProcessor processor = this.registry.getPendingRequests(tags); - if (processor != null) { - processor.log("resume-pending-request"); - processor.onNext(sendingSocket); - processor.onComplete(); + List> processors = this.registry.getPendingRequests(tags); + if (!CollectionUtils.isEmpty(processors)) { + processors.forEach(processor -> { + processor.log("resume-pending-request"); + processor.onNext(sendingSocket); + processor.onComplete(); + }); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 79011953..62dad519 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -18,7 +18,6 @@ package org.springframework.cloud.gateway.rsocket; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; import io.rsocket.RSocket; import org.apache.commons.logging.Log; @@ -27,36 +26,35 @@ import reactor.core.publisher.MonoProcessor; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.MultiValueMap; //TODO: name? public class Registry { private static final Log log = LogFactory.getLog(Registry.class); - //TODO: List> - private final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap<>(); + private final MultiValueMap> pendingRequests = new ConcurrentMultiValueMap<>(); - //TODO: List - private ConcurrentHashMap rsockets = new ConcurrentHashMap<>(); + private final MultiValueMap rsockets = new ConcurrentMultiValueMap<>(); public void register(List tags, RSocket rsocket) { Assert.notEmpty(tags, "tags may not be empty"); log.debug("Registered RSocket: " + tags); - rsockets.put(tags.get(0), rsocket); + rsockets.add(tags.get(0), rsocket); } - public RSocket getRegistered(List tags) { + public List getRegistered(List tags) { if (CollectionUtils.isEmpty(tags)) { return null; } return rsockets.get(tags.get(0)); } - public MonoProcessor getPendingRequests(List tags) { + public List> getPendingRequests(List tags) { if (CollectionUtils.isEmpty(tags)) { return null; } - MonoProcessor monoProcessor = this.pendingRequests.get(tags.get(0)); - if (monoProcessor != null) { + List> monoProcessor = this.pendingRequests.get(tags.get(0)); + if (!CollectionUtils.isEmpty(monoProcessor)) { log.debug("Found pending request: " + tags); this.pendingRequests.remove(tags.get(0)); } @@ -66,7 +64,7 @@ public class Registry { public void pendingRequest(List tags, MonoProcessor processor) { Assert.notEmpty(tags, "tags may not be empty"); log.debug("Adding pending request: " + tags); - this.pendingRequests.put(tags.get(0), processor); + this.pendingRequests.add(tags.get(0), processor); } } From 53ad824e8f0cd51ea5b822517a75cd14fe19bbfd Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 25 Jan 2019 12:16:24 -0800 Subject: [PATCH 09/67] Adds LoadBalancedRSocket --- .../cloud/gateway/rsocket/GatewayRSocket.java | 9 +- .../gateway/rsocket/LoadBalancedRSocket.java | 95 +++++++++++++++++++ .../cloud/gateway/rsocket/Registry.java | 11 ++- 3 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index 1ded41cf..38a6fbce 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -121,18 +121,13 @@ public class GatewayRSocket extends AbstractRSocket { private RSocket findRSocket(List tags) { if (tags == null) return null; - List rsockets = registry.getRegistered(tags); + RSocket rsocket = registry.getRegistered(tags); - if (CollectionUtils.isEmpty(rsockets)) { + if (rsocket == null) { log.debug("Unable to find destination RSocket for " + tags); - return null; } // TODO: deal with connecting to cluster? - // TODO: load balancing - RSocket rsocket = rsockets.get(0); - - // if not connected previously, initialize connection return rsocket; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java new file mode 100644 index 00000000..30791a49 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java @@ -0,0 +1,95 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import io.rsocket.AbstractRSocket; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class LoadBalancedRSocket extends AbstractRSocket { + + private final List delegates = new ArrayList<>(); //TODO: Concurrent? + + private final Function, RSocket> rsocketSelector; + + public LoadBalancedRSocket() { + this(null); + } + + // TODO: simple round robbin load balancing + public LoadBalancedRSocket(Function, RSocket> rsocketSelector) { + if (rsocketSelector != null) { + this.rsocketSelector = rsocketSelector; + } else { + this.rsocketSelector = rSockets -> { + if (!delegates.isEmpty()) { + return delegates.get(0); + } + return null; + }; + } + } + + public RSocket choose() { + RSocket rsocket = rsocketSelector.apply(delegates); + if (rsocket == null) { + throw new IllegalStateException("No RSockets available"); + } + return rsocket; + } + + public void addRSocket(RSocket rsocket) { + this.delegates.add(rsocket); + } + + public List getDelegates() { + return delegates; + } + + @Override + public Mono fireAndForget(Payload payload) { + return choose().fireAndForget(payload); + } + + @Override + public Mono requestResponse(Payload payload) { + return choose().requestResponse(payload); + } + + @Override + public Flux requestStream(Payload payload) { + return choose().requestStream(payload); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return choose().requestChannel(payloads); + } + + @Override + public Mono metadataPush(Payload payload) { + return choose().metadataPush(payload); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 62dad519..858196a6 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -18,6 +18,8 @@ package org.springframework.cloud.gateway.rsocket; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import io.rsocket.RSocket; import org.apache.commons.logging.Log; @@ -32,17 +34,20 @@ import org.springframework.util.MultiValueMap; public class Registry { private static final Log log = LogFactory.getLog(Registry.class); + //TODO: replace with PendingRequestRSocket private final MultiValueMap> pendingRequests = new ConcurrentMultiValueMap<>(); - private final MultiValueMap rsockets = new ConcurrentMultiValueMap<>(); + private final Map rsockets = new ConcurrentHashMap<>(); public void register(List tags, RSocket rsocket) { Assert.notEmpty(tags, "tags may not be empty"); + Assert.notNull(rsocket, "RSocket may not be null"); log.debug("Registered RSocket: " + tags); - rsockets.add(tags.get(0), rsocket); + LoadBalancedRSocket composite = rsockets.computeIfAbsent(tags.get(0), s -> new LoadBalancedRSocket()); + composite.addRSocket(rsocket); } - public List getRegistered(List tags) { + public RSocket getRegistered(List tags) { if (CollectionUtils.isEmpty(tags)) { return null; } From ede610f67fb262414e0e2235050215035321ec7f Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 25 Jan 2019 14:09:31 -0800 Subject: [PATCH 10/67] Adds PendingRequestRSocket replacing map in Registry --- .../rsocket/ConcurrentMultiValueMap.java | 240 ------------------ .../cloud/gateway/rsocket/GatewayRSocket.java | 68 ++--- .../rsocket/GatewaySocketAcceptor.java | 12 - .../rsocket/PendingRequestRSocket.java | 78 ++++++ .../cloud/gateway/rsocket/Registry.java | 52 ++-- .../gateway/rsocket/test/PingPongApp.java | 9 +- 6 files changed, 136 insertions(+), 323 deletions(-) delete mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java b/src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java deleted file mode 100644 index 6a0dc0b9..00000000 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/ConcurrentMultiValueMap.java +++ /dev/null @@ -1,240 +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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.springframework.cloud.gateway.rsocket; - -import java.io.Serializable; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.lang.Nullable; -import org.springframework.util.MultiValueMap; - -/** - * Simple implementation of {@link MultiValueMap} that wraps a {@link ConcurrentHashMap}, - * storing multiple values in a {@link LinkedList}. - * - *

This Map implementation is generally not thread-safe. It is primarily designed - * for data structures exposed from request objects, for use in a single thread only. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 3.0 - * @param the key type - * @param the value element type - */ -public class ConcurrentMultiValueMap implements MultiValueMap, Serializable, Cloneable { - - private static final long serialVersionUID = 3801124242820219131L; - - private final Map> targetMap; - - - /** - * Create a new LinkedMultiValueMap that wraps a {@link ConcurrentHashMap}. - */ - public ConcurrentMultiValueMap() { - this.targetMap = new ConcurrentHashMap<>(); - } - - /** - * Create a new LinkedMultiValueMap that wraps a {@link ConcurrentHashMap} - * with the given initial capacity. - * @param initialCapacity the initial capacity - */ - public ConcurrentMultiValueMap(int initialCapacity) { - this.targetMap = new ConcurrentHashMap<>(initialCapacity); - } - - /** - * Copy constructor: Create a new LinkedMultiValueMap with the same mappings as - * the specified Map. Note that this will be a shallow copy; its value-holding - * List entries will get reused and therefore cannot get modified independently. - * @param otherMap the Map whose mappings are to be placed in this Map - * @see #clone() - * @see #deepCopy() - */ - public ConcurrentMultiValueMap(Map> otherMap) { - this.targetMap = new ConcurrentHashMap<>(otherMap); - } - - - // MultiValueMap implementation - - @Override - @Nullable - public V getFirst(K key) { - List values = this.targetMap.get(key); - return (values != null ? values.get(0) : null); - } - - @Override - public void add(K key, @Nullable V value) { - List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); - values.add(value); - } - - @Override - public void addAll(K key, List values) { - List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); - currentValues.addAll(values); - } - - @Override - public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } - } - - @Override - public void set(K key, @Nullable V value) { - List values = new LinkedList<>(); - values.add(value); - this.targetMap.put(key, values); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - ConcurrentHashMap singleValueMap = new ConcurrentHashMap<>(this.targetMap.size()); - this.targetMap.forEach((key, value) -> singleValueMap.put(key, value.get(0))); - return singleValueMap; - } - - - // Map implementation - - @Override - public int size() { - return this.targetMap.size(); - } - - @Override - public boolean isEmpty() { - return this.targetMap.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.targetMap.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.targetMap.containsValue(value); - } - - @Override - @Nullable - public List get(Object key) { - return this.targetMap.get(key); - } - - @Override - @Nullable - public List put(K key, List value) { - return this.targetMap.put(key, value); - } - - @Override - @Nullable - public List remove(Object key) { - return this.targetMap.remove(key); - } - - @Override - public void putAll(Map> map) { - this.targetMap.putAll(map); - } - - @Override - public void clear() { - this.targetMap.clear(); - } - - @Override - public Set keySet() { - return this.targetMap.keySet(); - } - - @Override - public Collection> values() { - return this.targetMap.values(); - } - - @Override - public Set>> entrySet() { - return this.targetMap.entrySet(); - } - - - /** - * Create a deep copy of this Map. - * @return a copy of this Map, including a copy of each value-holding List entry - * (consistently using an independent modifiable {@link LinkedList} for each entry) - * along the lines of {@code MultiValueMap.addAll} semantics - * @since 4.2 - * @see #addAll(MultiValueMap) - * @see #clone() - */ - public ConcurrentMultiValueMap deepCopy() { - ConcurrentMultiValueMap copy = new ConcurrentMultiValueMap<>(this.targetMap.size()); - this.targetMap.forEach((key, value) -> copy.put(key, new LinkedList<>(value))); - return copy; - } - - /** - * Create a regular copy of this Map. - * @return a shallow copy of this Map, reusing this Map's value-holding List entries - * (even if some entries are shared or unmodifiable) along the lines of standard - * {@code Map.put} semantics - * @since 4.2 - * @see #put(Object, List) - * @see #putAll(Map) - * @see ConcurrentMultiValueMap#ConcurrentMultiValueMap(Map) - * @see #deepCopy() - */ - @Override - public ConcurrentMultiValueMap clone() { - return new ConcurrentMultiValueMap<>(this); - } - - @Override - public boolean equals(Object obj) { - return this.targetMap.equals(obj); - } - - @Override - public int hashCode() { - return this.targetMap.hashCode(); - } - - @Override - public String toString() { - return this.targetMap.toString(); - } - -} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index 38a6fbce..03b65a98 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -27,7 +27,6 @@ import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; import org.springframework.util.CollectionUtils; @@ -43,20 +42,8 @@ public class GatewayRSocket extends AbstractRSocket { @Override public Mono fireAndForget(Payload payload) { - List metadata = getRoutingMetadata(payload); - RSocket service = findRSocket(metadata); - - if (service != null) { - return service.fireAndForget(payload); - } - - //TODO: handle concurrency issues - MonoProcessor processor = MonoProcessor.create(); - this.registry.pendingRequest(metadata, processor); - - return processor - .log("pending-request") - .flatMap(rSocket -> rSocket.fireAndForget(payload)); + RSocket service = findRSocket(payload); + return service.fireAndForget(payload); } @Override @@ -67,59 +54,38 @@ public class GatewayRSocket extends AbstractRSocket { return payloadFlux; } - Payload payload = signal.get(); - List metadata = getRoutingMetadata(payload); - RSocket service = findRSocket(metadata); - - if (service != null) { - return service.requestChannel(payloadFlux); - } - - MonoProcessor processor = MonoProcessor.create(); - this.registry.pendingRequest(metadata, processor); - - return processor - .log("pending-request") - .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); + RSocket service = findRSocket(signal.get()); + return service.requestChannel(payloadFlux); }); } @Override public Mono requestResponse(Payload payload) { - List metadata = getRoutingMetadata(payload); - RSocket service = findRSocket(metadata); - - if (service != null) { - return service.requestResponse(payload); - } - - MonoProcessor processor = MonoProcessor.create(); - this.registry.pendingRequest(metadata, processor); - - return processor - .log("pending-request") - .flatMap(rSocket -> rSocket.requestResponse(payload)); + RSocket service = findRSocket(payload); + return service.requestResponse(payload); } @Override public Flux requestStream(Payload payload) { + RSocket service = findRSocket(payload); + return service.requestStream(payload); + } + + private RSocket findRSocket(Payload payload) { List metadata = getRoutingMetadata(payload); RSocket service = findRSocket(metadata); - if (service != null) { - return service.requestStream(payload); + if (service == null) { + PendingRequestRSocket pending = new PendingRequestRSocket(metadata); + this.registry.addListener(pending); //TODO: deal with removing? + service = pending; } - MonoProcessor processor = MonoProcessor.create(); - this.registry.pendingRequest(metadata, processor); - - return processor - .log("pending-request") - .flatMapMany(rSocket -> rSocket.requestStream(payload)); + return service; } private RSocket findRSocket(List tags) { - if (tags == null) return null; + if (tags == null) return null; //TODO: error RSocket rsocket = registry.getRegistered(tags); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 290ffc7e..f87f955b 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -24,9 +24,6 @@ import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import reactor.core.publisher.Mono; -import reactor.core.publisher.MonoProcessor; - -import org.springframework.util.CollectionUtils; public class GatewaySocketAcceptor implements SocketAcceptor { @@ -45,15 +42,6 @@ public class GatewaySocketAcceptor implements SocketAcceptor { String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); List tags = Collections.singletonList(annoucementMetadata); registry.register(tags, sendingSocket); - - List> processors = this.registry.getPendingRequests(tags); - if (!CollectionUtils.isEmpty(processors)) { - processors.forEach(processor -> { - processor.log("resume-pending-request"); - processor.onNext(sendingSocket); - processor.onComplete(); - }); - } } return Mono.just(proxyRSocket); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java new file mode 100644 index 00000000..88c15e95 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java @@ -0,0 +1,78 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import java.util.List; +import java.util.function.Consumer; + +import io.rsocket.AbstractRSocket; +import io.rsocket.Payload; +import io.rsocket.RSocket; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoProcessor; + +import org.springframework.cloud.gateway.rsocket.Registry.RegisteredEvent; + +public class PendingRequestRSocket extends AbstractRSocket implements Consumer { + + private final List routingMetadata; + private final MonoProcessor processor; + + public PendingRequestRSocket(List routingMetadata) { + this.routingMetadata = routingMetadata; + this.processor = MonoProcessor.create(); + } + + @Override + public void accept(RegisteredEvent registeredEvent) { + if (registeredEvent.matches(this.routingMetadata)) { + this.processor.onNext(registeredEvent.getRSocket()); + this.processor.onComplete(); + } + } + + @Override + public Mono fireAndForget(Payload payload) { + return processor + .log("pending-request") + .flatMap(rsocket -> rsocket.fireAndForget(payload)); + } + + @Override + public Mono requestResponse(Payload payload) { + return processor + .log("pending-request") + .flatMap(rsocket -> rsocket.requestResponse(payload)); + } + + @Override + public Flux requestStream(Payload payload) { + return processor + .log("pending-request") + .flatMapMany(rsocket -> rsocket.requestStream(payload)); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return processor + .log("pending-request") + .flatMapMany(rsocket -> rsocket.requestChannel(payloads)); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 858196a6..2c771cec 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -20,10 +20,13 @@ package org.springframework.cloud.gateway.rsocket; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import io.rsocket.RSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import reactor.core.Disposable; +import reactor.core.publisher.EmitterProcessor; import reactor.core.publisher.MonoProcessor; import org.springframework.util.Assert; @@ -34,17 +37,17 @@ import org.springframework.util.MultiValueMap; public class Registry { private static final Log log = LogFactory.getLog(Registry.class); - //TODO: replace with PendingRequestRSocket - private final MultiValueMap> pendingRequests = new ConcurrentMultiValueMap<>(); - private final Map rsockets = new ConcurrentHashMap<>(); + private final EmitterProcessor registeredEvents = EmitterProcessor.create(); + public void register(List tags, RSocket rsocket) { Assert.notEmpty(tags, "tags may not be empty"); Assert.notNull(rsocket, "RSocket may not be null"); log.debug("Registered RSocket: " + tags); LoadBalancedRSocket composite = rsockets.computeIfAbsent(tags.get(0), s -> new LoadBalancedRSocket()); composite.addRSocket(rsocket); + registeredEvents.onNext(new RegisteredEvent(tags, rsocket)); } public RSocket getRegistered(List tags) { @@ -54,22 +57,35 @@ public class Registry { return rsockets.get(tags.get(0)); } - public List> getPendingRequests(List tags) { - if (CollectionUtils.isEmpty(tags)) { - return null; - } - List> monoProcessor = this.pendingRequests.get(tags.get(0)); - if (!CollectionUtils.isEmpty(monoProcessor)) { - log.debug("Found pending request: " + tags); - this.pendingRequests.remove(tags.get(0)); - } - return monoProcessor; + public Disposable addListener(Consumer consumer) { + return this.registeredEvents.subscribe(consumer); } - public void pendingRequest(List tags, MonoProcessor processor) { - Assert.notEmpty(tags, "tags may not be empty"); - log.debug("Adding pending request: " + tags); - this.pendingRequests.add(tags.get(0), processor); - } + public static class RegisteredEvent { + private final List routingMetadata; + private final RSocket rSocket; + public RegisteredEvent(List routingMetadata, RSocket rSocket) { + Assert.notEmpty(routingMetadata, "routingMetadata may not be empty"); + Assert.notNull(rSocket, "RSocket may not be null"); + this.routingMetadata = routingMetadata; + this.rSocket = rSocket; + } + + public List getRoutingMetadata() { + return routingMetadata; + } + + public RSocket getRSocket() { + return rSocket; + } + + public boolean matches(List otherRoutingMetadata) { + if (!CollectionUtils.isEmpty(otherRoutingMetadata)) { + //TODO: key generation + return this.routingMetadata.get(0).equals(otherRoutingMetadata.get(0)); + } + return false; + } + } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 4e51ec8d..2ef43dc3 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -57,8 +57,13 @@ import static io.netty.buffer.Unpooled.EMPTY_BUFFER; public class PingPongApp { @Bean - public Ping ping() { - return new Ping(); + public Ping ping1() { + return new Ping("1"); + } + + @Bean + public Ping ping2() { + return new Ping("2"); } @Bean From f68286ec32cfe304ccc86d9aaac78c2d8b4b22de Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 29 Jan 2019 17:10:35 -0500 Subject: [PATCH 11/67] Removes unused imports --- .../org/springframework/cloud/gateway/rsocket/Registry.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 2c771cec..72a628eb 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -27,11 +27,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.Disposable; import reactor.core.publisher.EmitterProcessor; -import reactor.core.publisher.MonoProcessor; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; //TODO: name? public class Registry { From de3264a4a5c109842ae202052159aa99a95a3523 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 29 Jan 2019 23:48:07 -0500 Subject: [PATCH 12/67] Initial filter chain impl for SocketAcceptor --- .../rsocket/GatewaySocketAcceptor.java | 64 +++++- .../rsocket/RegistrySocketAcceptorFilter.java | 54 +++++ .../rsocket/filter/AbstractFilterChain.java | 109 +++++++++ .../gateway/rsocket/filter/FilterChain.java | 35 +++ .../rsocket/filter/RSocketExchange.java | 21 ++ .../gateway/rsocket/filter/RSocketFilter.java | 41 ++++ .../rsocket/GatewaySocketAcceptorTests.java | 216 ++++++++++++++++++ .../gateway/rsocket/test/PingPongApp.java | 32 ++- 8 files changed, 561 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index f87f955b..1f7d9f78 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.rsocket; -import java.util.Collections; import java.util.List; import io.rsocket.ConnectionSetupPayload; @@ -25,25 +24,72 @@ import io.rsocket.RSocket; import io.rsocket.SocketAcceptor; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; +import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; +import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; + public class GatewaySocketAcceptor implements SocketAcceptor { - private final Registry registry; private final RSocket proxyRSocket; + private final SocketAcceptorFilterChain filterChain; - public GatewaySocketAcceptor(Registry registry, RSocket proxyRSocket) { - this.registry = registry; + public GatewaySocketAcceptor(RSocket proxyRSocket, List filters) { this.proxyRSocket = proxyRSocket; + this.filterChain = new SocketAcceptorFilterChain(filters); } @Override public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata - String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); - List tags = Collections.singletonList(annoucementMetadata); - registry.register(tags, sendingSocket); + SocketAcceptorExchange exchange = new SocketAcceptorExchange(setup, sendingSocket); + + return this.filterChain.filter(exchange) + .log("socket acceptor filter chain") + .map(aVoid -> this.proxyRSocket) + // filter chain should always return empty + // TODO: is there a better way? + .switchIfEmpty(Mono.just(this.proxyRSocket)); + } + + public interface SocketAcceptorFilter extends RSocketFilter {} + + public static class SocketAcceptorExchange implements RSocketExchange { + private final ConnectionSetupPayload setup; + private final RSocket sendingSocket; + + public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { + this.setup = setup; + this.sendingSocket = sendingSocket; } - return Mono.just(proxyRSocket); + public ConnectionSetupPayload getSetup() { + return setup; + } + + public RSocket getSendingSocket() { + return sendingSocket; + } + } + + public static class SocketAcceptorFilterChain + extends AbstractFilterChain { + + /** + * Public constructor with the list of filters and the target handler to use. + * + * @param filters the filters ahead of the handler + */ + public SocketAcceptorFilterChain(List filters) { + super(filters); + } + + public SocketAcceptorFilterChain(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { + super(allFilters, currentFilter, next); + } + + @Override + protected SocketAcceptorFilterChain create(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { + return new SocketAcceptorFilterChain(allFilters, currentFilter, next); + } } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java new file mode 100644 index 00000000..e5828b4b --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java @@ -0,0 +1,54 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import io.rsocket.ConnectionSetupPayload; +import org.springframework.core.Ordered; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorExchange; +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilterChain; + +import java.util.Collections; +import java.util.List; + +public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { + private final Registry registry; + + public RegistrySocketAcceptorFilter(Registry registry) { + this.registry = registry; + } + + @Override + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + ConnectionSetupPayload setup = exchange.getSetup(); + if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata + String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); + List tags = Collections.singletonList(annoucementMetadata); + registry.register(tags, exchange.getSendingSocket()); + } + + return chain.filter(exchange); + } + + @Override + public int getOrder() { + return HIGHEST_PRECEDENCE + 1000; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java new file mode 100644 index 00000000..2c6c84cb --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java @@ -0,0 +1,109 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.filter; + +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; + +import reactor.core.publisher.Mono; + +import org.springframework.lang.Nullable; + +/** + * Default implementation of {@link FilterChain}. + * + *

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. + * + *

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 + implements FilterChain { + + protected final List 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 filters) { + this.allFilters = Collections.unmodifiableList(filters); + FC chain = initChain(filters); + this.currentFilter = (F)chain.currentFilter; + this.next = (FC)chain.next; + } + + private FC initChain(List filters) { + FC chain = create(filters, null, null); + ListIterator 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 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 allFilters, @Nullable F currentFilter, + @Nullable FC next); + + public List getFilters() { + return this.allFilters; + } + + @Override + @SuppressWarnings("unchecked") + public Mono filter(E exchange) { + return Mono.defer(() -> + this.currentFilter != null && this.next != null ? + this.currentFilter.filter(exchange, this.next) : + notEmpty()); + } + + private static final Mono INSTANCE = Mono.just("notempty").then(); + + private static Mono notEmpty() { + return INSTANCE; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java new file mode 100644 index 00000000..f0e2de2d --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java @@ -0,0 +1,35 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.filter; + +import reactor.core.publisher.Mono; + +/** + * Contract to allow a {@link RSocketFilter} to delegate to the next in the chain. + * + * @author Spencer Gibb + */ +public interface FilterChain { + + /** + * Delegate to the next {@code WebFilter} in the chain. + * @param exchange the current server exchange + * @return {@code Mono} to indicate when request handling is complete + */ + Mono filter(E exchange); +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java new file mode 100644 index 00000000..5247dee5 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java @@ -0,0 +1,21 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.filter; + +public interface RSocketExchange { +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java new file mode 100644 index 00000000..e59024b5 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java @@ -0,0 +1,41 @@ +/* + * 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. + * + */ + +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> { + + /** + * 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} to indicate when request processing is complete + */ + Mono filter(E exchange, FC chain); +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java new file mode 100644 index 00000000..09bb0294 --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java @@ -0,0 +1,216 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; + +import io.rsocket.ConnectionSetupPayload; +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 org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilterChain; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorExchange; +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * @author Rossen Stoyanchev + */ +public class GatewaySocketAcceptorTests { + + private static Log logger = LogFactory.getLog(GatewaySocketAcceptorTests.class); + + private RSocket proxyRSocket; + private ConnectionSetupPayload setupPayload; + private RSocket sendingSocket; + + @Before + public void init() { + proxyRSocket = mock(RSocket.class); + setupPayload = mock(ConnectionSetupPayload.class); + sendingSocket = mock(RSocket.class); + } + + + @Test + public void multipleFilters() { + TestFilter filter1 = new TestFilter(); + TestFilter filter2 = new TestFilter(); + TestFilter filter3 = new TestFilter(); + + RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + .accept(setupPayload, sendingSocket) + .block(Duration.ZERO); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertTrue(filter3.invoked()); + assertNotNull(socket); + } + + @Test + public void zeroFilters() { + RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Collections.emptyList()) + .accept(setupPayload, sendingSocket) + .block(Duration.ZERO); + + assertNotNull(socket); + } + + @Test + public void shortcircuitFilter() { + + TestFilter filter1 = new TestFilter(); + ShortcircuitingFilter filter2 = new ShortcircuitingFilter(); + TestFilter filter3 = new TestFilter(); + + + RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + .accept(setupPayload, sendingSocket) + .block(Duration.ZERO); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertFalse(filter3.invoked()); + assertNull(socket); + } + + @Test + public void asyncFilter() { + + AsyncFilter filter = new AsyncFilter(); + + RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Collections.singletonList(filter)) + .accept(setupPayload, sendingSocket) + .block(Duration.ofSeconds(5)); + + assertTrue(filter.invoked()); + assertNotNull(socket); + } + + @Test + public void handleErrorFromFilter() { + + /*MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); + MockServerHttpResponse response = new MockServerHttpResponse(); + + TestExceptionHandler exceptionHandler = new TestExceptionHandler(); + + WebHttpHandlerBuilder.webHandler(new StubWebHandler()) + .filter(new ExceptionFilter()) + .exceptionHandler(exceptionHandler).build() + .handle(request, response) + .block(); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertNotNull(exceptionHandler.ex); + assertEquals("boo", exceptionHandler.ex.getMessage());*/ + } + + + private static class TestFilter implements SocketAcceptorFilter { + + private volatile boolean invoked; + + public boolean invoked() { + return this.invoked; + } + + @Override + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + this.invoked = true; + return doFilter(exchange, chain); + } + + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + return chain.filter(exchange); + } + } + + + private static class ShortcircuitingFilter extends TestFilter { + + @Override + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + return Mono.empty(); + } + } + + + private static class AsyncFilter extends TestFilter { + + @Override + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + return doAsyncWork().flatMap(asyncResult -> { + logger.debug("Async result: " + asyncResult); + return chain.filter(exchange); + }); + } + + private Mono doAsyncWork() { + return Mono.delay(Duration.ofMillis(100L)).map(l -> "123"); + } + } + + + private static class ExceptionFilter implements SocketAcceptorFilter { + + @Override + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + return Mono.error(new IllegalStateException("boo")); + } + } + + + /*private static class TestExceptionHandler implements WebExceptionHandler { + + private Throwable ex; + + @Override + public Mono handle(SocketAcceptorExchange exchange, Throwable ex) { + this.ex = ex; + return Mono.error(ex); + } + }*/ + + + private static class StubWebHandler extends SocketAcceptorExchange { + + private volatile boolean invoked; + + public StubWebHandler() { + super(mock(ConnectionSetupPayload.class), mock(RSocket.class)); + } + + public boolean invoked() { + return this.invoked; + } + } + +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 2ef43dc3..6778e557 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.rsocket.test; import java.time.Duration; +import java.util.List; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; @@ -45,8 +46,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.gateway.rsocket.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.Metadata; import org.springframework.cloud.gateway.rsocket.Registry; +import org.springframework.cloud.gateway.rsocket.RegistrySocketAcceptorFilter; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; @@ -77,6 +80,31 @@ public class PingPongApp { return new Registry(); } + //TODO: move to auto-configuration + @Bean + public RegistrySocketAcceptorFilter registrySocketAcceptorFilter(Registry registry) { + return new RegistrySocketAcceptorFilter(registry); + } + + @Bean + public MySocketAcceptorFilter mySocketAcceptorFilter() { + return new MySocketAcceptorFilter(); + } + + static class MySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { + @Override + public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { + System.out.println("mySocketAcceptorFilter"); + return chain.filter(exchange); + } + + @Override + public int getOrder() { + return 0; + } + } + + //TODO: move to auto-configuration @Bean public GatewayRSocket gatewayRSocket(Registry registry) { return new GatewayRSocket(registry); @@ -84,8 +112,8 @@ public class PingPongApp { //TODO: move to auto-configuration @Bean - public GatewaySocketAcceptor socketAcceptor(Registry registry, GatewayRSocket rsocket) { - return new GatewaySocketAcceptor(registry, rsocket); + public GatewaySocketAcceptor socketAcceptor(GatewayRSocket rsocket, List filters) { + return new GatewaySocketAcceptor(rsocket, filters); } @Bean From 73c515377ecbcaddb126c55f5ecdb793ac8cb783 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 30 Jan 2019 00:06:27 -0500 Subject: [PATCH 13/67] Moves from Mono chain to Mono filtering just true values after chain is run effectively makes Mono.empty() and Mono.just(false) equivalent. --- .../rsocket/GatewaySocketAcceptor.java | 6 +- .../rsocket/RegistrySocketAcceptorFilter.java | 2 +- .../rsocket/filter/AbstractFilterChain.java | 10 +--- .../gateway/rsocket/filter/FilterChain.java | 2 +- .../gateway/rsocket/filter/RSocketFilter.java | 3 +- .../rsocket/GatewaySocketAcceptorTests.java | 55 +++++++++++++------ .../gateway/rsocket/test/PingPongApp.java | 2 +- 7 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 1f7d9f78..5857bbe1 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -45,10 +45,8 @@ public class GatewaySocketAcceptor implements SocketAcceptor { return this.filterChain.filter(exchange) .log("socket acceptor filter chain") - .map(aVoid -> this.proxyRSocket) - // filter chain should always return empty - // TODO: is there a better way? - .switchIfEmpty(Mono.just(this.proxyRSocket)); + .filter(bool -> bool) + .map(bool -> this.proxyRSocket); } public interface SocketAcceptorFilter extends RSocketFilter {} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java index e5828b4b..08810c54 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java @@ -36,7 +36,7 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order } @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { ConnectionSetupPayload setup = exchange.getSetup(); if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java index 2c6c84cb..92066e30 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java @@ -94,16 +94,12 @@ public abstract class AbstractFilterChain filter(E exchange) { + public Mono filter(E exchange) { return Mono.defer(() -> this.currentFilter != null && this.next != null ? this.currentFilter.filter(exchange, this.next) : - notEmpty()); + MONO_TRUE); } - private static final Mono INSTANCE = Mono.just("notempty").then(); - - private static Mono notEmpty() { - return INSTANCE; - } + private static final Mono MONO_TRUE = Mono.just(true); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java index f0e2de2d..2c47fe98 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java @@ -31,5 +31,5 @@ public interface FilterChain { * @param exchange the current server exchange * @return {@code Mono} to indicate when request handling is complete */ - Mono filter(E exchange); + Mono filter(E exchange); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java index e59024b5..b58af475 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java @@ -37,5 +37,6 @@ public interface RSocketFilter} to indicate when request processing is complete */ - Mono filter(E exchange, FC chain); + //TODO: consider a single value enum rather than boolean + Mono filter(E exchange, FC chain); } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java index 09bb0294..ea569f2a 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java @@ -101,6 +101,24 @@ public class GatewaySocketAcceptorTests { assertNull(socket); } + @Test + public void shortcircuitFalseFilter() { + + TestFilter filter1 = new TestFilter(); + FalseFilter filter2 = new FalseFilter(); + TestFilter filter3 = new TestFilter(); + + + RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + .accept(setupPayload, sendingSocket) + .block(Duration.ZERO); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertFalse(filter3.invoked()); + assertNull(socket); + } + @Test public void asyncFilter() { @@ -114,23 +132,17 @@ public class GatewaySocketAcceptorTests { assertNotNull(socket); } - @Test + //TODO: add exception handlers? + @Test(expected = IllegalStateException.class) public void handleErrorFromFilter() { - /*MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - MockServerHttpResponse response = new MockServerHttpResponse(); + ExceptionFilter filter = new ExceptionFilter(); - TestExceptionHandler exceptionHandler = new TestExceptionHandler(); + new GatewaySocketAcceptor(proxyRSocket, Collections.singletonList(filter)) + .accept(setupPayload, sendingSocket) + .block(Duration.ofSeconds(5)); - WebHttpHandlerBuilder.webHandler(new StubWebHandler()) - .filter(new ExceptionFilter()) - .exceptionHandler(exceptionHandler).build() - .handle(request, response) - .block(); - - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); - assertNotNull(exceptionHandler.ex); - assertEquals("boo", exceptionHandler.ex.getMessage());*/ + // assertNull(socket); } @@ -143,12 +155,12 @@ public class GatewaySocketAcceptorTests { } @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { this.invoked = true; return doFilter(exchange, chain); } - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return chain.filter(exchange); } } @@ -157,16 +169,23 @@ public class GatewaySocketAcceptorTests { private static class ShortcircuitingFilter extends TestFilter { @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return Mono.empty(); } } + private static class FalseFilter extends TestFilter { + + @Override + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + return Mono.just(false); + } + } private static class AsyncFilter extends TestFilter { @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return doAsyncWork().flatMap(asyncResult -> { logger.debug("Async result: " + asyncResult); return chain.filter(exchange); @@ -182,7 +201,7 @@ public class GatewaySocketAcceptorTests { private static class ExceptionFilter implements SocketAcceptorFilter { @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return Mono.error(new IllegalStateException("boo")); } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 6778e557..01891c0e 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -93,7 +93,7 @@ public class PingPongApp { static class MySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { @Override - public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { + public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { System.out.println("mySocketAcceptorFilter"); return chain.filter(exchange); } From 7d948dad00cbd55698d944eb86c90d33c605ad28 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 30 Jan 2019 00:49:46 -0500 Subject: [PATCH 14/67] Initial request filter chain --- .../cloud/gateway/rsocket/GatewayRSocket.java | 71 +++++- .../gateway/rsocket/GatewayRSocketTests.java | 216 +++++++++++++++++- .../rsocket/GatewaySocketAcceptorTests.java | 14 -- .../gateway/rsocket/test/PingPongApp.java | 5 +- 4 files changed, 270 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index 03b65a98..a6f2b854 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -28,6 +28,9 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; +import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; +import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; import org.springframework.util.CollectionUtils; public class GatewayRSocket extends AbstractRSocket { @@ -35,15 +38,17 @@ public class GatewayRSocket extends AbstractRSocket { private static final Log log = LogFactory.getLog(GatewayRSocket.class); private final Registry registry; + private final GatewayFilterChain filterChain; - public GatewayRSocket(Registry registry) { + public GatewayRSocket(Registry registry, List filters) { this.registry = registry; + this.filterChain = new GatewayFilterChain(filters); } @Override public Mono fireAndForget(Payload payload) { - RSocket service = findRSocket(payload); - return service.fireAndForget(payload); + return findRSocket(payload) + .flatMap(rSocket -> rSocket.fireAndForget(payload)); } @Override @@ -54,24 +59,24 @@ public class GatewayRSocket extends AbstractRSocket { return payloadFlux; } - RSocket service = findRSocket(signal.get()); - return service.requestChannel(payloadFlux); + return findRSocket(signal.get()) + .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); }); } @Override public Mono requestResponse(Payload payload) { - RSocket service = findRSocket(payload); - return service.requestResponse(payload); + return findRSocket(payload) + .flatMap(rSocket -> rSocket.requestResponse(payload)); } @Override public Flux requestStream(Payload payload) { - RSocket service = findRSocket(payload); - return service.requestStream(payload); + return findRSocket(payload) + .flatMapMany(rSocket -> rSocket.requestStream(payload)); } - private RSocket findRSocket(Payload payload) { + private Mono findRSocket(Payload payload) { List metadata = getRoutingMetadata(payload); RSocket service = findRSocket(metadata); @@ -81,7 +86,14 @@ public class GatewayRSocket extends AbstractRSocket { service = pending; } - return service; + GatewayExchange exchange = new GatewayExchange(payload); + + RSocket rSocket = service; + + return this.filterChain.filter(exchange) + .log("gateway filter chain") + .filter(bool -> bool) + .map(bool -> rSocket); } private RSocket findRSocket(List tags) { @@ -113,5 +125,42 @@ public class GatewayRSocket extends AbstractRSocket { log.debug("found routing metadata " + tags); return tags; } + + + public interface GatewayFilter extends RSocketFilter {} + + public static class GatewayExchange implements RSocketExchange { + private final Payload payload; + + public GatewayExchange(Payload payload) { + this.payload = payload; + } + + public Payload getPayload() { + return payload; + } + } + + public static class GatewayFilterChain + extends AbstractFilterChain { + + /** + * Public constructor with the list of filters and the target handler to use. + * + * @param filters the filters ahead of the handler + */ + public GatewayFilterChain(List filters) { + super(filters); + } + + public GatewayFilterChain(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + super(allFilters, currentFilter, next); + } + + @Override + protected GatewayFilterChain create(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + return new GatewayFilterChain(allFilters, currentFilter, next); + } + } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java index c87eb919..d21c386c 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java @@ -17,18 +17,216 @@ package org.springframework.cloud.gateway.rsocket; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; -@RunWith(SpringRunner.class) -@SpringBootTest +import io.netty.buffer.Unpooled; +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.Mono; + +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayExchange; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilterChain; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Rossen Stoyanchev + */ public class GatewayRSocketTests { - @Test - public void contextLoads() { + private static Log logger = LogFactory.getLog(GatewayRSocketTests.class); + + private Registry registry; + private Payload incomingPayload; + private RSocket rSocket; + + @Before + public void init() { + registry = mock(Registry.class); + incomingPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, + Metadata.encodeRouting("mock")); + + rSocket = mock(RSocket.class); + when(registry.getRegistered(anyList())).thenReturn(rSocket); + + 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 GatewayRSocket(registry, Arrays.asList(filter1, filter2, filter3)) + .requestResponse(incomingPayload) + .block(Duration.ZERO); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertTrue(filter3.invoked()); + assertNotNull(payload); + } + + @Test + public void zeroFilters() { + Payload payload = new GatewayRSocket(registry, Collections.emptyList()) + .requestResponse(incomingPayload) + .block(Duration.ZERO); + + assertNotNull(payload); + } + + @Test + public void shortcircuitFilter() { + + TestFilter filter1 = new TestFilter(); + ShortcircuitingFilter filter2 = new ShortcircuitingFilter(); + TestFilter filter3 = new TestFilter(); + + + Payload payload = new GatewayRSocket(registry, Arrays.asList(filter1, filter2, filter3)) + .requestResponse(incomingPayload) + .block(Duration.ZERO); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertFalse(filter3.invoked()); + assertNull(payload); + } + + @Test + public void shortcircuitFalseFilter() { + + TestFilter filter1 = new TestFilter(); + FalseFilter filter2 = new FalseFilter(); + TestFilter filter3 = new TestFilter(); + + + Payload payload = new GatewayRSocket(registry, Arrays.asList(filter1, filter2, filter3)) + .requestResponse(incomingPayload) + .block(Duration.ZERO); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertFalse(filter3.invoked()); + assertNull(payload); + } + + @Test + public void asyncFilter() { + + AsyncFilter filter = new AsyncFilter(); + + Payload payload = new GatewayRSocket(registry, Collections.singletonList(filter)) + .requestResponse(incomingPayload) + .block(Duration.ofSeconds(5)); + + assertTrue(filter.invoked()); + assertNotNull(payload); + } + + //TODO: add exception handlers? + @Test(expected = IllegalStateException.class) + public void handleErrorFromFilter() { + + ExceptionFilter filter = new ExceptionFilter(); + + new GatewayRSocket(registry, Collections.singletonList(filter)) + .requestResponse(incomingPayload) + .block(Duration.ofSeconds(5)); + + // assertNull(socket); + } + + + private static class TestFilter implements GatewayFilter { + + private volatile boolean invoked; + + public boolean invoked() { + return this.invoked; + } + + @Override + public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { + this.invoked = true; + return doFilter(exchange, chain); + } + + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + return chain.filter(exchange); + } + } + + + private static class ShortcircuitingFilter extends TestFilter { + + @Override + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + return Mono.empty(); + } + } + + private static class FalseFilter extends TestFilter { + + @Override + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + return Mono.just(false); + } + } + + private static class AsyncFilter extends TestFilter { + + @Override + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + return doAsyncWork().flatMap(asyncResult -> { + logger.debug("Async result: " + asyncResult); + return chain.filter(exchange); + }); + } + + private Mono doAsyncWork() { + return Mono.delay(Duration.ofMillis(100L)).map(l -> "123"); + } + } + + + private static class ExceptionFilter implements GatewayFilter { + + @Override + public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { + return Mono.error(new IllegalStateException("boo")); + } + } + + + /*private static class TestExceptionHandler implements WebExceptionHandler { + + private Throwable ex; + + @Override + public Mono handle(GatewayExchange exchange, Throwable ex) { + this.ex = ex; + return Mono.error(ex); + } + }*/ + +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java index ea569f2a..b0c19461 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java @@ -218,18 +218,4 @@ public class GatewaySocketAcceptorTests { } }*/ - - private static class StubWebHandler extends SocketAcceptorExchange { - - private volatile boolean invoked; - - public StubWebHandler() { - super(mock(ConnectionSetupPayload.class), mock(RSocket.class)); - } - - public boolean invoked() { - return this.invoked; - } - } - } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 01891c0e..68410e55 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -45,6 +45,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.gateway.rsocket.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.Metadata; @@ -106,8 +107,8 @@ public class PingPongApp { //TODO: move to auto-configuration @Bean - public GatewayRSocket gatewayRSocket(Registry registry) { - return new GatewayRSocket(registry); + public GatewayRSocket gatewayRSocket(Registry registry, List filters) { + return new GatewayRSocket(registry, filters); } //TODO: move to auto-configuration From f9d1becd7f3fa39202078d91dfad4ca70dbf683b Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 30 Jan 2019 11:06:02 -0500 Subject: [PATCH 15/67] Moves from Mono to Mono --- .../cloud/gateway/rsocket/GatewayRSocket.java | 1 - .../rsocket/GatewaySocketAcceptor.java | 1 - .../rsocket/RegistrySocketAcceptorFilter.java | 2 +- .../rsocket/filter/AbstractFilterChain.java | 7 ++-- .../gateway/rsocket/filter/FilterChain.java | 4 ++- .../gateway/rsocket/filter/RSocketFilter.java | 12 +++++-- .../gateway/rsocket/GatewayRSocketTests.java | 36 +++---------------- .../rsocket/GatewaySocketAcceptorTests.java | 36 +++---------------- .../gateway/rsocket/test/PingPongApp.java | 2 +- 9 files changed, 28 insertions(+), 73 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java index a6f2b854..e6279879 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java @@ -92,7 +92,6 @@ public class GatewayRSocket extends AbstractRSocket { return this.filterChain.filter(exchange) .log("gateway filter chain") - .filter(bool -> bool) .map(bool -> rSocket); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java index 5857bbe1..3d0f0805 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java @@ -45,7 +45,6 @@ public class GatewaySocketAcceptor implements SocketAcceptor { return this.filterChain.filter(exchange) .log("socket acceptor filter chain") - .filter(bool -> bool) .map(bool -> this.proxyRSocket); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java index 08810c54..3db0c8cd 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java @@ -36,7 +36,7 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order } @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { ConnectionSetupPayload setup = exchange.getSetup(); if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java index 92066e30..df295d1b 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java @@ -23,6 +23,7 @@ import java.util.ListIterator; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; import org.springframework.lang.Nullable; /** @@ -94,12 +95,12 @@ public abstract class AbstractFilterChain filter(E exchange) { + public Mono filter(E exchange) { return Mono.defer(() -> this.currentFilter != null && this.next != null ? this.currentFilter.filter(exchange, this.next) : - MONO_TRUE); + MONO_SUCCESS); } - private static final Mono MONO_TRUE = Mono.just(true); + private static final Mono MONO_SUCCESS = Mono.just(Success.INSTANCE); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java index 2c47fe98..0fdd3a30 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java @@ -19,6 +19,8 @@ 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. * @@ -31,5 +33,5 @@ public interface FilterChain { * @param exchange the current server exchange * @return {@code Mono} to indicate when request handling is complete */ - Mono filter(E exchange); + Mono filter(E exchange); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java index b58af475..e6eeb703 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java @@ -30,13 +30,19 @@ import reactor.core.publisher.Mono; */ public interface RSocketFilter> { + /** + * 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} to indicate when request processing is complete + * @return {@code Mono} to indicate when request processing is complete. */ - //TODO: consider a single value enum rather than boolean - Mono filter(E exchange, FC chain); + Mono filter(E exchange, FC chain); } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java index d21c386c..b111fdbd 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java @@ -112,24 +112,6 @@ public class GatewayRSocketTests { assertNull(payload); } - @Test - public void shortcircuitFalseFilter() { - - TestFilter filter1 = new TestFilter(); - FalseFilter filter2 = new FalseFilter(); - TestFilter filter3 = new TestFilter(); - - - Payload payload = new GatewayRSocket(registry, Arrays.asList(filter1, filter2, filter3)) - .requestResponse(incomingPayload) - .block(Duration.ZERO); - - assertTrue(filter1.invoked()); - assertTrue(filter2.invoked()); - assertFalse(filter3.invoked()); - assertNull(payload); - } - @Test public void asyncFilter() { @@ -166,12 +148,12 @@ public class GatewayRSocketTests { } @Override - public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { this.invoked = true; return doFilter(exchange, chain); } - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange); } } @@ -180,23 +162,15 @@ public class GatewayRSocketTests { private static class ShortcircuitingFilter extends TestFilter { @Override - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { return Mono.empty(); } } - private static class FalseFilter extends TestFilter { - - @Override - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { - return Mono.just(false); - } - } - private static class AsyncFilter extends TestFilter { @Override - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { return doAsyncWork().flatMap(asyncResult -> { logger.debug("Async result: " + asyncResult); return chain.filter(exchange); @@ -212,7 +186,7 @@ public class GatewayRSocketTests { private static class ExceptionFilter implements GatewayFilter { @Override - public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { return Mono.error(new IllegalStateException("boo")); } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java index b0c19461..09e50721 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java @@ -101,24 +101,6 @@ public class GatewaySocketAcceptorTests { assertNull(socket); } - @Test - public void shortcircuitFalseFilter() { - - TestFilter filter1 = new TestFilter(); - FalseFilter filter2 = new FalseFilter(); - TestFilter filter3 = new TestFilter(); - - - RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Arrays.asList(filter1, filter2, filter3)) - .accept(setupPayload, sendingSocket) - .block(Duration.ZERO); - - assertTrue(filter1.invoked()); - assertTrue(filter2.invoked()); - assertFalse(filter3.invoked()); - assertNull(socket); - } - @Test public void asyncFilter() { @@ -155,12 +137,12 @@ public class GatewaySocketAcceptorTests { } @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { this.invoked = true; return doFilter(exchange, chain); } - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return chain.filter(exchange); } } @@ -169,23 +151,15 @@ public class GatewaySocketAcceptorTests { private static class ShortcircuitingFilter extends TestFilter { @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return Mono.empty(); } } - private static class FalseFilter extends TestFilter { - - @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { - return Mono.just(false); - } - } - private static class AsyncFilter extends TestFilter { @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return doAsyncWork().flatMap(asyncResult -> { logger.debug("Async result: " + asyncResult); return chain.filter(exchange); @@ -201,7 +175,7 @@ public class GatewaySocketAcceptorTests { private static class ExceptionFilter implements SocketAcceptorFilter { @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { return Mono.error(new IllegalStateException("boo")); } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 68410e55..5b5acb4c 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -94,7 +94,7 @@ public class PingPongApp { static class MySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { @Override - public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { + public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { System.out.println("mySocketAcceptorFilter"); return chain.filter(exchange); } From 9f6faec2058597cecccc878c17c57663823439ed Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 30 Jan 2019 16:07:41 -0500 Subject: [PATCH 16/67] Adds autoconfiguration for Gateway RSocket. Includes GatewayRSocketProperties. Moves GatewayRSocketServer to a top level class that is a SmartLifeCycle. Also adds integration tests. fixes gh-10 --- pom.xml | 6 + .../GatewayRSocketAutoConfiguration.java | 107 ++++++++ .../rsocket/GatewayRSocketProperties.java | 118 +++++++++ .../gateway/rsocket/GatewayRSocketServer.java | 120 +++++++++ src/main/resources/META-INF/spring.factories | 3 + .../GatewayRSocketAutoConfigurationTests.java | 72 ++++++ .../GatewayRSocketIntegrationTests.java | 88 +++++++ .../gateway/rsocket/test/PingPongApp.java | 228 +++++++++--------- 8 files changed, 629 insertions(+), 113 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java create mode 100644 src/main/resources/META-INF/spring.factories create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java diff --git a/pom.xml b/pom.xml index 2db731e4..39c20118 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,12 @@ org.springframework.boot spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-configuration-processor + true io.rsocket diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java new file mode 100644 index 00000000..15d23f78 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java @@ -0,0 +1,107 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +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.RSocket; + +import io.rsocket.micrometer.MicrometerRSocketInterceptor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; +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) +public class GatewayRSocketAutoConfiguration { + + @Bean + public Registry registry() { + return new Registry(); + } + + @Bean + public RegistrySocketAcceptorFilter registrySocketAcceptorFilter(Registry registry) { + return new RegistrySocketAcceptorFilter(registry); + } + + @Bean + public GatewayRSocket gatewayRSocket(Registry registry, List filters) { + return new GatewayRSocket(registry, filters); + } + + @Bean + public GatewayRSocketProperties gatewayRSocketProperties() { + return new GatewayRSocketProperties(); + } + + @Bean + public GatewaySocketAcceptor socketAcceptor(GatewayRSocket rsocket, List filters) { + return new GatewaySocketAcceptor(rsocket, filters); + } + + @Configuration + @ConditionalOnClass({ MeterRegistry.class, MicrometerRSocketInterceptor.class }) + protected static class GatewayRSocketServerMicrometerConfiguration { + + private final GatewayRSocketProperties properties; + + public GatewayRSocketServerMicrometerConfiguration(GatewayRSocketProperties properties) { + this.properties = properties; + } + + @Bean + public MicrometerRSocketInterceptor micrometerRSocketInterceptor(MeterRegistry meterRegistry) { + Tags tags = Tags.of(properties.getServer().getMicrometerTags().toArray(new String[]{})); + List tagList = tags.stream().collect(Collectors.toList()); + return new MicrometerRSocketInterceptor(meterRegistry, tagList.toArray(new Tag[]{})); + } + + @Bean + public GatewayRSocketServer gatewayApp(GatewaySocketAcceptor socketAcceptor, + MicrometerRSocketInterceptor micrometerInterceptor) { + return new GatewayRSocketServer(properties, socketAcceptor, micrometerInterceptor); + } + + } + + @Configuration + @ConditionalOnMissingClass({ "io.micrometer.core.instrument.MeterRegistry", "io.rsocket.micrometer.MicrometerRSocketInterceptor" }) + protected static class GatewayRSocketServerConfiguration { + + @Bean + public GatewayRSocketServer gatewayApp(GatewayRSocketProperties properties, GatewaySocketAcceptor socketAcceptor) { + return new GatewayRSocketServer(properties, socketAcceptor); + } + + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java new file mode 100644 index 00000000..d1f7c3a3 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java @@ -0,0 +1,118 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.style.ToStringCreator; + +import java.util.ArrayList; +import java.util.List; + +@ConfigurationProperties("spring.cloud.gateway.rsocket") +public class GatewayRSocketProperties { + + /** + * Enable Gateway RSocket. + */ + private boolean enabled = true; + + private final Server server = new Server(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public Server getServer() { + return server; + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("enabled", enabled) + .append("server", server) + .toString(); + } + + /** + * Server properties. + */ + public static class Server { + + //TODO: other transports + public enum TransportType { TCP } + + /** + * Tag names and values to be supplied to Micrometer Interceptor. + */ + private List micrometerTags = new ArrayList<>(); + + /** + * Server port. + */ + private int port = 7002; //TODO: different default port? + + public Server() { + micrometerTags.add("component"); + micrometerTags.add("gateway"); + } + + /** + * Server transport type. Defaults to TCP. + */ + private TransportType transport = TransportType.TCP; + + public List getMicrometerTags() { + return micrometerTags; + } + + public void setMicrometerTags(List micrometerTags) { + this.micrometerTags = micrometerTags; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public TransportType getTransport() { + return transport; + } + + public void setTransport(TransportType transport) { + this.transport = transport; + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("micrometerTags", micrometerTags) + .append("port", port) + .append("transport", transport) + .toString(); + } + } + +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java new file mode 100644 index 00000000..e2735384 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java @@ -0,0 +1,120 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.rsocket.RSocketFactory; +import io.rsocket.SocketAcceptor; +import io.rsocket.plugins.RSocketInterceptor; +import io.rsocket.transport.netty.server.CloseableChannel; +import io.rsocket.transport.netty.server.TcpServerTransport; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.cloud.gateway.rsocket.GatewayRSocketProperties.Server.TransportType; +import org.springframework.context.SmartLifecycle; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; + +public class GatewayRSocketServer implements Ordered, SmartLifecycle { + + private static final Log log = LogFactory.getLog(GatewayRSocketServer.class); + private static final RSocketInterceptor[] EMPTY_INTERCEPTORS = new RSocketInterceptor[0]; + + private final GatewayRSocketProperties properties; + private final SocketAcceptor socketAcceptor; + private final List serverInterceptors; + private final AtomicBoolean running = new AtomicBoolean(); + private CloseableChannel closeableChannel; + + public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor) { + this(properties, socketAcceptor, EMPTY_INTERCEPTORS); + } + + public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor, RSocketInterceptor... interceptors) { + Assert.notNull(properties, "properties may not be null"); + Assert.notNull(socketAcceptor, "socketAcceptor may not be null"); + Assert.notNull(interceptors, "interceptors may not be null"); + this.properties = properties; + this.socketAcceptor = socketAcceptor; + this.serverInterceptors = Arrays.asList(interceptors); + } + + @Override + public int getOrder() { + // return 0; + return HIGHEST_PRECEDENCE; + } + + @Override + public void start() { + if (running.compareAndSet(false, true)) { + startServer(); + } + } + + @Override + public void stop() { + if (running.compareAndSet(true, false)) { + if (log.isInfoEnabled()) { + log.info("Stopping Gateway RSocket Server"); + } + if (closeableChannel != null) { + closeableChannel.dispose(); + } + } + } + + @Override + public boolean isRunning() { + return running.get(); + } + + protected void startServer() { + int port = properties.getServer().getPort(); + + TransportType transportType = properties.getServer().getTransport(); + TcpServerTransport transport; + switch (transportType) { + case TCP: + transport = TcpServerTransport.create(port); + break; + default: + throw new IllegalArgumentException("No support for server transport " + transportType); + } + + if (log.isInfoEnabled()) { + log.info("Starting Gateway RSocket Server on port: " + port + ", transport: " + transportType); + } + + RSocketFactory.ServerRSocketFactory factory = RSocketFactory.receive(); + + serverInterceptors.forEach(factory::addServerPlugin); + + factory.acceptor(this.socketAcceptor) + .transport(transport) + .start() + .map(closeableChannel -> { + this.closeableChannel = closeableChannel; + return closeableChannel; + }).subscribe(); + } +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..a472acb2 --- /dev/null +++ b/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.gateway.rsocket.GatewayRSocketAutoConfiguration diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java new file mode 100644 index 00000000..1f5fa361 --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java @@ -0,0 +1,72 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import io.micrometer.core.instrument.MeterRegistry; +import io.rsocket.micrometer.MicrometerRSocketInterceptor; +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.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +public class GatewayRSocketAutoConfigurationTests { + + @Test + public void gatewayRSocketWithMicrometerConfigured() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + GatewayRSocketAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class, + MetricsAutoConfiguration.class + )) + .run(context -> assertThat(context) + .hasSingleBean(Registry.class) + .hasSingleBean(RegistrySocketAcceptorFilter.class) + .hasSingleBean(GatewayRSocket.class) + .hasSingleBean(GatewayRSocketServer.class) + .hasSingleBean(GatewayRSocketProperties.class) + .hasSingleBean(GatewaySocketAcceptor.class) + .hasSingleBean(MicrometerRSocketInterceptor.class) + ); + } + + @Test + public void gatewayRSocketWithoutMicrometerConfigured() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + GatewayRSocketAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class, + MetricsAutoConfiguration.class + )) + .withClassLoader(new FilteredClassLoader(MeterRegistry.class, MicrometerRSocketInterceptor.class)) + .run(context -> assertThat(context) + .hasSingleBean(Registry.class) + .hasSingleBean(RegistrySocketAcceptorFilter.class) + .hasSingleBean(GatewayRSocket.class) + .hasSingleBean(GatewayRSocketServer.class) + .hasSingleBean(GatewayRSocketProperties.class) + .hasSingleBean(GatewaySocketAcceptor.class) + .doesNotHaveBean(MicrometerRSocketInterceptor.class) + ); + } +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java new file mode 100644 index 00000000..31f063f6 --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java @@ -0,0 +1,88 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.test.StepVerifier; + +import org.springframework.beans.factory.annotation.Autowired; +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.util.SocketUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = PingPongApp.class, + properties = { "ping.take=5" }, + webEnvironment = WebEnvironment.RANDOM_PORT) +public class GatewayRSocketIntegrationTests { + + private static int port; + + @Autowired + private PingPongApp.Ping ping; + + @Autowired + private PingPongApp.Pong pong; + + @Autowired + private GatewayRSocketProperties properties; + + @Autowired + private PingPongApp.MySocketAcceptorFilter mySocketAcceptorFilter; + + @Autowired + private PingPongApp.MyGatewayFilter myGatewayFilter; + + @Autowired + private GatewayRSocketServer server; + + @BeforeClass + public static void init() { + port = SocketUtils.findAvailableTcpPort(); + System.setProperty("spring.cloud.gateway.rsocket.server.port", String.valueOf(port)); + } + + @AfterClass + public static void after() { + System.clearProperty("spring.cloud.gateway.rsocket.server.port"); + } + + @Test + public void contextLoads() { + StepVerifier.create(ping.getPongFlux()) + .expectSubscription() + .then(() -> server.stop()) + .thenConsumeWhile(s -> true) + .verifyComplete(); + + assertThat(ping.getPongsReceived()).isGreaterThan(0); + assertThat(pong.getPingsReceived()).isGreaterThan(0); + assertThat(properties.getServer().getPort()).isNotEqualTo(7002); + assertThat(mySocketAcceptorFilter.invoked()).isTrue(); + assertThat(myGatewayFilter.invoked()).isTrue(); + assertThat(server.isRunning()).isFalse(); + } + +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 5b5acb4c..90923ebb 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -18,7 +18,8 @@ package org.springframework.cloud.gateway.rsocket.test; import java.time.Duration; -import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; @@ -28,14 +29,11 @@ import io.netty.buffer.ByteBufUtil; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.RSocketFactory; -import io.rsocket.SocketAcceptor; import io.rsocket.micrometer.MicrometerRSocketInterceptor; import io.rsocket.transport.netty.client.TcpClientTransport; -import io.rsocket.transport.netty.server.CloseableChannel; -import io.rsocket.transport.netty.server.TcpServerTransport; import io.rsocket.util.DefaultPayload; import io.rsocket.util.RSocketProxy; -import lombok.extern.log4j.Log4j2; +import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -43,17 +41,18 @@ 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.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayExchange; import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilterChain; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.Metadata; -import org.springframework.cloud.gateway.rsocket.Registry; -import org.springframework.cloud.gateway.rsocket.RegistrySocketAcceptorFilter; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; import static io.netty.buffer.Unpooled.EMPTY_BUFFER; @@ -66,6 +65,7 @@ public class PingPongApp { } @Bean + @ConditionalOnProperty("ping.two.enabled") public Ping ping2() { return new Ping("2"); } @@ -75,16 +75,9 @@ public class PingPongApp { return new Pong(); } - //TODO: move to auto-configuration @Bean - public Registry registry() { - return new Registry(); - } - - //TODO: move to auto-configuration - @Bean - public RegistrySocketAcceptorFilter registrySocketAcceptorFilter(Registry registry) { - return new RegistrySocketAcceptorFilter(registry); + public MyGatewayFilter myGatewayFilter() { + return new MyGatewayFilter(); } @Bean @@ -92,74 +85,10 @@ public class PingPongApp { return new MySocketAcceptorFilter(); } - static class MySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { - @Override - public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { - System.out.println("mySocketAcceptorFilter"); - return chain.filter(exchange); - } - - @Override - public int getOrder() { - return 0; - } - } - - //TODO: move to auto-configuration - @Bean - public GatewayRSocket gatewayRSocket(Registry registry, List filters) { - return new GatewayRSocket(registry, filters); - } - - //TODO: move to auto-configuration - @Bean - public GatewaySocketAcceptor socketAcceptor(GatewayRSocket rsocket, List filters) { - return new GatewaySocketAcceptor(rsocket, filters); - } - - @Bean - public GatewayApp gatewayApp(GatewaySocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { - return new GatewayApp(socketAcceptor, meterRegistry); - } - public static void main(String[] args) { SpringApplication.run(PingPongApp.class, args); } - @Log4j2 - static class GatewayApp implements Ordered, ApplicationListener { - - private final SocketAcceptor socketAcceptor; - private final MeterRegistry meterRegistry; - private MicrometerRSocketInterceptor interceptor; - - public GatewayApp(SocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { - this.socketAcceptor = socketAcceptor; - this.meterRegistry = meterRegistry; - } - - @Override - public int getOrder() { - // return 0; - return HIGHEST_PRECEDENCE; - } - - @Override - public void onApplicationEvent(ApplicationReadyEvent event) { - log.info("Starting Gateway App"); - interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "proxy")); - TcpServerTransport transport = TcpServerTransport.create(7002); - - Mono rsocketServer = RSocketFactory.receive() - .addServerPlugin(interceptor) - .acceptor(this.socketAcceptor) - .transport(transport) - .start(); - - rsocketServer.subscribe(); - } - } - static String reply(String in) { if (in.length() > 4) { in = in.substring(0, 4); @@ -174,16 +103,16 @@ public class PingPongApp { } } - @Log4j2 - static class Ping implements Ordered, ApplicationListener { + @Slf4j + public static class Ping implements Ordered, ApplicationListener { @Autowired - MeterRegistry meterRegistry; + private MeterRegistry meterRegistry; - private String id = ""; + private final String id; - public Ping() { - } + private final AtomicInteger pongsReceived = new AtomicInteger(); + private Flux pongFlux; public Ping(String id) { this.id = id; @@ -191,60 +120,87 @@ public class PingPongApp { @Override public int getOrder() { - // return Ordered.LOWEST_PRECEDENCE; 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.cloud.gateway.rsocket.server.port", + Integer.class, 7002); + + log.info("ping.take: " + take); + MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); ByteBuf announcementMetadata = Metadata.encodeAnnouncement("ping"); - RSocketFactory.connect() + pongFlux = RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) .addClientPlugin(interceptor) - .transport(TcpClientTransport.create(7002)) // proxy + .transport(TcpClientTransport.create(gatewayPort)) // proxy .start() .flatMapMany(socket -> - socket.requestChannel( - Flux.interval(Duration.ofSeconds(1)) - .map(i -> { - ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "ping" + id); - ByteBuf routingMetadata = Metadata.encodeRouting("pong"); - return DefaultPayload.create(data, routingMetadata); - }) - .onBackpressureDrop(payload -> log.debug("Dropped payload " + payload.getDataUtf8())) // this is needed in case pong is not available yet - ).map(Payload::getDataUtf8) - .doOnNext(str -> log.info("received " + str + " in Ping"+id)) - // .take(10) - .doFinally(signal -> socket.dispose()) - ) - .then() - .subscribe(); + { + Flux pong = socket.requestChannel( + Flux.interval(Duration.ofSeconds(1)) + .map(i -> { + ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "ping" + id); + ByteBuf routingMetadata = Metadata.encodeRouting("pong"); + return DefaultPayload.create(data, routingMetadata); + }) + .onBackpressureDrop(payload -> log.debug("Dropped payload " + payload.getDataUtf8())) // this is needed in case pong is not available yet + ).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; + } + ); + + pongFlux.subscribe(); + } + + public Flux getPongFlux() { + return pongFlux; + } + + public int getPongsReceived() { + return pongsReceived.get(); } } - @Log4j2 - static class Pong implements Ordered, ApplicationListener { + @Slf4j + public static class Pong implements Ordered, ApplicationListener { @Autowired - MeterRegistry meterRegistry; + private MeterRegistry meterRegistry; + + private final AtomicInteger pingsReceived = new AtomicInteger(); @Override public int getOrder() { - // return 1; 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(5000); + Thread.sleep(pongDelay); } catch (InterruptedException e) { e.printStackTrace(); } log.info("Starting Pong"); + Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", + Integer.class, 7002); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); ByteBuf announcementMetadata = Metadata.encodeAnnouncement("pong"); RSocketFactory.connect() @@ -252,9 +208,8 @@ public class PingPongApp { .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) .addClientPlugin(interceptor) .acceptor(this::accept) - .transport(TcpClientTransport.create(7002)) // proxy + .transport(TcpClientTransport.create(gatewayPort)) // proxy .start() - .then() .block(); } @@ -266,7 +221,10 @@ public class PingPongApp { public Flux requestChannel(Publisher payloads) { return Flux.from(payloads) .map(Payload::getDataUtf8) - .doOnNext(str -> log.info("received " + str + " in Pong")) + .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); @@ -277,5 +235,49 @@ public class PingPongApp { }; return pong; } + + public int getPingsReceived() { + return pingsReceived.get(); + } } + + @Slf4j + public static class MyGatewayFilter implements GatewayFilter { + private AtomicBoolean invoked = new AtomicBoolean(false); + + @Override + public Mono 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 filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.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(); + } + } + + } From 7d3ef5db179c264287b6e3aa938dd8dbad7b863d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 30 Jan 2019 17:14:52 -0500 Subject: [PATCH 17/67] Now uses a DirectProcessor and FluxSink to handle event publishing. fixes gh-3 --- .../cloud/gateway/rsocket/Registry.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 72a628eb..56bfed28 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -26,18 +26,26 @@ import io.rsocket.RSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.Disposable; -import reactor.core.publisher.EmitterProcessor; +import reactor.core.publisher.DirectProcessor; +import reactor.core.publisher.FluxSink; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +/** + * The Registry handles all RSocket connections that have been made that have associated + * announcement metadata. RSocket connections can then be found based on routing metadata. + * When a new RSocket is registered, a RegisteredEvent is pushed onto a DirectProcessor + * that is acting as an event bus for registered Consumers. + */ //TODO: name? public class Registry { private static final Log log = LogFactory.getLog(Registry.class); private final Map rsockets = new ConcurrentHashMap<>(); - private final EmitterProcessor registeredEvents = EmitterProcessor.create(); + private final DirectProcessor registeredEvents = DirectProcessor.create(); + private final FluxSink registeredEventsSink = registeredEvents.sink(FluxSink.OverflowStrategy.DROP); public void register(List tags, RSocket rsocket) { Assert.notEmpty(tags, "tags may not be empty"); @@ -45,7 +53,7 @@ public class Registry { log.debug("Registered RSocket: " + tags); LoadBalancedRSocket composite = rsockets.computeIfAbsent(tags.get(0), s -> new LoadBalancedRSocket()); composite.addRSocket(rsocket); - registeredEvents.onNext(new RegisteredEvent(tags, rsocket)); + registeredEventsSink.next(new RegisteredEvent(tags, rsocket)); } public RSocket getRegistered(List tags) { From 04a272a9aa0ffaec5227907a3bb2d04409a2f090 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 30 Jan 2019 17:30:37 -0500 Subject: [PATCH 18/67] Changes announcement metadata to List. Move Registry key gen to a Function. --- .../cloud/gateway/rsocket/Metadata.java | 33 ++++++++++++++--- .../cloud/gateway/rsocket/Registry.java | 37 ++++++++++++++++--- .../rsocket/RegistrySocketAcceptorFilter.java | 10 ++--- .../gateway/rsocket/test/PingPongApp.java | 4 +- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java index 4a3de661..8436477d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java @@ -34,25 +34,45 @@ public abstract class Metadata { private Metadata() {} - public static ByteBuf encodeAnnouncement(String announcement) { - return encodeAnnouncement(ByteBufAllocator.DEFAULT, announcement); + public static ByteBuf encodeAnnouncement(String... tags) { + return encodeAnnouncement(ByteBufAllocator.DEFAULT, tags); } - public static ByteBuf encodeAnnouncement(ByteBufAllocator allocator, String announcement) { + @SuppressWarnings("Duplicates") + public static ByteBuf encodeAnnouncement(ByteBufAllocator allocator, String... tags) { + Assert.notEmpty(tags, "tags may not be null or empty"); //TODO: is this true? ByteBuf byteBuf = allocator.buffer(); - ByteBufUtil.writeUtf8(byteBuf, announcement); + + for (String tag : tags) { + int tagLength = NumberUtils.requireUnsignedByte(ByteBufUtil.utf8Bytes(tag)); + byteBuf.writeByte(tagLength); + ByteBufUtil.reserveAndWriteUtf8(byteBuf, tag, tagLength); + } return byteBuf; } //TODO: either rsocket spec or custom spec - public static String decodeAnnouncement(ByteBuf metadata) { - return metadata.toString(StandardCharsets.UTF_8); + @SuppressWarnings("Duplicates") + public static List decodeAnnouncement(ByteBuf byteBuf) { + ArrayList tags = new ArrayList<>(); + + int offset = 0; + while (offset < byteBuf.readableBytes()) { //TODO: What is the best conditional here? + int tagLength = byteBuf.getByte(offset); + offset += Byte.BYTES; + String tag = byteBuf.toString(offset, tagLength, StandardCharsets.UTF_8); + tags.add(tag); + offset += tagLength; + } + + return tags; } public static ByteBuf encodeRouting(String... tags) { return encodeRouting(ByteBufAllocator.DEFAULT, tags); } + @SuppressWarnings("Duplicates") public static ByteBuf encodeRouting(ByteBufAllocator allocator, String... tags) { Assert.notEmpty(tags, "tags may not be null or empty"); //TODO: is this true? ByteBuf byteBuf = allocator.buffer(); @@ -65,6 +85,7 @@ public abstract class Metadata { return byteBuf; } + @SuppressWarnings("Duplicates") public static List decodeRouting(ByteBuf byteBuf) { ArrayList tags = new ArrayList<>(); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java index 56bfed28..ebab984d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; +import java.util.function.Function; import io.rsocket.RSocket; import org.apache.commons.logging.Log; @@ -46,21 +47,40 @@ public class Registry { private final DirectProcessor registeredEvents = DirectProcessor.create(); private final FluxSink registeredEventsSink = registeredEvents.sink(FluxSink.OverflowStrategy.DROP); + private final Function, String> keyFunction; + + public Registry() { + this(tags -> { + if (CollectionUtils.isEmpty(tags)) { + return null; // throw error? + } + //TODO: key generation + return tags.get(0); + }); + } + + public Registry(Function, String> keyFunction) { + this.keyFunction = keyFunction; + } + + public String computeKey(List tags) { + return keyFunction.apply(tags); + } public void register(List tags, RSocket rsocket) { Assert.notEmpty(tags, "tags may not be empty"); Assert.notNull(rsocket, "RSocket may not be null"); log.debug("Registered RSocket: " + tags); - LoadBalancedRSocket composite = rsockets.computeIfAbsent(tags.get(0), s -> new LoadBalancedRSocket()); + LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(tags), s -> new LoadBalancedRSocket()); composite.addRSocket(rsocket); - registeredEventsSink.next(new RegisteredEvent(tags, rsocket)); + registeredEventsSink.next(new RegisteredEvent(keyFunction, tags, rsocket)); } public RSocket getRegistered(List tags) { if (CollectionUtils.isEmpty(tags)) { return null; } - return rsockets.get(tags.get(0)); + return rsockets.get(computeKey(tags)); } public Disposable addListener(Consumer consumer) { @@ -68,12 +88,16 @@ public class Registry { } public static class RegisteredEvent { + private final Function, String> keyFunction; private final List routingMetadata; private final RSocket rSocket; - public RegisteredEvent(List routingMetadata, RSocket rSocket) { + public RegisteredEvent(Function, String> keyFunction, List routingMetadata, + RSocket rSocket) { + Assert.notNull(keyFunction, "keyFunction may not be null"); Assert.notEmpty(routingMetadata, "routingMetadata may not be empty"); Assert.notNull(rSocket, "RSocket may not be null"); + this.keyFunction = keyFunction; this.routingMetadata = routingMetadata; this.rSocket = rSocket; } @@ -88,8 +112,9 @@ public class Registry { public boolean matches(List otherRoutingMetadata) { if (!CollectionUtils.isEmpty(otherRoutingMetadata)) { - //TODO: key generation - return this.routingMetadata.get(0).equals(otherRoutingMetadata.get(0)); + String thisKey = keyFunction.apply(routingMetadata); + String otherKey = keyFunction.apply(otherRoutingMetadata); + return thisKey.equals(otherKey); } return false; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java index 3db0c8cd..f33f4a73 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java @@ -17,16 +17,15 @@ package org.springframework.cloud.gateway.rsocket; +import java.util.List; + import io.rsocket.ConnectionSetupPayload; -import org.springframework.core.Ordered; import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorExchange; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilterChain; - -import java.util.Collections; -import java.util.List; +import org.springframework.core.Ordered; public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { private final Registry registry; @@ -39,8 +38,7 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { ConnectionSetupPayload setup = exchange.getSetup(); if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata - String annoucementMetadata = Metadata.decodeAnnouncement(setup.sliceMetadata()); - List tags = Collections.singletonList(annoucementMetadata); + List tags = Metadata.decodeAnnouncement(setup.sliceMetadata()); registry.register(tags, exchange.getSendingSocket()); } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 90923ebb..a14b9340 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -134,7 +134,7 @@ public class PingPongApp { log.info("ping.take: " + take); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); - ByteBuf announcementMetadata = Metadata.encodeAnnouncement("ping"); + ByteBuf announcementMetadata = Metadata.encodeAnnouncement("ping", "ping"+id); pongFlux = RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) @@ -202,7 +202,7 @@ public class PingPongApp { Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", Integer.class, 7002); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); - ByteBuf announcementMetadata = Metadata.encodeAnnouncement("pong"); + ByteBuf announcementMetadata = Metadata.encodeAnnouncement("pong", "pong1"); RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) From 2e38d5f9c95abd7e1657be346eda9dddf52b8422 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 31 Jan 2019 17:08:27 -0500 Subject: [PATCH 19/67] Moves classes to logical packages --- .../GatewayRSocketAutoConfiguration.java | 11 ++++++++--- .../GatewayRSocketProperties.java | 2 +- .../gateway/rsocket/{ => registry}/Registry.java | 3 ++- .../{ => registry}/RegistrySocketAcceptorFilter.java | 9 +++++---- .../gateway/rsocket/{ => server}/GatewayRSocket.java | 4 +++- .../rsocket/{ => server}/GatewayRSocketServer.java | 5 +++-- .../rsocket/{ => server}/GatewaySocketAcceptor.java | 2 +- .../rsocket/{ => server}/LoadBalancedRSocket.java | 2 +- .../rsocket/{ => server}/PendingRequestRSocket.java | 4 ++-- .../gateway/rsocket/{ => support}/Metadata.java | 2 +- src/main/resources/META-INF/spring.factories | 2 +- .../GatewayRSocketAutoConfigurationTests.java | 7 ++++++- .../{ => server}/GatewayRSocketIntegrationTests.java | 3 ++- .../rsocket/{ => server}/GatewayRSocketTests.java | 10 ++++++---- .../{ => server}/GatewaySocketAcceptorTests.java | 8 ++++---- .../cloud/gateway/rsocket/test/PingPongApp.java | 12 ++++++------ 16 files changed, 52 insertions(+), 34 deletions(-) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => autoconfigure}/GatewayRSocketAutoConfiguration.java (85%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => autoconfigure}/GatewayRSocketProperties.java (97%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => registry}/Registry.java (96%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => registry}/RegistrySocketAcceptorFilter.java (77%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => server}/GatewayRSocket.java (96%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => server}/GatewayRSocketServer.java (93%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => server}/GatewaySocketAcceptor.java (98%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => server}/LoadBalancedRSocket.java (97%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => server}/PendingRequestRSocket.java (93%) rename src/main/java/org/springframework/cloud/gateway/rsocket/{ => support}/Metadata.java (98%) rename src/test/java/org/springframework/cloud/gateway/rsocket/{ => autoconfigure}/GatewayRSocketAutoConfigurationTests.java (86%) rename src/test/java/org/springframework/cloud/gateway/rsocket/{ => server}/GatewayRSocketIntegrationTests.java (94%) rename src/test/java/org/springframework/cloud/gateway/rsocket/{ => server}/GatewayRSocketTests.java (92%) rename src/test/java/org/springframework/cloud/gateway/rsocket/{ => server}/GatewaySocketAcceptorTests.java (93%) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java similarity index 85% rename from src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index 15d23f78..06347eb9 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.autoconfigure; import java.util.List; import java.util.stream.Collectors; @@ -30,8 +30,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java similarity index 97% rename from src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java index d1f7c3a3..ad487526 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketProperties.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.autoconfigure; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.core.style.ToStringCreator; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java similarity index 96% rename from src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index ebab984d..1f9c0775 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.registry; import java.util.List; import java.util.Map; @@ -26,6 +26,7 @@ import java.util.function.Function; import io.rsocket.RSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.gateway.rsocket.server.LoadBalancedRSocket; import reactor.core.Disposable; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.FluxSink; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java similarity index 77% rename from src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index f33f4a73..98e7b48c 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -15,16 +15,17 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.registry; import java.util.List; import io.rsocket.ConnectionSetupPayload; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorExchange; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilterChain; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorExchange; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilterChain; import org.springframework.core.Ordered; public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java similarity index 96% rename from src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index e6279879..82c0b38d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import java.util.List; @@ -25,6 +25,8 @@ import io.rsocket.RSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java similarity index 93% rename from src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java index e2735384..3e8cda53 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketServer.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import java.util.Arrays; import java.util.List; @@ -29,7 +29,8 @@ import io.rsocket.transport.netty.server.TcpServerTransport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.gateway.rsocket.GatewayRSocketProperties.Server.TransportType; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties.Server.TransportType; import org.springframework.context.SmartLifecycle; import org.springframework.core.Ordered; import org.springframework.util.Assert; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java similarity index 98% rename from src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java index 3d0f0805..0e41739c 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import java.util.List; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/LoadBalancedRSocket.java similarity index 97% rename from src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/server/LoadBalancedRSocket.java index 30791a49..c073a8c5 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/LoadBalancedRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/LoadBalancedRSocket.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java similarity index 93% rename from src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 88c15e95..50c6fb21 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import java.util.List; import java.util.function.Consumer; @@ -28,7 +28,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; -import org.springframework.cloud.gateway.rsocket.Registry.RegisteredEvent; +import org.springframework.cloud.gateway.rsocket.registry.Registry.RegisteredEvent; public class PendingRequestRSocket extends AbstractRSocket implements Consumer { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java similarity index 98% rename from src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java index 8436477d..20f2d768 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/Metadata.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.support; import java.nio.charset.StandardCharsets; import java.util.ArrayList; diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index a472acb2..4cf65ad1 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -1,3 +1,3 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.gateway.rsocket.GatewayRSocketAutoConfiguration +org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketAutoConfiguration diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java similarity index 86% rename from src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java rename to src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index 1f5fa361..8a7a7425 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketAutoConfigurationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.autoconfigure; import io.micrometer.core.instrument.MeterRegistry; import io.rsocket.micrometer.MicrometerRSocketInterceptor; @@ -26,6 +26,11 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfigu import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java similarity index 94% rename from src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java rename to src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java index 31f063f6..9d976b9a 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java @@ -15,12 +15,13 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import reactor.test.StepVerifier; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java similarity index 92% rename from src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java rename to src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index b111fdbd..69ca9502 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import java.time.Duration; import java.util.Arrays; @@ -29,11 +29,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayExchange; -import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; -import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilterChain; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayExchange; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilterChain; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptorTests.java similarity index 93% rename from src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java rename to src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptorTests.java index 09e50721..554f2b00 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptorTests.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket; +package org.springframework.cloud.gateway.rsocket.server; import java.time.Duration; import java.util.Arrays; @@ -27,11 +27,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilterChain; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilterChain; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorExchange; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorExchange; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index a14b9340..e73e1918 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -43,12 +43,12 @@ 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.GatewayRSocket.GatewayExchange; -import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilter; -import org.springframework.cloud.gateway.rsocket.GatewayRSocket.GatewayFilterChain; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor; -import org.springframework.cloud.gateway.rsocket.GatewaySocketAcceptor.SocketAcceptorFilter; -import org.springframework.cloud.gateway.rsocket.Metadata; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayExchange; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilterChain; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; From f18c745324126213a030382acd4a10f113a4097d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 31 Jan 2019 17:44:44 -0500 Subject: [PATCH 20/67] Moves from tags to name value pairs to routing and announcement metadata --- .../gateway/rsocket/registry/Registry.java | 39 ++++--- .../RegistrySocketAcceptorFilter.java | 8 +- .../rsocket/server/GatewayRSocket.java | 25 ++--- .../rsocket/server/PendingRequestRSocket.java | 6 +- .../gateway/rsocket/support/Metadata.java | 101 ++++++++++++------ .../rsocket/server/GatewayRSocketTests.java | 10 +- .../gateway/rsocket/test/PingPongApp.java | 8 +- 7 files changed, 116 insertions(+), 81 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 1f9c0775..32b3b22e 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.rsocket.registry; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @@ -26,11 +25,11 @@ import java.util.function.Function; import io.rsocket.RSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.gateway.rsocket.server.LoadBalancedRSocket; import reactor.core.Disposable; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.FluxSink; +import org.springframework.cloud.gateway.rsocket.server.LoadBalancedRSocket; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -48,7 +47,7 @@ public class Registry { private final DirectProcessor registeredEvents = DirectProcessor.create(); private final FluxSink registeredEventsSink = registeredEvents.sink(FluxSink.OverflowStrategy.DROP); - private final Function, String> keyFunction; + private final Function, String> keyFunction; public Registry() { this(tags -> { @@ -56,32 +55,32 @@ public class Registry { return null; // throw error? } //TODO: key generation - return tags.get(0); + return tags.get("name"); }); } - public Registry(Function, String> keyFunction) { + public Registry(Function, String> keyFunction) { this.keyFunction = keyFunction; } - public String computeKey(List tags) { - return keyFunction.apply(tags); + public String computeKey(Map properties) { + return keyFunction.apply(properties); } - public void register(List tags, RSocket rsocket) { - Assert.notEmpty(tags, "tags may not be empty"); + public void register(Map properties, RSocket rsocket) { + Assert.notEmpty(properties, "properties may not be empty"); Assert.notNull(rsocket, "RSocket may not be null"); - log.debug("Registered RSocket: " + tags); - LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(tags), s -> new LoadBalancedRSocket()); + log.debug("Registered RSocket: " + properties); + LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> new LoadBalancedRSocket()); composite.addRSocket(rsocket); - registeredEventsSink.next(new RegisteredEvent(keyFunction, tags, rsocket)); + registeredEventsSink.next(new RegisteredEvent(keyFunction, properties, rsocket)); } - public RSocket getRegistered(List tags) { - if (CollectionUtils.isEmpty(tags)) { + public RSocket getRegistered(Map properties) { + if (CollectionUtils.isEmpty(properties)) { return null; } - return rsockets.get(computeKey(tags)); + return rsockets.get(computeKey(properties)); } public Disposable addListener(Consumer consumer) { @@ -89,11 +88,11 @@ public class Registry { } public static class RegisteredEvent { - private final Function, String> keyFunction; - private final List routingMetadata; + private final Function, String> keyFunction; + private final Map routingMetadata; private final RSocket rSocket; - public RegisteredEvent(Function, String> keyFunction, List routingMetadata, + public RegisteredEvent(Function, String> keyFunction, Map routingMetadata, RSocket rSocket) { Assert.notNull(keyFunction, "keyFunction may not be null"); Assert.notEmpty(routingMetadata, "routingMetadata may not be empty"); @@ -103,7 +102,7 @@ public class Registry { this.rSocket = rSocket; } - public List getRoutingMetadata() { + public Map getRoutingMetadata() { return routingMetadata; } @@ -111,7 +110,7 @@ public class Registry { return rSocket; } - public boolean matches(List otherRoutingMetadata) { + public boolean matches(Map otherRoutingMetadata) { if (!CollectionUtils.isEmpty(otherRoutingMetadata)) { String thisKey = keyFunction.apply(routingMetadata); String otherKey = keyFunction.apply(otherRoutingMetadata); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index 98e7b48c..3601c27d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -17,15 +17,15 @@ package org.springframework.cloud.gateway.rsocket.registry; -import java.util.List; +import java.util.Map; import io.rsocket.ConnectionSetupPayload; -import org.springframework.cloud.gateway.rsocket.support.Metadata; import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorExchange; import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilterChain; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.core.Ordered; public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { @@ -39,8 +39,8 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { ConnectionSetupPayload setup = exchange.getSetup(); if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata - List tags = Metadata.decodeAnnouncement(setup.sliceMetadata()); - registry.register(tags, exchange.getSendingSocket()); + Map properties = Metadata.decodeProperties(setup.sliceMetadata()); + registry.register(properties, exchange.getSendingSocket()); } return chain.filter(exchange); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 82c0b38d..e3f3f015 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.rsocket.server; import java.util.List; +import java.util.Map; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; @@ -25,14 +26,14 @@ import io.rsocket.RSocket; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; -import org.springframework.cloud.gateway.rsocket.registry.Registry; -import org.springframework.cloud.gateway.rsocket.support.Metadata; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.util.CollectionUtils; public class GatewayRSocket extends AbstractRSocket { @@ -79,7 +80,7 @@ public class GatewayRSocket extends AbstractRSocket { } private Mono findRSocket(Payload payload) { - List metadata = getRoutingMetadata(payload); + Map metadata = getRoutingMetadata(payload); RSocket service = findRSocket(metadata); if (service == null) { @@ -97,34 +98,34 @@ public class GatewayRSocket extends AbstractRSocket { .map(bool -> rSocket); } - private RSocket findRSocket(List tags) { - if (tags == null) return null; //TODO: error + private RSocket findRSocket(Map properties) { + if (properties == null) return null; //TODO: error - RSocket rsocket = registry.getRegistered(tags); + RSocket rsocket = registry.getRegistered(properties); if (rsocket == null) { - log.debug("Unable to find destination RSocket for " + tags); + log.debug("Unable to find destination RSocket for " + properties); } // TODO: deal with connecting to cluster? return rsocket; } - private List getRoutingMetadata(Payload payload) { + private Map getRoutingMetadata(Payload payload) { if (payload == null || !payload.hasMetadata()) { // and metadata is routing return null; } // TODO: deal with composite metadata - List tags = Metadata.decodeRouting(payload.sliceMetadata()); + Map properties = Metadata.decodeProperties(payload.sliceMetadata()); - if (CollectionUtils.isEmpty(tags)) { + if (CollectionUtils.isEmpty(properties)) { return null; } - log.debug("found routing metadata " + tags); - return tags; + log.debug("found routing metadata " + properties); + return properties; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 50c6fb21..96034cae 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -17,7 +17,7 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.List; +import java.util.Map; import java.util.function.Consumer; import io.rsocket.AbstractRSocket; @@ -32,10 +32,10 @@ import org.springframework.cloud.gateway.rsocket.registry.Registry.RegisteredEve public class PendingRequestRSocket extends AbstractRSocket implements Consumer { - private final List routingMetadata; + private final Map routingMetadata; private final MonoProcessor processor; - public PendingRequestRSocket(List routingMetadata) { + public PendingRequestRSocket(Map routingMetadata) { this.routingMetadata = routingMetadata; this.processor = MonoProcessor.create(); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java index 20f2d768..f449c98c 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java @@ -19,7 +19,12 @@ package org.springframework.cloud.gateway.rsocket.support; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -27,6 +32,7 @@ import io.netty.buffer.ByteBufUtil; import io.rsocket.util.NumberUtils; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; public abstract class Metadata { @@ -34,12 +40,23 @@ public abstract class Metadata { private Metadata() {} - public static ByteBuf encodeAnnouncement(String... tags) { - return encodeAnnouncement(ByteBufAllocator.DEFAULT, tags); + public static ByteBuf encodeProperties(Map properties) { + return encodeProperties(ByteBufAllocator.DEFAULT, properties); } - @SuppressWarnings("Duplicates") - public static ByteBuf encodeAnnouncement(ByteBufAllocator allocator, String... tags) { + public static ByteBuf encodeProperties(ByteBufAllocator allocator, Map properties) { + Assert.notEmpty(properties, "tags may not be null or empty"); //TODO: is this true? + List pairs = properties.entrySet().stream() + .map(entry -> entry.getValue() + ":" + entry.getValue()) + .collect(Collectors.toList()); + return encodeTags(allocator, pairs.toArray(new String[0])); + } + + public static ByteBuf encodeTags(String... tags) { + return encodeTags(ByteBufAllocator.DEFAULT, tags); + } + + public static ByteBuf encodeTags(ByteBufAllocator allocator, String... tags) { Assert.notEmpty(tags, "tags may not be null or empty"); //TODO: is this true? ByteBuf byteBuf = allocator.buffer(); @@ -51,9 +68,16 @@ public abstract class Metadata { return byteBuf; } - //TODO: either rsocket spec or custom spec - @SuppressWarnings("Duplicates") - public static List decodeAnnouncement(ByteBuf byteBuf) { + public static Map decodeProperties(ByteBuf byteBuf) { + return decodeTags(byteBuf).stream() + .map(Pair::parse) + .filter(Objects::nonNull) + .collect(LinkedHashMap::new, + (map, pair) -> map.put(pair.name, pair.value), + HashMap::putAll); + } + + public static List decodeTags(ByteBuf byteBuf) { ArrayList tags = new ArrayList<>(); int offset = 0; @@ -68,36 +92,47 @@ public abstract class Metadata { return tags; } - public static ByteBuf encodeRouting(String... tags) { - return encodeRouting(ByteBufAllocator.DEFAULT, tags); - } + /** + * A single name value pair. + */ + public static class Pair { - @SuppressWarnings("Duplicates") - public static ByteBuf encodeRouting(ByteBufAllocator allocator, String... tags) { - Assert.notEmpty(tags, "tags may not be null or empty"); //TODO: is this true? - ByteBuf byteBuf = allocator.buffer(); + private String name; - for (String tag : tags) { - int tagLength = NumberUtils.requireUnsignedByte(ByteBufUtil.utf8Bytes(tag)); - byteBuf.writeByte(tagLength); - ByteBufUtil.reserveAndWriteUtf8(byteBuf, tag, tagLength); - } - return byteBuf; - } + private String value; - @SuppressWarnings("Duplicates") - public static List decodeRouting(ByteBuf byteBuf) { - ArrayList tags = new ArrayList<>(); - - int offset = 0; - while (offset < byteBuf.readableBytes()) { //TODO: What is the best conditional here? - int tagLength = byteBuf.getByte(offset); - offset += Byte.BYTES; - String tag = byteBuf.toString(offset, tagLength, StandardCharsets.UTF_8); - tags.add(tag); - offset += tagLength; + public Pair(String name, String value) { + Assert.hasLength(name, "Name must not be empty"); + this.name = name; + this.value = value; + } + + public static Pair parse(String pair) { + int index = getSeparatorIndex(pair); + String name = (index > 0) ? pair.substring(0, index) : pair; + String value = (index > 0) ? pair.substring(index + 1) : ""; + return of(name.trim(), value.trim()); + } + + private static int getSeparatorIndex(String pair) { + int colonIndex = pair.indexOf(':'); + int equalIndex = pair.indexOf('='); + if (colonIndex == -1) { + return equalIndex; + } + if (equalIndex == -1) { + return colonIndex; + } + return Math.min(colonIndex, equalIndex); + } + + private static Pair of(String name, String value) { + if (StringUtils.isEmpty(name) && StringUtils.isEmpty(value)) { + return null; + } + return new Pair(name, value); } - return tags; } + } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 69ca9502..758e772e 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -29,20 +29,20 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; -import org.springframework.cloud.gateway.rsocket.registry.Registry; -import org.springframework.cloud.gateway.rsocket.support.Metadata; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayExchange; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilterChain; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -61,10 +61,10 @@ public class GatewayRSocketTests { public void init() { registry = mock(Registry.class); incomingPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, - Metadata.encodeRouting("mock")); + Metadata.encodeTags("name:mock")); rSocket = mock(RSocket.class); - when(registry.getRegistered(anyList())).thenReturn(rSocket); + when(registry.getRegistered(anyMap())).thenReturn(rSocket); when(rSocket.requestResponse(any(Payload.class))) .thenReturn(Mono.just(DefaultPayload.create("response"))); diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index e73e1918..a6b3d2f1 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -134,7 +134,7 @@ public class PingPongApp { log.info("ping.take: " + take); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); - ByteBuf announcementMetadata = Metadata.encodeAnnouncement("ping", "ping"+id); + ByteBuf announcementMetadata = Metadata.encodeTags("name:ping", "id:ping"+id); pongFlux = RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) @@ -147,7 +147,7 @@ public class PingPongApp { Flux.interval(Duration.ofSeconds(1)) .map(i -> { ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "ping" + id); - ByteBuf routingMetadata = Metadata.encodeRouting("pong"); + ByteBuf routingMetadata = Metadata.encodeTags("name:pong"); return DefaultPayload.create(data, routingMetadata); }) .onBackpressureDrop(payload -> log.debug("Dropped payload " + payload.getDataUtf8())) // this is needed in case pong is not available yet @@ -202,7 +202,7 @@ public class PingPongApp { Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", Integer.class, 7002); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); - ByteBuf announcementMetadata = Metadata.encodeAnnouncement("pong", "pong1"); + ByteBuf announcementMetadata = Metadata.encodeTags("name:pong", "id:pong1"); RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) @@ -228,7 +228,7 @@ public class PingPongApp { .map(PingPongApp::reply) .map(reply -> { ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, reply); - ByteBuf routingMetadata = Metadata.encodeRouting("ping"); + ByteBuf routingMetadata = Metadata.encodeTags("name:ping"); return DefaultPayload.create(data, routingMetadata); }); } From 50ec7bb9912063461389adebe3bf9e679bc1cfae Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 31 Jan 2019 19:10:56 -0500 Subject: [PATCH 21/67] Moves SocketAcceptor to package & filter chain classes to top level --- .../GatewayRSocketAutoConfiguration.java | 6 +- .../LoadBalancedRSocket.java | 2 +- .../gateway/rsocket/registry/Registry.java | 1 - .../RegistrySocketAcceptorFilter.java | 6 +- .../rsocket/server/GatewayExchange.java | 33 +++++++ .../gateway/rsocket/server/GatewayFilter.java | 22 +++++ .../rsocket/server/GatewayFilterChain.java | 44 +++++++++ .../rsocket/server/GatewayPredicate.java | 23 +++++ .../rsocket/server/GatewayRSocket.java | 42 +-------- .../rsocket/server/GatewaySocketAcceptor.java | 92 ------------------- .../socketacceptor/GatewaySocketAcceptor.java | 47 ++++++++++ .../SocketAcceptorExchange.java | 40 ++++++++ .../socketacceptor/SocketAcceptorFilter.java | 22 +++++ .../SocketAcceptorFilterChain.java | 44 +++++++++ .../SocketAcceptorPredicate.java | 23 +++++ .../rsocket/support/AsyncPredicate.java | 51 ++++++++++ .../GatewayRSocketAutoConfigurationTests.java | 2 +- .../rsocket/server/GatewayRSocketTests.java | 3 - .../GatewaySocketAcceptorTests.java | 6 +- .../gateway/rsocket/test/PingPongApp.java | 13 +-- 20 files changed, 369 insertions(+), 153 deletions(-) rename src/main/java/org/springframework/cloud/gateway/rsocket/{server => registry}/LoadBalancedRSocket.java (97%) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java delete mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java rename src/test/java/org/springframework/cloud/gateway/rsocket/{server => socketacceptor}/GatewaySocketAcceptorTests.java (93%) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index 06347eb9..9a48b91e 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -31,10 +31,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayFilter; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java similarity index 97% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/LoadBalancedRSocket.java rename to src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java index c073a8c5..b49172de 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/LoadBalancedRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket.server; +package org.springframework.cloud.gateway.rsocket.registry; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 32b3b22e..569dbe43 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -29,7 +29,6 @@ import reactor.core.Disposable; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.FluxSink; -import org.springframework.cloud.gateway.rsocket.server.LoadBalancedRSocket; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index 3601c27d..a335f829 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -22,9 +22,9 @@ import java.util.Map; import io.rsocket.ConnectionSetupPayload; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorExchange; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilterChain; +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.cloud.gateway.rsocket.support.Metadata; import org.springframework.core.Ordered; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java new file mode 100644 index 00000000..c3663bf9 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -0,0 +1,33 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.server; + +import io.rsocket.Payload; +import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; + +public class GatewayExchange implements RSocketExchange { + private final Payload payload; + + public GatewayExchange(Payload payload) { + this.payload = payload; + } + + public Payload getPayload() { + return payload; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java new file mode 100644 index 00000000..fc7090ee --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java @@ -0,0 +1,22 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.server; + +import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; + +public interface GatewayFilter extends RSocketFilter {} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java new file mode 100644 index 00000000..b8627490 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java @@ -0,0 +1,44 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.server; + +import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; + +import java.util.List; + +public class GatewayFilterChain + extends AbstractFilterChain { + + /** + * Public constructor with the list of filters and the target handler to use. + * + * @param filters the filters ahead of the handler + */ + public GatewayFilterChain(List filters) { + super(filters); + } + + public GatewayFilterChain(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + super(allFilters, currentFilter, next); + } + + @Override + protected GatewayFilterChain create(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + return new GatewayFilterChain(allFilters, currentFilter, next); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java new file mode 100644 index 00000000..bc7b54c7 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java @@ -0,0 +1,23 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.server; + +import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; + +public interface GatewayPredicate extends AsyncPredicate { +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index e3f3f015..6279ad77 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -29,13 +29,14 @@ import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; -import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; -import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.util.CollectionUtils; +/** + * Acts as a proxy to other registered sockets. Looks up target RSocket + * via Registry. Creates GatewayExchange and executes a GatewayFilterChain. + */ public class GatewayRSocket extends AbstractRSocket { private static final Log log = LogFactory.getLog(GatewayRSocket.class); @@ -129,40 +130,5 @@ public class GatewayRSocket extends AbstractRSocket { } - public interface GatewayFilter extends RSocketFilter {} - - public static class GatewayExchange implements RSocketExchange { - private final Payload payload; - - public GatewayExchange(Payload payload) { - this.payload = payload; - } - - public Payload getPayload() { - return payload; - } - } - - public static class GatewayFilterChain - extends AbstractFilterChain { - - /** - * Public constructor with the list of filters and the target handler to use. - * - * @param filters the filters ahead of the handler - */ - public GatewayFilterChain(List filters) { - super(filters); - } - - public GatewayFilterChain(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { - super(allFilters, currentFilter, next); - } - - @Override - protected GatewayFilterChain create(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { - return new GatewayFilterChain(allFilters, currentFilter, next); - } - } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java deleted file mode 100644 index 0e41739c..00000000 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptor.java +++ /dev/null @@ -1,92 +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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.springframework.cloud.gateway.rsocket.server; - -import java.util.List; - -import io.rsocket.ConnectionSetupPayload; -import io.rsocket.RSocket; -import io.rsocket.SocketAcceptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; -import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; -import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; - -public class GatewaySocketAcceptor implements SocketAcceptor { - - private final RSocket proxyRSocket; - private final SocketAcceptorFilterChain filterChain; - - public GatewaySocketAcceptor(RSocket proxyRSocket, List filters) { - this.proxyRSocket = proxyRSocket; - this.filterChain = new SocketAcceptorFilterChain(filters); - } - - @Override - public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - - SocketAcceptorExchange exchange = new SocketAcceptorExchange(setup, sendingSocket); - - return this.filterChain.filter(exchange) - .log("socket acceptor filter chain") - .map(bool -> this.proxyRSocket); - } - - public interface SocketAcceptorFilter extends RSocketFilter {} - - public static class SocketAcceptorExchange implements RSocketExchange { - private final ConnectionSetupPayload setup; - private final RSocket sendingSocket; - - public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { - this.setup = setup; - this.sendingSocket = sendingSocket; - } - - public ConnectionSetupPayload getSetup() { - return setup; - } - - public RSocket getSendingSocket() { - return sendingSocket; - } - } - - public static class SocketAcceptorFilterChain - extends AbstractFilterChain { - - /** - * Public constructor with the list of filters and the target handler to use. - * - * @param filters the filters ahead of the handler - */ - public SocketAcceptorFilterChain(List filters) { - super(filters); - } - - public SocketAcceptorFilterChain(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { - super(allFilters, currentFilter, next); - } - - @Override - protected SocketAcceptorFilterChain create(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { - return new SocketAcceptorFilterChain(allFilters, currentFilter, next); - } - } -} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java new file mode 100644 index 00000000..a25c0ecc --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -0,0 +1,47 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.socketacceptor; + +import java.util.List; + +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import reactor.core.publisher.Mono; + +public class GatewaySocketAcceptor implements SocketAcceptor { + + private final RSocket proxyRSocket; + private final SocketAcceptorFilterChain filterChain; + + public GatewaySocketAcceptor(RSocket proxyRSocket, List filters) { + this.proxyRSocket = proxyRSocket; + this.filterChain = new SocketAcceptorFilterChain(filters); + } + + @Override + public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { + + SocketAcceptorExchange exchange = new SocketAcceptorExchange(setup, sendingSocket); + + return this.filterChain.filter(exchange) + .log("socket acceptor filter chain") + .map(bool -> this.proxyRSocket); + } + +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java new file mode 100644 index 00000000..444fa9d1 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java @@ -0,0 +1,40 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.socketacceptor; + +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.RSocket; +import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; + +public class SocketAcceptorExchange implements RSocketExchange { + private final ConnectionSetupPayload setup; + private final RSocket sendingSocket; + + public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { + this.setup = setup; + this.sendingSocket = sendingSocket; + } + + public ConnectionSetupPayload getSetup() { + return setup; + } + + public RSocket getSendingSocket() { + return sendingSocket; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java new file mode 100644 index 00000000..2564a93e --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java @@ -0,0 +1,22 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.socketacceptor; + +import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; + +public interface SocketAcceptorFilter extends RSocketFilter {} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java new file mode 100644 index 00000000..dd429450 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java @@ -0,0 +1,44 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.socketacceptor; + +import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; + +import java.util.List; + +public class SocketAcceptorFilterChain + extends AbstractFilterChain { + + /** + * Public constructor with the list of filters and the target handler to use. + * + * @param filters the filters ahead of the handler + */ + public SocketAcceptorFilterChain(List filters) { + super(filters); + } + + public SocketAcceptorFilterChain(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { + super(allFilters, currentFilter, next); + } + + @Override + protected SocketAcceptorFilterChain create(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { + return new SocketAcceptorFilterChain(allFilters, currentFilter, next); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java new file mode 100644 index 00000000..5137f1b2 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java @@ -0,0 +1,23 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.socketacceptor; + +import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; + +public interface SocketAcceptorPredicate extends AsyncPredicate { +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java b/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java new file mode 100644 index 00000000..b38d2f2b --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java @@ -0,0 +1,51 @@ +/* + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.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 extends Function> { + + default AsyncPredicate and(AsyncPredicate 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 negate() { + return t -> Mono.from(apply(t)).map(b -> !b); + } + + default AsyncPredicate or(AsyncPredicate other) { + Assert.notNull(other, "other must not be null"); + + return t -> Flux.zip(apply(t), other.apply(t)) + .map(tuple -> tuple.getT1() || tuple.getT2()); + } + +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index 8a7a7425..984afb2a 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -28,7 +28,7 @@ import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor; +import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 758e772e..8ca42572 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -32,9 +32,6 @@ import org.junit.Test; import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.registry.Registry; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayExchange; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilterChain; import org.springframework.cloud.gateway.rsocket.support.Metadata; import static org.junit.Assert.assertFalse; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java similarity index 93% rename from src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptorTests.java rename to src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java index 554f2b00..904aa142 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java @@ -15,7 +15,7 @@ * */ -package org.springframework.cloud.gateway.rsocket.server; +package org.springframework.cloud.gateway.rsocket.socketacceptor; import java.time.Duration; import java.util.Arrays; @@ -27,12 +27,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilterChain; import reactor.core.publisher.Mono; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorExchange; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index a6b3d2f1..83e24c1e 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -43,11 +43,12 @@ 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.server.GatewayRSocket.GatewayExchange; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilter; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket.GatewayFilterChain; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor; -import org.springframework.cloud.gateway.rsocket.server.GatewaySocketAcceptor.SocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayExchange; +import org.springframework.cloud.gateway.rsocket.server.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.server.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.cloud.gateway.rsocket.support.Metadata; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; @@ -263,7 +264,7 @@ public class PingPongApp { private AtomicBoolean invoked = new AtomicBoolean(false); @Override - public Mono filter(GatewaySocketAcceptor.SocketAcceptorExchange exchange, GatewaySocketAcceptor.SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { log.info("in custom socket acceptor filter"); invoked.compareAndSet(false, true); return chain.filter(exchange); From 0c0caa7277736b7199fba7c08882bcd836b1f1fc Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 31 Jan 2019 21:30:58 -0500 Subject: [PATCH 22/67] Adds SocketAcceptorPredicateFilter. This allows users to reject in coming connections based on SocketAcceptorExchange. fixes gh-14 --- .../GatewayRSocketAutoConfiguration.java | 15 +- .../RegistrySocketAcceptorFilter.java | 2 + .../SocketAcceptorExchange.java | 15 ++ .../SocketAcceptorPredicateFilter.java | 61 ++++++++ .../GatewayRSocketAutoConfigurationTests.java | 10 +- .../SocketAcceptorPredicateFilterTests.java | 136 ++++++++++++++++++ .../test/SocketAcceptorFilterOrderTests.java | 47 ++++++ 7 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index 9a48b91e..d99684b5 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -24,19 +24,21 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.rsocket.RSocket; - import io.rsocket.micrometer.MicrometerRSocketInterceptor; + import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.server.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter; -import org.springframework.cloud.gateway.rsocket.registry.Registry; -import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; +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; @@ -69,6 +71,11 @@ public class GatewayRSocketAutoConfiguration { return new GatewayRSocketProperties(); } + @Bean + public SocketAcceptorPredicateFilter socketAcceptorPredicateFilter(List predicates) { + return new SocketAcceptorPredicateFilter(predicates); + } + @Bean public GatewaySocketAcceptor socketAcceptor(GatewayRSocket rsocket, List filters) { return new GatewaySocketAcceptor(rsocket, filters); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index a335f829..01c0819f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -40,6 +40,8 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order ConnectionSetupPayload setup = exchange.getSetup(); if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata Map properties = Metadata.decodeProperties(setup.sliceMetadata()); + // enrich exchange to have metadata + exchange = new SocketAcceptorExchange(exchange.getSetup(), exchange.getSendingSocket(), properties); registry.register(properties, exchange.getSendingSocket()); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java index 444fa9d1..26a58143 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java @@ -17,17 +17,28 @@ package org.springframework.cloud.gateway.rsocket.socketacceptor; +import java.util.Collections; +import java.util.Map; + import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; + import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; public class SocketAcceptorExchange implements RSocketExchange { private final ConnectionSetupPayload setup; private final RSocket sendingSocket; + private final Map metadata; public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { + this(setup, sendingSocket, Collections.emptyMap()); + } + + public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket, + Map metadata) { this.setup = setup; this.sendingSocket = sendingSocket; + this.metadata = metadata; } public ConnectionSetupPayload getSetup() { @@ -37,4 +48,8 @@ public class SocketAcceptorExchange implements RSocketExchange { public RSocket getSendingSocket() { return sendingSocket; } + + public Map getMetadata() { + return metadata; + } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java new file mode 100644 index 00000000..7c81fb34 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java @@ -0,0 +1,61 @@ +/* + * 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. + * + */ + +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 predicate; + + //TODO: change from List to Flux? + public SocketAcceptorPredicateFilter(List predicates) { + Assert.notNull(predicates, "predicates may not be null"); + if (predicates.isEmpty()) { + predicate = exchange -> Mono.just(true); + } else { + AsyncPredicate 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 filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + return Mono.from(predicate.apply(exchange)) + .flatMap(test -> { + if (test) { + return chain.filter(exchange); + } + return Mono.empty(); + }); + } +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index 984afb2a..278c9bd8 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -26,11 +26,13 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfigu import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; -import org.springframework.cloud.gateway.rsocket.registry.Registry; -import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; +import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicate; +import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicateFilter; import static org.assertj.core.api.Assertions.assertThat; @@ -51,6 +53,8 @@ public class GatewayRSocketAutoConfigurationTests { .hasSingleBean(GatewayRSocketServer.class) .hasSingleBean(GatewayRSocketProperties.class) .hasSingleBean(GatewaySocketAcceptor.class) + .hasSingleBean(SocketAcceptorPredicateFilter.class) + .doesNotHaveBean(SocketAcceptorPredicate.class) .hasSingleBean(MicrometerRSocketInterceptor.class) ); } @@ -71,6 +75,8 @@ public class GatewayRSocketAutoConfigurationTests { .hasSingleBean(GatewayRSocketServer.class) .hasSingleBean(GatewayRSocketProperties.class) .hasSingleBean(GatewaySocketAcceptor.class) + .hasSingleBean(SocketAcceptorPredicateFilter.class) + .doesNotHaveBean(SocketAcceptorPredicate.class) .doesNotHaveBean(MicrometerRSocketInterceptor.class) ); } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java new file mode 100644 index 00000000..3fd8f358 --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java @@ -0,0 +1,136 @@ +/* + * 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. + * + */ + +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 result = runFilter(Collections.emptyList()); + StepVerifier.create(result) + .expectNext(Success.INSTANCE) + .verifyComplete(); + } + + @Test + public void singleTruePredicateWorks() { + TestPredicate predicate = new TestPredicate(true); + Mono result = runFilter(predicate); + StepVerifier.create(result) + .expectNext(Success.INSTANCE) + .verifyComplete(); + assertThat(predicate.invoked()).isTrue(); + } + + @Test + public void singleFalsePredicateWorks() { + TestPredicate predicate = new TestPredicate(false); + Mono 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 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 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 result = runFilter(truePredicate, truePredicate2); + StepVerifier.create(result) + .expectNext(Success.INSTANCE) + .verifyComplete(); + assertThat(truePredicate.invoked()).isTrue(); + assertThat(truePredicate2.invoked()).isTrue(); + } + + private Mono runFilter(SocketAcceptorPredicate predicate) { + return runFilter(Collections.singletonList(predicate)); + } + + private Mono runFilter(SocketAcceptorPredicate... predicates) { + return runFilter(Arrays.asList(predicates)); + } + + private Mono runFilter(List 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 test; + + public TestPredicate(boolean value) { + test = Mono.just(value); + } + + @Override + public Publisher apply(SocketAcceptorExchange exchange) { + invoked = true; + return test; + } + + public boolean invoked() { + return invoked; + } + } +} diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java new file mode 100644 index 00000000..f9895fa6 --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java @@ -0,0 +1,47 @@ +/* + * 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. + * + */ + +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.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; +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 RegistrySocketAcceptorFilter(mock(Registry.class)); + List filters = Arrays.asList(predicateFilter, registryFilter); + OrderComparator.sort(filters); + + assertThat(filters) + .containsExactly(registryFilter, predicateFilter); + } +} From c84ee76656c8d62f5cca82bd0f6a44a1159a9cf9 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sat, 2 Feb 2019 14:00:47 -0500 Subject: [PATCH 23/67] Adds Route and request predicate infrastructure. fixes gh-16 --- .../GatewayRSocketAutoConfiguration.java | 15 +- .../gateway/rsocket/registry/Registry.java | 16 +- .../rsocket/registry/RegistryRoutes.java | 91 ++++++++ .../cloud/gateway/rsocket/route/Route.java | 198 ++++++++++++++++++ .../cloud/gateway/rsocket/route/Routes.java | 57 +++++ .../rsocket/server/GatewayExchange.java | 22 +- .../rsocket/server/GatewayFilterChain.java | 16 +- .../rsocket/server/GatewayRSocket.java | 88 +++++--- .../rsocket/server/PendingRequestRSocket.java | 53 ++++- .../socketacceptor/GatewaySocketAcceptor.java | 3 +- .../gateway/rsocket/support/Metadata.java | 26 +++ .../GatewayRSocketAutoConfigurationTests.java | 3 + .../GatewayRSocketIntegrationTests.java | 6 +- .../rsocket/server/GatewayRSocketTests.java | 78 ++++++- .../gateway/rsocket/test/PingPongApp.java | 7 +- 15 files changed, 586 insertions(+), 93 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index d99684b5..6165bbe2 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -31,8 +31,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClas import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistryRoutes; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; -import org.springframework.cloud.gateway.rsocket.server.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; @@ -56,14 +57,22 @@ public class GatewayRSocketAutoConfiguration { return new Registry(); } + //TODO: CompositeRoutes + @Bean + public RegistryRoutes registryRoutes(Registry registry) { + RegistryRoutes registryRoutes = new RegistryRoutes(); + registry.addListener(registryRoutes); + return registryRoutes; + } + @Bean public RegistrySocketAcceptorFilter registrySocketAcceptorFilter(Registry registry) { return new RegistrySocketAcceptorFilter(registry); } @Bean - public GatewayRSocket gatewayRSocket(Registry registry, List filters) { - return new GatewayRSocket(registry, filters); + public GatewayRSocket gatewayRSocket(Registry registry, Routes routes) { + return new GatewayRSocket(registry, routes); } @Bean diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 569dbe43..48430a16 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -72,7 +72,7 @@ public class Registry { log.debug("Registered RSocket: " + properties); LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> new LoadBalancedRSocket()); composite.addRSocket(rsocket); - registeredEventsSink.next(new RegisteredEvent(keyFunction, properties, rsocket)); + registeredEventsSink.next(new RegisteredEvent(properties, rsocket)); } public RSocket getRegistered(Map properties) { @@ -87,16 +87,12 @@ public class Registry { } public static class RegisteredEvent { - private final Function, String> keyFunction; private final Map routingMetadata; private final RSocket rSocket; - public RegisteredEvent(Function, String> keyFunction, Map routingMetadata, - RSocket rSocket) { - Assert.notNull(keyFunction, "keyFunction may not be null"); + public RegisteredEvent(Map routingMetadata, RSocket rSocket) { Assert.notEmpty(routingMetadata, "routingMetadata may not be empty"); Assert.notNull(rSocket, "RSocket may not be null"); - this.keyFunction = keyFunction; this.routingMetadata = routingMetadata; this.rSocket = rSocket; } @@ -109,13 +105,5 @@ public class Registry { return rSocket; } - public boolean matches(Map otherRoutingMetadata) { - if (!CollectionUtils.isEmpty(otherRoutingMetadata)) { - String thisKey = keyFunction.apply(routingMetadata); - String otherKey = keyFunction.apply(otherRoutingMetadata); - return thisKey.equals(otherKey); - } - return false; - } } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java new file mode 100644 index 00000000..57ceb05c --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java @@ -0,0 +1,91 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.registry; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +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.route.Route; +import org.springframework.cloud.gateway.rsocket.route.Routes; + +/** + * Creates routes from RegisteredEvents + */ +public class RegistryRoutes implements Routes, Consumer { + + private static final Log log = LogFactory.getLog(RegistryRoutes.class); + + private Map routes = new ConcurrentHashMap<>(); + + @Override + public Flux getRoutes() { + //TODO: sorting + //TODO: caching + Collection routeCollection = routes.values(); + if (log.isDebugEnabled()) { + log.debug("Found routes: " + routeCollection); + } + return Flux.fromIterable(routeCollection); + } + + @Override + public void accept(Registry.RegisteredEvent registeredEvent) { + Map routingMetadata = registeredEvent.getRoutingMetadata(); + String id = getId(routingMetadata); + + routes.computeIfAbsent(id, key -> createRoute(id, routingMetadata)); + } + + private String getId(Map routingMetadata) { + String id = routingMetadata.get("name"); + if (id == null) { + id = UUID.randomUUID().toString(); + } + return id; + } + + private Route createRoute(String id, Map routingMetadata) { + Route route = Route.builder() + .id(id) + .routingMetadata(routingMetadata) + .predicate(exchange -> { + // TODO: standard predicates + // TODO: allow customized predicates + Map incomingRouting = exchange.getRoutingMetadata(); + boolean matches = incomingRouting.containsKey("name") + && incomingRouting.get("name").equals(routingMetadata.get("name")); + return Mono.just(matches); + }) + // TODO: allow customized filters + .build(); + + if (log.isDebugEnabled()) { + log.debug("Created Route for registered service " + route); + } + + return route; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java b/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java new file mode 100644 index 00000000..6febd8bb --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java @@ -0,0 +1,198 @@ +/* + * 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. + * + */ + +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.Map; +import java.util.Objects; + +import org.springframework.cloud.gateway.rsocket.server.GatewayExchange; +import org.springframework.cloud.gateway.rsocket.server.GatewayFilter; +import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; +import org.springframework.core.Ordered; +import org.springframework.core.style.ToStringCreator; +import org.springframework.util.Assert; + + +/** + * @author Spencer Gibb + */ +public class Route implements Ordered { + + private final String id; + + private final Map targetMetadata; + + private final int order; + + private final AsyncPredicate predicate; + + private final List gatewayFilters; + + public static Builder builder() { + return new Builder(); + } + + private Route(String id, Map targetMetadata, int order, AsyncPredicate predicate, List gatewayFilters) { + this.id = id; + this.targetMetadata = targetMetadata; + this.order = order; + this.predicate = predicate; + this.gatewayFilters = gatewayFilters; + } + + public static class Builder { + protected String id; + + protected Map routingMetadata; + + protected int order = 0; + + protected AsyncPredicate predicate; + + protected List 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 getPredicate() { + return this.predicate; + } + + public Builder routingMetadata(Map routingMetadata) { + this.routingMetadata = routingMetadata; + return this; + } + + public Builder setFilters(List gatewayFilters) { + this.gatewayFilters = gatewayFilters; + return this; + } + + public Builder filter(GatewayFilter gatewayFilter) { + this.gatewayFilters.add(gatewayFilter); + return this; + } + + public Builder filters(Collection gatewayFilters) { + this.gatewayFilters.addAll(gatewayFilters); + return this; + } + + public Builder filters(GatewayFilter... gatewayFilters) { + return filters(Arrays.asList(gatewayFilters)); + } + + + public Builder predicate(AsyncPredicate predicate) { + this.predicate = predicate; + return this; + } + + public Builder and(AsyncPredicate predicate) { + Assert.notNull(this.predicate, "can not call and() on null predicate"); + this.predicate = this.predicate.and(predicate); + return this; + } + + public Builder or(AsyncPredicate 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.notEmpty(this.routingMetadata, "targetMetadata can not be null or empty"); + Assert.notNull(this.predicate, "predicate can not be null"); + + return new Route(this.id, this.routingMetadata, this.order, predicate, this.gatewayFilters); + } + } + + public String getId() { + return this.id; + } + + public Map getTargetMetadata() { + return this.targetMetadata; + } + + public int getOrder() { + return order; + } + + public AsyncPredicate getPredicate() { + return this.predicate; + } + + public List 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.id) && + Objects.equals(targetMetadata, route.targetMetadata) && + Objects.equals(order, route.order) && + Objects.equals(predicate, route.predicate) && + Objects.equals(gatewayFilters, route.gatewayFilters); + } + + @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(); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java b/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java new file mode 100644 index 00000000..9279ad98 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java @@ -0,0 +1,57 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.route; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.gateway.rsocket.server.GatewayExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @author Spencer Gibb + */ +public interface Routes { + + Log log = LogFactory.getLog(Routes.class); + + Flux getRoutes(); + + default Mono 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; + }); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index c3663bf9..2197f699 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -17,17 +17,27 @@ package org.springframework.cloud.gateway.rsocket.server; -import io.rsocket.Payload; +import java.util.Map; + import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; public class GatewayExchange implements RSocketExchange { - private final Payload payload; - public GatewayExchange(Payload payload) { - this.payload = payload; + enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM, UNKNOWN } + + private final Type type; + private final Map routingMetadata; + + public GatewayExchange(Type type, Map routingMetadata) { + this.type = type; + this.routingMetadata = routingMetadata; } - public Payload getPayload() { - return payload; + public Type getType() { + return type; + } + + public Map getRoutingMetadata() { + return routingMetadata; } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java index b8627490..8dfcd125 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java @@ -17,10 +17,13 @@ package org.springframework.cloud.gateway.rsocket.server; -import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; - 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 { @@ -29,11 +32,11 @@ public class GatewayFilterChain * * @param filters the filters ahead of the handler */ - public GatewayFilterChain(List filters) { + private GatewayFilterChain(List filters) { super(filters); } - public GatewayFilterChain(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + protected GatewayFilterChain(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { super(allFilters, currentFilter, next); } @@ -41,4 +44,9 @@ public class GatewayFilterChain protected GatewayFilterChain create(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { return new GatewayFilterChain(allFilters, currentFilter, next); } + + public static Mono executeFilterChain(List filters, GatewayExchange exchange) { + return new GatewayFilterChain(filters) + .filter(exchange); + } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 6279ad77..b49e4388 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -17,8 +17,8 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.List; import java.util.Map; +import java.util.logging.Level; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; @@ -30,8 +30,16 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.util.CollectionUtils; +import reactor.util.context.Context; + +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.FIRE_AND_FORGET; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_CHANNEL; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_RESPONSE; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_STREAM; +import static org.springframework.cloud.gateway.rsocket.server.GatewayFilterChain.executeFilterChain; /** * Acts as a proxy to other registered sockets. Looks up target RSocket @@ -42,16 +50,24 @@ public class GatewayRSocket extends AbstractRSocket { private static final Log log = LogFactory.getLog(GatewayRSocket.class); private final Registry registry; - private final GatewayFilterChain filterChain; + private final Routes routes; - public GatewayRSocket(Registry registry, List filters) { + public GatewayRSocket(Registry registry, Routes routes) { this.registry = registry; - this.filterChain = new GatewayFilterChain(filters); + this.routes = routes; + } + + protected Registry getRegistry() { + return registry; + } + + protected Routes getRoutes() { + return routes; } @Override public Mono fireAndForget(Payload payload) { - return findRSocket(payload) + return findRSocket(FIRE_AND_FORGET, payload) .flatMap(rSocket -> rSocket.fireAndForget(payload)); } @@ -63,53 +79,61 @@ public class GatewayRSocket extends AbstractRSocket { return payloadFlux; } - return findRSocket(signal.get()) + return findRSocket(REQUEST_CHANNEL, signal.get()) .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); }); } @Override public Mono requestResponse(Payload payload) { - return findRSocket(payload) + return findRSocket(REQUEST_RESPONSE, payload) .flatMap(rSocket -> rSocket.requestResponse(payload)); } @Override public Flux requestStream(Payload payload) { - return findRSocket(payload) + return findRSocket(REQUEST_STREAM, payload) .flatMapMany(rSocket -> rSocket.requestStream(payload)); } - private Mono findRSocket(Payload payload) { + private Mono findRSocket(GatewayExchange.Type type, Payload payload) { Map metadata = getRoutingMetadata(payload); - RSocket service = findRSocket(metadata); + GatewayExchange exchange = new GatewayExchange(type, metadata); - if (service == null) { - PendingRequestRSocket pending = new PendingRequestRSocket(metadata); - this.registry.addListener(pending); //TODO: deal with removing? - service = pending; - } - - GatewayExchange exchange = new GatewayExchange(payload); - - RSocket rSocket = service; - - return this.filterChain.filter(exchange) - .log("gateway filter chain") - .map(bool -> rSocket); + return findRSocket(exchange) + // if a route can't be found or registered RSocket, create pending + .switchIfEmpty(createPendingRSocket(exchange)); } - private RSocket findRSocket(Map properties) { - if (properties == null) return null; //TODO: error - - RSocket rsocket = registry.getRegistered(properties); - - if (rsocket == null) { - log.debug("Unable to find destination RSocket for " + properties); + private Mono createPendingRSocket(GatewayExchange exchange) { + if (log.isDebugEnabled()) { + log.debug("creating pending RSocket for " + exchange.getRoutingMetadata()); } - // TODO: deal with connecting to cluster? + PendingRequestRSocket pending = constructPendingRSocket(exchange); + this.registry.addListener(pending); //TODO: deal with removing? + return Mono.just(pending); + } - return rsocket; + /* for testing */ PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { + return new PendingRequestRSocket(routes, exchange); + } + + private Mono findRSocket(GatewayExchange exchange) { + return this.routes.findRoute(exchange) + .log("find route", Level.FINE) + // TODO: see if I can store the route in the pending rsocket + .flatMap(route -> executeFilterChain(route.getFilters(), exchange) + .map(success -> { + RSocket rsocket = registry.getRegistered(exchange.getRoutingMetadata()); + + if (rsocket == null) { + log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); + } + return rsocket; + }) + .subscriberContext(Context.of("route", route))); + + // TODO: deal with connecting to cluster? } private Map getRoutingMetadata(Payload payload) { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 96034cae..ff5e824a 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.rsocket.server; import java.util.Map; import java.util.function.Consumer; +import java.util.logging.Level; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; @@ -28,51 +29,81 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; +import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; import org.springframework.cloud.gateway.rsocket.registry.Registry.RegisteredEvent; +import org.springframework.cloud.gateway.rsocket.route.Route; +import org.springframework.cloud.gateway.rsocket.route.Routes; +import org.springframework.cloud.gateway.rsocket.support.Metadata; public class PendingRequestRSocket extends AbstractRSocket implements Consumer { - private final Map routingMetadata; + private final Routes routes; + private final GatewayExchange pendingExchange; private final MonoProcessor processor; - public PendingRequestRSocket(Map routingMetadata) { - this.routingMetadata = routingMetadata; - this.processor = MonoProcessor.create(); + public PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange) { + this(routes, pendingExchange, MonoProcessor.create()); } + /* for testing */ PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange, MonoProcessor processor) { + this.routes = routes; + this.pendingExchange = pendingExchange; + this.processor = processor; + } + + /** + * Find route (if needed) using pendingExchange. + * If found, see if the route target matches the registered service. + * If it matches, execute filter chain. + * If chain is successful, send registered RSocket to processor. + * @param registeredEvent + */ @Override public void accept(RegisteredEvent registeredEvent) { - if (registeredEvent.matches(this.routingMetadata)) { - this.processor.onNext(registeredEvent.getRSocket()); - this.processor.onComplete(); + //TODO: cache route if not already here + this.routes.findRoute(pendingExchange) + .log("find route pending", Level.FINE) + // can this be replaced with filter? + .map(route -> executeFilterChain(route, registeredEvent.getRoutingMetadata())) + .subscribe(success -> { + this.processor.onNext(registeredEvent.getRSocket()); + this.processor.onComplete(); + }); + } + + private Mono executeFilterChain(Route route, Map annoucementMetadata) { + Map targetMetadata = route.getTargetMetadata(); + if (Metadata.matches(targetMetadata, annoucementMetadata)) { + return GatewayFilterChain.executeFilterChain(route.getFilters(), pendingExchange); } + return Mono.empty(); } @Override public Mono fireAndForget(Payload payload) { return processor - .log("pending-request") + .log("pending-request-faf", Level.FINE) .flatMap(rsocket -> rsocket.fireAndForget(payload)); } @Override public Mono requestResponse(Payload payload) { return processor - .log("pending-request") + .log("pending-request-rr", Level.FINE) .flatMap(rsocket -> rsocket.requestResponse(payload)); } @Override public Flux requestStream(Payload payload) { return processor - .log("pending-request") + .log("pending-request-rs", Level.FINE) .flatMapMany(rsocket -> rsocket.requestStream(payload)); } @Override public Flux requestChannel(Publisher payloads) { return processor - .log("pending-request") + .log("pending-request-rc", Level.FINE) .flatMapMany(rsocket -> rsocket.requestChannel(payloads)); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index a25c0ecc..8555f683 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.rsocket.socketacceptor; import java.util.List; +import java.util.logging.Level; import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; @@ -40,7 +41,7 @@ public class GatewaySocketAcceptor implements SocketAcceptor { SocketAcceptorExchange exchange = new SocketAcceptorExchange(setup, sendingSocket); return this.filterChain.filter(exchange) - .log("socket acceptor filter chain") + .log("socket acceptor filter chain", Level.FINE) .map(bool -> this.proxyRSocket); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java index f449c98c..0c8d3fdb 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java @@ -92,6 +92,32 @@ public abstract class Metadata { return tags; } + /** + * All keys in targetMetadata must have a "match" in announcementMetadata + * @param targetMetadata + * @param annoucementMetadata + * @return + */ + //TODO: find a way to make this more performant + public static boolean matches(Map targetMetadata, Map annoucementMetadata) { + if (targetMetadata != null && annoucementMetadata != null + // if announced key contains all target keys + && annoucementMetadata.keySet().containsAll(targetMetadata.keySet())) { + for (Map.Entry entry : targetMetadata.entrySet()) { + String targetKey = entry.getKey(); + String targetValue = entry.getValue(); + String announcementValue = annoucementMetadata.get(targetKey); + + //TODO: regex and possibly SpEL + if (!targetValue.equals(announcementValue)) { + return false; + } + } + return true; + } + return false; + } + /** * A single name value pair. */ diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index 278c9bd8..04dfa9da 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.registry.RegistryRoutes; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; @@ -48,6 +49,7 @@ public class GatewayRSocketAutoConfigurationTests { )) .run(context -> assertThat(context) .hasSingleBean(Registry.class) + .hasSingleBean(RegistryRoutes.class) .hasSingleBean(RegistrySocketAcceptorFilter.class) .hasSingleBean(GatewayRSocket.class) .hasSingleBean(GatewayRSocketServer.class) @@ -70,6 +72,7 @@ public class GatewayRSocketAutoConfigurationTests { .withClassLoader(new FilteredClassLoader(MeterRegistry.class, MicrometerRSocketInterceptor.class)) .run(context -> assertThat(context) .hasSingleBean(Registry.class) + .hasSingleBean(RegistryRoutes.class) .hasSingleBean(RegistrySocketAcceptorFilter.class) .hasSingleBean(GatewayRSocket.class) .hasSingleBean(GatewayRSocketServer.class) diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java index 9d976b9a..3e55d5cb 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java @@ -21,12 +21,12 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import reactor.test.StepVerifier; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.test.PingPongApp; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.SocketUtils; @@ -53,9 +53,6 @@ public class GatewayRSocketIntegrationTests { @Autowired private PingPongApp.MySocketAcceptorFilter mySocketAcceptorFilter; - @Autowired - private PingPongApp.MyGatewayFilter myGatewayFilter; - @Autowired private GatewayRSocketServer server; @@ -82,7 +79,6 @@ public class GatewayRSocketIntegrationTests { assertThat(pong.getPingsReceived()).isGreaterThan(0); assertThat(properties.getServer().getPort()).isNotEqualTo(7002); assertThat(mySocketAcceptorFilter.invoked()).isTrue(); - assertThat(myGatewayFilter.invoked()).isTrue(); assertThat(server.isRunning()).isFalse(); } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 8ca42572..cd552b21 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -20,6 +20,7 @@ package org.springframework.cloud.gateway.rsocket.server; import java.time.Duration; import java.util.Arrays; import java.util.Collections; +import java.util.List; import io.netty.buffer.Unpooled; import io.rsocket.Payload; @@ -29,14 +30,18 @@ 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 org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.route.Route; +import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; @@ -52,7 +57,6 @@ public class GatewayRSocketTests { private Registry registry; private Payload incomingPayload; - private RSocket rSocket; @Before public void init() { @@ -60,7 +64,7 @@ public class GatewayRSocketTests { incomingPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, Metadata.encodeTags("name:mock")); - rSocket = mock(RSocket.class); + RSocket rSocket = mock(RSocket.class); when(registry.getRegistered(anyMap())).thenReturn(rSocket); when(rSocket.requestResponse(any(Payload.class))) @@ -74,7 +78,7 @@ public class GatewayRSocketTests { TestFilter filter2 = new TestFilter(); TestFilter filter3 = new TestFilter(); - Payload payload = new GatewayRSocket(registry, Arrays.asList(filter1, filter2, filter3)) + Payload payload = new TestGatewayRSocket(registry, new TestRoutes(filter1, filter2, filter3)) .requestResponse(incomingPayload) .block(Duration.ZERO); @@ -86,7 +90,7 @@ public class GatewayRSocketTests { @Test public void zeroFilters() { - Payload payload = new GatewayRSocket(registry, Collections.emptyList()) + Payload payload = new TestGatewayRSocket(registry, new TestRoutes()) .requestResponse(incomingPayload) .block(Duration.ZERO); @@ -101,14 +105,20 @@ public class GatewayRSocketTests { TestFilter filter3 = new TestFilter(); - Payload payload = new GatewayRSocket(registry, Arrays.asList(filter1, filter2, filter3)) - .requestResponse(incomingPayload) - .block(Duration.ZERO); + TestGatewayRSocket gatewayRSocket = new TestGatewayRSocket(registry, new TestRoutes(filter1, filter2, filter3)); + Mono response = gatewayRSocket.requestResponse(incomingPayload); + + // a false filter will create a pending rsocket that blocks forever + // this tweaks the rsocket to compelte. + gatewayRSocket.processor.onNext(null); + + StepVerifier.withVirtualTime(() -> response) + .expectSubscription() + .verifyComplete(); assertTrue(filter1.invoked()); assertTrue(filter2.invoked()); assertFalse(filter3.invoked()); - assertNull(payload); } @Test @@ -116,7 +126,7 @@ public class GatewayRSocketTests { AsyncFilter filter = new AsyncFilter(); - Payload payload = new GatewayRSocket(registry, Collections.singletonList(filter)) + Payload payload = new TestGatewayRSocket(registry, new TestRoutes(filter)) .requestResponse(incomingPayload) .block(Duration.ofSeconds(5)); @@ -130,13 +140,59 @@ public class GatewayRSocketTests { ExceptionFilter filter = new ExceptionFilter(); - new GatewayRSocket(registry, Collections.singletonList(filter)) + new TestGatewayRSocket(registry, new TestRoutes(filter)) .requestResponse(incomingPayload) .block(Duration.ofSeconds(5)); // assertNull(socket); } + private static class TestGatewayRSocket extends GatewayRSocket { + + private final MonoProcessor processor = MonoProcessor.create(); + + public TestGatewayRSocket(Registry registry, Routes routes) { + super(registry, routes); + } + + @Override + PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { + return new PendingRequestRSocket(getRoutes(), exchange, processor); + } + + public MonoProcessor getProcessor() { + return processor; + } + } + + private static class TestRoutes implements Routes { + private final Route route; + private List filters; + + public TestRoutes() { + this(Collections.emptyList()); + } + + public TestRoutes(GatewayFilter... filters) { + this(Arrays.asList(filters)); + } + + public TestRoutes(List filters) { + this.filters = filters; + route = Route.builder() + .id("route1") + .routingMetadata(Collections.singletonMap("name", "mock")) + .predicate(exchange -> Mono.just(true)) + .filters(filters) + .build(); + } + + @Override + public Flux getRoutes() { + return Flux.just(route); + } + } + private static class TestFilter implements GatewayFilter { diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 83e24c1e..df6389d0 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -76,11 +76,6 @@ public class PingPongApp { return new Pong(); } - @Bean - public MyGatewayFilter myGatewayFilter() { - return new MyGatewayFilter(); - } - @Bean public MySocketAcceptorFilter mySocketAcceptorFilter() { return new MySocketAcceptorFilter(); @@ -132,7 +127,7 @@ public class PingPongApp { Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", Integer.class, 7002); - log.info("ping.take: " + take); + log.debug("ping.take: " + take); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); ByteBuf announcementMetadata = Metadata.encodeTags("name:ping", "id:ping"+id); From 4fc1c9e93532421956b485e4f740c892459802cb Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sat, 2 Feb 2019 14:18:48 -0500 Subject: [PATCH 24/67] Adds attributes to RSocketExchange Creates AbstractRSocketExchange with uses a ConcurrentHashMap for attrs. Stores Route in exchange attributes. fixes gh-17 --- .../filter/AbstractRSocketExchange.java | 31 ++++++++++++ .../rsocket/filter/RSocketExchange.java | 48 +++++++++++++++++++ .../rsocket/server/GatewayExchange.java | 6 ++- .../rsocket/server/GatewayRSocket.java | 24 +++++----- .../rsocket/server/PendingRequestRSocket.java | 23 +++++++-- .../SocketAcceptorExchange.java | 6 +-- 6 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java new file mode 100644 index 00000000..4b37ffff --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java @@ -0,0 +1,31 @@ +/* + * 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. + * + */ + +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 attributes = new ConcurrentHashMap<>(); + + @Override + public Map getAttributes() { + return this.attributes; + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java index 5247dee5..5bbffa77 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java @@ -17,5 +17,53 @@ 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. + */ + Map getAttributes(); + + /** + * Return the request attribute value if present. + * @param name the attribute name + * @param the attribute type + * @return the attribute value + */ + @SuppressWarnings("unchecked") + @Nullable + default 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 the attribute type + * @return the attribute value + */ + @SuppressWarnings("unchecked") + default 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 the attribute type + * @return the attribute value + */ + @SuppressWarnings("unchecked") + default T getAttributeOrDefault(String name, T defaultValue) { + return (T) getAttributes().getOrDefault(name, defaultValue); + } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index 2197f699..106204ae 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -19,9 +19,11 @@ package org.springframework.cloud.gateway.rsocket.server; import java.util.Map; -import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; +import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; -public class GatewayExchange implements RSocketExchange { +public class GatewayExchange extends AbstractRSocketExchange { + + public static final String ROUTE_ATTR = "__route_attr_"; enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM, UNKNOWN } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index b49e4388..96a9e148 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -33,8 +33,8 @@ import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.util.CollectionUtils; -import reactor.util.context.Context; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.FIRE_AND_FORGET; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_CHANNEL; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_RESPONSE; @@ -121,17 +121,19 @@ public class GatewayRSocket extends AbstractRSocket { private Mono findRSocket(GatewayExchange exchange) { return this.routes.findRoute(exchange) .log("find route", Level.FINE) - // TODO: see if I can store the route in the pending rsocket - .flatMap(route -> executeFilterChain(route.getFilters(), exchange) - .map(success -> { - RSocket rsocket = registry.getRegistered(exchange.getRoutingMetadata()); + .flatMap(route -> { + // put route in exchange for later use + exchange.getAttributes().put(ROUTE_ATTR, route); + return executeFilterChain(route.getFilters(), exchange) + .map(success -> { + RSocket rsocket = registry.getRegistered(exchange.getRoutingMetadata()); - if (rsocket == null) { - log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); - } - return rsocket; - }) - .subscriberContext(Context.of("route", route))); + if (rsocket == null) { + log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); + } + return rsocket; + }); + }); // TODO: deal with connecting to cluster? } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index ff5e824a..5ea78706 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -35,6 +35,8 @@ import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; + public class PendingRequestRSocket extends AbstractRSocket implements Consumer { private final Routes routes; @@ -60,17 +62,32 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer executeFilterChain(route, registeredEvent.getRoutingMetadata())) + .map(route -> { + if (!pendingExchange.getAttributes().containsKey(ROUTE_ATTR)) { + pendingExchange.getAttributes().put(ROUTE_ATTR, route); + } + return executeFilterChain(route, registeredEvent.getRoutingMetadata()); + }) .subscribe(success -> { this.processor.onNext(registeredEvent.getRSocket()); this.processor.onComplete(); }); } + private Mono findRoute() { + Mono routeMono; + if (pendingExchange.getAttributes().containsKey(ROUTE_ATTR)) { + Route r = pendingExchange.getRequiredAttribute(ROUTE_ATTR); + routeMono = Mono.just(r); + } else { + routeMono = this.routes.findRoute(pendingExchange); + } + return routeMono; + } + private Mono executeFilterChain(Route route, Map annoucementMetadata) { Map targetMetadata = route.getTargetMetadata(); if (Metadata.matches(targetMetadata, annoucementMetadata)) { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java index 26a58143..cd40f723 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java @@ -23,9 +23,9 @@ import java.util.Map; import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; -import org.springframework.cloud.gateway.rsocket.filter.RSocketExchange; +import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; -public class SocketAcceptorExchange implements RSocketExchange { +public class SocketAcceptorExchange extends AbstractRSocketExchange { private final ConnectionSetupPayload setup; private final RSocket sendingSocket; private final Map metadata; @@ -35,7 +35,7 @@ public class SocketAcceptorExchange implements RSocketExchange { } public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket, - Map metadata) { + Map metadata) { this.setup = setup; this.sendingSocket = sendingSocket; this.metadata = metadata; From 0ced6d9e4e800b13bc97634e1df4f418f2864727 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sat, 2 Feb 2019 14:21:05 -0500 Subject: [PATCH 25/67] Protects log statements with is*Enabled --- .../cloud/gateway/rsocket/server/GatewayRSocket.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 96a9e148..c8bc4acd 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -128,7 +128,7 @@ public class GatewayRSocket extends AbstractRSocket { .map(success -> { RSocket rsocket = registry.getRegistered(exchange.getRoutingMetadata()); - if (rsocket == null) { + if (rsocket == null && log.isDebugEnabled()) { log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); } return rsocket; @@ -151,7 +151,9 @@ public class GatewayRSocket extends AbstractRSocket { return null; } - log.debug("found routing metadata " + properties); + if (log.isDebugEnabled()) { + log.debug("found routing metadata " + properties); + } return properties; } From f00a9c6b73f9afaf8a049f339a86d228db58d5a8 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sat, 2 Feb 2019 14:25:00 -0500 Subject: [PATCH 26/67] Adds factory to create GatewayExchange from Payload --- .../rsocket/server/GatewayExchange.java | 32 ++++++++++++++++++- .../rsocket/server/GatewayRSocket.java | 25 +-------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index 106204ae..1f98254e 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -19,17 +19,47 @@ package org.springframework.cloud.gateway.rsocket.server; import java.util.Map; +import io.rsocket.Payload; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; +import org.springframework.cloud.gateway.rsocket.support.Metadata; +import org.springframework.util.CollectionUtils; public class GatewayExchange extends AbstractRSocketExchange { + private static final Log log = LogFactory.getLog(GatewayExchange.class); public static final String ROUTE_ATTR = "__route_attr_"; - enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM, UNKNOWN } + enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM } private final Type type; private final Map routingMetadata; + public static GatewayExchange fromPayload(Type type, Payload payload) { + return new GatewayExchange(type, getRoutingMetadata(payload)); + } + + private static Map getRoutingMetadata(Payload payload) { + if (payload == null || !payload.hasMetadata()) { // and metadata is routing + return null; + } + + // TODO: deal with composite metadata + + Map properties = Metadata.decodeProperties(payload.sliceMetadata()); + + if (CollectionUtils.isEmpty(properties)) { + return null; + } + + if (log.isDebugEnabled()) { + log.debug("found routing metadata " + properties); + } + return properties; + } + public GatewayExchange(Type type, Map routingMetadata) { this.type = type; this.routingMetadata = routingMetadata; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index c8bc4acd..58c04566 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.Map; import java.util.logging.Level; import io.rsocket.AbstractRSocket; @@ -31,8 +30,6 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Routes; -import org.springframework.cloud.gateway.rsocket.support.Metadata; -import org.springframework.util.CollectionUtils; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.FIRE_AND_FORGET; @@ -97,9 +94,7 @@ public class GatewayRSocket extends AbstractRSocket { } private Mono findRSocket(GatewayExchange.Type type, Payload payload) { - Map metadata = getRoutingMetadata(payload); - GatewayExchange exchange = new GatewayExchange(type, metadata); - + GatewayExchange exchange = GatewayExchange.fromPayload(type, payload); return findRSocket(exchange) // if a route can't be found or registered RSocket, create pending .switchIfEmpty(createPendingRSocket(exchange)); @@ -138,24 +133,6 @@ public class GatewayRSocket extends AbstractRSocket { // TODO: deal with connecting to cluster? } - private Map getRoutingMetadata(Payload payload) { - if (payload == null || !payload.hasMetadata()) { // and metadata is routing - return null; - } - - // TODO: deal with composite metadata - - Map properties = Metadata.decodeProperties(payload.sliceMetadata()); - - if (CollectionUtils.isEmpty(properties)) { - return null; - } - - if (log.isDebugEnabled()) { - log.debug("found routing metadata " + properties); - } - return properties; - } } From 91ef6560dc0f94d838f0a56696992a8b12939bcd Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sat, 2 Feb 2019 14:31:34 -0500 Subject: [PATCH 27/67] Disposes of subscription when a RSocket is accepted --- .../cloud/gateway/rsocket/server/GatewayRSocket.java | 4 +++- .../gateway/rsocket/server/PendingRequestRSocket.java | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 58c04566..c312022a 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -25,6 +25,7 @@ import io.rsocket.RSocket; 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; @@ -105,7 +106,8 @@ public class GatewayRSocket extends AbstractRSocket { log.debug("creating pending RSocket for " + exchange.getRoutingMetadata()); } PendingRequestRSocket pending = constructPendingRSocket(exchange); - this.registry.addListener(pending); //TODO: deal with removing? + Disposable disposable = this.registry.addListener(pending); + pending.setSubscriptionDisposable(disposable); return Mono.just(pending); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 5ea78706..a926da39 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -25,6 +25,7 @@ import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; import org.reactivestreams.Publisher; +import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; @@ -42,6 +43,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer processor; + private Disposable subscriptionDisposable; public PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange) { this(routes, pendingExchange, MonoProcessor.create()); @@ -74,6 +76,9 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer { this.processor.onNext(registeredEvent.getRSocket()); this.processor.onComplete(); + if (this.subscriptionDisposable != null) { + this.subscriptionDisposable.dispose(); + } }); } @@ -123,4 +128,8 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer rsocket.requestChannel(payloads)); } + + public void setSubscriptionDisposable(Disposable subscriptionDisposable) { + this.subscriptionDisposable = subscriptionDisposable; + } } From fd29f83b24f2088bc3f03c38570e14f294f7c849 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sun, 3 Feb 2019 20:26:57 -0500 Subject: [PATCH 28/67] Adds some documentation regarding GatewayRSocket behavior. --- .../rsocket/server/GatewayRSocket.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index c312022a..044eab1f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -40,8 +40,11 @@ import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.T import static org.springframework.cloud.gateway.rsocket.server.GatewayFilterChain.executeFilterChain; /** - * Acts as a proxy to other registered sockets. Looks up target RSocket - * via Registry. Creates GatewayExchange and executes a GatewayFilterChain. + * 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 AbstractRSocket { @@ -94,6 +97,13 @@ public class GatewayRSocket extends AbstractRSocket { .flatMapMany(rSocket -> rSocket.requestStream(payload)); } + /** + * Create GatewayExchange and attempt to locate target RSocket via filter chain. + * If not found, create a pending RSocket. + * @param type + * @param payload + * @return + */ private Mono findRSocket(GatewayExchange.Type type, Payload payload) { GatewayExchange exchange = GatewayExchange.fromPayload(type, payload); return findRSocket(exchange) @@ -111,10 +121,19 @@ public class GatewayRSocket extends AbstractRSocket { return Mono.just(pending); } + /** + * + */ /* for testing */ PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { return new PendingRequestRSocket(routes, exchange); } + /** + * First locate Route. If found, put route in exchange and execute filter chain. + * If successful, locate target RSocket. + * @param exchange + * @return Target RSocket or empty. + */ private Mono findRSocket(GatewayExchange exchange) { return this.routes.findRoute(exchange) .log("find route", Level.FINE) From ba011f750169b8ed5316fb18eee4db822c67274a Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sun, 3 Feb 2019 20:30:00 -0500 Subject: [PATCH 29/67] polish --- .../cloud/gateway/rsocket/server/GatewayRSocket.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 044eab1f..3b3d9d3f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -121,9 +121,6 @@ public class GatewayRSocket extends AbstractRSocket { return Mono.just(pending); } - /** - * - */ /* for testing */ PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { return new PendingRequestRSocket(routes, exchange); } @@ -154,7 +151,4 @@ public class GatewayRSocket extends AbstractRSocket { // TODO: deal with connecting to cluster? } - - } - From 7c3d4846272e760467dc0c11d0a7bcfaf100a1ca Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Sun, 3 Feb 2019 20:40:52 -0500 Subject: [PATCH 30/67] renames method --- .../cloud/gateway/rsocket/server/PendingRequestRSocket.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index a926da39..f6686a28 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -71,7 +71,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer { this.processor.onNext(registeredEvent.getRSocket()); @@ -93,7 +93,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer executeFilterChain(Route route, Map annoucementMetadata) { + private Mono matchRouteAndExecuteFilterChain(Route route, Map annoucementMetadata) { Map targetMetadata = route.getTargetMetadata(); if (Metadata.matches(targetMetadata, annoucementMetadata)) { return GatewayFilterChain.executeFilterChain(route.getFilters(), pendingExchange); From 86619618670e3e61427328eb217a0b1af57bf94e Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 4 Feb 2019 12:51:07 -0500 Subject: [PATCH 31/67] Moves filter chain to after processor receives signal. --- .../rsocket/server/GatewayExchange.java | 6 +- .../rsocket/server/PendingRequestRSocket.java | 66 ++++++++++++++----- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index 1f98254e..ce30cbfa 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -31,6 +31,7 @@ public class GatewayExchange extends AbstractRSocketExchange { private static final Log log = LogFactory.getLog(GatewayExchange.class); public static final String ROUTE_ATTR = "__route_attr_"; + static final String FIRST_PAYLOAD_ATTR = "firstPayload"; enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM } @@ -38,7 +39,10 @@ public class GatewayExchange extends AbstractRSocketExchange { private final Map routingMetadata; public static GatewayExchange fromPayload(Type type, Payload payload) { - return new GatewayExchange(type, getRoutingMetadata(payload)); + GatewayExchange exchange = new GatewayExchange(type, getRoutingMetadata(payload)); + //TODO: remove this when new RSocketServer method for requestChannel is here + exchange.getAttributes().put(FIRST_PAYLOAD_ATTR, payload); + return exchange; } private static Map getRoutingMetadata(Payload payload) { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index f6686a28..01b9adaf 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -24,11 +24,14 @@ import java.util.logging.Level; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +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.filter.RSocketFilter.Success; import org.springframework.cloud.gateway.rsocket.registry.Registry.RegisteredEvent; @@ -36,10 +39,15 @@ import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.FIRST_PAYLOAD_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; +import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_STREAM; +import static org.springframework.cloud.gateway.rsocket.server.GatewayFilterChain.executeFilterChain; public class PendingRequestRSocket extends AbstractRSocket implements Consumer { + private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); + private final Routes routes; private final GatewayExchange pendingExchange; private final MonoProcessor processor; @@ -58,8 +66,8 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer { + .flatMap(route -> { if (!pendingExchange.getAttributes().containsKey(ROUTE_ATTR)) { + if (log.isDebugEnabled()) { + log.debug("route not in exchange, adding."); + } pendingExchange.getAttributes().put(ROUTE_ATTR, route); } - return matchRouteAndExecuteFilterChain(route, registeredEvent.getRoutingMetadata()); + return matchRoute(route, registeredEvent.getRoutingMetadata()); }) - .subscribe(success -> { + .subscribe(route -> { this.processor.onNext(registeredEvent.getRSocket()); this.processor.onComplete(); if (this.subscriptionDisposable != null) { @@ -93,40 +104,59 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer matchRouteAndExecuteFilterChain(Route route, Map annoucementMetadata) { + private Mono matchRoute(Route route, Map annoucementMetadata) { Map targetMetadata = route.getTargetMetadata(); if (Metadata.matches(targetMetadata, annoucementMetadata)) { - return GatewayFilterChain.executeFilterChain(route.getFilters(), pendingExchange); + return Mono.just(route); } return Mono.empty(); } @Override public Mono fireAndForget(Payload payload) { - return processor - .log("pending-request-faf", Level.FINE) - .flatMap(rsocket -> rsocket.fireAndForget(payload)); + return processor("pending-request-faf", payload) + .flatMap(tuple -> tuple.getT1().fireAndForget(payload)); } @Override public Mono requestResponse(Payload payload) { - return processor - .log("pending-request-rr", Level.FINE) - .flatMap(rsocket -> rsocket.requestResponse(payload)); + return processor("pending-request-rr", payload) + .flatMap(tuple -> tuple.getT1().requestResponse(payload)); } @Override public Flux requestStream(Payload payload) { - return processor - .log("pending-request-rs", Level.FINE) - .flatMapMany(rsocket -> rsocket.requestStream(payload)); + return processor("pending-request-rs", payload) + .flatMapMany(tuple -> tuple.getT1().requestStream(payload)); } @Override public Flux requestChannel(Publisher payloads) { + //TODO: remove this when new RSocketServer method for requestChannel is here + // otherwise there is a dual subscription error + Payload firstPaylad = pendingExchange.getRequiredAttribute(FIRST_PAYLOAD_ATTR); + return processor("pending-request-rc", firstPaylad) + .flatMapMany(tuple -> tuple.getT1().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 + * @param payload + * @return + */ + protected Mono> processor(String logCategory, Payload payload) { return processor - .log("pending-request-rc", Level.FINE) - .flatMapMany(rsocket -> rsocket.requestChannel(payloads)); + .log(logCategory, Level.FINE) + .flatMap(rSocket -> { + Route route = pendingExchange.getAttribute(ROUTE_ATTR); + GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); + exchange.getAttributes().putAll(pendingExchange.getAttributes()); + return Mono.just(rSocket).zipWith(executeFilterChain(route.getFilters(), exchange)); + }); + } public void setSubscriptionDisposable(Disposable subscriptionDisposable) { From aa0bb7fe0cbfa86a2d60470ce5b5860e897b4efa Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 4 Feb 2019 13:15:45 -0500 Subject: [PATCH 32/67] Renames field processor to rSocketProcesor --- .../rsocket/server/PendingRequestRSocket.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 01b9adaf..6f51947f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -50,17 +50,17 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer processor; + private final MonoProcessor rSocketProcessor; private Disposable subscriptionDisposable; public PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange) { this(routes, pendingExchange, MonoProcessor.create()); } - /* for testing */ PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange, MonoProcessor processor) { + /* for testing */ PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange, MonoProcessor rSocketProcessor) { this.routes = routes; this.pendingExchange = pendingExchange; - this.processor = processor; + this.rSocketProcessor = rSocketProcessor; } /** @@ -85,8 +85,8 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer { - this.processor.onNext(registeredEvent.getRSocket()); - this.processor.onComplete(); + this.rSocketProcessor.onNext(registeredEvent.getRSocket()); + this.rSocketProcessor.onComplete(); if (this.subscriptionDisposable != null) { this.subscriptionDisposable.dispose(); } @@ -148,7 +148,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer> processor(String logCategory, Payload payload) { - return processor + return rSocketProcessor .log(logCategory, Level.FINE) .flatMap(rSocket -> { Route route = pendingExchange.getAttribute(ROUTE_ATTR); From 1108b57cb66ef9d528bfbf4d4c3f325dffd99286 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 4 Feb 2019 13:16:28 -0500 Subject: [PATCH 33/67] Updates description --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 39c20118..0b4cf916 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ spring-cloud-gateway-rsocket 0.0.1-SNAPSHOT spring-cloud-gateway-rsocket - Demo project for Spring Boot + Spring Cloud Gateway RSocket 1.8 From 636e264cc6d8335c774c22a0e54d375738ac9ad9 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 5 Feb 2019 13:24:30 -0500 Subject: [PATCH 34/67] Adds class names to reactor .log categories. --- .../gateway/rsocket/filter/AbstractFilterChain.java | 13 ++++++++++++- .../gateway/rsocket/server/GatewayRSocket.java | 2 +- .../rsocket/server/PendingRequestRSocket.java | 4 ++-- .../socketacceptor/GatewaySocketAcceptor.java | 4 ++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java index df295d1b..b47c602c 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java @@ -21,6 +21,8 @@ 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; @@ -42,6 +44,8 @@ import org.springframework.lang.Nullable; public abstract class AbstractFilterChain implements FilterChain { + private final Log log = LogFactory.getLog(getClass()); + protected final List allFilters; @Nullable @@ -99,7 +103,14 @@ public abstract class AbstractFilterChain this.currentFilter != null && this.next != null ? this.currentFilter.filter(exchange, this.next) : - MONO_SUCCESS); + getMonoSuccess()); + } + + private Mono getMonoSuccess() { + if (log.isDebugEnabled()) { + log.debug("filter chain completed with success"); + } + return MONO_SUCCESS; } private static final Mono MONO_SUCCESS = Mono.just(Success.INSTANCE); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 3b3d9d3f..80fb59f8 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -133,7 +133,7 @@ public class GatewayRSocket extends AbstractRSocket { */ private Mono findRSocket(GatewayExchange exchange) { return this.routes.findRoute(exchange) - .log("find route", Level.FINE) + .log(GatewayRSocket.class.getName()+".find route", Level.FINE) .flatMap(route -> { // put route in exchange for later use exchange.getAttributes().put(ROUTE_ATTR, route); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 6f51947f..de4c3603 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -73,7 +73,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer { if (!pendingExchange.getAttributes().containsKey(ROUTE_ATTR)) { @@ -149,7 +149,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer> processor(String logCategory, Payload payload) { return rSocketProcessor - .log(logCategory, Level.FINE) + .log(PendingRequestRSocket.class.getName()+"."+logCategory, Level.FINE) .flatMap(rSocket -> { Route route = pendingExchange.getAttribute(ROUTE_ATTR); GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index 8555f683..0ce2947b 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -41,8 +41,8 @@ public class GatewaySocketAcceptor implements SocketAcceptor { SocketAcceptorExchange exchange = new SocketAcceptorExchange(setup, sendingSocket); return this.filterChain.filter(exchange) - .log("socket acceptor filter chain", Level.FINE) - .map(bool -> this.proxyRSocket); + .log(GatewaySocketAcceptor.class.getName()+".socket acceptor filter chain", Level.FINE) + .map(success -> this.proxyRSocket); } } From f9588f37f5f881151369aa069b739d1ec9308543 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 5 Feb 2019 18:25:41 -0500 Subject: [PATCH 35/67] Moves to ResponderRSocket --- pom.xml | 3 +- .../GatewayRSocketAutoConfiguration.java | 12 +- .../metrics/MicrometerResponderRSocket.java | 207 ++++++++++++++++++ ...MicrometerResponderRSocketInterceptor.java | 53 +++++ .../gateway/rsocket/registry/Registry.java | 1 + .../rsocket/server/GatewayExchange.java | 6 +- .../rsocket/server/GatewayRSocket.java | 18 +- .../rsocket/server/PendingRequestRSocket.java | 20 +- .../GatewayRSocketAutoConfigurationTests.java | 7 +- 9 files changed, 295 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java create mode 100644 src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java diff --git a/pom.xml b/pom.xml index 0b4cf916..b78eebea 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ 1.8 - 0.11.15 + 0.11.16 Californium-SR4 @@ -69,6 +69,7 @@ io.rsocket rsocket-micrometer ${rsocket.version} + true io.rsocket diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index 6165bbe2..99a5c960 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -24,12 +24,12 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tags; import io.rsocket.RSocket; -import io.rsocket.micrometer.MicrometerRSocketInterceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocketInterceptor; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.registry.RegistryRoutes; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; @@ -91,7 +91,7 @@ public class GatewayRSocketAutoConfiguration { } @Configuration - @ConditionalOnClass({ MeterRegistry.class, MicrometerRSocketInterceptor.class }) + @ConditionalOnClass(MeterRegistry.class) protected static class GatewayRSocketServerMicrometerConfiguration { private final GatewayRSocketProperties properties; @@ -101,22 +101,22 @@ public class GatewayRSocketAutoConfiguration { } @Bean - public MicrometerRSocketInterceptor micrometerRSocketInterceptor(MeterRegistry meterRegistry) { + public MicrometerResponderRSocketInterceptor micrometerResponderRSocketInterceptor(MeterRegistry meterRegistry) { Tags tags = Tags.of(properties.getServer().getMicrometerTags().toArray(new String[]{})); List tagList = tags.stream().collect(Collectors.toList()); - return new MicrometerRSocketInterceptor(meterRegistry, tagList.toArray(new Tag[]{})); + return new MicrometerResponderRSocketInterceptor(meterRegistry, tagList.toArray(new Tag[]{})); } @Bean public GatewayRSocketServer gatewayApp(GatewaySocketAcceptor socketAcceptor, - MicrometerRSocketInterceptor micrometerInterceptor) { + MicrometerResponderRSocketInterceptor micrometerInterceptor) { return new GatewayRSocketServer(properties, socketAcceptor, micrometerInterceptor); } } @Configuration - @ConditionalOnMissingClass({ "io.micrometer.core.instrument.MeterRegistry", "io.rsocket.micrometer.MicrometerRSocketInterceptor" }) + @ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry") protected static class GatewayRSocketServerConfiguration { @Bean diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java new file mode 100644 index 00000000..bd3f88d0 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java @@ -0,0 +1,207 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.metrics; + +import java.util.Objects; +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.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.SignalType; + +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 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 NullPointerException if {@code delegate} or {@code meterRegistry} is {@code null} + */ + MicrometerResponderRSocket(RSocket delegate, MeterRegistry meterRegistry, Tag... tags) { + this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); + Objects.requireNonNull(meterRegistry, "meterRegistry must not be null"); + + 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 fireAndForget(Payload payload) { + return delegate.fireAndForget(payload).doFinally(requestFireAndForget); + } + + @Override + public Mono metadataPush(Payload payload) { + return delegate.metadataPush(payload).doFinally(metadataPush); + } + + @Override + public Mono onClose() { + return delegate.onClose(); + } + + @Override + public Flux requestChannel(Publisher payloads) { + return delegate.requestChannel(payloads).doFinally(requestChannel); + } + + @Override + public Mono requestResponse(Payload payload) { + return Mono.defer( + () -> { + Timer.Sample sample = requestResponse.start(); + + return delegate + .requestResponse(payload) + .doFinally(signalType -> requestResponse.accept(sample, signalType)); + }); + } + + @Override + public Flux requestStream(Payload payload) { + return delegate.requestStream(payload).doFinally(requestStream); + } + + @Override + public Flux requestChannel(Payload payload, Publisher payloads) { + if (delegate instanceof ResponderRSocket) { + ResponderRSocket rSocket = (ResponderRSocket) delegate; + return rSocket.requestChannel(payload, payloads); + } + return delegate.requestChannel(payloads); + } + + private static final class InteractionCounters implements Consumer { + + 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: + cancel.increment(); + break; + case ON_COMPLETE: + onComplete.increment(); + break; + case ON_ERROR: + onError.increment(); + break; + } + } + + private static Counter counter( + MeterRegistry meterRegistry, String interactionModel, SignalType signalType, Tag... tags) { + + return meterRegistry.counter( + "rsocket." + interactionModel, Tags.of(tags).and("signal.type", signalType.name())); + } + } + + private static final class InteractionTimers implements BiConsumer { + + 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: + sample.stop(cancel); + break; + case ON_COMPLETE: + sample.stop(onComplete); + break; + case ON_ERROR: + sample.stop(onError); + break; + } + } + + Timer.Sample start() { + return Timer.start(meterRegistry); + } + + private static Timer timer( + MeterRegistry meterRegistry, String interactionModel, SignalType signalType, Tag... tags) { + + return meterRegistry.timer( + "rsocket." + interactionModel, Tags.of(tags).and("signal.type", signalType.name())); + } + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java new file mode 100644 index 00000000..dd0f74aa --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java @@ -0,0 +1,53 @@ +/* + * 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. + * + */ + +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); + } + +} diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 48430a16..790473da 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -66,6 +66,7 @@ public class Registry { return keyFunction.apply(properties); } + //TODO: Mono? public void register(Map properties, RSocket rsocket) { Assert.notEmpty(properties, "properties may not be empty"); Assert.notNull(rsocket, "RSocket may not be null"); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index ce30cbfa..1f98254e 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -31,7 +31,6 @@ public class GatewayExchange extends AbstractRSocketExchange { private static final Log log = LogFactory.getLog(GatewayExchange.class); public static final String ROUTE_ATTR = "__route_attr_"; - static final String FIRST_PAYLOAD_ATTR = "firstPayload"; enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM } @@ -39,10 +38,7 @@ public class GatewayExchange extends AbstractRSocketExchange { private final Map routingMetadata; public static GatewayExchange fromPayload(Type type, Payload payload) { - GatewayExchange exchange = new GatewayExchange(type, getRoutingMetadata(payload)); - //TODO: remove this when new RSocketServer method for requestChannel is here - exchange.getAttributes().put(FIRST_PAYLOAD_ATTR, payload); - return exchange; + return new GatewayExchange(type, getRoutingMetadata(payload)); } private static Map getRoutingMetadata(Payload payload) { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 80fb59f8..2bc0bf2d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -22,6 +22,7 @@ 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; @@ -46,7 +47,7 @@ import static org.springframework.cloud.gateway.rsocket.server.GatewayFilterChai * an attempt to locate a target RSocket via the Registry is executed. If not found * a pending RSocket * is returned. */ -public class GatewayRSocket extends AbstractRSocket { +public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket { private static final Log log = LogFactory.getLog(GatewayRSocket.class); @@ -73,15 +74,14 @@ public class GatewayRSocket extends AbstractRSocket { } @Override - public Flux requestChannel(Publisher payloads) { - return Flux.from(payloads) - .switchOnFirst((signal, payloadFlux) -> { - if (!signal.hasValue()) { - return payloadFlux; + public Flux requestChannel(Payload payload, Publisher payloads) { + return findRSocket(REQUEST_CHANNEL, payload) + .flatMapMany(rSocket -> { + if (rSocket instanceof ResponderRSocket) { + ResponderRSocket socket = (ResponderRSocket) rSocket; + return socket.requestChannel(payload, payloads); } - - return findRSocket(REQUEST_CHANNEL, signal.get()) - .flatMapMany(rSocket -> rSocket.requestChannel(payloadFlux)); + return rSocket.requestChannel(payloads); }); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index de4c3603..88049356 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -24,6 +24,7 @@ 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; @@ -39,12 +40,11 @@ import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; -import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.FIRST_PAYLOAD_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_STREAM; import static org.springframework.cloud.gateway.rsocket.server.GatewayFilterChain.executeFilterChain; -public class PendingRequestRSocket extends AbstractRSocket implements Consumer { +public class PendingRequestRSocket extends AbstractRSocket implements ResponderRSocket, Consumer { private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); @@ -131,12 +131,16 @@ public class PendingRequestRSocket extends AbstractRSocket implements Consumer requestChannel(Publisher payloads) { - //TODO: remove this when new RSocketServer method for requestChannel is here - // otherwise there is a dual subscription error - Payload firstPaylad = pendingExchange.getRequiredAttribute(FIRST_PAYLOAD_ATTR); - return processor("pending-request-rc", firstPaylad) - .flatMapMany(tuple -> tuple.getT1().requestChannel(payloads)); + public Flux requestChannel(Payload payload, Publisher 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); + }); } /** diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index 04dfa9da..a4e98bf8 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfigu import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocketInterceptor; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.registry.RegistryRoutes; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; @@ -57,7 +58,7 @@ public class GatewayRSocketAutoConfigurationTests { .hasSingleBean(GatewaySocketAcceptor.class) .hasSingleBean(SocketAcceptorPredicateFilter.class) .doesNotHaveBean(SocketAcceptorPredicate.class) - .hasSingleBean(MicrometerRSocketInterceptor.class) + .hasSingleBean(MicrometerResponderRSocketInterceptor.class) ); } @@ -69,7 +70,7 @@ public class GatewayRSocketAutoConfigurationTests { CompositeMeterRegistryAutoConfiguration.class, MetricsAutoConfiguration.class )) - .withClassLoader(new FilteredClassLoader(MeterRegistry.class, MicrometerRSocketInterceptor.class)) + .withClassLoader(new FilteredClassLoader(MeterRegistry.class)) .run(context -> assertThat(context) .hasSingleBean(Registry.class) .hasSingleBean(RegistryRoutes.class) @@ -80,7 +81,7 @@ public class GatewayRSocketAutoConfigurationTests { .hasSingleBean(GatewaySocketAcceptor.class) .hasSingleBean(SocketAcceptorPredicateFilter.class) .doesNotHaveBean(SocketAcceptorPredicate.class) - .doesNotHaveBean(MicrometerRSocketInterceptor.class) + .doesNotHaveBean(MicrometerResponderRSocketInterceptor.class) ); } } From de26ba77f33ce6b25d89232794fa6c2585f4334c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 12 Feb 2019 11:41:47 -0500 Subject: [PATCH 36/67] Adds simple LoadBalancer interface. It is simply a function from a list of RSockets to a Mono. Also adds simple Round Robin LoadBalancer as the default impelementation. fixes gh-7 --- .../rsocket/registry/LoadBalancedRSocket.java | 89 +++++++++++++------ .../gateway/rsocket/registry/Registry.java | 3 +- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java index b49172de..a8a57365 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java @@ -20,44 +20,40 @@ package org.springframework.cloud.gateway.rsocket.registry; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; +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 java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; public class LoadBalancedRSocket extends AbstractRSocket { - private final List delegates = new ArrayList<>(); //TODO: Concurrent? + private static final Log log = LogFactory.getLog(LoadBalancedRSocket.class); - private final Function, RSocket> rsocketSelector; + private final List delegates = new CopyOnWriteArrayList<>(); - public LoadBalancedRSocket() { - this(null); + private final Map properties; + + private final LoadBalancer loadBalancer; + + public LoadBalancedRSocket(Map properties) { + this(properties, new RoundRobinLoadBalancer(properties)); } - // TODO: simple round robbin load balancing - public LoadBalancedRSocket(Function, RSocket> rsocketSelector) { - if (rsocketSelector != null) { - this.rsocketSelector = rsocketSelector; - } else { - this.rsocketSelector = rSockets -> { - if (!delegates.isEmpty()) { - return delegates.get(0); - } - return null; - }; - } + public LoadBalancedRSocket(Map properties, LoadBalancer loadBalancer) { + this.properties = properties; + this.loadBalancer = loadBalancer; } - public RSocket choose() { - RSocket rsocket = rsocketSelector.apply(delegates); - if (rsocket == null) { - throw new IllegalStateException("No RSockets available"); - } - return rsocket; + public Mono choose() { + return this.loadBalancer.apply(this.delegates); } public void addRSocket(RSocket rsocket) { @@ -65,31 +61,66 @@ public class LoadBalancedRSocket extends AbstractRSocket { } public List getDelegates() { - return delegates; + return this.delegates; } @Override public Mono fireAndForget(Payload payload) { - return choose().fireAndForget(payload); + return choose().flatMap(rSocket -> rSocket.fireAndForget(payload)); } @Override public Mono requestResponse(Payload payload) { - return choose().requestResponse(payload); + return choose().flatMap(rSocket -> rSocket.requestResponse(payload)); } @Override public Flux requestStream(Payload payload) { - return choose().requestStream(payload); + return choose().flatMapMany(rSocket -> rSocket.requestStream(payload)); } @Override public Flux requestChannel(Publisher payloads) { - return choose().requestChannel(payloads); + return choose().flatMapMany(rSocket -> rSocket.requestChannel(payloads)); } @Override public Mono metadataPush(Payload payload) { - return choose().metadataPush(payload); + return choose().flatMap(rSocket -> rSocket.metadataPush(payload)); + } + + //TODO: Flux as input? + //TODO: reuse commons load balancer? + public interface LoadBalancer extends Function, Mono> { + } + + public static class RoundRobinLoadBalancer implements LoadBalancer { + + private final AtomicInteger position; + private final Map properties; + + public RoundRobinLoadBalancer(Map properties) { + this(properties, new Random().nextInt(1000)); + } + + public RoundRobinLoadBalancer(Map properties, int seedPosition) { + this.properties = properties; + this.position = new AtomicInteger(seedPosition); + } + + @Override + public Mono apply(List rSockets) { + if (rSockets.isEmpty()) { + if (log.isWarnEnabled()) { + log.warn("No servers available for: " + this.properties); + } + return Mono.empty(); + } + // TODO: enforce order? + int pos = Math.abs(this.position.incrementAndGet()); + + RSocket rSocket = rSockets.get(pos % rSockets.size()); + return Mono.just(rSocket); + } } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 790473da..81be9c34 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -71,7 +71,8 @@ public class Registry { Assert.notEmpty(properties, "properties may not be empty"); Assert.notNull(rsocket, "RSocket may not be null"); log.debug("Registered RSocket: " + properties); - LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> new LoadBalancedRSocket()); + LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> + new LoadBalancedRSocket(properties)); composite.addRSocket(rsocket); registeredEventsSink.next(new RegisteredEvent(properties, rsocket)); } From 7aefa487df61e796b62fc544dcca7c412bdcc8b3 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 12 Feb 2019 12:08:19 -0500 Subject: [PATCH 37/67] Uses spring Assert rather than Objects.requireNonNull --- .../rsocket/metrics/MicrometerResponderRSocket.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java index bd3f88d0..2e98f2c7 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.rsocket.metrics; -import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -35,6 +34,8 @@ 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; @@ -59,12 +60,13 @@ public class MicrometerResponderRSocket implements ResponderRSocket { * @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 NullPointerException if {@code delegate} or {@code meterRegistry} is {@code null} + * @throws IllegalArgumentException if {@code delegate} or {@code meterRegistry} is {@code null} */ MicrometerResponderRSocket(RSocket delegate, MeterRegistry meterRegistry, Tag... tags) { - this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); - Objects.requireNonNull(meterRegistry, "meterRegistry must not be null"); + 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); From 6558d713fa56736193141a666e1d3bdb36fc183c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 12 Feb 2019 12:17:45 -0500 Subject: [PATCH 38/67] Changes comment to TODO --- .../gateway/rsocket/registry/RegistrySocketAcceptorFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index 01c0819f..6672f48f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -38,7 +38,7 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order @Override public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { ConnectionSetupPayload setup = exchange.getSetup(); - if (setup.hasMetadata()) { // and setup.metadataMimeType() is Announcement metadata + if (setup.hasMetadata()) { //TODO: and setup.metadataMimeType() is Announcement metadata Map properties = Metadata.decodeProperties(setup.sliceMetadata()); // enrich exchange to have metadata exchange = new SocketAcceptorExchange(exchange.getSetup(), exchange.getSendingSocket(), properties); From fa4a19e2eee7556741c204ea09d40b46abf58c4f Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 12 Feb 2019 12:44:12 -0500 Subject: [PATCH 39/67] polishes format and javadoc --- .../RegistrySocketAcceptorFilter.java | 5 +- .../GatewaySocketAcceptorTests.java | 67 +++++++++---------- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index 6672f48f..5b0f0e43 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -28,6 +28,9 @@ import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFi import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.core.Ordered; +/** + * Filter that enriches the SocketAcceptorExchange with metadata from ConnectionSetupPayload. + */ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { private final Registry registry; @@ -42,7 +45,7 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order Map properties = Metadata.decodeProperties(setup.sliceMetadata()); // enrich exchange to have metadata exchange = new SocketAcceptorExchange(exchange.getSetup(), exchange.getSendingSocket(), properties); - registry.register(properties, exchange.getSendingSocket()); + this.registry.register(properties, exchange.getSendingSocket()); } return chain.filter(exchange); diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java index 904aa142..5d11ed2b 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java @@ -29,10 +29,8 @@ import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** @@ -48,82 +46,80 @@ public class GatewaySocketAcceptorTests { @Before public void init() { - proxyRSocket = mock(RSocket.class); - setupPayload = mock(ConnectionSetupPayload.class); - sendingSocket = mock(RSocket.class); + this.proxyRSocket = mock(RSocket.class); + this.setupPayload = mock(ConnectionSetupPayload.class); + this.sendingSocket = mock(RSocket.class); } @Test - public void multipleFilters() { + public void multipleFilters() { TestFilter filter1 = new TestFilter(); TestFilter filter2 = new TestFilter(); TestFilter filter3 = new TestFilter(); - RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Arrays.asList(filter1, filter2, filter3)) - .accept(setupPayload, sendingSocket) + RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + .accept(this.setupPayload, this.sendingSocket) .block(Duration.ZERO); - assertTrue(filter1.invoked()); - assertTrue(filter2.invoked()); - assertTrue(filter3.invoked()); - assertNotNull(socket); + assertThat(filter1.invoked()).isTrue(); + assertThat(filter2.invoked()).isTrue(); + assertThat(filter3.invoked()).isTrue(); + assertThat(socket).isNotNull(); } @Test - public void zeroFilters() { - RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Collections.emptyList()) - .accept(setupPayload, sendingSocket) + public void zeroFilters() { + RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, Collections.emptyList()) + .accept(this.setupPayload, this.sendingSocket) .block(Duration.ZERO); - assertNotNull(socket); + assertThat(socket).isNotNull(); } @Test - public void shortcircuitFilter() { + public void shortcircuitFilter() { TestFilter filter1 = new TestFilter(); ShortcircuitingFilter filter2 = new ShortcircuitingFilter(); TestFilter filter3 = new TestFilter(); - RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Arrays.asList(filter1, filter2, filter3)) - .accept(setupPayload, sendingSocket) + RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + .accept(this.setupPayload, this.sendingSocket) .block(Duration.ZERO); - assertTrue(filter1.invoked()); - assertTrue(filter2.invoked()); - assertFalse(filter3.invoked()); - assertNull(socket); + assertThat(filter1.invoked()).isTrue(); + assertThat(filter2.invoked()).isTrue(); + assertThat(filter3.invoked()).isFalse(); + assertThat(socket).isNull(); } @Test - public void asyncFilter() { + public void asyncFilter() { AsyncFilter filter = new AsyncFilter(); - RSocket socket = new GatewaySocketAcceptor(proxyRSocket, Collections.singletonList(filter)) - .accept(setupPayload, sendingSocket) + RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, singletonList(filter)) + .accept(this.setupPayload, this.sendingSocket) .block(Duration.ofSeconds(5)); - assertTrue(filter.invoked()); - assertNotNull(socket); + assertThat(filter.invoked()).isTrue(); + assertThat(socket).isNotNull(); } //TODO: add exception handlers? @Test(expected = IllegalStateException.class) - public void handleErrorFromFilter() { + public void handleErrorFromFilter() { ExceptionFilter filter = new ExceptionFilter(); - new GatewaySocketAcceptor(proxyRSocket, Collections.singletonList(filter)) - .accept(setupPayload, sendingSocket) + new GatewaySocketAcceptor(this.proxyRSocket, singletonList(filter)) + .accept(this.setupPayload, this.sendingSocket) .block(Duration.ofSeconds(5)); - // assertNull(socket); } - private static class TestFilter implements SocketAcceptorFilter { private volatile boolean invoked; @@ -167,7 +163,6 @@ public class GatewaySocketAcceptorTests { } } - private static class ExceptionFilter implements SocketAcceptorFilter { @Override From a108de53e7377100fb50e7a2ec1de42e2fcc2f7f Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 13 Feb 2019 12:30:00 -0500 Subject: [PATCH 40/67] Adds missing doFinally to metrics --- .../gateway/rsocket/metrics/MicrometerResponderRSocket.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java index 2e98f2c7..2bfdeb92 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java @@ -120,9 +120,9 @@ public class MicrometerResponderRSocket implements ResponderRSocket { public Flux requestChannel(Payload payload, Publisher payloads) { if (delegate instanceof ResponderRSocket) { ResponderRSocket rSocket = (ResponderRSocket) delegate; - return rSocket.requestChannel(payload, payloads); + return rSocket.requestChannel(payload, payloads).doFinally(requestChannel); } - return delegate.requestChannel(payloads); + return delegate.requestChannel(payloads).doFinally(requestChannel); } private static final class InteractionCounters implements Consumer { From 2a9939a4806149d9837df09059e24e3cba061475 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Wed, 13 Feb 2019 12:32:04 -0500 Subject: [PATCH 41/67] Bumps to latest reactor --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b78eebea..47e6228b 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ io.projectreactor reactor-bom - Californium-SR4 + Californium-SR5 pom import From e5c14e3ff0d1fac6e50d3ec686e30341b0fffa9c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 14:05:41 -0500 Subject: [PATCH 42/67] Adds gateway metrics. Specifically adds existing micromenter integration for DuplexConnection and RSocket with specific tags. Also adds metrics tagged with the requester and responder including payload counters for request.stream and request.channel. fixes gh-15 --- pom.xml | 5 +- .../GatewayRSocketAutoConfiguration.java | 63 +++------- .../GatewayRSocketProperties.java | 11 ++ .../metrics/MicrometerResponderRSocket.java | 2 +- .../RegistrySocketAcceptorFilter.java | 15 +-- .../rsocket/server/GatewayRSocket.java | 110 +++++++++++++++--- .../rsocket/server/GatewayRSocketServer.java | 25 +++- .../socketacceptor/GatewaySocketAcceptor.java | 50 +++++++- .../GatewayRSocketAutoConfigurationTests.java | 29 +---- .../rsocket/server/GatewayRSocketTests.java | 18 ++- .../GatewaySocketAcceptorTests.java | 32 +++-- .../gateway/rsocket/test/PingPongApp.java | 2 +- src/test/resources/application.yml | 8 +- 13 files changed, 249 insertions(+), 121 deletions(-) diff --git a/pom.xml b/pom.xml index 47e6228b..c8b20088 100644 --- a/pom.xml +++ b/pom.xml @@ -69,13 +69,16 @@ io.rsocket rsocket-micrometer ${rsocket.version} - true io.rsocket rsocket-transport-netty ${rsocket.version} + + io.micrometer + micrometer-core + org.springframework.boot spring-boot-starter-actuator diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index 99a5c960..d11c547e 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -18,18 +18,13 @@ package org.springframework.cloud.gateway.rsocket.autoconfigure; 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.RSocket; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocketInterceptor; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.registry.RegistryRoutes; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; @@ -42,6 +37,7 @@ import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPr 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; /** * @author Spencer Gibb @@ -71,13 +67,18 @@ public class GatewayRSocketAutoConfiguration { } @Bean - public GatewayRSocket gatewayRSocket(Registry registry, Routes routes) { - return new GatewayRSocket(registry, routes); + public GatewayRSocket.Factory gatewayRSocketFactory(Registry registry, Routes routes, + MeterRegistry meterRegistry, GatewayRSocketProperties properties) { + return new GatewayRSocket.Factory(registry, routes, meterRegistry, properties); } @Bean - public GatewayRSocketProperties gatewayRSocketProperties() { - return new GatewayRSocketProperties(); + public GatewayRSocketProperties gatewayRSocketProperties(Environment env) { + GatewayRSocketProperties properties = new GatewayRSocketProperties(); + if (env.containsProperty("spring.application.name")) { + properties.setId(env.getProperty("spring.application.name")); // set default from env + } + return properties; } @Bean @@ -86,43 +87,15 @@ public class GatewayRSocketAutoConfiguration { } @Bean - public GatewaySocketAcceptor socketAcceptor(GatewayRSocket rsocket, List filters) { - return new GatewaySocketAcceptor(rsocket, filters); + public GatewaySocketAcceptor socketAcceptor(GatewayRSocket.Factory rsocketFactory, + List filters, MeterRegistry meterRegistry, + GatewayRSocketProperties properties) { + return new GatewaySocketAcceptor(rsocketFactory, filters, meterRegistry, properties); } - @Configuration - @ConditionalOnClass(MeterRegistry.class) - protected static class GatewayRSocketServerMicrometerConfiguration { - - private final GatewayRSocketProperties properties; - - public GatewayRSocketServerMicrometerConfiguration(GatewayRSocketProperties properties) { - this.properties = properties; - } - - @Bean - public MicrometerResponderRSocketInterceptor micrometerResponderRSocketInterceptor(MeterRegistry meterRegistry) { - Tags tags = Tags.of(properties.getServer().getMicrometerTags().toArray(new String[]{})); - List tagList = tags.stream().collect(Collectors.toList()); - return new MicrometerResponderRSocketInterceptor(meterRegistry, tagList.toArray(new Tag[]{})); - } - - @Bean - public GatewayRSocketServer gatewayApp(GatewaySocketAcceptor socketAcceptor, - MicrometerResponderRSocketInterceptor micrometerInterceptor) { - return new GatewayRSocketServer(properties, socketAcceptor, micrometerInterceptor); - } - - } - - @Configuration - @ConditionalOnMissingClass("io.micrometer.core.instrument.MeterRegistry") - protected static class GatewayRSocketServerConfiguration { - - @Bean - public GatewayRSocketServer gatewayApp(GatewayRSocketProperties properties, GatewaySocketAcceptor socketAcceptor) { - return new GatewayRSocketServer(properties, socketAcceptor); - } - + @Bean + public GatewayRSocketServer gatewayApp(GatewaySocketAcceptor socketAcceptor, + GatewayRSocketProperties properties) { + return new GatewayRSocketServer(properties, socketAcceptor); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java index ad487526..de21e576 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java @@ -31,6 +31,8 @@ public class GatewayRSocketProperties { */ private boolean enabled = true; + private String id = "gateway"; // TODO: + UUID? + private final Server server = new Server(); public boolean isEnabled() { @@ -41,6 +43,14 @@ public class GatewayRSocketProperties { this.enabled = enabled; } + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + public Server getServer() { return server; } @@ -49,6 +59,7 @@ public class GatewayRSocketProperties { public String toString() { return new ToStringCreator(this) .append("enabled", enabled) + .append("id", id) .append("server", server) .toString(); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java index 2bfdeb92..5c63b945 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java @@ -62,7 +62,7 @@ public class MicrometerResponderRSocket implements ResponderRSocket { * @param tags additional tags to attach to {@link Meter}s * @throws IllegalArgumentException if {@code delegate} or {@code meterRegistry} is {@code null} */ - MicrometerResponderRSocket(RSocket delegate, MeterRegistry meterRegistry, Tag... tags) { + public MicrometerResponderRSocket(RSocket delegate, MeterRegistry meterRegistry, Tag... tags) { Assert.notNull(delegate, "delegate must not be null"); Assert.notNull(meterRegistry, "meterRegistry must not be null"); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index 5b0f0e43..c40efc29 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -17,19 +17,16 @@ package org.springframework.cloud.gateway.rsocket.registry; -import java.util.Map; - -import io.rsocket.ConnectionSetupPayload; 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.cloud.gateway.rsocket.support.Metadata; import org.springframework.core.Ordered; +import org.springframework.util.CollectionUtils; /** - * Filter that enriches the SocketAcceptorExchange with metadata from ConnectionSetupPayload. + * Filter that registers the SendingSocket. */ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Ordered { private final Registry registry; @@ -40,12 +37,8 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order @Override public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { - ConnectionSetupPayload setup = exchange.getSetup(); - if (setup.hasMetadata()) { //TODO: and setup.metadataMimeType() is Announcement metadata - Map properties = Metadata.decodeProperties(setup.sliceMetadata()); - // enrich exchange to have metadata - exchange = new SocketAcceptorExchange(exchange.getSetup(), exchange.getSendingSocket(), properties); - this.registry.register(properties, exchange.getSendingSocket()); + if (!CollectionUtils.isEmpty(exchange.getMetadata())) { + this.registry.register(exchange.getMetadata(), exchange.getSendingSocket()); } return chain.filter(exchange); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 2bc0bf2d..cd103d4f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -17,8 +17,13 @@ package org.springframework.cloud.gateway.rsocket.server; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; import io.rsocket.AbstractRSocket; import io.rsocket.Payload; import io.rsocket.RSocket; @@ -30,6 +35,7 @@ import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Routes; @@ -53,12 +59,20 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private final Registry registry; private final Routes routes; + private final MeterRegistry meterRegistry; + private final GatewayRSocketProperties properties; + private final Map metadata; - public GatewayRSocket(Registry registry, Routes routes) { + GatewayRSocket(Registry registry, Routes routes, MeterRegistry meterRegistry, + GatewayRSocketProperties properties, Map metadata) { this.registry = registry; this.routes = routes; + this.meterRegistry = meterRegistry; + this.properties = properties; + this.metadata = metadata; } + protected Registry getRegistry() { return registry; } @@ -69,43 +83,87 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket @Override public Mono fireAndForget(Payload payload) { - return findRSocket(FIRE_AND_FORGET, payload) - .flatMap(rSocket -> rSocket.fireAndForget(payload)); + GatewayExchange exchange = GatewayExchange.fromPayload(FIRE_AND_FORGET, payload); + Tags tags = getTags(exchange); + return findRSocketOrCreatePending(exchange) + .flatMap(rSocket -> rSocket.fireAndForget(payload)) + .doOnError(t -> count("forward.request.fnf.error", tags)) + .doFinally(s -> count("forward.request.fnf", tags)); + } + + private Tags getTags(GatewayExchange exchange) { + //TODO: add tags to exchange + String responderName = this.metadata.get("name"); + String responderId = this.metadata.get("id"); + String requestorName = exchange.getRoutingMetadata().get("name"); + //String requestorId = TODO: where to get requestorId + //TODO: deal with missing tags? + return Tags.of("requester.name", requestorName, //"requester.id", requestorId, + "responder.name", responderName, "responder.id", responderId, + "gateway.id", this.properties.getId()); } @Override public Flux requestChannel(Payload payload, Publisher payloads) { - return findRSocket(REQUEST_CHANNEL, payload) + GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_CHANNEL, payload); + Tags tags = getTags(exchange); + Tags responderTags = tags.and("source", "responder"); + return findRSocketOrCreatePending(exchange) .flatMapMany(rSocket -> { + Tags requesterTags = tags.and("source", "requester"); + Flux flux = Flux.from(payloads) + .doOnNext(s -> count("forward.request.channel.payload", requesterTags)) + .doOnError(t -> count("forward.request.channel.error", requesterTags)) + .doFinally(s -> count("forward.request.channel", requesterTags)); + if (rSocket instanceof ResponderRSocket) { ResponderRSocket socket = (ResponderRSocket) rSocket; - return socket.requestChannel(payload, payloads); + return socket.requestChannel(payload, flux) + .log(GatewayRSocket.class.getName()+".request-channel", Level.FINE); } - return rSocket.requestChannel(payloads); - }); + return rSocket.requestChannel(flux); + }) + .doOnNext(s -> count("forward.request.channel.payload", responderTags)) + .doOnError(t -> count("forward.request.channel.error", responderTags)) + .doFinally(s -> count("forward.request.channel", responderTags)); + } + + private void count(String name, Tags responderTags) { + this.meterRegistry.counter(name, responderTags).increment(); } @Override public Mono requestResponse(Payload payload) { - return findRSocket(REQUEST_RESPONSE, payload) - .flatMap(rSocket -> rSocket.requestResponse(payload)); + AtomicReference timer = new AtomicReference<>(); + GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_RESPONSE, payload); + Tags tags = getTags(exchange); + return findRSocketOrCreatePending(exchange) + .flatMap(rSocket -> rSocket.requestResponse(payload)) + .doOnSubscribe(s -> timer.set(Timer.start(meterRegistry))) + .doOnError(t -> count("forward.request.response.error", tags)) + .doFinally(s -> timer.get().stop(meterRegistry.timer("forward.request.response", tags))); } @Override public Flux requestStream(Payload payload) { - return findRSocket(REQUEST_STREAM, payload) - .flatMapMany(rSocket -> rSocket.requestStream(payload)); + GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); + Tags tags = getTags(exchange); + return findRSocketOrCreatePending(exchange) + .flatMapMany(rSocket -> rSocket.requestStream(payload)) + // S N E F + //TODO: move tagnames to enum + .doOnNext(s -> count("forward.request.stream.payload", tags)) + .doOnError(t -> count("forward.request.stream.error", tags)) + .doFinally(s -> count("forward.request.stream", tags)); } /** - * Create GatewayExchange and attempt to locate target RSocket via filter chain. + * Attempt to locate target RSocket via filter chain. * If not found, create a pending RSocket. - * @param type - * @param payload + * @param exchange * @return */ - private Mono findRSocket(GatewayExchange.Type type, Payload payload) { - GatewayExchange exchange = GatewayExchange.fromPayload(type, payload); + private Mono findRSocketOrCreatePending(GatewayExchange exchange) { return findRSocket(exchange) // if a route can't be found or registered RSocket, create pending .switchIfEmpty(createPendingRSocket(exchange)); @@ -151,4 +209,24 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket // TODO: deal with connecting to cluster? } + public static class Factory { + private final Registry registry; + private final Routes routes; + private final MeterRegistry meterRegistry; + private final GatewayRSocketProperties properties; + + public Factory(Registry registry, Routes routes, MeterRegistry meterRegistry, + GatewayRSocketProperties properties) { + this.registry = registry; + this.routes = routes; + this.meterRegistry = meterRegistry; + this.properties = properties; + } + + public GatewayRSocket create(Map metadata) { + return new GatewayRSocket(this.registry, this.routes, this.meterRegistry, + this.properties, metadata); + } + } + } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java index 3e8cda53..acbbaceb 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java @@ -20,15 +20,21 @@ package org.springframework.cloud.gateway.rsocket.server; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +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; import io.rsocket.SocketAcceptor; +import io.rsocket.micrometer.MicrometerDuplexConnectionInterceptor; import io.rsocket.plugins.RSocketInterceptor; import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties.Server.TransportType; import org.springframework.context.SmartLifecycle; @@ -45,6 +51,8 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { private final List serverInterceptors; private final AtomicBoolean running = new AtomicBoolean(); private CloseableChannel closeableChannel; + @Autowired + MeterRegistry meterRegistry; public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor) { this(properties, socketAcceptor, EMPTY_INTERCEPTORS); @@ -90,9 +98,10 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { } protected void startServer() { - int port = properties.getServer().getPort(); + GatewayRSocketProperties.Server server = properties.getServer(); + int port = server.getPort(); - TransportType transportType = properties.getServer().getTransport(); + TransportType transportType = server.getTransport(); TcpServerTransport transport; switch (transportType) { case TCP: @@ -110,7 +119,17 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { serverInterceptors.forEach(factory::addServerPlugin); - factory.acceptor(this.socketAcceptor) + List micrometerTags = server.getMicrometerTags(); + Tag[] tags = Tags.of(micrometerTags.toArray(new String[] {})) + .and("gateway.id", properties.getId()) + .stream() + .collect(Collectors.toList()) + .toArray(new Tag[]{}); + + factory + //TODO: add as bean lice serverInterceptors above + .addConnectionPlugin(new MicrometerDuplexConnectionInterceptor(meterRegistry, tags)) + .acceptor(this.socketAcceptor) .transport(transport) .start() .map(closeableChannel -> { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index 0ce2947b..d0a95a77 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -18,31 +18,71 @@ 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 reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; +import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocket; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.support.Metadata; + public class GatewaySocketAcceptor implements SocketAcceptor { - private final RSocket proxyRSocket; private final SocketAcceptorFilterChain filterChain; + private final GatewayRSocket.Factory rSocketFactory; + private final MeterRegistry meterRegistry; + private final GatewayRSocketProperties properties; - public GatewaySocketAcceptor(RSocket proxyRSocket, List filters) { - this.proxyRSocket = proxyRSocket; + public GatewaySocketAcceptor(GatewayRSocket.Factory rSocketFactory, List filters, MeterRegistry meterRegistry, GatewayRSocketProperties properties) { + this.rSocketFactory = rSocketFactory; this.filterChain = new SocketAcceptorFilterChain(filters); + this.meterRegistry = meterRegistry; + this.properties = properties; } @Override + @SuppressWarnings("Duplicates") public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - SocketAcceptorExchange exchange = new SocketAcceptorExchange(setup, sendingSocket); + //decorate sendingSocket 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; + if (setup.hasMetadata()) { //TODO: and setup.metadataMimeType() is Announcement metadata or composite + Map properties = Metadata.decodeProperties(setup.sliceMetadata()); + metadataTags = Tags.of("service.name", properties.get("name")) + .and("service.id", properties.get("id")); + // enrich exchange to have metadata + exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags.and(metadataTags)), properties); + } else { + metadataTags = Tags.empty(); + 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.FINE) - .map(success -> this.proxyRSocket); + .map(success -> decorate(this.rSocketFactory.create(exchange.getMetadata()), responderTags)); + } + + private RSocket decorate(RSocket rSocket, Tags tags) { + Tag[] tagArray = tags.stream().collect(Collectors.toList()).toArray(new Tag[] {}); + return new MicrometerResponderRSocket(rSocket, meterRegistry, tagArray); } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index a4e98bf8..1b4fa204 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -17,14 +17,11 @@ package org.springframework.cloud.gateway.rsocket.autoconfigure; -import io.micrometer.core.instrument.MeterRegistry; -import io.rsocket.micrometer.MicrometerRSocketInterceptor; 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.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocketInterceptor; import org.springframework.cloud.gateway.rsocket.registry.Registry; @@ -41,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class GatewayRSocketAutoConfigurationTests { @Test - public void gatewayRSocketWithMicrometerConfigured() { + public void gatewayRSocketConfigured() { new ReactiveWebApplicationContextRunner() .withConfiguration(AutoConfigurations.of( GatewayRSocketAutoConfiguration.class, @@ -52,36 +49,12 @@ public class GatewayRSocketAutoConfigurationTests { .hasSingleBean(Registry.class) .hasSingleBean(RegistryRoutes.class) .hasSingleBean(RegistrySocketAcceptorFilter.class) - .hasSingleBean(GatewayRSocket.class) .hasSingleBean(GatewayRSocketServer.class) .hasSingleBean(GatewayRSocketProperties.class) .hasSingleBean(GatewaySocketAcceptor.class) .hasSingleBean(SocketAcceptorPredicateFilter.class) .doesNotHaveBean(SocketAcceptorPredicate.class) - .hasSingleBean(MicrometerResponderRSocketInterceptor.class) ); } - @Test - public void gatewayRSocketWithoutMicrometerConfigured() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - GatewayRSocketAutoConfiguration.class, - CompositeMeterRegistryAutoConfiguration.class, - MetricsAutoConfiguration.class - )) - .withClassLoader(new FilteredClassLoader(MeterRegistry.class)) - .run(context -> assertThat(context) - .hasSingleBean(Registry.class) - .hasSingleBean(RegistryRoutes.class) - .hasSingleBean(RegistrySocketAcceptorFilter.class) - .hasSingleBean(GatewayRSocket.class) - .hasSingleBean(GatewayRSocketServer.class) - .hasSingleBean(GatewayRSocketProperties.class) - .hasSingleBean(GatewaySocketAcceptor.class) - .hasSingleBean(SocketAcceptorPredicateFilter.class) - .doesNotHaveBean(SocketAcceptorPredicate.class) - .doesNotHaveBean(MicrometerResponderRSocketInterceptor.class) - ); - } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index cd552b21..3696315b 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -20,8 +20,11 @@ package org.springframework.cloud.gateway.rsocket.server; import java.time.Duration; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.netty.buffer.Unpooled; import io.rsocket.Payload; import io.rsocket.RSocket; @@ -35,6 +38,7 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import reactor.test.StepVerifier; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; @@ -58,11 +62,13 @@ public class GatewayRSocketTests { private Registry registry; private Payload incomingPayload; + //TODO: add tests for metrics and other request types + @Before public void init() { registry = mock(Registry.class); incomingPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, - Metadata.encodeTags("name:mock")); + Metadata.encodeTags("name:mock", "id:mock1")); RSocket rSocket = mock(RSocket.class); when(registry.getRegistered(anyMap())).thenReturn(rSocket); @@ -152,7 +158,15 @@ public class GatewayRSocketTests { private final MonoProcessor processor = MonoProcessor.create(); public TestGatewayRSocket(Registry registry, Routes routes) { - super(registry, routes); + super(registry, routes, new SimpleMeterRegistry(), new GatewayRSocketProperties(), + getMetadata()); + } + + private static Map getMetadata() { + HashMap map = new HashMap<>(); + map.put("name", "service"); + map.put("id", "service1"); + return map; } @Override diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java index 5d11ed2b..df701c95 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java @@ -21,6 +21,8 @@ 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.RSocket; import org.apache.commons.logging.Log; @@ -29,9 +31,14 @@ import org.junit.Before; import org.junit.Test; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; +import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; + import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * @author Rossen Stoyanchev @@ -40,17 +47,23 @@ public class GatewaySocketAcceptorTests { private static Log logger = LogFactory.getLog(GatewaySocketAcceptorTests.class); - private RSocket proxyRSocket; + private GatewayRSocket.Factory factory; private ConnectionSetupPayload setupPayload; private RSocket sendingSocket; + private MeterRegistry meterRegistry; + private GatewayRSocketProperties properties = new GatewayRSocketProperties(); @Before public void init() { - this.proxyRSocket = mock(RSocket.class); + this.factory = mock(GatewayRSocket.Factory.class); this.setupPayload = mock(ConnectionSetupPayload.class); this.sendingSocket = mock(RSocket.class); + this.meterRegistry = new SimpleMeterRegistry(); + + when(this.factory.create(anyMap())).thenReturn(mock(GatewayRSocket.class)); } + //TODO: test metrics @Test public void multipleFilters() { @@ -58,7 +71,8 @@ public class GatewaySocketAcceptorTests { TestFilter filter2 = new TestFilter(); TestFilter filter3 = new TestFilter(); - RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + RSocket socket = new GatewaySocketAcceptor(this.factory, Arrays.asList(filter1, filter2, filter3), + this.meterRegistry, this.properties) .accept(this.setupPayload, this.sendingSocket) .block(Duration.ZERO); @@ -70,7 +84,8 @@ public class GatewaySocketAcceptorTests { @Test public void zeroFilters() { - RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, Collections.emptyList()) + RSocket socket = new GatewaySocketAcceptor(this.factory, Collections.emptyList(), + this.meterRegistry, this.properties) .accept(this.setupPayload, this.sendingSocket) .block(Duration.ZERO); @@ -85,7 +100,8 @@ public class GatewaySocketAcceptorTests { TestFilter filter3 = new TestFilter(); - RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, Arrays.asList(filter1, filter2, filter3)) + RSocket socket = new GatewaySocketAcceptor(this.factory, Arrays.asList(filter1, filter2, filter3), + this.meterRegistry, this.properties) .accept(this.setupPayload, this.sendingSocket) .block(Duration.ZERO); @@ -100,7 +116,8 @@ public class GatewaySocketAcceptorTests { AsyncFilter filter = new AsyncFilter(); - RSocket socket = new GatewaySocketAcceptor(this.proxyRSocket, singletonList(filter)) + RSocket socket = new GatewaySocketAcceptor(this.factory, singletonList(filter), + this.meterRegistry, this.properties) .accept(this.setupPayload, this.sendingSocket) .block(Duration.ofSeconds(5)); @@ -114,7 +131,8 @@ public class GatewaySocketAcceptorTests { ExceptionFilter filter = new ExceptionFilter(); - new GatewaySocketAcceptor(this.proxyRSocket, singletonList(filter)) + new GatewaySocketAcceptor(this.factory, singletonList(filter), + this.meterRegistry, this.properties) .accept(this.setupPayload, this.sendingSocket) .block(Duration.ofSeconds(5)); diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index df6389d0..7108d777 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -224,7 +224,7 @@ public class PingPongApp { .map(PingPongApp::reply) .map(reply -> { ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, reply); - ByteBuf routingMetadata = Metadata.encodeTags("name:ping"); + ByteBuf routingMetadata = Metadata.encodeTags("name:ping", "id:"); return DefaultPayload.create(data, routingMetadata); }); } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 7fed6c08..e12210c9 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -1,3 +1,9 @@ logging: level: - org.springframework.cloud.gateway.rsocket: DEBUG \ No newline at end of file + org.springframework.cloud.gateway.rsocket: DEBUG + +management: + endpoints: + web: + exposure: + include: '*' \ No newline at end of file From 096962b0dcdff67cbf0b42bed00e704dfdd7c100 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 17:06:31 -0500 Subject: [PATCH 43/67] Adjust line length --- .../gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index d0a95a77..14f7e0bd 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -42,7 +42,8 @@ public class GatewaySocketAcceptor implements SocketAcceptor { private final MeterRegistry meterRegistry; private final GatewayRSocketProperties properties; - public GatewaySocketAcceptor(GatewayRSocket.Factory rSocketFactory, List filters, MeterRegistry meterRegistry, GatewayRSocketProperties properties) { + public GatewaySocketAcceptor(GatewayRSocket.Factory rSocketFactory, List filters, + MeterRegistry meterRegistry, GatewayRSocketProperties properties) { this.rSocketFactory = rSocketFactory; this.filterChain = new SocketAcceptorFilterChain(filters); this.meterRegistry = meterRegistry; From 6ee15ff50f716d469238af85be0765c814340e63 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 17:06:43 -0500 Subject: [PATCH 44/67] Updates comment --- .../cloud/gateway/rsocket/server/GatewayRSocket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index cd103d4f..a8fe9ce3 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -96,7 +96,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket String responderName = this.metadata.get("name"); String responderId = this.metadata.get("id"); String requestorName = exchange.getRoutingMetadata().get("name"); - //String requestorId = TODO: where to get requestorId + //String requestorId = TODO: how to get requestorId (it's part of metadata in Registry) //TODO: deal with missing tags? return Tags.of("requester.name", requestorName, //"requester.id", requestorId, "responder.name", responderName, "responder.id", responderId, From 3c9f69ae94d8ba8625a0b76933d626ad5c218ff8 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 17:07:00 -0500 Subject: [PATCH 45/67] Updates comment --- .../cloud/gateway/rsocket/server/GatewayRSocketServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java index acbbaceb..72979641 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java @@ -127,7 +127,7 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { .toArray(new Tag[]{}); factory - //TODO: add as bean lice serverInterceptors above + //TODO: add as bean like serverInterceptors above .addConnectionPlugin(new MicrometerDuplexConnectionInterceptor(meterRegistry, tags)) .acceptor(this.socketAcceptor) .transport(transport) From eb3b3aae28574697f88a5c2423e5f966ca47ad6f Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 17:37:24 -0500 Subject: [PATCH 46/67] Moves tags to GatewayExchange --- .../rsocket/server/GatewayExchange.java | 27 ++++++- .../rsocket/server/GatewayRSocket.java | 79 +++++++++++++------ 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index 1f98254e..b914a792 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.rsocket.server; 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; @@ -32,10 +33,26 @@ public class GatewayExchange extends AbstractRSocketExchange { private static final Log log = LogFactory.getLog(GatewayExchange.class); public static final String ROUTE_ATTR = "__route_attr_"; - enum Type { FIRE_AND_FORGET, REQUEST_CHANNEL, REQUEST_RESPONSE, REQUEST_STREAM } + enum Type { + FIRE_AND_FORGET("request.fnf"), + REQUEST_CHANNEL("request.channel"), + REQUEST_RESPONSE("request.response"), + REQUEST_STREAM("request.stream"); + + private String key; + + Type(String key) { + this.key = key; + } + + String getKey() { + return this.key; + } + } private final Type type; private final Map routingMetadata; + private Tags tags = Tags.empty(); public static GatewayExchange fromPayload(Type type, Payload payload) { return new GatewayExchange(type, getRoutingMetadata(payload)); @@ -72,4 +89,12 @@ public class GatewayExchange extends AbstractRSocketExchange { public Map getRoutingMetadata() { return routingMetadata; } + + public Tags getTags() { + return this.tags; + } + + public void setTags(Tags tags) { + this.tags = tags; + } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index a8fe9ce3..12848731 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -38,6 +38,7 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Routes; +import org.springframework.util.StringUtils; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.FIRE_AND_FORGET; @@ -83,12 +84,18 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket @Override public Mono fireAndForget(Payload payload) { - GatewayExchange exchange = GatewayExchange.fromPayload(FIRE_AND_FORGET, payload); - Tags tags = getTags(exchange); + GatewayExchange exchange = createExchange(FIRE_AND_FORGET, payload); return findRSocketOrCreatePending(exchange) .flatMap(rSocket -> rSocket.fireAndForget(payload)) - .doOnError(t -> count("forward.request.fnf.error", tags)) - .doFinally(s -> count("forward.request.fnf", tags)); + .doOnError(t -> count(exchange, "error")) + .doFinally(s -> count(exchange, "")); + } + + private GatewayExchange createExchange(GatewayExchange.Type type, Payload payload) { + GatewayExchange exchange = GatewayExchange.fromPayload(type, payload); + Tags tags = getTags(exchange); + exchange.setTags(tags); + return exchange; } private Tags getTags(GatewayExchange exchange) { @@ -105,16 +112,15 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket @Override public Flux requestChannel(Payload payload, Publisher payloads) { - GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_CHANNEL, payload); - Tags tags = getTags(exchange); - Tags responderTags = tags.and("source", "responder"); + GatewayExchange exchange = createExchange(REQUEST_CHANNEL, payload); + Tags responderTags = Tags.of("source", "responder"); return findRSocketOrCreatePending(exchange) .flatMapMany(rSocket -> { - Tags requesterTags = tags.and("source", "requester"); + Tags requesterTags = Tags.of("source", "requester"); Flux flux = Flux.from(payloads) - .doOnNext(s -> count("forward.request.channel.payload", requesterTags)) - .doOnError(t -> count("forward.request.channel.error", requesterTags)) - .doFinally(s -> count("forward.request.channel", requesterTags)); + .doOnNext(s -> count(exchange, "payload", requesterTags)) + .doOnError(t -> count(exchange, "error", requesterTags)) + .doFinally(s -> count(exchange, requesterTags)); if (rSocket instanceof ResponderRSocket) { ResponderRSocket socket = (ResponderRSocket) rSocket; @@ -123,38 +129,59 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket } return rSocket.requestChannel(flux); }) - .doOnNext(s -> count("forward.request.channel.payload", responderTags)) - .doOnError(t -> count("forward.request.channel.error", responderTags)) - .doFinally(s -> count("forward.request.channel", responderTags)); + .doOnNext(s -> count(exchange, "payload", responderTags)) + .doOnError(t -> count(exchange, "error", responderTags)) + .doFinally(s -> count(exchange, responderTags)); } - private void count(String name, Tags responderTags) { - this.meterRegistry.counter(name, responderTags).increment(); + private void count(GatewayExchange exchange, String suffix) { + count(exchange, suffix, Tags.empty()); + } + + private void count(GatewayExchange exchange, Tags additionalTags) { + count(exchange, null, additionalTags); + } + + private 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(); + } + + private String getMetricName(GatewayExchange exchange) { + return getMetricName(exchange, null); + } + + private 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(); } @Override public Mono requestResponse(Payload payload) { AtomicReference timer = new AtomicReference<>(); - GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_RESPONSE, payload); - Tags tags = getTags(exchange); + GatewayExchange exchange = createExchange(REQUEST_RESPONSE, payload); return findRSocketOrCreatePending(exchange) .flatMap(rSocket -> rSocket.requestResponse(payload)) .doOnSubscribe(s -> timer.set(Timer.start(meterRegistry))) - .doOnError(t -> count("forward.request.response.error", tags)) - .doFinally(s -> timer.get().stop(meterRegistry.timer("forward.request.response", tags))); + .doOnError(t -> count(exchange, "error")) + .doFinally(s -> timer.get().stop(meterRegistry.timer(getMetricName(exchange), exchange.getTags()))); } @Override public Flux requestStream(Payload payload) { - GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); - Tags tags = getTags(exchange); + GatewayExchange exchange = createExchange(REQUEST_STREAM, payload); return findRSocketOrCreatePending(exchange) .flatMapMany(rSocket -> rSocket.requestStream(payload)) // S N E F - //TODO: move tagnames to enum - .doOnNext(s -> count("forward.request.stream.payload", tags)) - .doOnError(t -> count("forward.request.stream.error", tags)) - .doFinally(s -> count("forward.request.stream", tags)); + .doOnNext(s -> count(exchange, "payload")) + .doOnError(t -> count(exchange, "error")) + .doFinally(s -> count(exchange, Tags.empty())); } /** From c16a71a98c2bd0b2388f3a19716e738bf3fe961a Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 18:05:17 -0500 Subject: [PATCH 47/67] Moves finding Route outside of PendingRequestRSocket --- .../rsocket/server/GatewayRSocket.java | 34 +++++++++++- .../rsocket/server/PendingRequestRSocket.java | 55 ++++--------------- .../rsocket/server/GatewayRSocketTests.java | 5 +- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 12848731..8d7cb420 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.rsocket.server; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.logging.Level; import io.micrometer.core.instrument.MeterRegistry; @@ -37,7 +38,9 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.registry.Registry; +import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.util.StringUtils; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; @@ -207,7 +210,36 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket } /* for testing */ PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { - return new PendingRequestRSocket(routes, exchange); + Function> routeFinder = registeredEvent -> + getRouteMono(registeredEvent, exchange); + return new PendingRequestRSocket(routeFinder); + } + + protected Mono getRouteMono(Registry.RegisteredEvent registeredEvent, GatewayExchange exchange) { + return findRoute(exchange) + .log(PendingRequestRSocket.class.getName()+".find route pending", Level.FINE) + // can this be replaced with filter? + .flatMap(route -> { + return matchRoute(route, registeredEvent.getRoutingMetadata()); + }); + } + + private Mono findRoute(GatewayExchange exchange) { + Mono routeMono; + /*if (this.route != null) { //TODO: cache Route? + routeMono = Mono.just(route); + } else {*/ + routeMono = this.routes.findRoute(exchange); + //} + return routeMono; + } + + private Mono matchRoute(Route route, Map annoucementMetadata) { + Map targetMetadata = route.getTargetMetadata(); + if (Metadata.matches(targetMetadata, annoucementMetadata)) { + return Mono.just(route); + } + return Mono.empty(); } /** diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 88049356..452a3f5b 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -17,8 +17,8 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; import java.util.logging.Level; import io.rsocket.AbstractRSocket; @@ -37,8 +37,6 @@ import reactor.util.function.Tuple2; import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; import org.springframework.cloud.gateway.rsocket.registry.Registry.RegisteredEvent; import org.springframework.cloud.gateway.rsocket.route.Route; -import org.springframework.cloud.gateway.rsocket.route.Routes; -import org.springframework.cloud.gateway.rsocket.support.Metadata; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_STREAM; @@ -48,18 +46,18 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); - private final Routes routes; - private final GatewayExchange pendingExchange; + private final Function> routeFinder; private final MonoProcessor rSocketProcessor; private Disposable subscriptionDisposable; + private Route route; - public PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange) { - this(routes, pendingExchange, MonoProcessor.create()); + public PendingRequestRSocket(Function> routeFinder) { + this(routeFinder, MonoProcessor.create()); } - /* for testing */ PendingRequestRSocket(Routes routes, GatewayExchange pendingExchange, MonoProcessor rSocketProcessor) { - this.routes = routes; - this.pendingExchange = pendingExchange; + /* for testing */ PendingRequestRSocket(Function> routeFinder, + MonoProcessor rSocketProcessor) { + this.routeFinder = routeFinder; this.rSocketProcessor = rSocketProcessor; } @@ -72,19 +70,9 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR */ @Override public void accept(RegisteredEvent registeredEvent) { - findRoute() - .log(PendingRequestRSocket.class.getName()+".find route pending", Level.FINE) - // can this be replaced with filter? - .flatMap(route -> { - if (!pendingExchange.getAttributes().containsKey(ROUTE_ATTR)) { - if (log.isDebugEnabled()) { - log.debug("route not in exchange, adding."); - } - pendingExchange.getAttributes().put(ROUTE_ATTR, route); - } - return matchRoute(route, registeredEvent.getRoutingMetadata()); - }) + this.routeFinder.apply(registeredEvent) .subscribe(route -> { + this.route = route; this.rSocketProcessor.onNext(registeredEvent.getRSocket()); this.rSocketProcessor.onComplete(); if (this.subscriptionDisposable != null) { @@ -93,25 +81,6 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR }); } - private Mono findRoute() { - Mono routeMono; - if (pendingExchange.getAttributes().containsKey(ROUTE_ATTR)) { - Route r = pendingExchange.getRequiredAttribute(ROUTE_ATTR); - routeMono = Mono.just(r); - } else { - routeMono = this.routes.findRoute(pendingExchange); - } - return routeMono; - } - - private Mono matchRoute(Route route, Map annoucementMetadata) { - Map targetMetadata = route.getTargetMetadata(); - if (Metadata.matches(targetMetadata, annoucementMetadata)) { - return Mono.just(route); - } - return Mono.empty(); - } - @Override public Mono fireAndForget(Payload payload) { return processor("pending-request-faf", payload) @@ -155,9 +124,9 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR return rSocketProcessor .log(PendingRequestRSocket.class.getName()+"."+logCategory, Level.FINE) .flatMap(rSocket -> { - Route route = pendingExchange.getAttribute(ROUTE_ATTR); GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); - exchange.getAttributes().putAll(pendingExchange.getAttributes()); + exchange.getAttributes().put(ROUTE_ATTR, route); + //exchange.getAttributes().putAll(pendingExchange.getAttributes()); return Mono.just(rSocket).zipWith(executeFilterChain(route.getFilters(), exchange)); }); diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 3696315b..2a03de11 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.netty.buffer.Unpooled; @@ -171,7 +172,9 @@ public class GatewayRSocketTests { @Override PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { - return new PendingRequestRSocket(getRoutes(), exchange, processor); + Function> routeFinder = registeredEvent -> + getRouteMono(registeredEvent, exchange); + return new PendingRequestRSocket(routeFinder, processor); } public MonoProcessor getProcessor() { From 084ca3d1b373584881350243b67bff63a262e744 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 18:53:53 -0500 Subject: [PATCH 48/67] Adds metadata callback for pending request. --- .../cloud/gateway/rsocket/server/GatewayRSocket.java | 10 ++++++---- .../gateway/rsocket/server/PendingRequestRSocket.java | 10 ++++++++-- .../gateway/rsocket/server/GatewayRSocketTests.java | 6 +++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 8d7cb420..b88e9e8c 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -106,9 +106,8 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket String responderName = this.metadata.get("name"); String responderId = this.metadata.get("id"); String requestorName = exchange.getRoutingMetadata().get("name"); - //String requestorId = TODO: how to get requestorId (it's part of metadata in Registry) - //TODO: deal with missing tags? - return Tags.of("requester.name", requestorName, //"requester.id", requestorId, + //requestor.id happens in a callback, later + return Tags.of("requester.name", requestorName, "responder.name", responderName, "responder.id", responderId, "gateway.id", this.properties.getId()); } @@ -212,7 +211,10 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket /* for testing */ PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { Function> routeFinder = registeredEvent -> getRouteMono(registeredEvent, exchange); - return new PendingRequestRSocket(routeFinder); + return new PendingRequestRSocket(routeFinder, map -> { + Tags tags = exchange.getTags().and("requester.id", map.get("id")); + exchange.setTags(tags); + }); } protected Mono getRouteMono(Registry.RegisteredEvent registeredEvent, GatewayExchange exchange) { diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index 452a3f5b..fb8fbacd 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -17,6 +17,7 @@ package org.springframework.cloud.gateway.rsocket.server; +import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; @@ -47,17 +48,21 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); private final Function> routeFinder; + private final Consumer> metadataCallback; private final MonoProcessor rSocketProcessor; private Disposable subscriptionDisposable; private Route route; - public PendingRequestRSocket(Function> routeFinder) { - this(routeFinder, MonoProcessor.create()); + public PendingRequestRSocket(Function> routeFinder, + Consumer> metadataCallback) { + this(routeFinder, metadataCallback, MonoProcessor.create()); } /* for testing */ PendingRequestRSocket(Function> routeFinder, + Consumer> metadataCallback, MonoProcessor rSocketProcessor) { this.routeFinder = routeFinder; + this.metadataCallback = metadataCallback; this.rSocketProcessor = rSocketProcessor; } @@ -73,6 +78,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR 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) { diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 2a03de11..9f708033 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.netty.buffer.Unpooled; import io.rsocket.Payload; @@ -174,7 +175,10 @@ public class GatewayRSocketTests { PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { Function> routeFinder = registeredEvent -> getRouteMono(registeredEvent, exchange); - return new PendingRequestRSocket(routeFinder, processor); + return new PendingRequestRSocket(routeFinder, map -> { + Tags tags = exchange.getTags().and("requester.id", map.get("id")); + exchange.setTags(tags); + }, processor); } public MonoProcessor getProcessor() { From aca40d601aecb0a264fe8a23f60666e276d5e760 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 19:07:05 -0500 Subject: [PATCH 49/67] Changes Registry to return LoadBalancedRSocket. Changes LoadBalancedRSocket to just take a list of EnrichedRSocket. Adds EnrichedRSocket which is delegate plus metadata. --- .../rsocket/registry/LoadBalancedRSocket.java | 85 ++++++++----------- .../gateway/rsocket/registry/Registry.java | 6 +- .../rsocket/server/GatewayRSocket.java | 27 ++++-- .../rsocket/server/GatewayRSocketTests.java | 2 +- 4 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java index a8a57365..c2941fc5 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java @@ -17,15 +17,6 @@ package org.springframework.cloud.gateway.rsocket.registry; -import io.rsocket.AbstractRSocket; -import io.rsocket.Payload; -import io.rsocket.RSocket; -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 java.util.List; import java.util.Map; import java.util.Random; @@ -33,93 +24,87 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -public class LoadBalancedRSocket extends AbstractRSocket { +import io.rsocket.RSocket; +import io.rsocket.util.RSocketProxy; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import reactor.core.publisher.Mono; + +public class LoadBalancedRSocket { private static final Log log = LogFactory.getLog(LoadBalancedRSocket.class); - private final List delegates = new CopyOnWriteArrayList<>(); - - private final Map properties; + private final List delegates = new CopyOnWriteArrayList<>(); + private final String serviceName; private final LoadBalancer loadBalancer; - public LoadBalancedRSocket(Map properties) { - this(properties, new RoundRobinLoadBalancer(properties)); + public LoadBalancedRSocket(String serviceName) { + this(serviceName, new RoundRobinLoadBalancer(serviceName)); } - public LoadBalancedRSocket(Map properties, LoadBalancer loadBalancer) { - this.properties = properties; + public LoadBalancedRSocket(String serviceName, LoadBalancer loadBalancer) { + this.serviceName = serviceName; this.loadBalancer = loadBalancer; } - public Mono choose() { + public Mono choose() { return this.loadBalancer.apply(this.delegates); } - public void addRSocket(RSocket rsocket) { - this.delegates.add(rsocket); + public void addRSocket(RSocket rsocket, Map metadata) { + this.delegates.add(new EnrichedRSocket(rsocket, metadata)); } - public List getDelegates() { + public List getDelegates() { return this.delegates; } - @Override - public Mono fireAndForget(Payload payload) { - return choose().flatMap(rSocket -> rSocket.fireAndForget(payload)); - } + public static class EnrichedRSocket extends RSocketProxy { - @Override - public Mono requestResponse(Payload payload) { - return choose().flatMap(rSocket -> rSocket.requestResponse(payload)); - } + private final Map metadata; - @Override - public Flux requestStream(Payload payload) { - return choose().flatMapMany(rSocket -> rSocket.requestStream(payload)); - } + public EnrichedRSocket(RSocket source, Map metadata) { + super(source); + this.metadata = metadata; + } - @Override - public Flux requestChannel(Publisher payloads) { - return choose().flatMapMany(rSocket -> rSocket.requestChannel(payloads)); - } - - @Override - public Mono metadataPush(Payload payload) { - return choose().flatMap(rSocket -> rSocket.metadataPush(payload)); + public Map getMetadata() { + return this.metadata; + } } //TODO: Flux as input? //TODO: reuse commons load balancer? - public interface LoadBalancer extends Function, Mono> { + public interface LoadBalancer extends Function, Mono> { } public static class RoundRobinLoadBalancer implements LoadBalancer { private final AtomicInteger position; - private final Map properties; + private final String serviceName; - public RoundRobinLoadBalancer(Map properties) { - this(properties, new Random().nextInt(1000)); + public RoundRobinLoadBalancer(String serviceName) { + this(serviceName, new Random().nextInt(1000)); } - public RoundRobinLoadBalancer(Map properties, int seedPosition) { - this.properties = properties; + public RoundRobinLoadBalancer(String serviceName, int seedPosition) { + this.serviceName = serviceName; this.position = new AtomicInteger(seedPosition); } @Override - public Mono apply(List rSockets) { + public Mono apply(List rSockets) { if (rSockets.isEmpty()) { if (log.isWarnEnabled()) { - log.warn("No servers available for: " + this.properties); + log.warn("No servers available for: " + this.serviceName); } return Mono.empty(); } // TODO: enforce order? int pos = Math.abs(this.position.incrementAndGet()); - RSocket rSocket = rSockets.get(pos % rSockets.size()); + EnrichedRSocket rSocket = rSockets.get(pos % rSockets.size()); return Mono.just(rSocket); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 81be9c34..ef9abb09 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -72,12 +72,12 @@ public class Registry { Assert.notNull(rsocket, "RSocket may not be null"); log.debug("Registered RSocket: " + properties); LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> - new LoadBalancedRSocket(properties)); - composite.addRSocket(rsocket); + new LoadBalancedRSocket(properties.get("name"))); + composite.addRSocket(rsocket, properties); registeredEventsSink.next(new RegisteredEvent(properties, rsocket)); } - public RSocket getRegistered(Map properties) { + public LoadBalancedRSocket getRegistered(Map properties) { if (CollectionUtils.isEmpty(properties)) { return null; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index b88e9e8c..db33d67a 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -37,6 +37,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; +import org.springframework.cloud.gateway.rsocket.registry.LoadBalancedRSocket; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; @@ -257,19 +258,31 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket // put route in exchange for later use exchange.getAttributes().put(ROUTE_ATTR, route); return executeFilterChain(route.getFilters(), exchange) - .map(success -> { - RSocket rsocket = registry.getRegistered(exchange.getRoutingMetadata()); + .flatMap(success -> { + LoadBalancedRSocket loadBalancedRSocket = registry + .getRegistered(exchange.getRoutingMetadata()); - if (rsocket == null && log.isDebugEnabled()) { - log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); - } - return rsocket; - }); + return loadBalancedRSocket.choose(); + }).map(enrichedRSocket -> { + Map metadata = enrichedRSocket + .getMetadata(); + Tags tags = exchange.getTags().and("requester.id", metadata.get("id")); + exchange.setTags(tags); + return enrichedRSocket; + }).cast(RSocket.class) + .switchIfEmpty(doOnEmpty(exchange)); }); // TODO: deal with connecting to cluster? } + private Mono doOnEmpty(GatewayExchange exchange) { + if (log.isDebugEnabled()) { + log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); + } + return Mono.empty(); + } + public static class Factory { private final Registry registry; private final Routes routes; diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 9f708033..687cef04 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -73,7 +73,7 @@ public class GatewayRSocketTests { Metadata.encodeTags("name:mock", "id:mock1")); RSocket rSocket = mock(RSocket.class); - when(registry.getRegistered(anyMap())).thenReturn(rSocket); + //FIXME: when(registry.getRegistered(anyMap())).thenReturn(rSocket); when(rSocket.requestResponse(any(Payload.class))) .thenReturn(Mono.just(DefaultPayload.create("response"))); From 43bc2fd7a7b900101884537c2ebad370dbf2f55e Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 20:51:05 -0500 Subject: [PATCH 50/67] Fixes GatewayRSocketTests --- .../rsocket/server/GatewayRSocketTests.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 687cef04..394f9475 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -41,6 +41,8 @@ import reactor.core.publisher.MonoProcessor; import reactor.test.StepVerifier; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; +import org.springframework.cloud.gateway.rsocket.registry.LoadBalancedRSocket; +import org.springframework.cloud.gateway.rsocket.registry.LoadBalancedRSocket.EnrichedRSocket; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; @@ -73,7 +75,12 @@ public class GatewayRSocketTests { Metadata.encodeTags("name:mock", "id:mock1")); RSocket rSocket = mock(RSocket.class); - //FIXME: when(registry.getRegistered(anyMap())).thenReturn(rSocket); + LoadBalancedRSocket loadBalancedRSocket = mock(LoadBalancedRSocket.class); + when(registry.getRegistered(anyMap())).thenReturn(loadBalancedRSocket); + + Mono mono = Mono + .just(new EnrichedRSocket(rSocket, getMetadata())); + when(loadBalancedRSocket.choose()).thenReturn(mono); when(rSocket.requestResponse(any(Payload.class))) .thenReturn(Mono.just(DefaultPayload.create("response"))); @@ -164,13 +171,6 @@ public class GatewayRSocketTests { getMetadata()); } - private static Map getMetadata() { - HashMap map = new HashMap<>(); - map.put("name", "service"); - map.put("id", "service1"); - return map; - } - @Override PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { Function> routeFinder = registeredEvent -> @@ -186,6 +186,13 @@ public class GatewayRSocketTests { } } + private static Map getMetadata() { + HashMap map = new HashMap<>(); + map.put("name", "service"); + map.put("id", "service1"); + return map; + } + private static class TestRoutes implements Routes { private final Route route; private List filters; From 8fcdb32b478ab1040fd533ad24fe0a7fe9942493 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 20:57:12 -0500 Subject: [PATCH 51/67] changes groupId to drop rsocket --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c8b20088..da4f74db 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 2.1.2.RELEASE - org.springframework.cloud.rsocket + org.springframework.cloud spring-cloud-gateway-rsocket 0.0.1-SNAPSHOT spring-cloud-gateway-rsocket From 2784169725cbb95626d3412c4cbe979649e5282c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 15 Feb 2019 21:37:57 -0500 Subject: [PATCH 52/67] Moved rsocket deps to dep mgmt --- pom.xml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index da4f74db..4c1c544c 100644 --- a/pom.xml +++ b/pom.xml @@ -34,7 +34,7 @@ 1.8 0.11.16 - Californium-SR4 + Californium-SR5 @@ -42,10 +42,30 @@ io.projectreactor reactor-bom - Californium-SR5 + ${reactor.version} pom import + + org.springframework.boot + spring-boot-configuration-processor + true + + + io.rsocket + rsocket-core + ${rsocket.version} + + + io.rsocket + rsocket-micrometer + ${rsocket.version} + + + io.rsocket + rsocket-transport-netty + ${rsocket.version} + @@ -63,17 +83,14 @@ io.rsocket rsocket-core - ${rsocket.version} io.rsocket rsocket-micrometer - ${rsocket.version} io.rsocket rsocket-transport-netty - ${rsocket.version} io.micrometer From 36a6e11c3bba7987d745d04d60708bc980ad7271 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 19 Feb 2019 13:37:12 -0500 Subject: [PATCH 53/67] Removes erroneous entry from dependency management. --- pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pom.xml b/pom.xml index 4c1c544c..13cf8ea6 100644 --- a/pom.xml +++ b/pom.xml @@ -46,11 +46,6 @@ pom import - - org.springframework.boot - spring-boot-configuration-processor - true - io.rsocket rsocket-core From 1c61d73eae56ec7724a59fdc7afd5ff59b596524 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 19 Feb 2019 13:37:31 -0500 Subject: [PATCH 54/67] Adds todo for errorConsumer --- .../cloud/gateway/rsocket/server/GatewayRSocketServer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java index 72979641..aa0eec32 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java @@ -129,6 +129,7 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { factory //TODO: add as bean like serverInterceptors above .addConnectionPlugin(new MicrometerDuplexConnectionInterceptor(meterRegistry, tags)) + //.errorConsumer() TODO: add errorConsumer .acceptor(this.socketAcceptor) .transport(transport) .start() From fae3d466adb6db42010a17e0005bcc849996ac73 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 19 Feb 2019 13:39:21 -0500 Subject: [PATCH 55/67] Adds support for Registry.deregister(). Also tests Metadata.matches() See gh-20 --- .../rsocket/registry/LoadBalancedRSocket.java | 16 +++++ .../gateway/rsocket/registry/Registry.java | 16 ++++- .../gateway/rsocket/support/Metadata.java | 40 ++++++------ .../rsocket/support/MetadataTests.java | 61 +++++++++++++++++++ 4 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java index c2941fc5..359877ae 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java @@ -30,6 +30,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.support.Metadata; + public class LoadBalancedRSocket { private static final Log log = LogFactory.getLog(LoadBalancedRSocket.class); @@ -56,6 +58,16 @@ public class LoadBalancedRSocket { this.delegates.add(new EnrichedRSocket(rsocket, metadata)); } + public void remove(Map metadata) { + //TODO: move delegates to a map for easy removal + this.delegates.stream() + .filter(enriched -> Metadata.matches(metadata, enriched.getMetadata())) + .findFirst() + .ifPresent(this.delegates::remove); + } + + + public List getDelegates() { return this.delegates; } @@ -72,6 +84,10 @@ public class LoadBalancedRSocket { public Map getMetadata() { return this.metadata; } + + public RSocket getSource() { + return this.source; + } } //TODO: Flux as input? diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index ef9abb09..831d5b0a 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -70,13 +70,27 @@ public class Registry { public void register(Map properties, RSocket rsocket) { Assert.notEmpty(properties, "properties may not be empty"); Assert.notNull(rsocket, "RSocket may not be null"); - log.debug("Registered RSocket: " + properties); + if (log.isDebugEnabled()) { + log.debug("Registering RSocket: " + properties); + } LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> new LoadBalancedRSocket(properties.get("name"))); composite.addRSocket(rsocket, properties); registeredEventsSink.next(new RegisteredEvent(properties, rsocket)); } + public void deregister(Map metadata) { + Assert.notEmpty(metadata, "metadata may not be empty"); + if (log.isDebugEnabled()) { + log.debug("Deregistering RSocket: " + metadata); + } + String key = computeKey(metadata); + LoadBalancedRSocket loadBalanced = this.rsockets.get(key); + if (loadBalanced != null) { + loadBalanced.remove(metadata); + } + } + public LoadBalancedRSocket getRegistered(Map properties) { if (CollectionUtils.isEmpty(properties)) { return null; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java index 0c8d3fdb..db4ed647 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java @@ -93,29 +93,29 @@ public abstract class Metadata { } /** - * All keys in targetMetadata must have a "match" in announcementMetadata - * @param targetMetadata - * @param annoucementMetadata - * @return + * Matches leftMetadata to rightMetadata. rightMetadata must contain all key with + * equal values (ignoring case) of leftMetadata. + * @param leftMetadata first metadata to compare. + * @param rightMetadata second metadata to compare. + * @return true if all keys and values (case-insensitive) from leftMetadata are in rightMetadata. */ //TODO: find a way to make this more performant - public static boolean matches(Map targetMetadata, Map annoucementMetadata) { - if (targetMetadata != null && annoucementMetadata != null - // if announced key contains all target keys - && annoucementMetadata.keySet().containsAll(targetMetadata.keySet())) { - for (Map.Entry entry : targetMetadata.entrySet()) { - String targetKey = entry.getKey(); - String targetValue = entry.getValue(); - String announcementValue = annoucementMetadata.get(targetKey); - - //TODO: regex and possibly SpEL - if (!targetValue.equals(announcementValue)) { - return false; - } - } - return true; + public static boolean matches(Map leftMetadata, Map rightMetadata) { + if (leftMetadata == null || rightMetadata == null) { + return false; } - return false; + + for (Map.Entry entry : leftMetadata.entrySet()) { + String enrichedValue = rightMetadata.get(entry.getKey()); + if (enrichedValue == null || + //TODO: regex and possibly SpEL? + !enrichedValue.equalsIgnoreCase(entry.getValue())) { + return false; + } + } + + // all entries in metadata exist and match corresponding entries in enriched.metadata + return true; } /** diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java new file mode 100644 index 00000000..d58e17bb --- /dev/null +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java @@ -0,0 +1,61 @@ +/* + * 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. + * + */ + +package org.springframework.cloud.gateway.rsocket.support; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.IntStream; + +import org.junit.Test; + +import org.springframework.util.Assert; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.cloud.gateway.rsocket.support.Metadata.matches; + +public class MetadataTests { + + @Test + public void nullMetadataDoesNotMatch() { + assertThat(matches(null, new HashMap<>())).isFalse(); + + assertThat(matches(new HashMap<>(), null)).isFalse(); + } + + @Test + public void metadataSubsetMatches() { + assertThat(matches(metadata(2), metadata(3))).isTrue(); + } + + @Test + public void metadataEqualSetMatches() { + assertThat(matches(metadata(3), metadata(3))).isTrue(); + } + + @Test + public void metadataSuperSetDoesNotMatch() { + assertThat(matches(metadata(3), metadata(2))).isFalse(); + } + + private Map metadata(int size) { + Assert.isTrue(size > 0, "size must be > 0"); + HashMap metadata = new HashMap<>(); + IntStream.rangeClosed(1, size).forEach(i -> metadata.put("key"+i, "val"+i)); + return metadata; + } +} From 8ba1f97b969e0475be81a7fee6375e0811c88b82 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 19 Feb 2019 13:40:07 -0500 Subject: [PATCH 56/67] Removes entry from Registry when rsocket closed or there is an error. fixes gh-20 --- .../gateway/rsocket/server/GatewayRSocket.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index db33d67a..0c41e882 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -42,6 +42,7 @@ import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; @@ -75,6 +76,18 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket this.meterRegistry = meterRegistry; this.properties = properties; this.metadata = metadata; + this.onClose() + .doOnSuccess(v -> registry.deregister(metadata)) + //.doOnNext(v -> log.error("OnClose doOnNext")) + .doOnError(t -> { + if (log.isErrorEnabled()) { + log.error("Error received, deregistering " + metadata, t); + } + registry.deregister(metadata); + }) + //.doOnTerminate(() -> log.error("OnClose doOnTerminate")) + //.doFinally(st -> log.error("OnClose doFinally")) + .subscribe(); } @@ -107,6 +120,9 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket String responderName = this.metadata.get("name"); String responderId = this.metadata.get("id"); String requestorName = exchange.getRoutingMetadata().get("name"); + Assert.hasText(responderName, "responderName must not be empty"); + Assert.hasText(responderId, "responderId must not be empty"); + Assert.hasText(requestorName, "requestorName must not be empty"); //requestor.id happens in a callback, later return Tags.of("requester.name", requestorName, "responder.name", responderName, "responder.id", responderId, From 7b1e9c7a4bf645d594300eaec64077f01c93843f Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 19 Feb 2019 17:17:13 -0500 Subject: [PATCH 57/67] Changes reactor log() calls to Level.FINEST This equates to a TRACE log level. --- .../cloud/gateway/rsocket/server/GatewayRSocket.java | 6 +++--- .../cloud/gateway/rsocket/server/PendingRequestRSocket.java | 2 +- .../rsocket/socketacceptor/GatewaySocketAcceptor.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 0c41e882..4ac9e3c6 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -144,7 +144,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket if (rSocket instanceof ResponderRSocket) { ResponderRSocket socket = (ResponderRSocket) rSocket; return socket.requestChannel(payload, flux) - .log(GatewayRSocket.class.getName()+".request-channel", Level.FINE); + .log(GatewayRSocket.class.getName()+".request-channel", Level.FINEST); } return rSocket.requestChannel(flux); }) @@ -236,7 +236,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket protected Mono getRouteMono(Registry.RegisteredEvent registeredEvent, GatewayExchange exchange) { return findRoute(exchange) - .log(PendingRequestRSocket.class.getName()+".find route pending", Level.FINE) + .log(PendingRequestRSocket.class.getName()+".find route pending", Level.FINEST) // can this be replaced with filter? .flatMap(route -> { return matchRoute(route, registeredEvent.getRoutingMetadata()); @@ -269,7 +269,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket */ private Mono findRSocket(GatewayExchange exchange) { return this.routes.findRoute(exchange) - .log(GatewayRSocket.class.getName()+".find route", Level.FINE) + .log(GatewayRSocket.class.getName()+".find route", Level.FINEST) .flatMap(route -> { // put route in exchange for later use exchange.getAttributes().put(ROUTE_ATTR, route); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index fb8fbacd..e45ae1d8 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -128,7 +128,7 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR */ protected Mono> processor(String logCategory, Payload payload) { return rSocketProcessor - .log(PendingRequestRSocket.class.getName()+"."+logCategory, Level.FINE) + .log(PendingRequestRSocket.class.getName()+"."+logCategory, Level.FINEST) .flatMap(rSocket -> { GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); exchange.getAttributes().put(ROUTE_ATTR, route); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index 14f7e0bd..67326c6f 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -77,7 +77,7 @@ public class GatewaySocketAcceptor implements SocketAcceptor { //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.FINE) + .log(GatewaySocketAcceptor.class.getName()+".socket acceptor filter chain", Level.FINEST) .map(success -> decorate(this.rSocketFactory.create(exchange.getMetadata()), responderTags)); } From f0a9ce02c8530e9c9ea3d385f5e4e0b076323d5e Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 21 Feb 2019 21:50:33 -0500 Subject: [PATCH 58/67] Moves from Map to Metadata type. Aligns closer to future changes to announcement and routing metadata extensions. --- .../GatewayRSocketAutoConfiguration.java | 4 +- .../rsocket/registry/LoadBalancedRSocket.java | 12 +- .../gateway/rsocket/registry/Registry.java | 55 ++---- .../rsocket/registry/RegistryRoutes.java | 14 +- .../RegistrySocketAcceptorFilter.java | 4 +- .../cloud/gateway/rsocket/route/Route.java | 14 +- .../rsocket/server/GatewayExchange.java | 21 +- .../rsocket/server/GatewayRSocket.java | 19 +- .../rsocket/server/GatewayRSocketServer.java | 18 +- .../rsocket/server/PendingRequestRSocket.java | 8 +- .../socketacceptor/GatewaySocketAcceptor.java | 9 +- .../SocketAcceptorExchange.java | 10 +- .../gateway/rsocket/support/Metadata.java | 180 ++++++++++-------- .../rsocket/server/GatewayRSocketTests.java | 18 +- .../GatewaySocketAcceptorTests.java | 5 +- .../rsocket/support/MetadataTests.java | 27 +++ .../gateway/rsocket/test/PingPongApp.java | 8 +- 17 files changed, 227 insertions(+), 199 deletions(-) diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index d11c547e..39470961 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -95,7 +95,7 @@ public class GatewayRSocketAutoConfiguration { @Bean public GatewayRSocketServer gatewayApp(GatewaySocketAcceptor socketAcceptor, - GatewayRSocketProperties properties) { - return new GatewayRSocketServer(properties, socketAcceptor); + GatewayRSocketProperties properties, MeterRegistry meterRegistry) { + return new GatewayRSocketServer(properties, socketAcceptor, meterRegistry); } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java index 359877ae..42c6d3c5 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java @@ -54,14 +54,14 @@ public class LoadBalancedRSocket { return this.loadBalancer.apply(this.delegates); } - public void addRSocket(RSocket rsocket, Map metadata) { + public void addRSocket(RSocket rsocket, Metadata metadata) { this.delegates.add(new EnrichedRSocket(rsocket, metadata)); } - public void remove(Map metadata) { + public void remove(Metadata metadata) { //TODO: move delegates to a map for easy removal this.delegates.stream() - .filter(enriched -> Metadata.matches(metadata, enriched.getMetadata())) + .filter(enriched -> metadata.matches(enriched.getMetadata())) .findFirst() .ifPresent(this.delegates::remove); } @@ -74,14 +74,14 @@ public class LoadBalancedRSocket { public static class EnrichedRSocket extends RSocketProxy { - private final Map metadata; + private final Metadata metadata; - public EnrichedRSocket(RSocket source, Map metadata) { + public EnrichedRSocket(RSocket source, Metadata metadata) { super(source); this.metadata = metadata; } - public Map getMetadata() { + public Metadata getMetadata() { return this.metadata; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 831d5b0a..9c27ffd5 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -20,7 +20,6 @@ package org.springframework.cloud.gateway.rsocket.registry; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; -import java.util.function.Function; import io.rsocket.RSocket; import org.apache.commons.logging.Log; @@ -29,8 +28,8 @@ import reactor.core.Disposable; import reactor.core.publisher.DirectProcessor; import reactor.core.publisher.FluxSink; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** * The Registry handles all RSocket connections that have been made that have associated @@ -46,56 +45,36 @@ public class Registry { private final DirectProcessor registeredEvents = DirectProcessor.create(); private final FluxSink registeredEventsSink = registeredEvents.sink(FluxSink.OverflowStrategy.DROP); - private final Function, String> keyFunction; public Registry() { - this(tags -> { - if (CollectionUtils.isEmpty(tags)) { - return null; // throw error? - } - //TODO: key generation - return tags.get("name"); - }); - } - - public Registry(Function, String> keyFunction) { - this.keyFunction = keyFunction; - } - - public String computeKey(Map properties) { - return keyFunction.apply(properties); } //TODO: Mono? - public void register(Map properties, RSocket rsocket) { - Assert.notEmpty(properties, "properties may not be empty"); + public void register(Metadata metadata, RSocket rsocket) { + Assert.notNull(metadata, "metadata may not be null"); Assert.notNull(rsocket, "RSocket may not be null"); if (log.isDebugEnabled()) { - log.debug("Registering RSocket: " + properties); + log.debug("Registering RSocket: " + metadata); } - LoadBalancedRSocket composite = rsockets.computeIfAbsent(computeKey(properties), s -> - new LoadBalancedRSocket(properties.get("name"))); - composite.addRSocket(rsocket, properties); - registeredEventsSink.next(new RegisteredEvent(properties, rsocket)); + LoadBalancedRSocket composite = rsockets.computeIfAbsent(metadata.getName(), s -> + new LoadBalancedRSocket(metadata.getName())); + composite.addRSocket(rsocket, metadata); + registeredEventsSink.next(new RegisteredEvent(metadata, rsocket)); } - public void deregister(Map metadata) { - Assert.notEmpty(metadata, "metadata may not be empty"); + public void deregister(Metadata metadata) { + Assert.notNull(metadata, "metadata may not be null"); if (log.isDebugEnabled()) { log.debug("Deregistering RSocket: " + metadata); } - String key = computeKey(metadata); - LoadBalancedRSocket loadBalanced = this.rsockets.get(key); + LoadBalancedRSocket loadBalanced = this.rsockets.get(metadata.getName()); if (loadBalanced != null) { loadBalanced.remove(metadata); } } - public LoadBalancedRSocket getRegistered(Map properties) { - if (CollectionUtils.isEmpty(properties)) { - return null; - } - return rsockets.get(computeKey(properties)); + public LoadBalancedRSocket getRegistered(Metadata metadata) { + return rsockets.get(metadata.getName()); } public Disposable addListener(Consumer consumer) { @@ -103,17 +82,17 @@ public class Registry { } public static class RegisteredEvent { - private final Map routingMetadata; + private final Metadata routingMetadata; private final RSocket rSocket; - public RegisteredEvent(Map routingMetadata, RSocket rSocket) { - Assert.notEmpty(routingMetadata, "routingMetadata may not be empty"); + public RegisteredEvent(Metadata routingMetadata, RSocket rSocket) { + Assert.notNull(routingMetadata, "routingMetadata may not be null"); Assert.notNull(rSocket, "RSocket may not be null"); this.routingMetadata = routingMetadata; this.rSocket = rSocket; } - public Map getRoutingMetadata() { + public Metadata getRoutingMetadata() { return routingMetadata; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java index 57ceb05c..11120ad6 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java @@ -30,6 +30,7 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; +import org.springframework.cloud.gateway.rsocket.support.Metadata; /** * Creates routes from RegisteredEvents @@ -53,30 +54,29 @@ public class RegistryRoutes implements Routes, Consumer routingMetadata = registeredEvent.getRoutingMetadata(); + Metadata routingMetadata = registeredEvent.getRoutingMetadata(); String id = getId(routingMetadata); routes.computeIfAbsent(id, key -> createRoute(id, routingMetadata)); } - private String getId(Map routingMetadata) { - String id = routingMetadata.get("name"); + private String getId(Metadata routingMetadata) { + String id = routingMetadata.getName(); if (id == null) { id = UUID.randomUUID().toString(); } return id; } - private Route createRoute(String id, Map routingMetadata) { + private Route createRoute(String id, Metadata routingMetadata) { Route route = Route.builder() .id(id) .routingMetadata(routingMetadata) .predicate(exchange -> { // TODO: standard predicates // TODO: allow customized predicates - Map incomingRouting = exchange.getRoutingMetadata(); - boolean matches = incomingRouting.containsKey("name") - && incomingRouting.get("name").equals(routingMetadata.get("name")); + Metadata incomingRouting = exchange.getRoutingMetadata(); + boolean matches = incomingRouting.getName().equalsIgnoreCase(routingMetadata.getName()); return Mono.just(matches); }) // TODO: allow customized filters diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java index c40efc29..50b6ba6a 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java @@ -23,7 +23,7 @@ import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorEx import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilter; import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorFilterChain; import org.springframework.core.Ordered; -import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; /** * Filter that registers the SendingSocket. @@ -37,7 +37,7 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order @Override public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { - if (!CollectionUtils.isEmpty(exchange.getMetadata())) { + if (exchange.getMetadata() != null && StringUtils.hasLength(exchange.getMetadata().getName())) { this.registry.register(exchange.getMetadata(), exchange.getSendingSocket()); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java b/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java index 6febd8bb..f9108d66 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java @@ -22,12 +22,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import org.springframework.cloud.gateway.rsocket.server.GatewayExchange; import org.springframework.cloud.gateway.rsocket.server.GatewayFilter; import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import org.springframework.core.Ordered; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; @@ -40,7 +40,7 @@ public class Route implements Ordered { private final String id; - private final Map targetMetadata; + private final Metadata targetMetadata; private final int order; @@ -52,7 +52,7 @@ public class Route implements Ordered { return new Builder(); } - private Route(String id, Map targetMetadata, int order, AsyncPredicate predicate, List gatewayFilters) { + private Route(String id, Metadata targetMetadata, int order, AsyncPredicate predicate, List gatewayFilters) { this.id = id; this.targetMetadata = targetMetadata; this.order = order; @@ -63,7 +63,7 @@ public class Route implements Ordered { public static class Builder { protected String id; - protected Map routingMetadata; + protected Metadata routingMetadata; protected int order = 0; @@ -91,7 +91,7 @@ public class Route implements Ordered { return this.predicate; } - public Builder routingMetadata(Map routingMetadata) { + public Builder routingMetadata(Metadata routingMetadata) { this.routingMetadata = routingMetadata; return this; } @@ -141,7 +141,7 @@ public class Route implements Ordered { public Route build() { Assert.notNull(this.id, "id can not be null"); - Assert.notEmpty(this.routingMetadata, "targetMetadata can not be null or empty"); + Assert.notNull(this.routingMetadata, "targetMetadata can not be null"); Assert.notNull(this.predicate, "predicate can not be null"); return new Route(this.id, this.routingMetadata, this.order, predicate, this.gatewayFilters); @@ -152,7 +152,7 @@ public class Route implements Ordered { return this.id; } - public Map getTargetMetadata() { + public Metadata getTargetMetadata() { return this.targetMetadata; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index b914a792..a5f09b64 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -17,8 +17,6 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.Map; - import io.micrometer.core.instrument.Tags; import io.rsocket.Payload; import org.apache.commons.logging.Log; @@ -26,7 +24,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; import org.springframework.cloud.gateway.rsocket.support.Metadata; -import org.springframework.util.CollectionUtils; public class GatewayExchange extends AbstractRSocketExchange { @@ -51,33 +48,29 @@ public class GatewayExchange extends AbstractRSocketExchange { } private final Type type; - private final Map routingMetadata; + private final Metadata routingMetadata; private Tags tags = Tags.empty(); public static GatewayExchange fromPayload(Type type, Payload payload) { return new GatewayExchange(type, getRoutingMetadata(payload)); } - private static Map getRoutingMetadata(Payload payload) { + private static Metadata getRoutingMetadata(Payload payload) { if (payload == null || !payload.hasMetadata()) { // and metadata is routing return null; } // TODO: deal with composite metadata - Map properties = Metadata.decodeProperties(payload.sliceMetadata()); - - if (CollectionUtils.isEmpty(properties)) { - return null; - } + Metadata metadata = Metadata.decodeMetadata(payload.sliceMetadata()); if (log.isDebugEnabled()) { - log.debug("found routing metadata " + properties); + log.debug("found routing metadata " + metadata); } - return properties; + return metadata; } - public GatewayExchange(Type type, Map routingMetadata) { + public GatewayExchange(Type type, Metadata routingMetadata) { this.type = type; this.routingMetadata = routingMetadata; } @@ -86,7 +79,7 @@ public class GatewayExchange extends AbstractRSocketExchange { return type; } - public Map getRoutingMetadata() { + public Metadata getRoutingMetadata() { return routingMetadata; } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 4ac9e3c6..08810a44 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.logging.Level; @@ -67,10 +66,10 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private final Routes routes; private final MeterRegistry meterRegistry; private final GatewayRSocketProperties properties; - private final Map metadata; + private final Metadata metadata; GatewayRSocket(Registry registry, Routes routes, MeterRegistry meterRegistry, - GatewayRSocketProperties properties, Map metadata) { + GatewayRSocketProperties properties, Metadata metadata) { this.registry = registry; this.routes = routes; this.meterRegistry = meterRegistry; @@ -117,9 +116,9 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private Tags getTags(GatewayExchange exchange) { //TODO: add tags to exchange - String responderName = this.metadata.get("name"); + String responderName = this.metadata.getName(); String responderId = this.metadata.get("id"); - String requestorName = exchange.getRoutingMetadata().get("name"); + String requestorName = exchange.getRoutingMetadata().getName(); Assert.hasText(responderName, "responderName must not be empty"); Assert.hasText(responderId, "responderId must not be empty"); Assert.hasText(requestorName, "requestorName must not be empty"); @@ -253,9 +252,9 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket return routeMono; } - private Mono matchRoute(Route route, Map annoucementMetadata) { - Map targetMetadata = route.getTargetMetadata(); - if (Metadata.matches(targetMetadata, annoucementMetadata)) { + private Mono matchRoute(Route route, Metadata annoucementMetadata) { + Metadata targetMetadata = route.getTargetMetadata(); + if (targetMetadata.matches(annoucementMetadata)) { return Mono.just(route); } return Mono.empty(); @@ -280,7 +279,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket return loadBalancedRSocket.choose(); }).map(enrichedRSocket -> { - Map metadata = enrichedRSocket + Metadata metadata = enrichedRSocket .getMetadata(); Tags tags = exchange.getTags().and("requester.id", metadata.get("id")); exchange.setTags(tags); @@ -313,7 +312,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket this.properties = properties; } - public GatewayRSocket create(Map metadata) { + public GatewayRSocket create(Metadata metadata) { return new GatewayRSocket(this.registry, this.routes, this.meterRegistry, this.properties, metadata); } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java index aa0eec32..dc4435d7 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java @@ -34,7 +34,6 @@ import io.rsocket.transport.netty.server.TcpServerTransport; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties.Server.TransportType; import org.springframework.context.SmartLifecycle; @@ -51,19 +50,20 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { private final List serverInterceptors; private final AtomicBoolean running = new AtomicBoolean(); private CloseableChannel closeableChannel; - @Autowired - MeterRegistry meterRegistry; + private final MeterRegistry meterRegistry; - public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor) { - this(properties, socketAcceptor, EMPTY_INTERCEPTORS); + public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { + this(properties, socketAcceptor, meterRegistry, EMPTY_INTERCEPTORS); } - public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor, RSocketInterceptor... interceptors) { + public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor, MeterRegistry meterRegistry, RSocketInterceptor... interceptors) { Assert.notNull(properties, "properties may not be null"); Assert.notNull(socketAcceptor, "socketAcceptor may not be null"); + Assert.notNull(meterRegistry, "meterRegistry may not be null"); Assert.notNull(interceptors, "interceptors may not be null"); this.properties = properties; this.socketAcceptor = socketAcceptor; + this.meterRegistry = meterRegistry; this.serverInterceptors = Arrays.asList(interceptors); } @@ -129,7 +129,11 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { factory //TODO: add as bean like serverInterceptors above .addConnectionPlugin(new MicrometerDuplexConnectionInterceptor(meterRegistry, tags)) - //.errorConsumer() TODO: add errorConsumer + .errorConsumer(throwable -> { + if (log.isDebugEnabled()) { + log.debug("Error with connection", throwable); + } + }) //TODO: add configurable errorConsumer .acceptor(this.socketAcceptor) .transport(transport) .start() diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index e45ae1d8..da568449 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -17,7 +17,6 @@ package org.springframework.cloud.gateway.rsocket.server; -import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; @@ -38,6 +37,7 @@ import reactor.util.function.Tuple2; import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter.Success; import org.springframework.cloud.gateway.rsocket.registry.Registry.RegisteredEvent; import org.springframework.cloud.gateway.rsocket.route.Route; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.ROUTE_ATTR; import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_STREAM; @@ -48,18 +48,18 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); private final Function> routeFinder; - private final Consumer> metadataCallback; + private final Consumer metadataCallback; private final MonoProcessor rSocketProcessor; private Disposable subscriptionDisposable; private Route route; public PendingRequestRSocket(Function> routeFinder, - Consumer> metadataCallback) { + Consumer metadataCallback) { this(routeFinder, metadataCallback, MonoProcessor.create()); } /* for testing */ PendingRequestRSocket(Function> routeFinder, - Consumer> metadataCallback, + Consumer metadataCallback, MonoProcessor rSocketProcessor) { this.routeFinder = routeFinder; this.metadataCallback = metadataCallback; diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index 67326c6f..c3a11c13 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -18,7 +18,6 @@ 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; @@ -62,11 +61,11 @@ public class GatewaySocketAcceptor implements SocketAcceptor { Tags metadataTags; SocketAcceptorExchange exchange; if (setup.hasMetadata()) { //TODO: and setup.metadataMimeType() is Announcement metadata or composite - Map properties = Metadata.decodeProperties(setup.sliceMetadata()); - metadataTags = Tags.of("service.name", properties.get("name")) - .and("service.id", properties.get("id")); + Metadata metadata = Metadata.decodeMetadata(setup.sliceMetadata()); + metadataTags = Tags.of("service.name", metadata.getName()) + .and("service.id", metadata.get("id")); // enrich exchange to have metadata - exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags.and(metadataTags)), properties); + exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags.and(metadataTags)), metadata); } else { metadataTags = Tags.empty(); exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags)); diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java index cd40f723..4083323d 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java @@ -18,24 +18,24 @@ package org.springframework.cloud.gateway.rsocket.socketacceptor; import java.util.Collections; -import java.util.Map; import io.rsocket.ConnectionSetupPayload; import io.rsocket.RSocket; import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; +import org.springframework.cloud.gateway.rsocket.support.Metadata; public class SocketAcceptorExchange extends AbstractRSocketExchange { private final ConnectionSetupPayload setup; private final RSocket sendingSocket; - private final Map metadata; + private final Metadata metadata; public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { - this(setup, sendingSocket, Collections.emptyMap()); + this(setup, sendingSocket, new Metadata(null, Collections.emptyMap())); } public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket, - Map metadata) { + Metadata metadata) { this.setup = setup; this.sendingSocket = sendingSocket; this.metadata = metadata; @@ -49,7 +49,7 @@ public class SocketAcceptorExchange extends AbstractRSocketExchange { return sendingSocket; } - public Map getMetadata() { + public Metadata getMetadata() { return metadata; } } diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java index db4ed647..e2ca8358 100644 --- a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java +++ b/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java @@ -18,78 +18,129 @@ package org.springframework.cloud.gateway.rsocket.support; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicInteger; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufUtil; import io.rsocket.util.NumberUtils; +import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -public abstract class Metadata { +public class Metadata { public static final String ROUTING_MIME_TYPE = "message/x.rsocket.routing.v0"; - private Metadata() {} + private final String name; + private final Map properties; - public static ByteBuf encodeProperties(Map properties) { - return encodeProperties(ByteBufAllocator.DEFAULT, properties); + public Metadata(String name, Map properties) { + this.name = name; + this.properties = properties; } - public static ByteBuf encodeProperties(ByteBufAllocator allocator, Map properties) { - Assert.notEmpty(properties, "tags may not be null or empty"); //TODO: is this true? - List pairs = properties.entrySet().stream() - .map(entry -> entry.getValue() + ":" + entry.getValue()) - .collect(Collectors.toList()); - return encodeTags(allocator, pairs.toArray(new String[0])); + public String getName() { + return this.name; } - public static ByteBuf encodeTags(String... tags) { - return encodeTags(ByteBufAllocator.DEFAULT, tags); + public Map getProperties() { + return this.properties; } - public static ByteBuf encodeTags(ByteBufAllocator allocator, String... tags) { - Assert.notEmpty(tags, "tags may not be null or empty"); //TODO: is this true? + public String get(String key) { + return this.properties.get(key); + } + + public String put(String key, String value) { + return this.properties.put(key, value); + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("name", name) + .append("properties", properties) + .toString(); + } + + public static Builder from(String name) { + return new Builder(name); + } + + public static ByteBuf encode(Metadata metadata) { + return encode(ByteBufAllocator.DEFAULT, metadata); + } + + public static ByteBuf encode(ByteBufAllocator allocator, Metadata metadata) { + return encode(allocator, metadata.getName(), metadata.getProperties()); + } + + public static ByteBuf encode(String name, Map properties) { + return encode(ByteBufAllocator.DEFAULT, name, properties); + } + + public static ByteBuf encode(ByteBufAllocator allocator, String name, + Map properties) { + Assert.hasText(name, "name may not be empty"); + Assert.notNull(properties, "properties may not be null"); + Assert.notNull(allocator, "allocator may not be null"); ByteBuf byteBuf = allocator.buffer(); - for (String tag : tags) { - int tagLength = NumberUtils.requireUnsignedByte(ByteBufUtil.utf8Bytes(tag)); - byteBuf.writeByte(tagLength); - ByteBufUtil.reserveAndWriteUtf8(byteBuf, tag, tagLength); - } + encodeString(byteBuf, name); + + properties.entrySet().stream().forEach(entry -> { + encodeString(byteBuf, entry.getKey()); + encodeString(byteBuf, entry.getValue()); + }); return byteBuf; } - public static Map decodeProperties(ByteBuf byteBuf) { - return decodeTags(byteBuf).stream() - .map(Pair::parse) - .filter(Objects::nonNull) - .collect(LinkedHashMap::new, - (map, pair) -> map.put(pair.name, pair.value), - HashMap::putAll); + private static void encodeString(ByteBuf byteBuf, String s) { + int length = NumberUtils.requireUnsignedByte(ByteBufUtil.utf8Bytes(s)); + byteBuf.writeByte(length); + ByteBufUtil.reserveAndWriteUtf8(byteBuf, s, length); } - public static List decodeTags(ByteBuf byteBuf) { - ArrayList tags = new ArrayList<>(); + public static Metadata decodeMetadata(ByteBuf byteBuf) { + AtomicInteger offset = new AtomicInteger(0); - int offset = 0; - while (offset < byteBuf.readableBytes()) { //TODO: What is the best conditional here? - int tagLength = byteBuf.getByte(offset); - offset += Byte.BYTES; - String tag = byteBuf.toString(offset, tagLength, StandardCharsets.UTF_8); - tags.add(tag); - offset += tagLength; + String name = decodeString(byteBuf, offset); + + Map properties = new LinkedHashMap<>(); + while (offset.get() < byteBuf.readableBytes()) { //TODO: What is the best conditional here? + String key = decodeString(byteBuf, offset); + String value = null; + if (offset.get() < byteBuf.readableBytes()) { + value = decodeString(byteBuf, offset); + } + properties.put(key, value); } - return tags; + return new Metadata(name, properties); + } + + private 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 boolean matches(Metadata other) { + if (other == null) { + return false; + } + if (other.getName() == null) { + return false; + } + if (!getName().equalsIgnoreCase(other.getName())) { + return false; + } + return matches(getProperties(), other.getProperties()); } /** @@ -118,47 +169,26 @@ public abstract class Metadata { return true; } - /** - * A single name value pair. - */ - public static class Pair { + public static class Builder { + private final Metadata metadata; - private String name; - - private String value; - - public Pair(String name, String value) { - Assert.hasLength(name, "Name must not be empty"); - this.name = name; - this.value = value; + public Builder(String name) { + Assert.hasText(name, "Name must not be empty."); + this.metadata = new Metadata(name, new LinkedHashMap<>()); } - public static Pair parse(String pair) { - int index = getSeparatorIndex(pair); - String name = (index > 0) ? pair.substring(0, index) : pair; - String value = (index > 0) ? pair.substring(index + 1) : ""; - return of(name.trim(), value.trim()); + public Builder with(String key, String value) { + this.metadata.put(key, value); + return this; } - private static int getSeparatorIndex(String pair) { - int colonIndex = pair.indexOf(':'); - int equalIndex = pair.indexOf('='); - if (colonIndex == -1) { - return equalIndex; - } - if (equalIndex == -1) { - return colonIndex; - } - return Math.min(colonIndex, equalIndex); + public Metadata build() { + return this.metadata; } - private static Pair of(String name, String value) { - if (StringUtils.isEmpty(name) && StringUtils.isEmpty(value)) { - return null; - } - return new Pair(name, value); + public ByteBuf encode() { + return Metadata.encode(build()); } - } } diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 394f9475..530caa53 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -20,9 +20,7 @@ package org.springframework.cloud.gateway.rsocket.server; import java.time.Duration; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Function; import io.micrometer.core.instrument.Tags; @@ -52,7 +50,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -72,11 +69,11 @@ public class GatewayRSocketTests { public void init() { registry = mock(Registry.class); incomingPayload = DefaultPayload.create(Unpooled.EMPTY_BUFFER, - Metadata.encodeTags("name:mock", "id:mock1")); + Metadata.from("mock").with("id", "mock1").encode()); RSocket rSocket = mock(RSocket.class); LoadBalancedRSocket loadBalancedRSocket = mock(LoadBalancedRSocket.class); - when(registry.getRegistered(anyMap())).thenReturn(loadBalancedRSocket); + when(registry.getRegistered(any(Metadata.class))).thenReturn(loadBalancedRSocket); Mono mono = Mono .just(new EnrichedRSocket(rSocket, getMetadata())); @@ -186,11 +183,10 @@ public class GatewayRSocketTests { } } - private static Map getMetadata() { - HashMap map = new HashMap<>(); - map.put("name", "service"); - map.put("id", "service1"); - return map; + private static Metadata getMetadata() { + return Metadata.from("service") + .with("id", "service1") + .build(); } private static class TestRoutes implements Routes { @@ -209,7 +205,7 @@ public class GatewayRSocketTests { this.filters = filters; route = Route.builder() .id("route1") - .routingMetadata(Collections.singletonMap("name", "mock")) + .routingMetadata(Metadata.from("mock").build()) .predicate(exchange -> Mono.just(true)) .filters(filters) .build(); diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java index df701c95..6328d1fb 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java @@ -33,10 +33,11 @@ import reactor.core.publisher.Mono; import org.springframework.cloud.gateway.rsocket.autoconfigure.GatewayRSocketProperties; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; +import org.springframework.cloud.gateway.rsocket.support.Metadata; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -60,7 +61,7 @@ public class GatewaySocketAcceptorTests { this.sendingSocket = mock(RSocket.class); this.meterRegistry = new SimpleMeterRegistry(); - when(this.factory.create(anyMap())).thenReturn(mock(GatewayRSocket.class)); + when(this.factory.create(any(Metadata.class))).thenReturn(mock(GatewayRSocket.class)); } //TODO: test metrics diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java b/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java index d58e17bb..f329e79d 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.IntStream; +import io.netty.buffer.ByteBuf; import org.junit.Test; import org.springframework.util.Assert; @@ -30,6 +31,32 @@ import static org.springframework.cloud.gateway.rsocket.support.Metadata.matches public class MetadataTests { + @Test + public void encodeAndDecodeJustName() { + ByteBuf byteBuf = Metadata.from("test").encode(); + assertMetadata(byteBuf, "test"); + } + + @Test + public void encodeAndDecodeWorks() { + ByteBuf byteBuf = Metadata.from("test1") + .with("key1111", "val111111") + .with("key22", "val222") + .encode(); + Metadata metadata = assertMetadata(byteBuf, "test1"); + Map properties = metadata.getProperties(); + assertThat(properties).hasSize(2) + .containsOnlyKeys("key1111", "key22") + .containsValues("val111111", "val222"); + } + + private Metadata assertMetadata(ByteBuf byteBuf, String name) { + Metadata metadata = Metadata.decodeMetadata(byteBuf); + assertThat(metadata).isNotNull(); + assertThat(metadata.getName()).isEqualTo(name); + return metadata; + } + @Test public void nullMetadataDoesNotMatch() { assertThat(matches(null, new HashMap<>())).isFalse(); diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index 7108d777..e8380bf0 100644 --- a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -130,7 +130,7 @@ public class PingPongApp { log.debug("ping.take: " + take); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); - ByteBuf announcementMetadata = Metadata.encodeTags("name:ping", "id:ping"+id); + ByteBuf announcementMetadata = Metadata.from("ping").with("id", "ping"+id).encode(); pongFlux = RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) @@ -143,7 +143,7 @@ public class PingPongApp { Flux.interval(Duration.ofSeconds(1)) .map(i -> { ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "ping" + id); - ByteBuf routingMetadata = Metadata.encodeTags("name:pong"); + ByteBuf routingMetadata = Metadata.from("pong").encode(); return DefaultPayload.create(data, routingMetadata); }) .onBackpressureDrop(payload -> log.debug("Dropped payload " + payload.getDataUtf8())) // this is needed in case pong is not available yet @@ -198,7 +198,7 @@ public class PingPongApp { Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", Integer.class, 7002); MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); - ByteBuf announcementMetadata = Metadata.encodeTags("name:pong", "id:pong1"); + ByteBuf announcementMetadata = Metadata.from("pong").with("id", "pong1").encode(); RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) @@ -224,7 +224,7 @@ public class PingPongApp { .map(PingPongApp::reply) .map(reply -> { ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, reply); - ByteBuf routingMetadata = Metadata.encodeTags("name:ping", "id:"); + ByteBuf routingMetadata = Metadata.from("ping").encode(); return DefaultPayload.create(data, routingMetadata); }); } From cc674f19d8ab14e3ae341b99881df305c92a2111 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 21 Feb 2019 22:08:51 -0500 Subject: [PATCH 59/67] moved code to submodule, preparatory to importing to mainline gateway repository --- .gitignore | 33 ++--- pom.xml | 94 ++------------ spring-cloud-gateway-rsocket/pom.xml | 121 ++++++++++++++++++ .../GatewayRSocketAutoConfiguration.java | 0 .../GatewayRSocketProperties.java | 0 .../rsocket/filter/AbstractFilterChain.java | 0 .../filter/AbstractRSocketExchange.java | 0 .../gateway/rsocket/filter/FilterChain.java | 0 .../rsocket/filter/RSocketExchange.java | 0 .../gateway/rsocket/filter/RSocketFilter.java | 0 .../metrics/MicrometerResponderRSocket.java | 0 ...MicrometerResponderRSocketInterceptor.java | 0 .../rsocket/registry/LoadBalancedRSocket.java | 0 .../gateway/rsocket/registry/Registry.java | 0 .../rsocket/registry/RegistryRoutes.java | 0 .../RegistrySocketAcceptorFilter.java | 0 .../cloud/gateway/rsocket/route/Route.java | 0 .../cloud/gateway/rsocket/route/Routes.java | 0 .../rsocket/server/GatewayExchange.java | 0 .../gateway/rsocket/server/GatewayFilter.java | 0 .../rsocket/server/GatewayFilterChain.java | 0 .../rsocket/server/GatewayPredicate.java | 0 .../rsocket/server/GatewayRSocket.java | 0 .../rsocket/server/GatewayRSocketServer.java | 0 .../rsocket/server/PendingRequestRSocket.java | 0 .../socketacceptor/GatewaySocketAcceptor.java | 0 .../SocketAcceptorExchange.java | 0 .../socketacceptor/SocketAcceptorFilter.java | 0 .../SocketAcceptorFilterChain.java | 0 .../SocketAcceptorPredicate.java | 0 .../SocketAcceptorPredicateFilter.java | 0 .../rsocket/support/AsyncPredicate.java | 0 .../gateway/rsocket/support/Metadata.java | 0 .../main/resources/META-INF/spring.factories | 0 .../GatewayRSocketAutoConfigurationTests.java | 0 .../GatewayRSocketIntegrationTests.java | 0 .../rsocket/server/GatewayRSocketTests.java | 0 .../GatewaySocketAcceptorTests.java | 0 .../SocketAcceptorPredicateFilterTests.java | 0 .../rsocket/support/MetadataTests.java | 0 .../gateway/rsocket/test/PingPongApp.java | 0 .../test/SocketAcceptorFilterOrderTests.java | 0 .../src}/test/resources/application.yml | 0 43 files changed, 143 insertions(+), 105 deletions(-) create mode 100644 spring-cloud-gateway-rsocket/pom.xml rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/main/resources/META-INF/spring.factories (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java (100%) rename {src => spring-cloud-gateway-rsocket/src}/test/resources/application.yml (100%) diff --git a/.gitignore b/.gitignore index 82eca336..81428507 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,20 @@ -/target/ -!.mvn/wrapper/maven-wrapper.jar - -### STS ### -.apt_generated +*~ +#* +*# +.#* .classpath -.factorypath .project -.settings +.settings/ .springBeans -.sts4-cache - -### IntelliJ IDEA ### +target/ +bin/ +_site/ .idea -*.iws *.iml *.ipr - -### NetBeans ### -/nbproject/private/ -/build/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ \ No newline at end of file +*.iws +.factorypath +*.log +.shelf +*.swp +*.swo diff --git a/pom.xml b/pom.xml index 13cf8ea6..e715ac76 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ --> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot @@ -25,97 +25,19 @@ 2.1.2.RELEASE + pom org.springframework.cloud - spring-cloud-gateway-rsocket + spring-cloud-gateway-rsocket-parent 0.0.1-SNAPSHOT - spring-cloud-gateway-rsocket - Spring Cloud Gateway RSocket + spring-cloud-gateway-rsocket-parent + Spring Cloud Gateway RSocket Parent 1.8 - 0.11.16 - Californium-SR5 - - - - io.projectreactor - reactor-bom - ${reactor.version} - pom - import - - - io.rsocket - rsocket-core - ${rsocket.version} - - - io.rsocket - rsocket-micrometer - ${rsocket.version} - - - io.rsocket - rsocket-transport-netty - ${rsocket.version} - - - - - - - org.springframework.boot - spring-boot-starter-webflux - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - io.rsocket - rsocket-core - - - io.rsocket - rsocket-micrometer - - - io.rsocket - rsocket-transport-netty - - - io.micrometer - micrometer-core - - - org.springframework.boot - spring-boot-starter-actuator - test - - - org.projectlombok - lombok - test - - - org.springframework.boot - spring-boot-starter-test - test - - - io.projectreactor - reactor-test - test - - - - - - - + + spring-cloud-gateway-rsocket + diff --git a/spring-cloud-gateway-rsocket/pom.xml b/spring-cloud-gateway-rsocket/pom.xml new file mode 100644 index 00000000..a60b37de --- /dev/null +++ b/spring-cloud-gateway-rsocket/pom.xml @@ -0,0 +1,121 @@ + + + + + 4.0.0 + + org.springframework.cloud + spring-cloud-gateway-rsocket-parent + 0.0.1-SNAPSHOT + .. + + org.springframework.cloud + spring-cloud-gateway-rsocket + 0.0.1-SNAPSHOT + spring-cloud-gateway-rsocket + Spring Cloud Gateway RSocket + + + 1.8 + 0.11.16 + Californium-SR5 + + + + + + io.projectreactor + reactor-bom + ${reactor.version} + pom + import + + + io.rsocket + rsocket-core + ${rsocket.version} + + + io.rsocket + rsocket-micrometer + ${rsocket.version} + + + io.rsocket + rsocket-transport-netty + ${rsocket.version} + + + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + io.rsocket + rsocket-core + + + io.rsocket + rsocket-micrometer + + + io.rsocket + rsocket-transport-netty + + + io.micrometer + micrometer-core + + + org.springframework.boot + spring-boot-starter-actuator + test + + + org.projectlombok + lombok + test + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + + + + + diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistrySocketAcceptorFilter.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java diff --git a/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java similarity index 100% rename from src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java rename to spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java diff --git a/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-rsocket/src/main/resources/META-INF/spring.factories similarity index 100% rename from src/main/resources/META-INF/spring.factories rename to spring-cloud-gateway-rsocket/src/main/resources/META-INF/spring.factories diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java diff --git a/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java similarity index 100% rename from src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java rename to spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java diff --git a/src/test/resources/application.yml b/spring-cloud-gateway-rsocket/src/test/resources/application.yml similarity index 100% rename from src/test/resources/application.yml rename to spring-cloud-gateway-rsocket/src/test/resources/application.yml From 7054630260031fcdefb42da1835444a5d4f1af96 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 21 Feb 2019 22:42:26 -0500 Subject: [PATCH 60/67] Bumps to 2.2.0.BUILD-SNAPSHOT --- docs/pom.xml | 2 +- pom.xml | 3 +- spring-cloud-gateway-core/pom.xml | 2 +- spring-cloud-gateway-dependencies/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-rsocket/pom.xml | 78 +++++++++++++++++++++-- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 9 files changed, 81 insertions(+), 14 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index bde3fffe..7b39dcc7 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT spring-cloud-gateway-docs pom diff --git a/pom.xml b/pom.xml index 2b4a0c7c..6cf123be 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT pom Spring Cloud Gateway @@ -134,6 +134,7 @@ spring-cloud-gateway-mvc spring-cloud-gateway-webflux spring-cloud-gateway-core + spring-cloud-gateway-rsocket spring-cloud-starter-gateway spring-cloud-gateway-sample docs diff --git a/spring-cloud-gateway-core/pom.xml b/spring-cloud-gateway-core/pom.xml index 9e3daa28..743f9dd2 100644 --- a/spring-cloud-gateway-core/pom.xml +++ b/spring-cloud-gateway-core/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT .. spring-cloud-gateway-core diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index d1b556d6..b436efb9 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -11,7 +11,7 @@ spring-cloud-gateway-dependencies - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 89f340fd..d764896b 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-gateway-rsocket/pom.xml b/spring-cloud-gateway-rsocket/pom.xml index a60b37de..e6e8a9e5 100644 --- a/spring-cloud-gateway-rsocket/pom.xml +++ b/spring-cloud-gateway-rsocket/pom.xml @@ -21,14 +21,13 @@ 4.0.0 org.springframework.cloud - spring-cloud-gateway-rsocket-parent - 0.0.1-SNAPSHOT - .. + spring-cloud-gateway + 2.2.0.BUILD-SNAPSHOT + .. org.springframework.cloud spring-cloud-gateway-rsocket - 0.0.1-SNAPSHOT - spring-cloud-gateway-rsocket + Spring Cloud Gateway RSocket Spring Cloud Gateway RSocket @@ -112,10 +111,77 @@ test - + + org.apache.maven.plugins + maven-compiler-plugin + + + -parameters + + + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + + test-jar + + + + + + + java8plus + + [1.8,2.0) + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -parameters + + + + + + + diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index fbc929c6..554c1d95 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index edae55ad..0bf76eae 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT .. diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 4a1f6cb1..062daf73 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 2.1.1.BUILD-SNAPSHOT + 2.2.0.BUILD-SNAPSHOT .. spring-cloud-starter-gateway From fff66902df4ac26ca5af6b674986204cff0a5678 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 21 Feb 2019 22:42:39 -0500 Subject: [PATCH 61/67] Updates for checkstyle. --- .../GatewayRSocketAutoConfiguration.java | 13 +- .../GatewayRSocketProperties.java | 35 ++--- .../rsocket/filter/AbstractFilterChain.java | 30 ++-- .../filter/AbstractRSocketExchange.java | 2 +- .../gateway/rsocket/filter/FilterChain.java | 2 +- .../rsocket/filter/RSocketExchange.java | 3 +- .../gateway/rsocket/filter/RSocketFilter.java | 24 +-- .../metrics/MicrometerResponderRSocket.java | 96 ++++++------ ...MicrometerResponderRSocketInterceptor.java | 8 +- .../rsocket/registry/LoadBalancedRSocket.java | 22 +-- .../gateway/rsocket/registry/Registry.java | 20 ++- .../rsocket/registry/RegistryRoutes.java | 15 +- .../RegistrySocketAcceptorFilter.java | 9 +- .../cloud/gateway/rsocket/route/Route.java | 111 +++++++------- .../cloud/gateway/rsocket/route/Routes.java | 36 +++-- .../rsocket/server/GatewayExchange.java | 20 ++- .../gateway/rsocket/server/GatewayFilter.java | 6 +- .../rsocket/server/GatewayFilterChain.java | 15 +- .../rsocket/server/GatewayPredicate.java | 2 +- .../rsocket/server/GatewayRSocket.java | 123 ++++++++------- .../rsocket/server/GatewayRSocketServer.java | 46 +++--- .../rsocket/server/PendingRequestRSocket.java | 82 +++++----- .../socketacceptor/GatewaySocketAcceptor.java | 43 ++++-- .../SocketAcceptorExchange.java | 5 +- .../socketacceptor/SocketAcceptorFilter.java | 6 +- .../SocketAcceptorFilterChain.java | 17 ++- .../SocketAcceptorPredicate.java | 2 +- .../SocketAcceptorPredicateFilter.java | 23 +-- .../rsocket/support/AsyncPredicate.java | 1 - .../gateway/rsocket/support/Metadata.java | 35 +++-- .../GatewayRSocketAutoConfigurationTests.java | 18 +-- .../GatewayRSocketIntegrationTests.java | 18 +-- .../rsocket/server/GatewayRSocketTests.java | 133 ++++++++-------- .../GatewaySocketAcceptorTests.java | 89 ++++++----- .../SocketAcceptorPredicateFilterTests.java | 37 ++--- .../rsocket/support/MetadataTests.java | 13 +- .../gateway/rsocket/test/PingPongApp.java | 142 ++++++++++-------- .../test/SocketAcceptorFilterOrderTests.java | 14 +- src/checkstyle/checkstyle-suppressions.xml | 1 + 39 files changed, 711 insertions(+), 606 deletions(-) diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java index 39470961..5147b101 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfiguration.java @@ -12,7 +12,6 @@ * 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; @@ -53,7 +52,7 @@ public class GatewayRSocketAutoConfiguration { return new Registry(); } - //TODO: CompositeRoutes + // TODO: CompositeRoutes @Bean public RegistryRoutes registryRoutes(Registry registry) { RegistryRoutes registryRoutes = new RegistryRoutes(); @@ -76,13 +75,15 @@ public class GatewayRSocketAutoConfiguration { public GatewayRSocketProperties gatewayRSocketProperties(Environment env) { GatewayRSocketProperties properties = new GatewayRSocketProperties(); if (env.containsProperty("spring.application.name")) { - properties.setId(env.getProperty("spring.application.name")); // set default from env + properties.setId(env.getProperty("spring.application.name")); // set default + // from env } return properties; } @Bean - public SocketAcceptorPredicateFilter socketAcceptorPredicateFilter(List predicates) { + public SocketAcceptorPredicateFilter socketAcceptorPredicateFilter( + List predicates) { return new SocketAcceptorPredicateFilter(predicates); } @@ -90,7 +91,8 @@ public class GatewayRSocketAutoConfiguration { public GatewaySocketAcceptor socketAcceptor(GatewayRSocket.Factory rsocketFactory, List filters, MeterRegistry meterRegistry, GatewayRSocketProperties properties) { - return new GatewaySocketAcceptor(rsocketFactory, filters, meterRegistry, properties); + return new GatewaySocketAcceptor(rsocketFactory, filters, meterRegistry, + properties); } @Bean @@ -98,4 +100,5 @@ public class GatewayRSocketAutoConfiguration { GatewayRSocketProperties properties, MeterRegistry meterRegistry) { return new GatewayRSocketServer(properties, socketAcceptor, meterRegistry); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java index de21e576..4f4a3064 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketProperties.java @@ -12,17 +12,16 @@ * 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 org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.style.ToStringCreator; - import java.util.ArrayList; import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.core.style.ToStringCreator; + @ConfigurationProperties("spring.cloud.gateway.rsocket") public class GatewayRSocketProperties { @@ -57,11 +56,8 @@ public class GatewayRSocketProperties { @Override public String toString() { - return new ToStringCreator(this) - .append("enabled", enabled) - .append("id", id) - .append("server", server) - .toString(); + return new ToStringCreator(this).append("enabled", enabled).append("id", id) + .append("server", server).toString(); } /** @@ -69,8 +65,15 @@ public class GatewayRSocketProperties { */ public static class Server { - //TODO: other transports - public enum TransportType { TCP } + // TODO: other transports + public enum TransportType { + + /** + * TCP Transport type. + */ + TCP + + } /** * Tag names and values to be supplied to Micrometer Interceptor. @@ -80,7 +83,7 @@ public class GatewayRSocketProperties { /** * Server port. */ - private int port = 7002; //TODO: different default port? + private int port = 7002; // TODO: different default port? public Server() { micrometerTags.add("component"); @@ -118,12 +121,10 @@ public class GatewayRSocketProperties { @Override public String toString() { - return new ToStringCreator(this) - .append("micrometerTags", micrometerTags) - .append("port", port) - .append("transport", transport) - .toString(); + return new ToStringCreator(this).append("micrometerTags", micrometerTags) + .append("port", port).append("transport", transport).toString(); } + } } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java index b47c602c..21e44b1d 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractFilterChain.java @@ -12,7 +12,6 @@ * 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; @@ -31,14 +30,17 @@ import org.springframework.lang.Nullable; /** * Default implementation of {@link FilterChain}. * - *

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. + *

+ * 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. * - *

This class is immutable and thread-safe. It can be created once and - * re-used to handle request concurrently. + *

+ * 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 @@ -54,7 +56,6 @@ public abstract class AbstractFilterChain filters) { this.allFilters = Collections.unmodifiableList(filters); FC chain = initChain(filters); - this.currentFilter = (F)chain.currentFilter; - this.next = (FC)chain.next; + this.currentFilter = (F) chain.currentFilter; + this.next = (FC) chain.next; } private FC initChain(List filters) { @@ -80,7 +81,7 @@ public abstract class AbstractFilterChain allFilters, @Nullable F currentFilter, - @Nullable FC next) { + @Nullable FC next) { this.allFilters = allFilters; this.currentFilter = currentFilter; @@ -91,7 +92,7 @@ public abstract class AbstractFilterChain allFilters, @Nullable F currentFilter, - @Nullable FC next); + @Nullable FC next); public List getFilters() { return this.allFilters; @@ -100,10 +101,8 @@ public abstract class AbstractFilterChain filter(E exchange) { - return Mono.defer(() -> - this.currentFilter != null && this.next != null ? - this.currentFilter.filter(exchange, this.next) : - getMonoSuccess()); + return Mono.defer(() -> this.currentFilter != null && this.next != null + ? this.currentFilter.filter(exchange, this.next) : getMonoSuccess()); } private Mono getMonoSuccess() { @@ -114,4 +113,5 @@ public abstract class AbstractFilterChain MONO_SUCCESS = Mono.just(Success.INSTANCE); + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java index 4b37ffff..1ebcc74b 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/AbstractRSocketExchange.java @@ -12,7 +12,6 @@ * 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; @@ -28,4 +27,5 @@ public abstract class AbstractRSocketExchange implements RSocketExchange { public Map getAttributes() { return this.attributes; } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java index 0fdd3a30..041e2e8a 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/FilterChain.java @@ -12,7 +12,6 @@ * 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; @@ -34,4 +33,5 @@ public interface FilterChain { * @return {@code Mono} to indicate when request handling is complete */ Mono filter(E exchange); + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java index 5bbffa77..52f4806e 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketExchange.java @@ -12,7 +12,6 @@ * 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; @@ -26,6 +25,7 @@ public interface RSocketExchange { /** * Return a mutable map of request attributes for the current exchange. + * @return current attributes. */ Map getAttributes(); @@ -66,4 +66,5 @@ public interface RSocketExchange { default T getAttributeOrDefault(String name, T defaultValue) { return (T) getAttributes().getOrDefault(name, defaultValue); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java index e6eeb703..6b19f231 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/filter/RSocketFilter.java @@ -12,7 +12,6 @@ * 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; @@ -20,9 +19,9 @@ 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. + * 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 * @@ -31,18 +30,23 @@ import reactor.core.publisher.Mono; public interface RSocketFilter> { /** - * 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 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 + 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}. + * 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} to indicate when request processing is complete. */ Mono filter(E exchange, FC chain); + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java index 5c63b945..dd47bde3 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java @@ -12,7 +12,6 @@ * 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; @@ -56,22 +55,27 @@ public class MicrometerResponderRSocket implements ResponderRSocket { /** * 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} + * @throws IllegalArgumentException if {@code delegate} or {@code meterRegistry} is + * {@code null} */ - public MicrometerResponderRSocket(RSocket delegate, MeterRegistry meterRegistry, Tag... tags) { + 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); + 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 @@ -101,14 +105,12 @@ public class MicrometerResponderRSocket implements ResponderRSocket { @Override public Mono requestResponse(Payload payload) { - return Mono.defer( - () -> { - Timer.Sample sample = requestResponse.start(); + return Mono.defer(() -> { + Timer.Sample sample = requestResponse.start(); - return delegate - .requestResponse(payload) - .doFinally(signalType -> requestResponse.accept(sample, signalType)); - }); + return delegate.requestResponse(payload) + .doFinally(signalType -> requestResponse.accept(sample, signalType)); + }); } @Override @@ -133,7 +135,8 @@ public class MicrometerResponderRSocket implements ResponderRSocket { private final Counter onError; - private InteractionCounters(MeterRegistry meterRegistry, String interactionModel, Tag... tags) { + 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); @@ -142,27 +145,29 @@ public class MicrometerResponderRSocket implements ResponderRSocket { @Override public void accept(SignalType signalType) { switch (signalType) { - case CANCEL: - cancel.increment(); - break; - case ON_COMPLETE: - onComplete.increment(); - break; - case ON_ERROR: - onError.increment(); - break; + case CANCEL: + cancel.increment(); + break; + case ON_COMPLETE: + onComplete.increment(); + break; + case ON_ERROR: + onError.increment(); + break; } } - private static Counter counter( - MeterRegistry meterRegistry, String interactionModel, SignalType signalType, Tag... tags) { + private static Counter counter(MeterRegistry meterRegistry, + String interactionModel, SignalType signalType, Tag... tags) { - return meterRegistry.counter( - "rsocket." + interactionModel, Tags.of(tags).and("signal.type", signalType.name())); + return meterRegistry.counter("rsocket." + interactionModel, + Tags.of(tags).and("signal.type", signalType.name())); } + } - private static final class InteractionTimers implements BiConsumer { + private static final class InteractionTimers + implements BiConsumer { private final Timer cancel; @@ -172,7 +177,8 @@ public class MicrometerResponderRSocket implements ResponderRSocket { private final Timer onError; - private InteractionTimers(MeterRegistry meterRegistry, String interactionModel, Tag... tags) { + private InteractionTimers(MeterRegistry meterRegistry, String interactionModel, + Tag... tags) { this.meterRegistry = meterRegistry; this.cancel = timer(meterRegistry, interactionModel, CANCEL, tags); @@ -183,15 +189,15 @@ public class MicrometerResponderRSocket implements ResponderRSocket { @Override public void accept(Timer.Sample sample, SignalType signalType) { switch (signalType) { - case CANCEL: - sample.stop(cancel); - break; - case ON_COMPLETE: - sample.stop(onComplete); - break; - case ON_ERROR: - sample.stop(onError); - break; + case CANCEL: + sample.stop(cancel); + break; + case ON_COMPLETE: + sample.stop(onComplete); + break; + case ON_ERROR: + sample.stop(onError); + break; } } @@ -199,11 +205,13 @@ public class MicrometerResponderRSocket implements ResponderRSocket { return Timer.start(meterRegistry); } - private static Timer timer( - MeterRegistry meterRegistry, String interactionModel, SignalType signalType, Tag... tags) { + private static Timer timer(MeterRegistry meterRegistry, String interactionModel, + SignalType signalType, Tag... tags) { - return meterRegistry.timer( - "rsocket." + interactionModel, Tags.of(tags).and("signal.type", signalType.name())); + return meterRegistry.timer("rsocket." + interactionModel, + Tags.of(tags).and("signal.type", signalType.name())); } + } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java index dd0f74aa..f8480e42 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocketInterceptor.java @@ -12,7 +12,6 @@ * 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; @@ -33,13 +32,14 @@ public class MicrometerResponderRSocketInterceptor implements RSocketInterceptor /** * 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"); + public MicrometerResponderRSocketInterceptor(MeterRegistry meterRegistry, + Tag... tags) { + this.meterRegistry = Objects.requireNonNull(meterRegistry, + "meterRegistry must not be null"); this.tags = tags; } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java index 42c6d3c5..e02bfdd9 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/LoadBalancedRSocket.java @@ -12,13 +12,11 @@ * 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.registry; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; @@ -39,6 +37,7 @@ public class LoadBalancedRSocket { private final List delegates = new CopyOnWriteArrayList<>(); private final String serviceName; + private final LoadBalancer loadBalancer; public LoadBalancedRSocket(String serviceName) { @@ -59,15 +58,12 @@ public class LoadBalancedRSocket { } public void remove(Metadata metadata) { - //TODO: move delegates to a map for easy removal + // TODO: move delegates to a map for easy removal this.delegates.stream() - .filter(enriched -> metadata.matches(enriched.getMetadata())) - .findFirst() + .filter(enriched -> metadata.matches(enriched.getMetadata())).findFirst() .ifPresent(this.delegates::remove); } - - public List getDelegates() { return this.delegates; } @@ -88,16 +84,20 @@ public class LoadBalancedRSocket { public RSocket getSource() { return this.source; } + } - //TODO: Flux as input? - //TODO: reuse commons load balancer? - public interface LoadBalancer extends Function, Mono> { + // TODO: Flux as input? + // TODO: reuse commons load balancer? + public interface LoadBalancer + extends Function, Mono> { + } public static class RoundRobinLoadBalancer implements LoadBalancer { private final AtomicInteger position; + private final String serviceName; public RoundRobinLoadBalancer(String serviceName) { @@ -123,5 +123,7 @@ public class LoadBalancedRSocket { EnrichedRSocket rSocket = rSockets.get(pos % rSockets.size()); return Mono.just(rSocket); } + } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java index 9c27ffd5..2185a114 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/Registry.java @@ -12,7 +12,6 @@ * 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.registry; @@ -37,27 +36,31 @@ import org.springframework.util.Assert; * When a new RSocket is registered, a RegisteredEvent is pushed onto a DirectProcessor * that is acting as an event bus for registered Consumers. */ -//TODO: name? +// TODO: name? public class Registry { + private static final Log log = LogFactory.getLog(Registry.class); private final Map rsockets = new ConcurrentHashMap<>(); - private final DirectProcessor registeredEvents = DirectProcessor.create(); - private final FluxSink registeredEventsSink = registeredEvents.sink(FluxSink.OverflowStrategy.DROP); + private final DirectProcessor registeredEvents = DirectProcessor + .create(); + + private final FluxSink registeredEventsSink = registeredEvents + .sink(FluxSink.OverflowStrategy.DROP); public Registry() { } - //TODO: Mono? + // TODO: Mono? public void register(Metadata metadata, RSocket rsocket) { Assert.notNull(metadata, "metadata may not be null"); Assert.notNull(rsocket, "RSocket may not be null"); if (log.isDebugEnabled()) { log.debug("Registering RSocket: " + metadata); } - LoadBalancedRSocket composite = rsockets.computeIfAbsent(metadata.getName(), s -> - new LoadBalancedRSocket(metadata.getName())); + LoadBalancedRSocket composite = rsockets.computeIfAbsent(metadata.getName(), + s -> new LoadBalancedRSocket(metadata.getName())); composite.addRSocket(rsocket, metadata); registeredEventsSink.next(new RegisteredEvent(metadata, rsocket)); } @@ -82,7 +85,9 @@ public class Registry { } public static class RegisteredEvent { + private final Metadata routingMetadata; + private final RSocket rSocket; public RegisteredEvent(Metadata routingMetadata, RSocket rSocket) { @@ -101,4 +106,5 @@ public class Registry { } } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java index 11120ad6..60d136d0 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/registry/RegistryRoutes.java @@ -12,7 +12,6 @@ * 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.registry; @@ -33,7 +32,7 @@ import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; /** - * Creates routes from RegisteredEvents + * Creates routes from RegisteredEvents. */ public class RegistryRoutes implements Routes, Consumer { @@ -43,8 +42,8 @@ public class RegistryRoutes implements Routes, Consumer getRoutes() { - //TODO: sorting - //TODO: caching + // TODO: sorting + // TODO: caching Collection routeCollection = routes.values(); if (log.isDebugEnabled()) { log.debug("Found routes: " + routeCollection); @@ -69,14 +68,13 @@ public class RegistryRoutes implements Routes, Consumer { // TODO: standard predicates // TODO: allow customized predicates Metadata incomingRouting = exchange.getRoutingMetadata(); - boolean matches = incomingRouting.getName().equalsIgnoreCase(routingMetadata.getName()); + boolean matches = incomingRouting.getName() + .equalsIgnoreCase(routingMetadata.getName()); return Mono.just(matches); }) // TODO: allow customized filters @@ -88,4 +86,5 @@ public class RegistryRoutes implements Routes, Consumer filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { - if (exchange.getMetadata() != null && StringUtils.hasLength(exchange.getMetadata().getName())) { + public Mono filter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { + if (exchange.getMetadata() != null + && StringUtils.hasLength(exchange.getMetadata().getName())) { this.registry.register(exchange.getMetadata(), exchange.getSendingSocket()); } @@ -48,4 +50,5 @@ public class RegistrySocketAcceptorFilter implements SocketAcceptorFilter, Order public int getOrder() { return HIGHEST_PRECEDENCE + 1000; } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java index f9108d66..e645b7e4 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Route.java @@ -12,7 +12,6 @@ * 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; @@ -32,7 +31,6 @@ import org.springframework.core.Ordered; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; - /** * @author Spencer Gibb */ @@ -52,7 +50,9 @@ public class Route implements Ordered { return new Builder(); } - private Route(String id, Metadata targetMetadata, int order, AsyncPredicate predicate, List gatewayFilters) { + private Route(String id, Metadata targetMetadata, int order, + AsyncPredicate predicate, + List gatewayFilters) { this.id = id; this.targetMetadata = targetMetadata; this.order = order; @@ -60,7 +60,57 @@ public class Route implements Ordered { this.gatewayFilters = gatewayFilters; } + public String getId() { + return this.id; + } + + public Metadata getTargetMetadata() { + return this.targetMetadata; + } + + public int getOrder() { + return order; + } + + public AsyncPredicate getPredicate() { + return this.predicate; + } + + public List 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.id) + && Objects.equals(targetMetadata, route.targetMetadata) + && Objects.equals(order, route.order) + && Objects.equals(predicate, route.predicate) + && Objects.equals(gatewayFilters, route.gatewayFilters); + } + + @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 Metadata routingMetadata; @@ -71,7 +121,8 @@ public class Route implements Ordered { protected List gatewayFilters = new ArrayList<>(); - protected Builder() {} + protected Builder() { + } public Builder id(String id) { this.id = id; @@ -115,7 +166,6 @@ public class Route implements Ordered { return filters(Arrays.asList(gatewayFilters)); } - public Builder predicate(AsyncPredicate predicate) { this.predicate = predicate; return this; @@ -144,55 +194,10 @@ public class Route implements Ordered { Assert.notNull(this.routingMetadata, "targetMetadata can not be null"); Assert.notNull(this.predicate, "predicate can not be null"); - return new Route(this.id, this.routingMetadata, this.order, predicate, this.gatewayFilters); + return new Route(this.id, this.routingMetadata, this.order, predicate, + this.gatewayFilters); } + } - public String getId() { - return this.id; - } - - public Metadata getTargetMetadata() { - return this.targetMetadata; - } - - public int getOrder() { - return order; - } - - public AsyncPredicate getPredicate() { - return this.predicate; - } - - public List 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.id) && - Objects.equals(targetMetadata, route.targetMetadata) && - Objects.equals(order, route.order) && - Objects.equals(predicate, route.predicate) && - Objects.equals(gatewayFilters, route.gatewayFilters); - } - - @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(); - } } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java index 9279ad98..4db13e31 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/route/Routes.java @@ -12,46 +12,50 @@ * 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 org.springframework.cloud.gateway.rsocket.server.GatewayExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.springframework.cloud.gateway.rsocket.server.GatewayExchange; + /** * @author Spencer Gibb */ public interface Routes { + /** log. */ Log log = LogFactory.getLog(Routes.class); Flux getRoutes(); default Mono 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 -> { + // 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; }); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java index a5f09b64..fa9d896b 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayExchange.java @@ -12,7 +12,6 @@ * 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.server; @@ -25,16 +24,23 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; import org.springframework.cloud.gateway.rsocket.support.Metadata; +/** + * 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_"; enum Type { - FIRE_AND_FORGET("request.fnf"), - REQUEST_CHANNEL("request.channel"), - REQUEST_RESPONSE("request.response"), - REQUEST_STREAM("request.stream"); + + FIRE_AND_FORGET("request.fnf"), REQUEST_CHANNEL( + "request.channel"), REQUEST_RESPONSE( + "request.response"), REQUEST_STREAM("request.stream"); private String key; @@ -45,10 +51,13 @@ public class GatewayExchange extends AbstractRSocketExchange { String getKey() { return this.key; } + } private final Type type; + private final Metadata routingMetadata; + private Tags tags = Tags.empty(); public static GatewayExchange fromPayload(Type type, Payload payload) { @@ -90,4 +99,5 @@ public class GatewayExchange extends AbstractRSocketExchange { public void setTags(Tags tags) { this.tags = tags; } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java index fc7090ee..f5a88089 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilter.java @@ -12,11 +12,13 @@ * 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.server; import org.springframework.cloud.gateway.rsocket.filter.RSocketFilter; -public interface GatewayFilter extends RSocketFilter {} +public interface GatewayFilter + extends RSocketFilter { + +} diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java index 8dfcd125..00c3d9da 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayFilterChain.java @@ -12,7 +12,6 @@ * 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.server; @@ -29,24 +28,26 @@ public class 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 filters) { super(filters); } - protected GatewayFilterChain(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + protected GatewayFilterChain(List allFilters, + GatewayFilter currentFilter, GatewayFilterChain next) { super(allFilters, currentFilter, next); } @Override - protected GatewayFilterChain create(List allFilters, GatewayFilter currentFilter, GatewayFilterChain next) { + protected GatewayFilterChain create(List allFilters, + GatewayFilter currentFilter, GatewayFilterChain next) { return new GatewayFilterChain(allFilters, currentFilter, next); } - public static Mono executeFilterChain(List filters, GatewayExchange exchange) { - return new GatewayFilterChain(filters) - .filter(exchange); + public static Mono executeFilterChain(List filters, + GatewayExchange exchange) { + return new GatewayFilterChain(filters).filter(exchange); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java index bc7b54c7..07d6d406 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayPredicate.java @@ -12,7 +12,6 @@ * 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.server; @@ -20,4 +19,5 @@ package org.springframework.cloud.gateway.rsocket.server; import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; public interface GatewayPredicate extends AsyncPredicate { + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 08810a44..8ec4c8df 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -12,7 +12,6 @@ * 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.server; @@ -52,20 +51,24 @@ import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.T import static org.springframework.cloud.gateway.rsocket.server.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. + * 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 AbstractRSocket implements ResponderRSocket { private static final Log log = LogFactory.getLog(GatewayRSocket.class); private final Registry registry; + private final Routes routes; + private final MeterRegistry meterRegistry; + private final GatewayRSocketProperties properties; + private final Metadata metadata; GatewayRSocket(Registry registry, Routes routes, MeterRegistry meterRegistry, @@ -75,21 +78,19 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket this.meterRegistry = meterRegistry; this.properties = properties; this.metadata = metadata; - this.onClose() - .doOnSuccess(v -> registry.deregister(metadata)) - //.doOnNext(v -> log.error("OnClose doOnNext")) + this.onClose().doOnSuccess(v -> registry.deregister(metadata)) + // .doOnNext(v -> log.error("OnClose doOnNext")) .doOnError(t -> { if (log.isErrorEnabled()) { log.error("Error received, deregistering " + metadata, t); } registry.deregister(metadata); }) - //.doOnTerminate(() -> log.error("OnClose doOnTerminate")) - //.doFinally(st -> log.error("OnClose doFinally")) + // .doOnTerminate(() -> log.error("OnClose doOnTerminate")) + // .doFinally(st -> log.error("OnClose doFinally")) .subscribe(); } - protected Registry getRegistry() { return registry; } @@ -110,44 +111,42 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private GatewayExchange createExchange(GatewayExchange.Type type, Payload payload) { GatewayExchange exchange = GatewayExchange.fromPayload(type, payload); Tags tags = getTags(exchange); - exchange.setTags(tags); + exchange.setTags(tags); return exchange; } private Tags getTags(GatewayExchange exchange) { - //TODO: add tags to exchange + // TODO: add tags to exchange String responderName = this.metadata.getName(); String responderId = this.metadata.get("id"); String requestorName = exchange.getRoutingMetadata().getName(); Assert.hasText(responderName, "responderName must not be empty"); Assert.hasText(responderId, "responderId must not be empty"); Assert.hasText(requestorName, "requestorName must not be empty"); - //requestor.id happens in a callback, later - return Tags.of("requester.name", requestorName, - "responder.name", responderName, "responder.id", responderId, - "gateway.id", this.properties.getId()); + // requestor.id happens in a callback, later + return Tags.of("requester.name", requestorName, "responder.name", responderName, + "responder.id", responderId, "gateway.id", this.properties.getId()); } @Override public Flux requestChannel(Payload payload, Publisher payloads) { GatewayExchange exchange = createExchange(REQUEST_CHANNEL, payload); Tags responderTags = Tags.of("source", "responder"); - return findRSocketOrCreatePending(exchange) - .flatMapMany(rSocket -> { - Tags requesterTags = Tags.of("source", "requester"); - Flux flux = Flux.from(payloads) - .doOnNext(s -> count(exchange, "payload", requesterTags)) - .doOnError(t -> count(exchange, "error", requesterTags)) - .doFinally(s -> count(exchange, requesterTags)); + return findRSocketOrCreatePending(exchange).flatMapMany(rSocket -> { + Tags requesterTags = Tags.of("source", "requester"); + Flux flux = Flux.from(payloads) + .doOnNext(s -> count(exchange, "payload", requesterTags)) + .doOnError(t -> count(exchange, "error", requesterTags)) + .doFinally(s -> count(exchange, requesterTags)); - if (rSocket instanceof ResponderRSocket) { - ResponderRSocket socket = (ResponderRSocket) rSocket; - return socket.requestChannel(payload, flux) - .log(GatewayRSocket.class.getName()+".request-channel", Level.FINEST); - } - return rSocket.requestChannel(flux); - }) - .doOnNext(s -> count(exchange, "payload", responderTags)) + if (rSocket instanceof ResponderRSocket) { + ResponderRSocket socket = (ResponderRSocket) rSocket; + return socket.requestChannel(payload, flux).log( + GatewayRSocket.class.getName() + ".request-channel", + Level.FINEST); + } + return rSocket.requestChannel(flux); + }).doOnNext(s -> count(exchange, "payload", responderTags)) .doOnError(t -> count(exchange, "error", responderTags)) .doFinally(s -> count(exchange, responderTags)); } @@ -188,7 +187,8 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket .flatMap(rSocket -> rSocket.requestResponse(payload)) .doOnSubscribe(s -> timer.set(Timer.start(meterRegistry))) .doOnError(t -> count(exchange, "error")) - .doFinally(s -> timer.get().stop(meterRegistry.timer(getMetricName(exchange), exchange.getTags()))); + .doFinally(s -> timer.get().stop(meterRegistry + .timer(getMetricName(exchange), exchange.getTags()))); } @Override @@ -203,9 +203,9 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket } /** - * Attempt to locate target RSocket via filter chain. - * If not found, create a pending RSocket. - * @param exchange + * Attempt to locate target RSocket via filter chain. If not found, create a pending + * RSocket. + * @param exchange GatewayExchange * @return */ private Mono findRSocketOrCreatePending(GatewayExchange exchange) { @@ -224,18 +224,21 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket return Mono.just(pending); } - /* for testing */ PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { - Function> routeFinder = registeredEvent -> - getRouteMono(registeredEvent, exchange); + /* for testing */ PendingRequestRSocket constructPendingRSocket( + GatewayExchange exchange) { + Function> routeFinder = registeredEvent -> getRouteMono( + registeredEvent, exchange); return new PendingRequestRSocket(routeFinder, map -> { Tags tags = exchange.getTags().and("requester.id", map.get("id")); exchange.setTags(tags); }); } - protected Mono getRouteMono(Registry.RegisteredEvent registeredEvent, GatewayExchange exchange) { + protected Mono getRouteMono(Registry.RegisteredEvent registeredEvent, + GatewayExchange exchange) { return findRoute(exchange) - .log(PendingRequestRSocket.class.getName()+".find route pending", Level.FINEST) + .log(PendingRequestRSocket.class.getName() + ".find route pending", + Level.FINEST) // can this be replaced with filter? .flatMap(route -> { return matchRoute(route, registeredEvent.getRoutingMetadata()); @@ -244,11 +247,12 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private Mono findRoute(GatewayExchange exchange) { Mono routeMono; - /*if (this.route != null) { //TODO: cache Route? - routeMono = Mono.just(route); - } else {*/ - routeMono = this.routes.findRoute(exchange); - //} + /* + * if (this.route != null) { //TODO: cache Route? routeMono = Mono.just(route); } + * else { + */ + routeMono = this.routes.findRoute(exchange); + // } return routeMono; } @@ -261,14 +265,14 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket } /** - * First locate Route. If found, put route in exchange and execute filter chain. - * If successful, locate target RSocket. - * @param exchange + * First locate Route. If found, put route in exchange and execute filter chain. If + * successful, locate target RSocket. + * @param exchange GatewayExchange. * @return Target RSocket or empty. */ private Mono findRSocket(GatewayExchange exchange) { return this.routes.findRoute(exchange) - .log(GatewayRSocket.class.getName()+".find route", Level.FINEST) + .log(GatewayRSocket.class.getName() + ".find route", Level.FINEST) .flatMap(route -> { // put route in exchange for later use exchange.getAttributes().put(ROUTE_ATTR, route); @@ -279,13 +283,12 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket return loadBalancedRSocket.choose(); }).map(enrichedRSocket -> { - Metadata metadata = enrichedRSocket - .getMetadata(); - Tags tags = exchange.getTags().and("requester.id", metadata.get("id")); + Metadata metadata = enrichedRSocket.getMetadata(); + Tags tags = exchange.getTags().and("requester.id", + metadata.get("id")); exchange.setTags(tags); return enrichedRSocket; - }).cast(RSocket.class) - .switchIfEmpty(doOnEmpty(exchange)); + }).cast(RSocket.class).switchIfEmpty(doOnEmpty(exchange)); }); // TODO: deal with connecting to cluster? @@ -293,15 +296,20 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private Mono doOnEmpty(GatewayExchange exchange) { if (log.isDebugEnabled()) { - log.debug("Unable to find destination RSocket for " + exchange.getRoutingMetadata()); + log.debug("Unable to find destination RSocket for " + + exchange.getRoutingMetadata()); } return Mono.empty(); } public static class Factory { + private final Registry registry; + private final Routes routes; + private final MeterRegistry meterRegistry; + private final GatewayRSocketProperties properties; public Factory(Registry registry, Routes routes, MeterRegistry meterRegistry, @@ -316,6 +324,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket return new GatewayRSocket(this.registry, this.routes, this.meterRegistry, this.properties, metadata); } + } } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java index dc4435d7..c59aae21 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketServer.java @@ -12,7 +12,6 @@ * 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.server; @@ -43,20 +42,29 @@ import org.springframework.util.Assert; public class GatewayRSocketServer implements Ordered, SmartLifecycle { private static final Log log = LogFactory.getLog(GatewayRSocketServer.class); + private static final RSocketInterceptor[] EMPTY_INTERCEPTORS = new RSocketInterceptor[0]; private final GatewayRSocketProperties properties; + private final SocketAcceptor socketAcceptor; + private final List serverInterceptors; + private final AtomicBoolean running = new AtomicBoolean(); + private CloseableChannel closeableChannel; + private final MeterRegistry meterRegistry; - public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { + public GatewayRSocketServer(GatewayRSocketProperties properties, + SocketAcceptor socketAcceptor, MeterRegistry meterRegistry) { this(properties, socketAcceptor, meterRegistry, EMPTY_INTERCEPTORS); } - public GatewayRSocketServer(GatewayRSocketProperties properties, SocketAcceptor socketAcceptor, MeterRegistry meterRegistry, RSocketInterceptor... interceptors) { + public GatewayRSocketServer(GatewayRSocketProperties properties, + SocketAcceptor socketAcceptor, MeterRegistry meterRegistry, + RSocketInterceptor... interceptors) { Assert.notNull(properties, "properties may not be null"); Assert.notNull(socketAcceptor, "socketAcceptor may not be null"); Assert.notNull(meterRegistry, "meterRegistry may not be null"); @@ -104,15 +112,17 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { TransportType transportType = server.getTransport(); TcpServerTransport transport; switch (transportType) { - case TCP: - transport = TcpServerTransport.create(port); - break; - default: - throw new IllegalArgumentException("No support for server transport " + transportType); + case TCP: + transport = TcpServerTransport.create(port); + break; + default: + throw new IllegalArgumentException( + "No support for server transport " + transportType); } if (log.isInfoEnabled()) { - log.info("Starting Gateway RSocket Server on port: " + port + ", transport: " + transportType); + log.info("Starting Gateway RSocket Server on port: " + port + ", transport: " + + transportType); } RSocketFactory.ServerRSocketFactory factory = RSocketFactory.receive(); @@ -121,25 +131,23 @@ public class GatewayRSocketServer implements Ordered, SmartLifecycle { List micrometerTags = server.getMicrometerTags(); Tag[] tags = Tags.of(micrometerTags.toArray(new String[] {})) - .and("gateway.id", properties.getId()) - .stream() - .collect(Collectors.toList()) - .toArray(new Tag[]{}); + .and("gateway.id", properties.getId()).stream() + .collect(Collectors.toList()).toArray(new Tag[] {}); factory - //TODO: add as bean like serverInterceptors above - .addConnectionPlugin(new MicrometerDuplexConnectionInterceptor(meterRegistry, tags)) + // 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 - .acceptor(this.socketAcceptor) - .transport(transport) - .start() + }) // TODO: add configurable errorConsumer + .acceptor(this.socketAcceptor).transport(transport).start() .map(closeableChannel -> { this.closeableChannel = closeableChannel; return closeableChannel; }).subscribe(); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java index da568449..2a4f4de4 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/PendingRequestRSocket.java @@ -12,7 +12,6 @@ * 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.server; @@ -43,14 +42,19 @@ import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.R import static org.springframework.cloud.gateway.rsocket.server.GatewayExchange.Type.REQUEST_STREAM; import static org.springframework.cloud.gateway.rsocket.server.GatewayFilterChain.executeFilterChain; -public class PendingRequestRSocket extends AbstractRSocket implements ResponderRSocket, Consumer { +public class PendingRequestRSocket extends AbstractRSocket + implements ResponderRSocket, Consumer { private static final Log log = LogFactory.getLog(PendingRequestRSocket.class); private final Function> routeFinder; + private final Consumer metadataCallback; + private final MonoProcessor rSocketProcessor; + private Disposable subscriptionDisposable; + private Route route; public PendingRequestRSocket(Function> routeFinder, @@ -58,7 +62,8 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR this(routeFinder, metadataCallback, MonoProcessor.create()); } - /* for testing */ PendingRequestRSocket(Function> routeFinder, + /* for testing */ PendingRequestRSocket( + Function> routeFinder, Consumer metadataCallback, MonoProcessor rSocketProcessor) { this.routeFinder = routeFinder; @@ -67,24 +72,23 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR } /** - * 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 + * 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(); - } - }); + 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 @@ -107,33 +111,36 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR @Override public Flux requestChannel(Payload payload, Publisher 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); - }); + 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 - * @param payload + * 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> processor(String logCategory, Payload payload) { + protected Mono> processor(String logCategory, + Payload payload) { return rSocketProcessor - .log(PendingRequestRSocket.class.getName()+"."+logCategory, Level.FINEST) + .log(PendingRequestRSocket.class.getName() + "." + logCategory, + Level.FINEST) .flatMap(rSocket -> { - GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, payload); + GatewayExchange exchange = GatewayExchange.fromPayload(REQUEST_STREAM, + payload); exchange.getAttributes().put(ROUTE_ATTR, route); - //exchange.getAttributes().putAll(pendingExchange.getAttributes()); - return Mono.just(rSocket).zipWith(executeFilterChain(route.getFilters(), exchange)); + // exchange.getAttributes().putAll(pendingExchange.getAttributes()); + return Mono.just(rSocket) + .zipWith(executeFilterChain(route.getFilters(), exchange)); }); } @@ -141,4 +148,5 @@ public class PendingRequestRSocket extends AbstractRSocket implements ResponderR public void setSubscriptionDisposable(Disposable subscriptionDisposable) { this.subscriptionDisposable = subscriptionDisposable; } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index c3a11c13..2854cdc8 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -12,7 +12,6 @@ * 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; @@ -37,12 +36,16 @@ import org.springframework.cloud.gateway.rsocket.support.Metadata; public class GatewaySocketAcceptor implements SocketAcceptor { private final SocketAcceptorFilterChain filterChain; + private final GatewayRSocket.Factory rSocketFactory; + private final MeterRegistry meterRegistry; + private final GatewayRSocketProperties properties; - public GatewaySocketAcceptor(GatewayRSocket.Factory rSocketFactory, List filters, - MeterRegistry meterRegistry, GatewayRSocketProperties properties) { + public GatewaySocketAcceptor(GatewayRSocket.Factory rSocketFactory, + List filters, MeterRegistry meterRegistry, + GatewayRSocketProperties properties) { this.rSocketFactory = rSocketFactory; this.filterChain = new SocketAcceptorFilterChain(filters); this.meterRegistry = meterRegistry; @@ -53,31 +56,41 @@ public class GatewaySocketAcceptor implements SocketAcceptor { @SuppressWarnings("Duplicates") public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - //decorate sendingSocket with metrics + // decorate sendingSocket with metrics // current gateway id, type requester, service name (from metadata), service id - Tags requesterTags = Tags.of("gateway.id", properties.getId(), "type", "requester"); + Tags requesterTags = Tags.of("gateway.id", properties.getId(), "type", + "requester"); Tags metadataTags; SocketAcceptorExchange exchange; - if (setup.hasMetadata()) { //TODO: and setup.metadataMimeType() is Announcement metadata or composite + if (setup.hasMetadata()) { // TODO: and setup.metadataMimeType() is Announcement + // metadata or composite Metadata metadata = Metadata.decodeMetadata(setup.sliceMetadata()); - metadataTags = Tags.of("service.name", metadata.getName()) - .and("service.id", metadata.get("id")); + metadataTags = Tags.of("service.name", metadata.getName()).and("service.id", + metadata.get("id")); // enrich exchange to have metadata - exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags.and(metadataTags)), metadata); - } else { + exchange = new SocketAcceptorExchange(setup, + decorate(sendingSocket, requesterTags.and(metadataTags)), metadata); + } + else { metadataTags = Tags.empty(); - exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags)); + exchange = new SocketAcceptorExchange(setup, + decorate(sendingSocket, requesterTags)); } - Tags responderTags = Tags.of("gateway.id", properties.getId(), "type", "responder") + 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) + // 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 -> decorate(this.rSocketFactory.create(exchange.getMetadata()), responderTags)); + .log(GatewaySocketAcceptor.class.getName() + + ".socket acceptor filter chain", Level.FINEST) + .map(success -> decorate( + this.rSocketFactory.create(exchange.getMetadata()), + responderTags)); } private RSocket decorate(RSocket rSocket, Tags tags) { diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java index 4083323d..1108e20c 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorExchange.java @@ -12,7 +12,6 @@ * 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; @@ -26,8 +25,11 @@ import org.springframework.cloud.gateway.rsocket.filter.AbstractRSocketExchange; import org.springframework.cloud.gateway.rsocket.support.Metadata; public class SocketAcceptorExchange extends AbstractRSocketExchange { + private final ConnectionSetupPayload setup; + private final RSocket sendingSocket; + private final Metadata metadata; public SocketAcceptorExchange(ConnectionSetupPayload setup, RSocket sendingSocket) { @@ -52,4 +54,5 @@ public class SocketAcceptorExchange extends AbstractRSocketExchange { public Metadata getMetadata() { return metadata; } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java index 2564a93e..c27c2791 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilter.java @@ -12,11 +12,13 @@ * 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 {} +public interface SocketAcceptorFilter + extends RSocketFilter { + +} diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java index dd429450..e21672d1 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorFilterChain.java @@ -12,33 +12,34 @@ * 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.AbstractFilterChain; - import java.util.List; -public class SocketAcceptorFilterChain - extends AbstractFilterChain { +import org.springframework.cloud.gateway.rsocket.filter.AbstractFilterChain; + +public class SocketAcceptorFilterChain extends + AbstractFilterChain { /** * Public constructor with the list of filters and the target handler to use. - * * @param filters the filters ahead of the handler */ public SocketAcceptorFilterChain(List filters) { super(filters); } - public SocketAcceptorFilterChain(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { + public SocketAcceptorFilterChain(List allFilters, + SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { super(allFilters, currentFilter, next); } @Override - protected SocketAcceptorFilterChain create(List allFilters, SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { + protected SocketAcceptorFilterChain create(List allFilters, + SocketAcceptorFilter currentFilter, SocketAcceptorFilterChain next) { return new SocketAcceptorFilterChain(allFilters, currentFilter, next); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java index 5137f1b2..836ba951 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicate.java @@ -12,7 +12,6 @@ * 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; @@ -20,4 +19,5 @@ package org.springframework.cloud.gateway.rsocket.socketacceptor; import org.springframework.cloud.gateway.rsocket.support.AsyncPredicate; public interface SocketAcceptorPredicate extends AsyncPredicate { + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java index 7c81fb34..e659d58c 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilter.java @@ -12,7 +12,6 @@ * 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; @@ -29,12 +28,13 @@ public class SocketAcceptorPredicateFilter implements SocketAcceptorFilter, Orde private final AsyncPredicate predicate; - //TODO: change from List to Flux? + // TODO: change from List to Flux? public SocketAcceptorPredicateFilter(List predicates) { Assert.notNull(predicates, "predicates may not be null"); if (predicates.isEmpty()) { predicate = exchange -> Mono.just(true); - } else { + } + else { AsyncPredicate combined = predicates.get(0); for (SocketAcceptorPredicate p : predicates.subList(1, predicates.size())) { combined = combined.and(p); @@ -49,13 +49,14 @@ public class SocketAcceptorPredicateFilter implements SocketAcceptorFilter, Orde } @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { - return Mono.from(predicate.apply(exchange)) - .flatMap(test -> { - if (test) { - return chain.filter(exchange); - } - return Mono.empty(); - }); + public Mono filter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { + return Mono.from(predicate.apply(exchange)).flatMap(test -> { + if (test) { + return chain.filter(exchange); + } + return Mono.empty(); + }); } + } diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java index b38d2f2b..5707c86b 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/AsyncPredicate.java @@ -12,7 +12,6 @@ * 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; diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java index e2ca8358..c6f5357d 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/support/Metadata.java @@ -12,7 +12,6 @@ * 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; @@ -32,9 +31,19 @@ import org.springframework.util.Assert; public class Metadata { + /** + * Mime type of routing extension. + */ public static final String ROUTING_MIME_TYPE = "message/x.rsocket.routing.v0"; + /** + * The logical name. + */ private final String name; + + /** + * Keys and values associated with name. + */ private final Map properties; public Metadata(String name, Map properties) { @@ -60,10 +69,8 @@ public class Metadata { @Override public String toString() { - return new ToStringCreator(this) - .append("name", name) - .append("properties", properties) - .toString(); + return new ToStringCreator(this).append("name", name) + .append("properties", properties).toString(); } public static Builder from(String name) { @@ -110,7 +117,8 @@ public class Metadata { String name = decodeString(byteBuf, offset); Map properties = new LinkedHashMap<>(); - while (offset.get() < byteBuf.readableBytes()) { //TODO: What is the best conditional here? + while (offset.get() < byteBuf.readableBytes()) { // TODO: What is the best + // conditional here? String key = decodeString(byteBuf, offset); String value = null; if (offset.get() < byteBuf.readableBytes()) { @@ -148,10 +156,12 @@ public class Metadata { * equal values (ignoring case) of leftMetadata. * @param leftMetadata first metadata to compare. * @param rightMetadata second metadata to compare. - * @return true if all keys and values (case-insensitive) from leftMetadata are in rightMetadata. + * @return true if all keys and values (case-insensitive) from leftMetadata are in + * rightMetadata. */ - //TODO: find a way to make this more performant - public static boolean matches(Map leftMetadata, Map rightMetadata) { + // TODO: find a way to make this more performant + public static boolean matches(Map leftMetadata, + Map rightMetadata) { if (leftMetadata == null || rightMetadata == null) { return false; } @@ -159,17 +169,19 @@ public class Metadata { for (Map.Entry entry : leftMetadata.entrySet()) { String enrichedValue = rightMetadata.get(entry.getKey()); if (enrichedValue == null || - //TODO: regex and possibly SpEL? + // TODO: regex and possibly SpEL? !enrichedValue.equalsIgnoreCase(entry.getValue())) { return false; } } - // all entries in metadata exist and match corresponding entries in enriched.metadata + // all entries in metadata exist and match corresponding entries in + // enriched.metadata return true; } public static class Builder { + private final Metadata metadata; public Builder(String name) { @@ -189,6 +201,7 @@ public class Metadata { public ByteBuf encode() { return Metadata.encode(build()); } + } } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java index 1b4fa204..bcdefad8 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/autoconfigure/GatewayRSocketAutoConfigurationTests.java @@ -12,7 +12,6 @@ * 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; @@ -23,11 +22,9 @@ import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegi import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; -import org.springframework.cloud.gateway.rsocket.metrics.MicrometerResponderRSocketInterceptor; import org.springframework.cloud.gateway.rsocket.registry.Registry; import org.springframework.cloud.gateway.rsocket.registry.RegistryRoutes; import org.springframework.cloud.gateway.rsocket.registry.RegistrySocketAcceptorFilter; -import org.springframework.cloud.gateway.rsocket.server.GatewayRSocket; import org.springframework.cloud.gateway.rsocket.server.GatewayRSocketServer; import org.springframework.cloud.gateway.rsocket.socketacceptor.GatewaySocketAcceptor; import org.springframework.cloud.gateway.rsocket.socketacceptor.SocketAcceptorPredicate; @@ -40,21 +37,18 @@ public class GatewayRSocketAutoConfigurationTests { @Test public void gatewayRSocketConfigured() { new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - GatewayRSocketAutoConfiguration.class, - CompositeMeterRegistryAutoConfiguration.class, - MetricsAutoConfiguration.class - )) - .run(context -> assertThat(context) - .hasSingleBean(Registry.class) + .withConfiguration( + AutoConfigurations.of(GatewayRSocketAutoConfiguration.class, + CompositeMeterRegistryAutoConfiguration.class, + MetricsAutoConfiguration.class)) + .run(context -> assertThat(context).hasSingleBean(Registry.class) .hasSingleBean(RegistryRoutes.class) .hasSingleBean(RegistrySocketAcceptorFilter.class) .hasSingleBean(GatewayRSocketServer.class) .hasSingleBean(GatewayRSocketProperties.class) .hasSingleBean(GatewaySocketAcceptor.class) .hasSingleBean(SocketAcceptorPredicateFilter.class) - .doesNotHaveBean(SocketAcceptorPredicate.class) - ); + .doesNotHaveBean(SocketAcceptorPredicate.class)); } } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java index 3e55d5cb..c4dfb6bf 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketIntegrationTests.java @@ -12,7 +12,6 @@ * 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.server; @@ -34,9 +33,8 @@ import org.springframework.util.SocketUtils; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) -@SpringBootTest(classes = PingPongApp.class, - properties = { "ping.take=5" }, - webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest(classes = PingPongApp.class, properties = { + "ping.take=5" }, webEnvironment = WebEnvironment.RANDOM_PORT) public class GatewayRSocketIntegrationTests { private static int port; @@ -59,7 +57,8 @@ public class GatewayRSocketIntegrationTests { @BeforeClass public static void init() { port = SocketUtils.findAvailableTcpPort(); - System.setProperty("spring.cloud.gateway.rsocket.server.port", String.valueOf(port)); + System.setProperty("spring.cloud.gateway.rsocket.server.port", + String.valueOf(port)); } @AfterClass @@ -68,12 +67,9 @@ public class GatewayRSocketIntegrationTests { } @Test - public void contextLoads() { - StepVerifier.create(ping.getPongFlux()) - .expectSubscription() - .then(() -> server.stop()) - .thenConsumeWhile(s -> true) - .verifyComplete(); + public void contextLoads() { + StepVerifier.create(ping.getPongFlux()).expectSubscription() + .then(() -> server.stop()).thenConsumeWhile(s -> true).verifyComplete(); assertThat(ping.getPongsReceived()).isGreaterThan(0); assertThat(pong.getPingsReceived()).isGreaterThan(0); diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 530caa53..9d2f426c 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -12,7 +12,6 @@ * 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.server; @@ -46,9 +45,7 @@ import org.springframework.cloud.gateway.rsocket.route.Route; import org.springframework.cloud.gateway.rsocket.route.Routes; import org.springframework.cloud.gateway.rsocket.support.Metadata; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +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; @@ -61,9 +58,10 @@ public class GatewayRSocketTests { private static Log logger = LogFactory.getLog(GatewayRSocketTests.class); private Registry registry; + private Payload incomingPayload; - //TODO: add tests for metrics and other request types + // TODO: add tests for metrics and other request types @Before public void init() { @@ -83,95 +81,94 @@ public class GatewayRSocketTests { .thenReturn(Mono.just(DefaultPayload.create("response"))); } - @Test - public void multipleFilters() { + public void multipleFilters() { TestFilter filter1 = new TestFilter(); TestFilter filter2 = new TestFilter(); TestFilter filter3 = new TestFilter(); - Payload payload = new TestGatewayRSocket(registry, new TestRoutes(filter1, filter2, filter3)) - .requestResponse(incomingPayload) - .block(Duration.ZERO); + Payload payload = new TestGatewayRSocket(registry, + new TestRoutes(filter1, filter2, filter3)) + .requestResponse(incomingPayload).block(Duration.ZERO); - assertTrue(filter1.invoked()); - assertTrue(filter2.invoked()); - assertTrue(filter3.invoked()); - assertNotNull(payload); + assertThat(filter1.invoked()).isTrue(); + assertThat(filter2.invoked()).isTrue(); + assertThat(filter3.invoked()).isTrue(); + assertThat(payload).isNotNull(); } @Test - public void zeroFilters() { + public void zeroFilters() { Payload payload = new TestGatewayRSocket(registry, new TestRoutes()) - .requestResponse(incomingPayload) - .block(Duration.ZERO); + .requestResponse(incomingPayload).block(Duration.ZERO); - assertNotNull(payload); + assertThat(payload).isNotNull(); } @Test - public void shortcircuitFilter() { + public void shortcircuitFilter() { TestFilter filter1 = new TestFilter(); ShortcircuitingFilter filter2 = new ShortcircuitingFilter(); TestFilter filter3 = new TestFilter(); - - TestGatewayRSocket gatewayRSocket = new TestGatewayRSocket(registry, new TestRoutes(filter1, filter2, filter3)); + TestGatewayRSocket gatewayRSocket = new TestGatewayRSocket(registry, + new TestRoutes(filter1, filter2, filter3)); Mono response = gatewayRSocket.requestResponse(incomingPayload); // a false filter will create a pending rsocket that blocks forever // this tweaks the rsocket to compelte. gatewayRSocket.processor.onNext(null); - StepVerifier.withVirtualTime(() -> response) - .expectSubscription() + StepVerifier.withVirtualTime(() -> response).expectSubscription() .verifyComplete(); - assertTrue(filter1.invoked()); - assertTrue(filter2.invoked()); - assertFalse(filter3.invoked()); + assertThat(filter1.invoked()).isTrue(); + assertThat(filter2.invoked()).isTrue(); + assertThat(filter3.invoked()).isFalse(); } @Test - public void asyncFilter() { + public void asyncFilter() { AsyncFilter filter = new AsyncFilter(); Payload payload = new TestGatewayRSocket(registry, new TestRoutes(filter)) - .requestResponse(incomingPayload) - .block(Duration.ofSeconds(5)); + .requestResponse(incomingPayload).block(Duration.ofSeconds(5)); - assertTrue(filter.invoked()); - assertNotNull(payload); + assertThat(filter.invoked()).isTrue(); + assertThat(payload).isNotNull(); } - //TODO: add exception handlers? + // TODO: add exception handlers? @Test(expected = IllegalStateException.class) - public void handleErrorFromFilter() { + public void handleErrorFromFilter() { ExceptionFilter filter = new ExceptionFilter(); new TestGatewayRSocket(registry, new TestRoutes(filter)) - .requestResponse(incomingPayload) - .block(Duration.ofSeconds(5)); + .requestResponse(incomingPayload).block(Duration.ofSeconds(5)); // assertNull(socket); } + private static Metadata getMetadata() { + return Metadata.from("service").with("id", "service1").build(); + } + private static class TestGatewayRSocket extends GatewayRSocket { private final MonoProcessor processor = MonoProcessor.create(); - public TestGatewayRSocket(Registry registry, Routes routes) { - super(registry, routes, new SimpleMeterRegistry(), new GatewayRSocketProperties(), - getMetadata()); + TestGatewayRSocket(Registry registry, Routes routes) { + super(registry, routes, new SimpleMeterRegistry(), + new GatewayRSocketProperties(), getMetadata()); } @Override PendingRequestRSocket constructPendingRSocket(GatewayExchange exchange) { - Function> routeFinder = registeredEvent -> - getRouteMono(registeredEvent, exchange); + Function> routeFinder = registeredEvent -> getRouteMono( + registeredEvent, exchange); return new PendingRequestRSocket(routeFinder, map -> { Tags tags = exchange.getTags().and("requester.id", map.get("id")); exchange.setTags(tags); @@ -181,42 +178,36 @@ public class GatewayRSocketTests { public MonoProcessor getProcessor() { return processor; } - } - private static Metadata getMetadata() { - return Metadata.from("service") - .with("id", "service1") - .build(); } private static class TestRoutes implements Routes { + private final Route route; + private List filters; - public TestRoutes() { + TestRoutes() { this(Collections.emptyList()); } - public TestRoutes(GatewayFilter... filters) { + TestRoutes(GatewayFilter... filters) { this(Arrays.asList(filters)); } - public TestRoutes(List filters) { + TestRoutes(List filters) { this.filters = filters; - route = Route.builder() - .id("route1") + route = Route.builder().id("route1") .routingMetadata(Metadata.from("mock").build()) - .predicate(exchange -> Mono.just(true)) - .filters(filters) - .build(); + .predicate(exchange -> Mono.just(true)).filters(filters).build(); } @Override public Flux getRoutes() { return Flux.just(route); } - } + } private static class TestFilter implements GatewayFilter { @@ -232,24 +223,28 @@ public class GatewayRSocketTests { return doFilter(exchange, chain); } - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono doFilter(GatewayExchange exchange, + GatewayFilterChain chain) { return chain.filter(exchange); } - } + } private static class ShortcircuitingFilter extends TestFilter { @Override - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono doFilter(GatewayExchange exchange, + GatewayFilterChain chain) { return Mono.empty(); } + } private static class AsyncFilter extends TestFilter { @Override - public Mono doFilter(GatewayExchange exchange, GatewayFilterChain chain) { + public Mono doFilter(GatewayExchange exchange, + GatewayFilterChain chain) { return doAsyncWork().flatMap(asyncResult -> { logger.debug("Async result: " + asyncResult); return chain.filter(exchange); @@ -259,8 +254,8 @@ public class GatewayRSocketTests { private Mono doAsyncWork() { return Mono.delay(Duration.ofMillis(100L)).map(l -> "123"); } - } + } private static class ExceptionFilter implements GatewayFilter { @@ -268,18 +263,16 @@ public class GatewayRSocketTests { public Mono filter(GatewayExchange exchange, GatewayFilterChain chain) { return Mono.error(new IllegalStateException("boo")); } + } - - /*private static class TestExceptionHandler implements WebExceptionHandler { - - private Throwable ex; - - @Override - public Mono handle(GatewayExchange exchange, Throwable ex) { - this.ex = ex; - return Mono.error(ex); - } - }*/ + /* + * private static class TestExceptionHandler implements WebExceptionHandler { + * + * private Throwable ex; + * + * @Override public Mono handle(GatewayExchange exchange, Throwable ex) { + * this.ex = ex; return Mono.error(ex); } } + */ } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java index 6328d1fb..c36fd30a 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptorTests.java @@ -12,7 +12,6 @@ * 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; @@ -49,10 +48,14 @@ public class GatewaySocketAcceptorTests { private static Log logger = LogFactory.getLog(GatewaySocketAcceptorTests.class); private GatewayRSocket.Factory factory; + private ConnectionSetupPayload setupPayload; + private RSocket sendingSocket; + private MeterRegistry meterRegistry; - private GatewayRSocketProperties properties = new GatewayRSocketProperties(); + + private GatewayRSocketProperties properties = new GatewayRSocketProperties(); @Before public void init() { @@ -61,10 +64,11 @@ public class GatewaySocketAcceptorTests { this.sendingSocket = mock(RSocket.class); this.meterRegistry = new SimpleMeterRegistry(); - when(this.factory.create(any(Metadata.class))).thenReturn(mock(GatewayRSocket.class)); + when(this.factory.create(any(Metadata.class))) + .thenReturn(mock(GatewayRSocket.class)); } - //TODO: test metrics + // TODO: test metrics @Test public void multipleFilters() { @@ -72,10 +76,10 @@ public class GatewaySocketAcceptorTests { TestFilter filter2 = new TestFilter(); TestFilter filter3 = new TestFilter(); - RSocket socket = new GatewaySocketAcceptor(this.factory, Arrays.asList(filter1, filter2, filter3), - this.meterRegistry, this.properties) - .accept(this.setupPayload, this.sendingSocket) - .block(Duration.ZERO); + RSocket socket = new GatewaySocketAcceptor(this.factory, + Arrays.asList(filter1, filter2, filter3), this.meterRegistry, + this.properties).accept(this.setupPayload, this.sendingSocket) + .block(Duration.ZERO); assertThat(filter1.invoked()).isTrue(); assertThat(filter2.invoked()).isTrue(); @@ -85,10 +89,10 @@ public class GatewaySocketAcceptorTests { @Test public void zeroFilters() { - RSocket socket = new GatewaySocketAcceptor(this.factory, Collections.emptyList(), + RSocket socket = new GatewaySocketAcceptor(this.factory, Collections.emptyList(), this.meterRegistry, this.properties) - .accept(this.setupPayload, this.sendingSocket) - .block(Duration.ZERO); + .accept(this.setupPayload, this.sendingSocket) + .block(Duration.ZERO); assertThat(socket).isNotNull(); } @@ -100,11 +104,10 @@ public class GatewaySocketAcceptorTests { ShortcircuitingFilter filter2 = new ShortcircuitingFilter(); TestFilter filter3 = new TestFilter(); - - RSocket socket = new GatewaySocketAcceptor(this.factory, Arrays.asList(filter1, filter2, filter3), - this.meterRegistry, this.properties) - .accept(this.setupPayload, this.sendingSocket) - .block(Duration.ZERO); + RSocket socket = new GatewaySocketAcceptor(this.factory, + Arrays.asList(filter1, filter2, filter3), this.meterRegistry, + this.properties).accept(this.setupPayload, this.sendingSocket) + .block(Duration.ZERO); assertThat(filter1.invoked()).isTrue(); assertThat(filter2.invoked()).isTrue(); @@ -117,25 +120,24 @@ public class GatewaySocketAcceptorTests { AsyncFilter filter = new AsyncFilter(); - RSocket socket = new GatewaySocketAcceptor(this.factory, singletonList(filter), + RSocket socket = new GatewaySocketAcceptor(this.factory, singletonList(filter), this.meterRegistry, this.properties) - .accept(this.setupPayload, this.sendingSocket) - .block(Duration.ofSeconds(5)); + .accept(this.setupPayload, this.sendingSocket) + .block(Duration.ofSeconds(5)); assertThat(filter.invoked()).isTrue(); assertThat(socket).isNotNull(); } - //TODO: add exception handlers? + // 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) - .accept(this.setupPayload, this.sendingSocket) - .block(Duration.ofSeconds(5)); + new GatewaySocketAcceptor(this.factory, singletonList(filter), this.meterRegistry, + this.properties).accept(this.setupPayload, this.sendingSocket) + .block(Duration.ofSeconds(5)); } @@ -148,29 +150,34 @@ public class GatewaySocketAcceptorTests { } @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { this.invoked = true; return doFilter(exchange, chain); } - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { return chain.filter(exchange); } - } + } private static class ShortcircuitingFilter extends TestFilter { @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { return Mono.empty(); } + } private static class AsyncFilter extends TestFilter { @Override - public Mono doFilter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono doFilter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { return doAsyncWork().flatMap(asyncResult -> { logger.debug("Async result: " + asyncResult); return chain.filter(exchange); @@ -180,26 +187,26 @@ public class GatewaySocketAcceptorTests { private Mono doAsyncWork() { return Mono.delay(Duration.ofMillis(100L)).map(l -> "123"); } + } private static class ExceptionFilter implements SocketAcceptorFilter { @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { return Mono.error(new IllegalStateException("boo")); } + } - - /*private static class TestExceptionHandler implements WebExceptionHandler { - - private Throwable ex; - - @Override - public Mono handle(SocketAcceptorExchange exchange, Throwable ex) { - this.ex = ex; - return Mono.error(ex); - } - }*/ + /* + * private static class TestExceptionHandler implements WebExceptionHandler { + * + * private Throwable ex; + * + * @Override public Mono handle(SocketAcceptorExchange exchange, Throwable ex) { + * this.ex = ex; return Mono.error(ex); } } + */ } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java index 3fd8f358..dca6e359 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/socketacceptor/SocketAcceptorPredicateFilterTests.java @@ -12,7 +12,6 @@ * 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; @@ -38,18 +37,14 @@ public class SocketAcceptorPredicateFilterTests { @Test public void noPredicateWorks() { Mono result = runFilter(Collections.emptyList()); - StepVerifier.create(result) - .expectNext(Success.INSTANCE) - .verifyComplete(); + StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete(); } @Test public void singleTruePredicateWorks() { TestPredicate predicate = new TestPredicate(true); Mono result = runFilter(predicate); - StepVerifier.create(result) - .expectNext(Success.INSTANCE) - .verifyComplete(); + StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete(); assertThat(predicate.invoked()).isTrue(); } @@ -57,8 +52,7 @@ public class SocketAcceptorPredicateFilterTests { public void singleFalsePredicateWorks() { TestPredicate predicate = new TestPredicate(false); Mono result = runFilter(predicate); - StepVerifier.create(result) - .verifyComplete(); + StepVerifier.create(result).verifyComplete(); assertThat(predicate.invoked()).isTrue(); } @@ -68,11 +62,10 @@ public class SocketAcceptorPredicateFilterTests { TestPredicate predicate = new TestPredicate(false); TestPredicate predicate2 = new TestPredicate(false); Mono result = runFilter(predicate, predicate2); - StepVerifier.create(result) - .verifyComplete(); + StepVerifier.create(result).verifyComplete(); assertThat(predicate.invoked()).isTrue(); - assertThat(predicate2.invoked()).isTrue(); //Async predicates don't short circuit + assertThat(predicate2.invoked()).isTrue(); // Async predicates don't short circuit } @Test @@ -80,8 +73,7 @@ public class SocketAcceptorPredicateFilterTests { TestPredicate truePredicate = new TestPredicate(true); TestPredicate falsePredicate = new TestPredicate(false); Mono result = runFilter(truePredicate, falsePredicate); - StepVerifier.create(result) - .verifyComplete(); + StepVerifier.create(result).verifyComplete(); assertThat(truePredicate.invoked()).isTrue(); assertThat(falsePredicate.invoked()).isTrue(); } @@ -91,9 +83,7 @@ public class SocketAcceptorPredicateFilterTests { TestPredicate truePredicate = new TestPredicate(true); TestPredicate truePredicate2 = new TestPredicate(true); Mono result = runFilter(truePredicate, truePredicate2); - StepVerifier.create(result) - .expectNext(Success.INSTANCE) - .verifyComplete(); + StepVerifier.create(result).expectNext(Success.INSTANCE).verifyComplete(); assertThat(truePredicate.invoked()).isTrue(); assertThat(truePredicate2.invoked()).isTrue(); } @@ -107,9 +97,12 @@ public class SocketAcceptorPredicateFilterTests { } private Mono runFilter(List predicates) { - SocketAcceptorPredicateFilter filter = new SocketAcceptorPredicateFilter(predicates); - SocketAcceptorExchange exchange = new SocketAcceptorExchange(mock(ConnectionSetupPayload.class), mock(RSocket.class)); - SocketAcceptorFilterChain filterChain = new SocketAcceptorFilterChain(Collections.singletonList(filter)); + 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); } @@ -119,7 +112,7 @@ public class SocketAcceptorPredicateFilterTests { private final Mono test; - public TestPredicate(boolean value) { + TestPredicate(boolean value) { test = Mono.just(value); } @@ -132,5 +125,7 @@ public class SocketAcceptorPredicateFilterTests { public boolean invoked() { return invoked; } + } + } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java index f329e79d..74138294 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/support/MetadataTests.java @@ -12,7 +12,6 @@ * 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; @@ -39,14 +38,11 @@ public class MetadataTests { @Test public void encodeAndDecodeWorks() { - ByteBuf byteBuf = Metadata.from("test1") - .with("key1111", "val111111") - .with("key22", "val222") - .encode(); + ByteBuf byteBuf = Metadata.from("test1").with("key1111", "val111111") + .with("key22", "val222").encode(); Metadata metadata = assertMetadata(byteBuf, "test1"); Map properties = metadata.getProperties(); - assertThat(properties).hasSize(2) - .containsOnlyKeys("key1111", "key22") + assertThat(properties).hasSize(2).containsOnlyKeys("key1111", "key22") .containsValues("val111111", "val222"); } @@ -82,7 +78,8 @@ public class MetadataTests { private Map metadata(int size) { Assert.isTrue(size > 0, "size must be > 0"); HashMap metadata = new HashMap<>(); - IntStream.rangeClosed(1, size).forEach(i -> metadata.put("key"+i, "val"+i)); + IntStream.rangeClosed(1, size).forEach(i -> metadata.put("key" + i, "val" + i)); return metadata; } + } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java index e8380bf0..6abe9a7a 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/PingPongApp.java @@ -12,7 +12,6 @@ * 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; @@ -90,17 +89,18 @@ public class PingPongApp { 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); + case "ping": + return "pong"; + case "pong": + return "ping"; + default: + throw new IllegalArgumentException("Value must be ping or pong, not " + in); } } @Slf4j - public static class Ping implements Ordered, ApplicationListener { + public static class Ping + implements Ordered, ApplicationListener { @Autowired private MeterRegistry meterRegistry; @@ -108,6 +108,7 @@ public class PingPongApp { private final String id; private final AtomicInteger pongsReceived = new AtomicInteger(); + private Flux pongFlux; public Ping(String id) { @@ -121,44 +122,53 @@ public class PingPongApp { @Override public void onApplicationEvent(ApplicationReadyEvent event) { - log.info("Starting Ping"+id); + log.info("Starting Ping" + id); ConfigurableEnvironment env = event.getApplicationContext().getEnvironment(); Integer take = env.getProperty("ping.take", Integer.class, null); - Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", - Integer.class, 7002); + Integer gatewayPort = env.getProperty( + "spring.cloud.gateway.rsocket.server.port", Integer.class, 7002); log.debug("ping.take: " + take); - MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "ping")); - ByteBuf announcementMetadata = Metadata.from("ping").with("id", "ping"+id).encode(); + MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor( + meterRegistry, Tag.of("component", "ping")); + ByteBuf announcementMetadata = Metadata.from("ping").with("id", "ping" + id) + .encode(); pongFlux = RSocketFactory.connect() .metadataMimeType(Metadata.ROUTING_MIME_TYPE) - .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) + .setupPayload( + DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) .addClientPlugin(interceptor) .transport(TcpClientTransport.create(gatewayPort)) // proxy - .start() - .flatMapMany(socket -> - { - Flux pong = socket.requestChannel( - Flux.interval(Duration.ofSeconds(1)) - .map(i -> { - ByteBuf data = ByteBufUtil.writeUtf8(ByteBufAllocator.DEFAULT, "ping" + id); - ByteBuf routingMetadata = Metadata.from("pong").encode(); - return DefaultPayload.create(data, routingMetadata); - }) - .onBackpressureDrop(payload -> log.debug("Dropped payload " + payload.getDataUtf8())) // this is needed in case pong is not available yet - ).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; - } - ); + .start().flatMapMany(socket -> { + Flux pong = socket.requestChannel( + Flux.interval(Duration.ofSeconds(1)).map(i -> { + ByteBuf data = ByteBufUtil.writeUtf8( + ByteBufAllocator.DEFAULT, "ping" + id); + ByteBuf routingMetadata = Metadata.from("pong") + .encode(); + return DefaultPayload.create(data, routingMetadata); + }).onBackpressureDrop(payload -> log.debug( + "Dropped payload " + payload.getDataUtf8())) // this + // is + // needed + // in + // case + // pong + // is + // not + // available + // yet + ).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; + }); pongFlux.subscribe(); } @@ -170,10 +180,12 @@ public class PingPongApp { public int getPongsReceived() { return pongsReceived.get(); } + } @Slf4j - public static class Pong implements Ordered, ApplicationListener { + public static class Pong + implements Ordered, ApplicationListener { @Autowired private MeterRegistry meterRegistry; @@ -191,22 +203,23 @@ public class PingPongApp { Integer pongDelay = env.getProperty("pong.delay", Integer.class, 5000); try { Thread.sleep(pongDelay); - } catch (InterruptedException e) { + } + catch (InterruptedException e) { e.printStackTrace(); } log.info("Starting Pong"); - Integer gatewayPort = env.getProperty("spring.cloud.gateway.rsocket.server.port", - Integer.class, 7002); - MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor(meterRegistry, Tag.of("component", "pong")); - ByteBuf announcementMetadata = Metadata.from("pong").with("id", "pong1").encode(); - RSocketFactory.connect() - .metadataMimeType(Metadata.ROUTING_MIME_TYPE) - .setupPayload(DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) - .addClientPlugin(interceptor) - .acceptor(this::accept) + Integer gatewayPort = env.getProperty( + "spring.cloud.gateway.rsocket.server.port", Integer.class, 7002); + MicrometerRSocketInterceptor interceptor = new MicrometerRSocketInterceptor( + meterRegistry, Tag.of("component", "pong")); + ByteBuf announcementMetadata = Metadata.from("pong").with("id", "pong1") + .encode(); + RSocketFactory.connect().metadataMimeType(Metadata.ROUTING_MIME_TYPE) + .setupPayload( + DefaultPayload.create(EMPTY_BUFFER, announcementMetadata)) + .addClientPlugin(interceptor).acceptor(this::accept) .transport(TcpClientTransport.create(gatewayPort)) // proxy - .start() - .block(); + .start().block(); } @SuppressWarnings("Duplicates") @@ -215,18 +228,15 @@ public class PingPongApp { @Override public Flux requestChannel(Publisher 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 = Metadata.from("ping").encode(); - return DefaultPayload.create(data, routingMetadata); - }); + 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 = Metadata.from("ping").encode(); + return DefaultPayload.create(data, routingMetadata); + }); } }; return pong; @@ -235,10 +245,12 @@ public class PingPongApp { public int getPingsReceived() { return pingsReceived.get(); } + } @Slf4j public static class MyGatewayFilter implements GatewayFilter { + private AtomicBoolean invoked = new AtomicBoolean(false); @Override @@ -251,6 +263,7 @@ public class PingPongApp { public boolean invoked() { return invoked.get(); } + } @Slf4j @@ -259,7 +272,8 @@ public class PingPongApp { private AtomicBoolean invoked = new AtomicBoolean(false); @Override - public Mono filter(SocketAcceptorExchange exchange, SocketAcceptorFilterChain chain) { + public Mono filter(SocketAcceptorExchange exchange, + SocketAcceptorFilterChain chain) { log.info("in custom socket acceptor filter"); invoked.compareAndSet(false, true); return chain.filter(exchange); @@ -273,7 +287,7 @@ public class PingPongApp { public boolean invoked() { return invoked.get(); } + } - } diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java index f9895fa6..d30c937b 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/test/SocketAcceptorFilterOrderTests.java @@ -12,7 +12,6 @@ * 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; @@ -36,12 +35,15 @@ public class SocketAcceptorFilterOrderTests { @Test public void predicateFilterAfterRegistryFilter() { - SocketAcceptorFilter predicateFilter = new SocketAcceptorPredicateFilter(Collections.emptyList()); - SocketAcceptorFilter registryFilter = new RegistrySocketAcceptorFilter(mock(Registry.class)); - List filters = Arrays.asList(predicateFilter, registryFilter); + SocketAcceptorFilter predicateFilter = new SocketAcceptorPredicateFilter( + Collections.emptyList()); + SocketAcceptorFilter registryFilter = new RegistrySocketAcceptorFilter( + mock(Registry.class)); + List filters = Arrays.asList(predicateFilter, + registryFilter); OrderComparator.sort(filters); - assertThat(filters) - .containsExactly(registryFilter, predicateFilter); + assertThat(filters).containsExactly(registryFilter, predicateFilter); } + } diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 9adff450..00a2ac62 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -3,6 +3,7 @@ "-//Puppy Crawl//DTD Suppressions 1.1//EN" "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd"> + From f6881d30409b96446db799bb4d667e1c8cdb42fc Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 21 Feb 2019 23:00:43 -0500 Subject: [PATCH 62/67] Adds rsocket dependency management --- spring-cloud-gateway-dependencies/pom.xml | 21 +++++++++++++++++++++ spring-cloud-gateway-rsocket/pom.xml | 17 ----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index b436efb9..c68d08ff 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -18,6 +18,7 @@ Spring Cloud Gateway Dependencies + 0.11.16 @@ -37,11 +38,31 @@ spring-cloud-gateway-core ${project.version} + + org.springframework.cloud + spring-cloud-gateway-rsocket + ${project.version} + org.springframework.cloud spring-cloud-starter-gateway ${project.version} + + io.rsocket + rsocket-core + ${rsocket.version} + + + io.rsocket + rsocket-micrometer + ${rsocket.version} + + + io.rsocket + rsocket-transport-netty + ${rsocket.version} + diff --git a/spring-cloud-gateway-rsocket/pom.xml b/spring-cloud-gateway-rsocket/pom.xml index e6e8a9e5..28e24b6e 100644 --- a/spring-cloud-gateway-rsocket/pom.xml +++ b/spring-cloud-gateway-rsocket/pom.xml @@ -31,8 +31,6 @@ Spring Cloud Gateway RSocket - 1.8 - 0.11.16 Californium-SR5 @@ -45,21 +43,6 @@ pom import - - io.rsocket - rsocket-core - ${rsocket.version} - - - io.rsocket - rsocket-micrometer - ${rsocket.version} - - - io.rsocket - rsocket-transport-netty - ${rsocket.version} - From 18724f447d00ac10eb3fde2a19c421208d4f5e08 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 25 Feb 2019 13:56:19 -0500 Subject: [PATCH 63/67] Flips requester and responder metadata. --- .../rsocket/server/GatewayRSocket.java | 25 +++++++++---------- .../rsocket/server/GatewayRSocketTests.java | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java index 8ec4c8df..6f89297a 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocket.java @@ -117,15 +117,15 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket private Tags getTags(GatewayExchange exchange) { // TODO: add tags to exchange - String responderName = this.metadata.getName(); - String responderId = this.metadata.get("id"); - String requestorName = exchange.getRoutingMetadata().getName(); + String requesterName = this.metadata.getName(); + String requesterId = this.metadata.get("id"); + String responderName = exchange.getRoutingMetadata().getName(); Assert.hasText(responderName, "responderName must not be empty"); - Assert.hasText(responderId, "responderId must not be empty"); - Assert.hasText(requestorName, "requestorName must not be empty"); - // requestor.id happens in a callback, later - return Tags.of("requester.name", requestorName, "responder.name", responderName, - "responder.id", responderId, "gateway.id", this.properties.getId()); + 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()); } @Override @@ -229,7 +229,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket Function> routeFinder = registeredEvent -> getRouteMono( registeredEvent, exchange); return new PendingRequestRSocket(routeFinder, map -> { - Tags tags = exchange.getTags().and("requester.id", map.get("id")); + Tags tags = exchange.getTags().and("responder.id", map.get("id")); exchange.setTags(tags); }); } @@ -240,9 +240,8 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket .log(PendingRequestRSocket.class.getName() + ".find route pending", Level.FINEST) // can this be replaced with filter? - .flatMap(route -> { - return matchRoute(route, registeredEvent.getRoutingMetadata()); - }); + .flatMap( + route -> matchRoute(route, registeredEvent.getRoutingMetadata())); } private Mono findRoute(GatewayExchange exchange) { @@ -284,7 +283,7 @@ public class GatewayRSocket extends AbstractRSocket implements ResponderRSocket return loadBalancedRSocket.choose(); }).map(enrichedRSocket -> { Metadata metadata = enrichedRSocket.getMetadata(); - Tags tags = exchange.getTags().and("requester.id", + Tags tags = exchange.getTags().and("responder.id", metadata.get("id")); exchange.setTags(tags); return enrichedRSocket; diff --git a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java index 9d2f426c..6e5e790f 100644 --- a/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java +++ b/spring-cloud-gateway-rsocket/src/test/java/org/springframework/cloud/gateway/rsocket/server/GatewayRSocketTests.java @@ -170,7 +170,7 @@ public class GatewayRSocketTests { Function> routeFinder = registeredEvent -> getRouteMono( registeredEvent, exchange); return new PendingRequestRSocket(routeFinder, map -> { - Tags tags = exchange.getTags().and("requester.id", map.get("id")); + Tags tags = exchange.getTags().and("responder.id", map.get("id")); exchange.setTags(tags); }, processor); } From 2549c3aea0e9d6a329697afec2be04890299c348 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 25 Feb 2019 14:44:19 -0500 Subject: [PATCH 64/67] Updates to rsocket-java 0.11.17 --- spring-cloud-gateway-dependencies/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index c68d08ff..1ae6a10b 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -18,7 +18,7 @@ Spring Cloud Gateway Dependencies - 0.11.16 + 0.11.17 From 42246e80a8ec8dee4f8e1752a733d329e54909da Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 26 Feb 2019 12:04:31 -0500 Subject: [PATCH 65/67] polish --- .../gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index 2854cdc8..e1bc3e02 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -56,7 +56,7 @@ public class GatewaySocketAcceptor implements SocketAcceptor { @SuppressWarnings("Duplicates") public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { - // decorate sendingSocket with metrics + // decorate GatewayRSocket with metrics // current gateway id, type requester, service name (from metadata), service id Tags requesterTags = Tags.of("gateway.id", properties.getId(), "type", From f9cb397a1f4f57d0efe7386b0a113bf0e1e4e998 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 26 Feb 2019 12:04:55 -0500 Subject: [PATCH 66/67] Guards against exceptions thrown by MeterRegistry --- .../metrics/MicrometerResponderRSocket.java | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java index dd47bde3..f698d719 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/metrics/MicrometerResponderRSocket.java @@ -28,6 +28,8 @@ 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; @@ -41,6 +43,8 @@ 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; @@ -146,22 +150,36 @@ public class MicrometerResponderRSocket implements ResponderRSocket { public void accept(SignalType signalType) { switch (signalType) { case CANCEL: - cancel.increment(); + if (this.cancel != null) { + this.cancel.increment(); + } break; case ON_COMPLETE: - onComplete.increment(); + if (this.onComplete != null) { + this.onComplete.increment(); + } break; case ON_ERROR: - onError.increment(); + if (this.onError != null) { + this.onError.increment(); + } break; } } - private static Counter counter(MeterRegistry meterRegistry, - String interactionModel, SignalType signalType, Tag... tags) { + private Counter counter(MeterRegistry meterRegistry, String interactionModel, + SignalType signalType, Tag... tags) { - return meterRegistry.counter("rsocket." + interactionModel, - Tags.of(tags).and("signal.type", signalType.name())); + 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; + } } } @@ -190,13 +208,19 @@ public class MicrometerResponderRSocket implements ResponderRSocket { public void accept(Timer.Sample sample, SignalType signalType) { switch (signalType) { case CANCEL: - sample.stop(cancel); + if (this.cancel != null) { + sample.stop(this.cancel); + } break; case ON_COMPLETE: - sample.stop(onComplete); + if (this.onComplete != null) { + sample.stop(this.onComplete); + } break; case ON_ERROR: - sample.stop(onError); + if (this.onError != null) { + sample.stop(this.onError); + } break; } } @@ -208,8 +232,16 @@ public class MicrometerResponderRSocket implements ResponderRSocket { private static Timer timer(MeterRegistry meterRegistry, String interactionModel, SignalType signalType, Tag... tags) { - return meterRegistry.timer("rsocket." + interactionModel, - Tags.of(tags).and("signal.type", signalType.name())); + 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; + } } } From 1990dbaa19bffd6ff20afb30e5fa2ac43316d39c Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 26 Feb 2019 15:01:36 -0500 Subject: [PATCH 67/67] Replaces empty tags with tags with values of UNKNOWN --- .../gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java index e1bc3e02..eee73510 100644 --- a/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java +++ b/spring-cloud-gateway-rsocket/src/main/java/org/springframework/cloud/gateway/rsocket/socketacceptor/GatewaySocketAcceptor.java @@ -74,7 +74,8 @@ public class GatewaySocketAcceptor implements SocketAcceptor { decorate(sendingSocket, requesterTags.and(metadataTags)), metadata); } else { - metadataTags = Tags.empty(); + metadataTags = Tags.of("service.name", "UNKNOWN").and("service.id", + "UNKNOWN"); exchange = new SocketAcceptorExchange(setup, decorate(sendingSocket, requesterTags)); }