diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..1ef0730a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml new file mode 100644 index 00000000..0bde8aec --- /dev/null +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -0,0 +1,10 @@ +name: "Validate Gradle Wrapper" +on: [push, pull_request] + +jobs: + validation: + name: "Gradle wrapper validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..1c2ef26f --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,39 @@ +name: PR Build + +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: [8, 11, 15] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + - uses: actions/cache@v2.1.4 + id: gradle-cache + with: + path: | + ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + - uses: actions/cache@v2.1.4 + id: gradle-wrapper-cache + with: + path: | + ~/.gradle/wrapper + key: ${{ runner.os }}-gradlewrapper-${{ hashFiles('gradle/wrapper/*') }} + - name: Build + run: ./gradlew build + validation: + name: "Gradle Validation" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..d964c850 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,49 @@ +name: Release + +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+ + - v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+ + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: 8 + - uses: actions/cache@v2.1.4 + id: gradle-cache + with: + path: | + ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + - uses: actions/cache@v2.1.4 + id: gradle-wrapper-cache + with: + path: | + ~/.gradle/wrapper + key: ${{ runner.os }}-gradlewrapper-${{ hashFiles('gradle/wrapper/*') }} + - name: Build candidate + if: contains(github.ref, '-rc.') + run: ./gradlew --info --stacktrace -Prelease.useLastTag=true candidate + env: + NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} + NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} + NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} + NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} + - name: Build release + if: (!contains(github.ref, '-rc.')) + run: ./gradlew --info -Prelease.useLastTag=true final + env: + NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }} + NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }} + NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} + NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} + NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} + NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 00000000..bf13e660 --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,38 @@ +name: Snapshot + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: 8 + - uses: actions/cache@v2.1.4 + id: gradle-cache + with: + path: | + ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + - uses: actions/cache@v2.1.4 + id: gradle-wrapper-cache + with: + path: | + ~/.gradle/wrapper + key: ${{ runner.os }}-gradlewrapper-${{ hashFiles('gradle/wrapper/*') }} + - name: Build + run: ./gradlew build snapshot + env: + NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }} + NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }} + NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }} + NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }} diff --git a/.gitignore b/.gitignore index 70bdbb95..a169682b 100644 --- a/.gitignore +++ b/.gitignore @@ -61,5 +61,10 @@ atlassian-ide-plugin.xml .settings .metadata +zuul-sample/src/main/generated/ + # NetBeans specific files/directories .nbattrs + +# publishing secrets +secrets/signing-key diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2337f8d0..00000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: java -jdk: - - openjdk8 - - openjdk11 -install: "./installViaTravis.sh" -script: "./buildViaTravis.sh" -cache: - directories: - - "$HOME/.gradle" -env: - global: - - secure: Hrc3t9ev/oGkIkeHk+vSYeqvIN5kW6mhtSLovvhyR3KV2TRBqjHdWW3c5MDgvetZu2s/DgUnQNtkHWrDpK+tXpT8gbJda+c2MkjmkZZH6hoVF2ASSWauDjBt8lbXcANDi2JJI3pMYxgK+H3Y9MgP1Sn+1FPdYm1MiUSpCT7Sxzc= - - secure: PW/r0i02LqX8z/GAipforVN7Xj51F+H3oIzMxoJByM++CYp6Nx+EVym04+1rkHKVFbq7IXFdqe5pji3xrVsEilaB9jVDbmB2WdSmNxYvTHNTFtBmbmYMylc+hmV9pPh66JctyE9urq1q3x/e3stE1vVPHwqty8yDPCopsmlJRnc= - - secure: FyNZfd3ML1DEgEfvORzVgBS3w8T7MKhcJVCuWJeAk+7oO4oVZdW9Es+xM1ylbZxpSQEDJlXopsjUXHaICYXGU3LvMsWtbm5LEJLBfLTVs0vlwZev2Us9KVChUf3JhjDmQVGOJL0+P+spjacN31xt3CL1qDkwSShvX2rX2RiRInw= - - secure: Ak7IXZVWhEricCsiVH1Pbqjp8ywIvqB1FBxdQlZ+GkZzRekxa51BI9ZTLwuWfQvqJgmb+IXKWg7mvHu7DVFFCRRSb2C70C0CZICbIu2C9Xe5g15k4aFw/0vz+a7qVdvT+Q/ZlQ4vlxdcAt7Oh/MyUZ6f3+jO7w45v+8a04spmgY= diff --git a/build.gradle b/build.gradle index 475b03c6..5e38389b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,9 @@ plugins { - id 'nebula.netflixoss' version '8.0.0' - id 'nebula.dependency-lock' version '8.0.0' + id 'nebula.netflixoss' version '9.1.0' + id 'nebula.dependency-lock' version '10.1.0' id "com.google.osdetector" version "1.6.2" + id "me.champeau.gradle.jmh" version "0.5.0" } ext.githubProjectName = rootProject.name @@ -19,39 +20,34 @@ configurations.all { exclude group: 'asm', module: 'asm-all' } +allprojects { + repositories { + mavenCentral() + } +} + subprojects { apply plugin: 'nebula.netflixoss' apply plugin: 'java' - apply plugin: 'groovy' apply plugin: 'nebula.javadoc-jar' apply plugin: 'nebula.dependency-lock' - - repositories { - jcenter() - mavenLocal() - } + apply plugin: 'me.champeau.gradle.jmh' license { ignoreFailures = false + excludes([ + "**/META-INF/services/javax.annotation.processing.Processor", + "**/META-INF/gradle/incremental.annotation.processors", + "**/*.cert", + "**/*.jks", + "**/*.key", + ]) } group = "com.netflix.${githubProjectName}" sourceCompatibility = '1.8' - sourceSets { - test { - java { - srcDirs = ['src/main/java', 'src/test/java'] - include '**/*.java' - } - groovy { - srcDirs = ['filters', 'src/main/groovy'] - include '**/*.groovy' - } - } - } - eclipse { classpath { downloadSources = true @@ -61,21 +57,20 @@ subprojects { tasks.withType(Javadoc).each { it.classpath = sourceSets.main.compileClasspath + // Ignore Javadoc warnings for now, re-enable after Zuul 3. + it.options.addStringOption('Xdoclint:none', '-quiet') } - dependencies { - ext.versions_guice = "4.2.2" - - // Use guice-4 while debugging a startup error that is hidden by guice-3's lack of java8 lambda support. - compile(group: 'com.google.inject', name: 'guice', version: "${versions_guice}") - compile(group: 'com.google.inject.extensions', name: 'guice-multibindings', version: "${versions_guice}") - compile(group: 'com.google.inject.extensions', name: 'guice-grapher', version: "${versions_guice}") - compile(group: 'com.google.inject.extensions', name: 'guice-assistedinject', version: "${versions_guice}") - compile(group: 'com.google.inject.extensions', name: 'guice-servlet', version: "${versions_guice}") - compile(group: 'com.google.inject.extensions', name: 'guice-throwingproviders', version: "${versions_guice}") + ext { + libraries = [ + guava: "com.google.guava:guava:29.0-jre", + junit: "junit:junit:4.13", + mockito: 'org.mockito:mockito-core:3.+', + slf4j: "org.slf4j:slf4j-api:1.7.25", + truth: 'com.google.truth:truth:1.0.1' + ] } - test { testLogging { showStandardStreams = true diff --git a/buildViaTravis.sh b/buildViaTravis.sh deleted file mode 100755 index 014d5e0a..00000000 --- a/buildViaTravis.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# This script will build the project. - -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" - ./gradlew build -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then - echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' - ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" -Prelease.scope=patch build snapshot -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then - echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' - case "$TRAVIS_TAG" in - *-rc\.*) - ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" candidate - ;; - *) - ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final - ;; - esac -else - echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' - ./gradlew build -fi diff --git a/dependencies.lock b/dependencies.lock index 544b7b4d..1ad8d353 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,3 +1,26 @@ { - + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhCompileClasspath": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhRuntimeClasspath": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index ca8c932a..552b2b43 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ -versions_groovy=2.4.4 -versions_ribbon=2.2.4 -versions_netty=4.1.45.Final +versions_groovy=3.0.3 +versions_ribbon=2.4.4 +versions_netty=4.1.63.Final release.scope=patch +release.version=2.3.1-SNAPSHOT diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738cb..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7c4388a9..2a563242 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708ff..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d5937..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/installViaTravis.sh b/installViaTravis.sh deleted file mode 100755 index 68e45a05..00000000 --- a/installViaTravis.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# This script will build the project. - -if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then - echo -e "Assemble Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" - ./gradlew assemble -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then - echo -e 'Assemble Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' - ./gradlew -Prelease.travisci=true assemble -elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then - echo -e 'Assemble Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' - ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true assemble -else - echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' - ./gradlew assemble -fi diff --git a/settings.gradle b/settings.gradle index 72506e6c..7e8dcbc0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,9 @@ rootProject.name='zuul' include 'zuul-core' +include 'zuul-groovy' +include 'zuul-guice' +include 'zuul-processor' include 'zuul-sample' +include 'zuul-discovery' + diff --git a/zuul-core/build.gradle b/zuul-core/build.gradle index eadbea2b..144b95e7 100644 --- a/zuul-core/build.gradle +++ b/zuul-core/build.gradle @@ -1,64 +1,50 @@ -apply plugin: "groovy" apply plugin: "com.google.osdetector" +apply plugin: "java-library" dependencies { - compile "log4j:log4j:1.2.17" - compile "commons-io:commons-io:2.4" - compile "commons-fileupload:commons-fileupload:1.3" - compile 'commons-collections:commons-collections:3.2.2' - compile 'commons-configuration:commons-configuration:1.8' - compile "org.apache.commons:commons-lang3:3.4" - compile "com.google.guava:guava:28.1-jre" - compile "org.codehaus.groovy:groovy-all:${versions_groovy}" - compile "org.slf4j:slf4j-api:1.7.25" - compile "org.json:json:20090211" - compile 'org.bouncycastle:bcpg-jdk15on:1.+' - compile 'org.bouncycastle:bcprov-jdk15on:1.+' - compile 'com.fasterxml.jackson.core:jackson-core:2.9.8' - compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8' + implementation libraries.guava + // TODO(carl-mastrangelo): this can be implementation; remove Logger from public api points. + api libraries.slf4j + implementation 'org.bouncycastle:bcprov-jdk15on:1.+' + implementation 'com.fasterxml.jackson.core:jackson-core:2.12.1' + api 'com.fasterxml.jackson.core:jackson-databind:2.12.1' - compile "com.netflix.archaius:archaius-core:0.7.5" - compile "com.netflix.servo:servo-core:0.7.2" - compile "com.netflix.spectator:spectator-api:0.59.0" - compile "com.netflix.netflix-commons:netflix-commons-util:0.3.0" + api "com.netflix.archaius:archaius-core:0.7.5" + api "com.netflix.spectator:spectator-api:0.123.1" + api "com.netflix.netflix-commons:netflix-commons-util:0.3.0" - compile "com.netflix.ribbon:ribbon-core:${versions_ribbon}" - compile "com.netflix.ribbon:ribbon-httpclient:${versions_ribbon}" - compile "com.netflix.ribbon:ribbon-loadbalancer:${versions_ribbon}" - compile "com.netflix.ribbon:ribbon-eureka:${versions_ribbon}" - compile "com.netflix.eureka:eureka-client:1.9.4" - compile "io.reactivex:rxjava:1.2.1" + api project(":zuul-discovery") - compile "io.netty:netty-common:${versions_netty}" - compile "io.netty:netty-buffer:${versions_netty}" - compile "io.netty:netty-codec-http:${versions_netty}" - compile "io.netty:netty-codec-http2:${versions_netty}" - compile "io.netty:netty-codec-haproxy:${versions_netty}" - compile "io.netty:netty-handler:${versions_netty}" - compile "io.netty:netty-resolver:${versions_netty}" - compile "io.netty:netty-transport:${versions_netty}" - compile "io.netty:netty-transport-native-epoll:${versions_netty}:linux-x86_64" - compile "io.netty:netty-transport-native-kqueue:${versions_netty}:osx-x86_64" - runtime "io.netty:netty-tcnative-boringssl-static:2.0.28.Final" + api "com.netflix.ribbon:ribbon-core:${versions_ribbon}" + api "com.netflix.ribbon:ribbon-archaius:${versions_ribbon}" - // To ensure that zuul-netty gets this correct later version. - compile "com.netflix.governator:governator:1.+" - compile "com.netflix.governator:governator-archaius:1.+" - compile "com.netflix.governator:governator-core:1.+" + api "com.netflix.eureka:eureka-client:1.9.18" + api "io.reactivex:rxjava:1.2.1" - compile "junit:junit:latest.release" - compile "org.mockito:mockito-core:1.9.+" + // TODO(carl-mastrangelo): some of these could probably be implementation. Do a deeper check. + api "io.netty:netty-common:${versions_netty}" + api "io.netty:netty-buffer:${versions_netty}" + api "io.netty:netty-codec-http:${versions_netty}" + api "io.netty:netty-codec-http2:${versions_netty}" + api "io.netty:netty-handler:${versions_netty}" + api "io.netty:netty-transport:${versions_netty}" - compile 'io.perfmark:perfmark-api:0.20.1' + implementation "io.netty:netty-codec-haproxy:${versions_netty}" + implementation "io.netty:netty-transport-native-epoll:${versions_netty}:linux-x86_64" + implementation "io.netty:netty-transport-native-kqueue:${versions_netty}:osx-x86_64" + runtimeOnly "io.netty:netty-tcnative-boringssl-static:2.0.38.Final" - testCompile "com.netflix.governator:governator-test-junit:1.+" - testCompile 'junit:junit:4.13-rc-1' - testRuntime 'org.slf4j:slf4j-simple:1.7.29' -} + implementation 'io.perfmark:perfmark-api:0.23.0' + implementation 'javax.inject:javax.inject:1' + + testImplementation libraries.junit, + libraries.mockito, + libraries.truth + testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.29' -jar { - from sourceSets.main.allGroovy + jmh 'org.openjdk.jmh:jmh-core:1.+' + jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.+' } // Silences log statements during tests. This still allows normal failures to be printed. @@ -67,3 +53,15 @@ test { showStandardStreams = false } } + +// ./gradlew --no-daemon clean :zuul-core:jmh +jmh { + profilers = ["gc"] + timeOnIteration = "1s" + warmup = "1s" + fork = 1 + warmupIterations = 10 + iterations 5 + // Not sure why duplicate classes are aon the path. Something Nebula related I think. + duplicateClassesStrategy = 'EXCLUDE' +} diff --git a/zuul-core/dependencies.lock b/zuul-core/dependencies.lock index 8e0a474c..41e52f0a 100644 --- a/zuul-core/dependencies.lock +++ b/zuul-core/dependencies.lock @@ -1,1720 +1,598 @@ { - "compile": { + "compileClasspath": { "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "29.0-jre" }, "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" + "locked": "0.7.5" }, "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" + "locked": "0.3.0" }, - "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" + "locked": "0.123.1" }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" + "com.netflix.zuul:zuul-discovery": { + "project": true }, "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" - }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" + "locked": "1.2.1" }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "javax.inject:javax.inject": { + "locked": "1" }, "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" - }, - "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" + "locked": "1.68" }, "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" + "locked": "1.7.25" } }, - "compileClasspath": { - "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" - }, - "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.netflix.archaius:archaius-core": { - "locked": "0.7.5", - "requested": "0.7.5" - }, - "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.29" }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.7.2", - "requested": "0.7.2" - }, - "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" - }, - "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" - }, - "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" - }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "org.openjdk.jmh:jmh-generator-annprocess": { + "locked": "1.29" }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" - }, - "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" - }, - "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" } }, - "default": { + "jmhCompileClasspath": { "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "29.0-jre" }, "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" + "locked": "0.7.5" }, "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" + "locked": "0.3.0" }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" + "locked": "0.123.1" }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" + "com.netflix.zuul:zuul-discovery": { + "project": true }, "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "locked": "2.0.28.Final", - "requested": "2.0.28.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" + "locked": "1.2.1" }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "javax.inject:javax.inject": { + "locked": "1" }, "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "locked": "1.68" }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" + "org.openjdk.jmh:jmh-core": { + "locked": "1.29" }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" + "org.openjdk.jmh:jmh-generator-annprocess": { + "locked": "1.29" }, - "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" }, "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" + "locked": "1.7.25" } }, - "runtime": { + "jmhRuntimeClasspath": { "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "com.google.truth:truth": { + "locked": "1.0.1" }, "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" + "locked": "0.7.6" }, "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" + "locked": "0.123.1" }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" + "com.netflix.zuul:zuul-discovery": { + "project": true }, "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-tcnative-boringssl-static": { - "locked": "2.0.28.Final", - "requested": "2.0.28.Final" + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" + "locked": "1.2.1" }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" + "javax.inject:javax.inject": { + "locked": "1" }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "junit:junit": { + "locked": "4.13.1" }, "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" + "locked": "1.68" }, "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" - }, - "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" - } - }, - "runtimeClasspath": { - "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" - }, - "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "3.9.0" }, - "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" - }, - "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" + "org.openjdk.jmh:jmh-core": { + "locked": "1.29" }, - "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" + "org.openjdk.jmh:jmh-generator-annprocess": { + "locked": "1.29" }, - "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" - }, - "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" - }, - "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "locked": "2.0.28.Final", - "requested": "2.0.28.Final" - }, - "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" - }, - "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" - }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" - }, - "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" }, "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + }, + "org.slf4j:slf4j-simple": { + "locked": "1.7.29" } }, - "testCompile": { + "runtimeClasspath": { "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" }, "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" + "locked": "0.7.6" }, "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-test-junit": { - "locked": "1.17.10", - "requested": "1.+" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" + "locked": "0.123.1" }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" + "com.netflix.zuul:zuul-discovery": { + "project": true }, "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "io.netty:netty-tcnative-boringssl-static": { + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" + "locked": "1.2.1" }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "javax.inject:javax.inject": { + "locked": "1" }, "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" - }, - "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" + "locked": "1.68" }, "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" } }, "testCompileClasspath": { "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "29.0-jre" }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "com.google.truth:truth": { + "locked": "1.0.1" }, "com.netflix.archaius:archaius-core": { - "locked": "0.7.5", - "requested": "0.7.5" + "locked": "0.7.5" }, "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-test-junit": { - "locked": "1.17.10", - "requested": "1.+" + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.7.2", - "requested": "0.7.2" - }, - "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" - }, - "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "0.3.0" }, - "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" - }, - "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" - }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" - }, - "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" - }, - "org.slf4j:slf4j-api": { - "locked": "1.7.25", - "requested": "1.7.25" - } - }, - "testRuntime": { - "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" - }, - "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" - }, - "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-test-junit": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" + "locked": "0.123.1" }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" + "com.netflix.zuul:zuul-discovery": { + "project": true }, "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "locked": "2.0.28.Final", - "requested": "2.0.28.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" - }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" + "locked": "1.2.1" }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" + "javax.inject:javax.inject": { + "locked": "1" }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "junit:junit": { + "locked": "4.13.1" }, "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" + "locked": "1.68" }, "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" + "locked": "3.9.0" }, "org.slf4j:slf4j-api": { - "locked": "1.7.29", - "requested": "1.7.25" - }, - "org.slf4j:slf4j-simple": { - "locked": "1.7.29", - "requested": "1.7.29" + "locked": "1.7.25" } }, "testRuntimeClasspath": { "com.fasterxml.jackson.core:jackson-core": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { - "locked": "2.9.8", - "requested": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { - "locked": "28.1-jre", - "requested": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "locked": "4.2.2", - "requested": "4.2.2" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" }, - "com.google.inject:guice": { - "locked": "4.2.2", - "requested": "4.2.2" + "com.google.truth:truth": { + "locked": "1.0.1" }, "com.netflix.archaius:archaius-core": { - "locked": "0.7.6", - "requested": "0.7.5" + "locked": "0.7.6" }, "com.netflix.eureka:eureka-client": { - "locked": "1.9.4", - "requested": "1.9.4" - }, - "com.netflix.governator:governator": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-archaius": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-core": { - "locked": "1.17.10", - "requested": "1.+" - }, - "com.netflix.governator:governator-test-junit": { - "locked": "1.17.10", - "requested": "1.+" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { - "locked": "0.3.0", - "requested": "0.3.0" + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-core": { - "locked": "2.2.4", - "requested": "2.2.4" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-eureka": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "locked": "2.2.4", - "requested": "2.2.4" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-loadbalancer": { - "locked": "2.2.4", - "requested": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "locked": "0.12.21", - "requested": "0.7.2" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { - "locked": "0.59.0", - "requested": "0.59.0" - }, - "commons-collections:commons-collections": { - "locked": "3.2.2", - "requested": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "locked": "1.8", - "requested": "1.8" + "locked": "0.123.1" }, - "commons-fileupload:commons-fileupload": { - "locked": "1.3", - "requested": "1.3" - }, - "commons-io:commons-io": { - "locked": "2.4", - "requested": "2.4" + "com.netflix.zuul:zuul-discovery": { + "project": true }, "io.netty:netty-buffer": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-tcnative-boringssl-static": { - "locked": "2.0.28.Final", - "requested": "2.0.28.Final" + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { - "locked": "4.1.45.Final", - "requested": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { - "locked": "0.20.1", - "requested": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { - "locked": "1.2.1", - "requested": "1.2.1" + "locked": "1.2.1" }, - "junit:junit": { - "locked": "4.13", - "requested": "latest.release" + "javax.inject:javax.inject": { + "locked": "1" }, - "log4j:log4j": { - "locked": "1.2.17", - "requested": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "locked": "3.4", - "requested": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "locked": "1.64", - "requested": "1.+" + "junit:junit": { + "locked": "4.13.1" }, "org.bouncycastle:bcprov-jdk15on": { - "locked": "1.64", - "requested": "1.+" - }, - "org.codehaus.groovy:groovy-all": { - "locked": "2.4.4", - "requested": "2.4.4" - }, - "org.json:json": { - "locked": "20090211", - "requested": "20090211" + "locked": "1.68" }, "org.mockito:mockito-core": { - "locked": "1.9.5", - "requested": "1.9.+" + "locked": "3.9.0" }, "org.slf4j:slf4j-api": { - "locked": "1.7.29", - "requested": "1.7.25" + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" }, "org.slf4j:slf4j-simple": { - "locked": "1.7.29", - "requested": "1.7.29" + "locked": "1.7.29" } } } \ No newline at end of file diff --git a/zuul-core/src/jmh/java/com/netflix/zuul/message/HeadersBenchmark.java b/zuul-core/src/jmh/java/com/netflix/zuul/message/HeadersBenchmark.java new file mode 100644 index 00000000..b4a03e45 --- /dev/null +++ b/zuul-core/src/jmh/java/com/netflix/zuul/message/HeadersBenchmark.java @@ -0,0 +1,167 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.message; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; + + +@State(Scope.Thread) +public class HeadersBenchmark { + + @State(Scope.Thread) + public static class AddHeaders { + @Param({"0", "1", "5", "10", "30"}) + public int count; + + @Param({"10"}) + public int nameLength; + + private String[] stringNames; + private HeaderName[] names; + private String[] values; + + @Setup + public void setUp() { + stringNames = new String[count]; + names = new HeaderName[stringNames.length]; + values = new String[stringNames.length]; + for (int i = 0; i < stringNames.length; i++) { + UUID uuid = new UUID(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong()); + String name = uuid.toString(); + assert name.length() >= nameLength; + name = name.substring(0, nameLength); + names[i] = new HeaderName(name); + stringNames[i] = name; + values[i] = name; + } + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Headers addHeaders_string() { + Headers headers = new Headers(); + for (int i = 0; i < count; i++) { + headers.add(stringNames[i], values[i]); + } + return headers; + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Headers addHeaders_headerName() { + Headers headers = new Headers(); + for (int i = 0; i < count; i++) { + headers.add(names[i], values[i]); + } + return headers; + } + } + + + @State(Scope.Thread) + public static class GetSetHeaders { + @Param({"1", "5", "10", "30"}) + public int count; + + @Param({"10"}) + public int nameLength; + + private String[] stringNames; + private HeaderName[] names; + private String[] values; + Headers headers; + + @Setup + public void setUp() { + headers = new Headers(); + stringNames = new String[count]; + names = new HeaderName[stringNames.length]; + values = new String[stringNames.length]; + for (int i = 0; i < stringNames.length; i++) { + UUID uuid = new UUID(ThreadLocalRandom.current().nextLong(), ThreadLocalRandom.current().nextLong()); + String name = uuid.toString(); + assert name.length() >= nameLength; + name = name.substring(0, nameLength); + names[i] = new HeaderName(name); + stringNames[i] = name; + values[i] = name; + headers.add(names[i], values[i]); + } + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void setHeader_first() { + headers.set(names[0], "blah"); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void setHeader_last() { + headers.set(names[count - 1], "blah"); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public List getHeader_first() { + return headers.getAll(names[0]); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public List getHeader_last() { + return headers.getAll(names[count - 1]); + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public void entries(Blackhole blackhole) { + for (Header header : headers.entries()) { + blackhole.consume(header); + } + } + + + + } + + @Benchmark + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public Headers newHeaders() { + return new Headers(); + } + +} \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/netty/common/CloseOnIdleStateHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/CloseOnIdleStateHandler.java index c2a4f91d..b3268d4c 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/CloseOnIdleStateHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/CloseOnIdleStateHandler.java @@ -1,20 +1,19 @@ /** * Copyright 2018 Netflix, Inc. - * - * 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. + *

+ * 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 com.netflix.netty.common; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Registry; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.timeout.IdleStateEvent; @@ -22,14 +21,20 @@ /** * Just listens for the IdleStateEvent and closes the channel if received. */ -public class CloseOnIdleStateHandler extends ChannelInboundHandlerAdapter -{ +public class CloseOnIdleStateHandler extends ChannelInboundHandlerAdapter { + + private final Counter counter; + + public CloseOnIdleStateHandler(Registry registry, String metricId) { + this.counter = registry.counter("server.connections.idle.timeout", "id", metricId); + } + @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception - { + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { super.userEventTriggered(ctx, evt); if (evt instanceof IdleStateEvent) { + counter.increment(); ctx.close(); } } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/HttpLifecycleChannelHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/HttpLifecycleChannelHandler.java index 5b04f222..bd41acda 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/HttpLifecycleChannelHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/HttpLifecycleChannelHandler.java @@ -16,6 +16,7 @@ package com.netflix.netty.common; +import com.google.common.annotations.VisibleForTesting; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; import io.netty.channel.Channel; @@ -37,12 +38,14 @@ public abstract class HttpLifecycleChannelHandler { public static final AttributeKey ATTR_HTTP_REQ = AttributeKey.newInstance("_http_request"); public static final AttributeKey ATTR_HTTP_RESP = AttributeKey.newInstance("_http_response"); - + public static final AttributeKey ATTR_HTTP_PIPELINE_REJECT = AttributeKey.newInstance("_http_pipeline_reject"); + protected enum State { STARTED, COMPLETED } - private static final AttributeKey ATTR_STATE = AttributeKey.newInstance("_httplifecycle_state"); + @VisibleForTesting + protected static final AttributeKey ATTR_STATE = AttributeKey.newInstance("_httplifecycle_state"); protected static boolean fireStartEvent(ChannelHandlerContext ctx, HttpRequest request) { @@ -56,8 +59,8 @@ protected static boolean fireStartEvent(ChannelHandlerContext ctx, HttpRequest r // without waiting for the response from the first. And we don't support HTTP Pipelining. LOG.error("Received a http request on connection where we already have a request being processed. " + "Closing the connection now. channel = " + channel.id().asLongText()); + channel.attr(ATTR_HTTP_PIPELINE_REJECT).set(Boolean.TRUE); channel.close(); - ctx.pipeline().fireUserEventTriggered(new RejectedPipeliningEvent()); return false; } @@ -105,7 +108,7 @@ public enum CompleteReason // IDLE, DISCONNECT, DEREGISTER, -// PIPELINE_REJECT, + PIPELINE_REJECT, EXCEPTION, CLOSE // FAILURE_CLIENT_CANCELLED, @@ -175,7 +178,5 @@ public HttpResponse getResponse() return response; } } - - public static class RejectedPipeliningEvent - {} + } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/HttpRequestReadTimeoutHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/HttpRequestReadTimeoutHandler.java index 6dc26ad0..43639ee7 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/HttpRequestReadTimeoutHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/HttpRequestReadTimeoutHandler.java @@ -16,7 +16,7 @@ package com.netflix.netty.common; -import com.netflix.servo.monitor.BasicCounter; +import com.netflix.spectator.api.Counter; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; import io.netty.channel.ChannelHandlerContext; @@ -44,9 +44,9 @@ public class HttpRequestReadTimeoutHandler extends ChannelInboundHandlerAdapter private final long timeout; private final TimeUnit unit; - private final BasicCounter httpRequestReadTimeoutCounter; + private final Counter httpRequestReadTimeoutCounter; - protected HttpRequestReadTimeoutHandler(long timeout, TimeUnit unit, BasicCounter httpRequestReadTimeoutCounter) + protected HttpRequestReadTimeoutHandler(long timeout, TimeUnit unit, Counter httpRequestReadTimeoutCounter) { this.timeout = timeout; this.unit = unit; @@ -56,11 +56,8 @@ protected HttpRequestReadTimeoutHandler(long timeout, TimeUnit unit, BasicCounte /** * Factory which ensures that this handler is added to the pipeline using the * correct name. - * - * @param timeout - * @param unit */ - public static void addLast(ChannelPipeline pipeline, long timeout, TimeUnit unit, BasicCounter httpRequestReadTimeoutCounter) + public static void addLast(ChannelPipeline pipeline, long timeout, TimeUnit unit, Counter httpRequestReadTimeoutCounter) { HttpRequestReadTimeoutHandler handler = new HttpRequestReadTimeoutHandler(timeout, unit, httpRequestReadTimeoutCounter); pipeline.addLast(HANDLER_NAME, handler); diff --git a/zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java index 8773e3fa..d5ae0e76 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java @@ -107,9 +107,12 @@ public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { addPassportState(ctx, PassportState.SERVER_CH_CLOSE); - - fireCompleteEventIfNotAlready(ctx, CompleteReason.CLOSE); - + // This will likely expand based on more specific reasons for completion + if (ctx.channel().attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT).get() == null) { + fireCompleteEventIfNotAlready(ctx, CompleteReason.CLOSE); + } else { + fireCompleteEventIfNotAlready(ctx, CompleteReason.PIPELINE_REJECT); + } super.close(ctx, promise); } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/SourceAddressChannelHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/SourceAddressChannelHandler.java index 78450876..45485432 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/SourceAddressChannelHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/SourceAddressChannelHandler.java @@ -32,46 +32,42 @@ import javax.annotation.Nullable; /** - * Stores the source IP address as an attribute of the channel. This has the advantage of allowing - * us to overwrite it if we have more info (eg. ELB sends a HAProxyMessage with info of REAL source - * host + port). - * - * User: michaels@netflix.com - * Date: 4/14/16 - * Time: 4:29 PM + * Stores the source IP address as an attribute of the channel. This has the advantage of allowing us to overwrite it if + * we have more info (eg. ELB sends a HAProxyMessage with info of REAL source host + port). + *

+ * User: michaels@netflix.com Date: 4/14/16 Time: 4:29 PM */ @ChannelHandler.Sharable public final class SourceAddressChannelHandler extends ChannelInboundHandlerAdapter { + /** - * Indicates the actual source (remote) address of the channel. This can be different than the - * one {@link Channel} returns if the connection is being proxied. (e.g. over HAProxy) + * Indicates the actual source (remote) address of the channel. This can be different than the one {@link Channel} + * returns if the connection is being proxied. (e.g. over HAProxy) */ public static final AttributeKey ATTR_REMOTE_ADDR = AttributeKey.newInstance("_remote_addr"); - /** Use {@link #ATTR_REMOTE_ADDR} instead. */ + /** + * Indicates the destination address received from Proxy Protocol. Not set otherwise + */ + public static final AttributeKey ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS = + AttributeKey.newInstance("_proxy_protocol_destination_address"); + + /** + * Use {@link #ATTR_REMOTE_ADDR} instead. + */ @Deprecated public static final AttributeKey ATTR_SOURCE_INET_ADDR = AttributeKey.newInstance("_source_inet_addr"); /** - * The host address of the source. This is derived from {@link #ATTR_REMOTE_ADDR}. If the - * address is an IPv6 address, the scope identifier is absent. + * The host address of the source. This is derived from {@link #ATTR_REMOTE_ADDR}. If the address is an IPv6 + * address, the scope identifier is absent. */ public static final AttributeKey ATTR_SOURCE_ADDRESS = AttributeKey.newInstance("_source_address"); /** - * Indicates the actual source (remote) port of the channel, if present. This can be different - * than the one {@link Channel} returns if the connection is being proxies. (e.g. over - * HAProxy). - * @deprecated use {@link #ATTR_REMOTE_ADDR} instead, and check if it is an {@code - * InetSocketAddress}. - */ - @Deprecated - public static final AttributeKey ATTR_SOURCE_PORT = AttributeKey.newInstance("_source_port"); - - /** - * Indicates the local address of the channel. This can be different than the - * one {@link Channel} returns if the connection is being proxied. (e.g. over HAProxy) + * Indicates the local address of the channel. This can be different than the one {@link Channel} returns if the + * connection is being proxied. (e.g. over HAProxy) */ public static final AttributeKey ATTR_LOCAL_ADDR = AttributeKey.newInstance("_local_addr"); @@ -83,33 +79,27 @@ public final class SourceAddressChannelHandler extends ChannelInboundHandlerAdap AttributeKey.newInstance("_local_inet_addr"); /** - * The local address of this channel. This is derived from {@code channel.localAddress()}, or from the - * Proxy Protocol preface if provided. If the address is an IPv6 address, the scope identifier is absent. - * Unlike {@link #ATTR_SERVER_LOCAL_ADDRESS}, this value is overwritten with the Proxy Protocol local address - * (e.g. the LB's local address), if enabled. + * The local address of this channel. This is derived from {@code channel.localAddress()}, or from the Proxy + * Protocol preface if provided. If the address is an IPv6 address, the scope identifier is absent. Unlike {@link + * #ATTR_SERVER_LOCAL_ADDRESS}, this value is overwritten with the Proxy Protocol local address (e.g. the LB's local + * address), if enabled. */ public static final AttributeKey ATTR_LOCAL_ADDRESS = AttributeKey.newInstance("_local_address"); /** - * The port number of the local socket, or {@code -1} if not appropriate. Use {@link #ATTR_LOCAL_ADDR} instead. - */ - @Deprecated - public static final AttributeKey ATTR_LOCAL_PORT = AttributeKey.newInstance("_local_port"); - - - /** - * The actual local address of the channel, in string form. If the address is an IPv6 address, the scope - * identifier is absent. Unlike {@link #ATTR_LOCAL_ADDRESS}, this is not overwritten by the Proxy Protocol message - * if present. + * The actual local address of the channel, in string form. If the address is an IPv6 address, the scope identifier + * is absent. Unlike {@link #ATTR_LOCAL_ADDRESS}, this is not overwritten by the Proxy Protocol message if + * present. * * @deprecated Use {@code channel.localAddress()} instead. */ @Deprecated - public static final AttributeKey ATTR_SERVER_LOCAL_ADDRESS = AttributeKey.newInstance("_server_local_address"); + public static final AttributeKey ATTR_SERVER_LOCAL_ADDRESS = + AttributeKey.newInstance("_server_local_address"); /** - * The port number of the local socket, or {@code -1} if not appropriate. Unlike {@link #ATTR_LOCAL_PORT}, this - * is not overwritten by the Proxy Protocol message if present. + * The port number of the local socket, or {@code -1} if not appropriate. This is not overwritten by the Proxy + * Protocol message if present. * * @deprecated Use {@code channel.localAddress()} instead. */ @@ -117,19 +107,15 @@ public final class SourceAddressChannelHandler extends ChannelInboundHandlerAdap public static final AttributeKey ATTR_SERVER_LOCAL_PORT = AttributeKey.newInstance("_server_local_port"); @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception - { + public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.channel().attr(ATTR_REMOTE_ADDR).set(ctx.channel().remoteAddress()); InetSocketAddress sourceAddress = sourceAddress(ctx.channel()); ctx.channel().attr(ATTR_SOURCE_INET_ADDR).setIfAbsent(sourceAddress); ctx.channel().attr(ATTR_SOURCE_ADDRESS).setIfAbsent(getHostAddress(sourceAddress)); - ctx.channel().attr(ATTR_SOURCE_PORT).setIfAbsent(sourceAddress.getPort()); - ctx.channel().attr(ATTR_LOCAL_ADDR).set(ctx.channel().localAddress()); InetSocketAddress localAddress = localAddress(ctx.channel()); ctx.channel().attr(ATTR_LOCAL_INET_ADDR).setIfAbsent(localAddress); ctx.channel().attr(ATTR_LOCAL_ADDRESS).setIfAbsent(getHostAddress(localAddress)); - ctx.channel().attr(ATTR_LOCAL_PORT).setIfAbsent(localAddress.getPort()); // ATTR_LOCAL_ADDRESS and ATTR_LOCAL_PORT get overwritten with what is received in // Proxy Protocol (via the LB), so set local server's address, port explicitly ctx.channel().attr(ATTR_SERVER_LOCAL_ADDRESS).setIfAbsent(localAddress.getAddress().getHostAddress()); @@ -162,8 +148,7 @@ static String getHostAddress(InetSocketAddress socketAddress) { } } - private InetSocketAddress sourceAddress(Channel channel) - { + private InetSocketAddress sourceAddress(Channel channel) { SocketAddress remoteSocketAddr = channel.remoteAddress(); if (null != remoteSocketAddr && InetSocketAddress.class.isAssignableFrom(remoteSocketAddr.getClass())) { InetSocketAddress inetSocketAddress = (InetSocketAddress) remoteSocketAddr; @@ -174,8 +159,7 @@ private InetSocketAddress sourceAddress(Channel channel) return null; } - private InetSocketAddress localAddress(Channel channel) - { + private InetSocketAddress localAddress(Channel channel) { SocketAddress localSocketAddress = channel.localAddress(); if (null != localSocketAddress && InetSocketAddress.class.isAssignableFrom(localSocketAddress.getClass())) { InetSocketAddress inetSocketAddress = (InetSocketAddress) localSocketAddress; diff --git a/zuul-core/src/main/java/com/netflix/netty/common/channel/config/CommonChannelConfigKeys.java b/zuul-core/src/main/java/com/netflix/netty/common/channel/config/CommonChannelConfigKeys.java index 81d6bc24..9a2be27c 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/channel/config/CommonChannelConfigKeys.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/channel/config/CommonChannelConfigKeys.java @@ -21,7 +21,7 @@ import com.netflix.zuul.netty.server.ServerTimeout; import com.netflix.zuul.netty.ssl.SslContextFactory; import io.netty.handler.ssl.SslContext; -import io.netty.util.DomainNameMapping; +import io.netty.util.AsyncMapping; /** * User: michaels@netflix.com @@ -51,7 +51,7 @@ public class CommonChannelConfigKeys public static final ChannelConfigKey isSSlFromIntermediary = new ChannelConfigKey<>("isSSlFromIntermediary", false); public static final ChannelConfigKey serverSslConfig = new ChannelConfigKey<>("serverSslConfig"); public static final ChannelConfigKey sslContextFactory = new ChannelConfigKey<>("sslContextFactory"); - public static final ChannelConfigKey> sniMapping = new ChannelConfigKey<>("sniMapping"); + public static final ChannelConfigKey> sniMapping = new ChannelConfigKey<>("sniMapping"); // HTTP/2 specific: public static final ChannelConfigKey maxConcurrentStreams = new ChannelConfigKey<>("maxConcurrentStreams", 100); diff --git a/zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java index 20f403ae..fc732eae 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java @@ -16,6 +16,8 @@ package com.netflix.netty.common.metrics; +import com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent; +import com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason; import com.netflix.netty.common.HttpServerLifecycleChannelHandler; import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Gauge; @@ -84,13 +86,12 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc if (evt instanceof HttpServerLifecycleChannelHandler.StartEvent) { incrementCurrentRequestsInFlight(ctx); } + else if (evt instanceof HttpServerLifecycleChannelHandler.CompleteEvent && ((CompleteEvent) evt).getReason() == CompleteReason.PIPELINE_REJECT ) { + unSupportedPipeliningCounter.increment(); + } else if (evt instanceof HttpServerLifecycleChannelHandler.CompleteEvent) { decrementCurrentRequestsIfOneInflight(ctx); } - else if (evt instanceof HttpLifecycleChannelHandler.RejectedPipeliningEvent) { - unSupportedPipeliningCounter.increment(); - } - super.userEventTriggered(ctx, evt); } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/metrics/ServerChannelMetrics.java b/zuul-core/src/main/java/com/netflix/netty/common/metrics/ServerChannelMetrics.java deleted file mode 100644 index 3e9ae7c9..00000000 --- a/zuul-core/src/main/java/com/netflix/netty/common/metrics/ServerChannelMetrics.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.netty.common.metrics; - -import com.netflix.netty.common.throttle.MaxInboundConnectionsHandler; -import com.netflix.servo.DefaultMonitorRegistry; -import com.netflix.servo.monitor.BasicCounter; -import com.netflix.servo.monitor.BasicGauge; -import com.netflix.servo.monitor.Gauge; -import com.netflix.servo.monitor.MonitorConfig; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.timeout.IdleStateEvent; -import io.netty.util.AttributeKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.atomic.AtomicInteger; - -/** - * User: Mike Smith - * Date: 3/5/16 - * Time: 5:47 PM - */ -@ChannelHandler.Sharable -public class ServerChannelMetrics extends ChannelInboundHandlerAdapter -{ - private static final Logger LOG = LoggerFactory.getLogger(ServerChannelMetrics.class); - private static final AttributeKey ATTR_CURRENT_CONNS = AttributeKey.newInstance("_server_connections_count"); - - private final Gauge currentConnectionsGauge; - private final AtomicInteger currentConnections = new AtomicInteger(0); - private final BasicCounter totalConnections; - private final BasicCounter connectionClosed; - private final BasicCounter connectionIdleTimeout; - private final BasicCounter connectionErrors; - private final BasicCounter connectionThrottled; - - public ServerChannelMetrics(String id) - { - super(); - - String metricNamePrefix = "server.connections."; - currentConnectionsGauge = new BasicGauge<>(MonitorConfig.builder(metricNamePrefix + "current").withTag("id", id).build(), - () -> currentConnections.get() ); - DefaultMonitorRegistry.getInstance().register(currentConnectionsGauge); - - totalConnections = createCounter(metricNamePrefix + "connect", id); - connectionErrors = createCounter(metricNamePrefix + "errors", id); - connectionClosed = createCounter(metricNamePrefix + "close", id); - connectionIdleTimeout = createCounter(metricNamePrefix + "idle.timeout", id); - connectionThrottled = createCounter(metricNamePrefix + "throttled", id); - } - - private static BasicCounter createCounter(String name, String id) - { - BasicCounter counter = new BasicCounter(MonitorConfig.builder(name).withTag("id", id).build()); - DefaultMonitorRegistry.getInstance().register(counter); - return counter; - } - - public static int currentConnectionCountFromChannel(Channel ch) - { - AtomicInteger count = ch.attr(ATTR_CURRENT_CONNS).get(); - return count == null ? 0 : count.get(); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception - { - currentConnections.incrementAndGet(); - totalConnections.increment(); - ctx.channel().attr(ATTR_CURRENT_CONNS).set(currentConnections); - - super.channelActive(ctx); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception - { - try { - super.channelInactive(ctx); - } - finally { - currentConnections.decrementAndGet(); - connectionClosed.increment(); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception - { - connectionErrors.increment(); - if (LOG.isInfoEnabled()) { - LOG.info("Connection error caught. " + String.valueOf(cause), cause); - } - super.exceptionCaught(ctx, cause); - } - - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception - { - if (evt == MaxInboundConnectionsHandler.CONNECTION_THROTTLED_EVENT) { - connectionThrottled.increment(); - } - else if (evt instanceof IdleStateEvent) { - connectionIdleTimeout.increment(); - } - - super.userEventTriggered(ctx, evt); - } -} diff --git a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandler.java index 5af8414e..3c83c927 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandler.java @@ -16,142 +16,53 @@ package com.netflix.netty.common.proxyprotocol; -import com.google.common.net.InetAddresses; -import com.netflix.netty.common.SourceAddressChannelHandler; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFutureListener; +import static com.google.common.base.Preconditions.checkNotNull; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Registry; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; -import io.netty.handler.codec.haproxy.HAProxyMessage; -import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; -import io.netty.util.AttributeKey; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; - -import io.netty.util.ReferenceCounted; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import io.netty.handler.codec.ProtocolDetectionState; +import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; /** - * Copies any decoded HAProxyMessage into the channel attributes, and doesn't pass - * it any further along the pipeline. - * - * Use in conjunction with OptionalHAProxyMessageDecoder if ProxyProtocol is enabled on the ELB. - * - * User: michaels@netflix.com - * Date: 3/24/16 - * Time: 11:59 AM + * Decides if we need to decode a HAProxyMessage. If so, adds the decoder followed by the handler. + * Else, removes itself from the pipeline. */ -public class ElbProxyProtocolChannelHandler extends ChannelInboundHandlerAdapter -{ - public static final String NAME = "ElbProxyProtocolChannelHandler"; - public static final AttributeKey ATTR_HAPROXY_MESSAGE = AttributeKey.newInstance("_haproxy_message"); - public static final AttributeKey ATTR_HAPROXY_VERSION = AttributeKey.newInstance("_haproxy_version"); - - private static final Logger logger = LoggerFactory.getLogger("ElbProxyProtocolChannelHandler"); +public final class ElbProxyProtocolChannelHandler extends ChannelInboundHandlerAdapter { + public static final String NAME = ElbProxyProtocolChannelHandler.class.getSimpleName(); private final boolean withProxyProtocol; + private final Registry spectatorRegistry; + private final Counter hapmDecodeFailure; - public ElbProxyProtocolChannelHandler(boolean withProxyProtocol) - { + public ElbProxyProtocolChannelHandler(Registry registry, boolean withProxyProtocol) { this.withProxyProtocol = withProxyProtocol; + this.spectatorRegistry = checkNotNull(registry); + this.hapmDecodeFailure = spectatorRegistry.counter("zuul.hapm.failure"); } - /** - * Setup the required handlers on pipeline using this method. - * - * @param pipeline - */ - public void addProxyProtocol(ChannelPipeline pipeline) - { + public void addProxyProtocol(ChannelPipeline pipeline) { pipeline.addLast(NAME, this); - - if (withProxyProtocol) { - pipeline.addBefore(NAME, OptionalHAProxyMessageDecoder.NAME, new OptionalHAProxyMessageDecoder()); - } } @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception - { - if (withProxyProtocol) { - if (msg instanceof HAProxyMessage && msg != null) { - HAProxyMessage hapm = (HAProxyMessage) msg; - Channel channel = ctx.channel(); - channel.attr(ATTR_HAPROXY_MESSAGE).set(hapm); - ctx.channel().closeFuture().addListener((ChannelFutureListener) future -> { - if (hapm instanceof ReferenceCounted) { - hapm.release(); - } - }); - channel.attr(ATTR_HAPROXY_VERSION).set(hapm.protocolVersion()); - // Get the real host and port that the client connected to ELB with. - String destinationAddress = hapm.destinationAddress(); - if (destinationAddress != null) { - channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).set(destinationAddress); - channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_PORT).set(hapm.destinationPort()); - - SocketAddress addr; - out: { - switch (hapm.proxiedProtocol()) { - case UNKNOWN: - throw new IllegalArgumentException("unknown proxy protocl" + destinationAddress); - case TCP4: - case TCP6: - addr = new InetSocketAddress( - InetAddresses.forString(destinationAddress), hapm.destinationPort()); - break out; - case UNIX_STREAM: // TODO: implement - case UDP4: - case UDP6: - case UNIX_DGRAM: - throw new IllegalArgumentException("unknown proxy protocol" + destinationAddress); - } - throw new AssertionError(hapm.proxiedProtocol()); - } - channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).set(addr); - } - - // Get the real client IP from the ProxyProtocol message sent by the ELB, and overwrite the SourceAddress - // channel attribute. - String sourceAddress = hapm.sourceAddress(); - if (sourceAddress != null) { - channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(sourceAddress); - channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_PORT).set(hapm.sourcePort()); - - SocketAddress addr; - out: { - switch (hapm.proxiedProtocol()) { - case UNKNOWN: - throw new IllegalArgumentException("unknown proxy protocl" + sourceAddress); - case TCP4: - case TCP6: - addr = new InetSocketAddress( - InetAddresses.forString(sourceAddress), hapm.sourcePort()); - break out; - case UNIX_STREAM: // TODO: implement - case UDP4: - case UDP6: - case UNIX_DGRAM: - throw new IllegalArgumentException("unknown proxy protocol" + sourceAddress); - } - throw new AssertionError(hapm.proxiedProtocol()); - } - channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).set(addr); - } - - // TODO - fire an additional event to notify interested parties that we now know the IP? - - // Remove ourselves (this handler) from the channel now, as no more work to do. - ctx.pipeline().remove(this); - - // Do not continue propagating the message. - return; + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (withProxyProtocol && isHAPMDetected(msg)) { + ctx.pipeline().addAfter(NAME, null, new HAProxyMessageChannelHandler()) + .replace(this, null, new HAProxyMessageDecoder()); + } else { + if (withProxyProtocol) { + // This likely means initialization was requested with proxy protocol, but we failed to decode the message + hapmDecodeFailure.increment(); } + ctx.pipeline().remove(this); } - super.channelRead(ctx, msg); } + + private boolean isHAPMDetected(Object msg) { + return HAProxyMessageDecoder.detectProtocol((ByteBuf) msg).state() == ProtocolDetectionState.DETECTED; + } } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandler.java new file mode 100644 index 00000000..5739c4a2 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandler.java @@ -0,0 +1,125 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * 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 com.netflix.netty.common.proxyprotocol; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.net.InetAddresses; +import com.netflix.netty.common.SourceAddressChannelHandler; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.haproxy.HAProxyMessage; +import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; +import io.netty.util.AttributeKey; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +/** + * Copies any decoded HAProxyMessage into the channel attributes, and doesn't pass it any further along the pipeline. + * Use in conjunction with HAProxyMessageDecoder if proxy protocol is enabled on the ELB. + */ + +public final class HAProxyMessageChannelHandler extends ChannelInboundHandlerAdapter { + + public static final AttributeKey ATTR_HAPROXY_MESSAGE = + AttributeKey.newInstance("_haproxy_message"); + public static final AttributeKey ATTR_HAPROXY_VERSION = + AttributeKey.newInstance("_haproxy_version"); + + @VisibleForTesting + static final Attrs.Key HAPM_DEST_PORT = Attrs.newKey("hapm_port"); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof HAProxyMessage) { + HAProxyMessage hapm = (HAProxyMessage) msg; + Channel channel = ctx.channel(); + channel.attr(ATTR_HAPROXY_MESSAGE).set(hapm); + ctx.channel().closeFuture().addListener((ChannelFutureListener) future -> hapm.release()); + channel.attr(ATTR_HAPROXY_VERSION).set(hapm.protocolVersion()); + // Get the real host and port that the client connected to ELB with. + String destinationAddress = hapm.destinationAddress(); + if (destinationAddress != null) { + channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).set(destinationAddress); + SocketAddress addr; + out: + { + switch (hapm.proxiedProtocol()) { + case UNKNOWN: + throw new IllegalArgumentException("unknown proxy protocl" + destinationAddress); + case TCP4: + case TCP6: + InetSocketAddress inetAddr = new InetSocketAddress( + InetAddresses.forString(destinationAddress), hapm.destinationPort()); + addr = inetAddr; + // setting PPv2 explicitly because SourceAddressChannelHandler.ATTR_LOCAL_ADDR could be PPv2 or not + channel.attr(SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS).set(inetAddr); + Attrs attrs = ctx.channel().attr(Server.CONN_DIMENSIONS).get(); + HAPM_DEST_PORT.put(attrs, hapm.destinationPort()); + break out; + case UNIX_STREAM: // TODO: implement + case UDP4: + case UDP6: + case UNIX_DGRAM: + throw new IllegalArgumentException("unknown proxy protocol" + destinationAddress); + } + throw new AssertionError(hapm.proxiedProtocol()); + } + channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).set(addr); + } + + // Get the real client IP from the ProxyProtocol message sent by the ELB, and overwrite the SourceAddress + // channel attribute. + String sourceAddress = hapm.sourceAddress(); + if (sourceAddress != null) { + channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(sourceAddress); + + SocketAddress addr; + out: + { + switch (hapm.proxiedProtocol()) { + case UNKNOWN: + throw new IllegalArgumentException("unknown proxy protocl" + sourceAddress); + case TCP4: + case TCP6: + addr = new InetSocketAddress( + InetAddresses.forString(sourceAddress), hapm.sourcePort()); + break out; + case UNIX_STREAM: // TODO: implement + case UDP4: + case UDP6: + case UNIX_DGRAM: + throw new IllegalArgumentException("unknown proxy protocol" + sourceAddress); + } + throw new AssertionError(hapm.proxiedProtocol()); + } + channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).set(addr); + } + + // TODO - fire an additional event to notify interested parties that we now know the IP? + + // Remove ourselves (this handler) from the channel now, as no more work to do. + ctx.pipeline().remove(this); + + // Do not continue propagating the message. + return; + } + } +} diff --git a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/OptionalHAProxyMessageDecoder.java b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/OptionalHAProxyMessageDecoder.java deleted file mode 100644 index 0455fe71..00000000 --- a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/OptionalHAProxyMessageDecoder.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.netty.common.proxyprotocol; - -import com.netflix.config.CachedDynamicBooleanProperty; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.ProtocolDetectionResult; -import io.netty.handler.codec.ProtocolDetectionState; -import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; -import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; -import io.netty.util.CharsetUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Chooses whether a new connection is prefixed with the ProxyProtocol from an ELB. If it is, then - * it adds a HAProxyMessageDecoder into the pipeline after THIS handler. - * - * User: michaels@netflix.com - * Date: 3/24/16 - * Time: 3:13 PM - */ -public final class OptionalHAProxyMessageDecoder extends ChannelInboundHandlerAdapter -{ - // TODO(carl-mastrangelo): delete the name, as the class name is good enough. - public static final String NAME = "OptionalHAProxyMessageDecoder"; - private static final Logger logger = LoggerFactory.getLogger("OptionalHAProxyMessageDecoder"); - - // TODO(https://github.com/Netflix/zuul/issues/623): delete this property - private static final CachedDynamicBooleanProperty dumpHAProxyByteBuf = - new CachedDynamicBooleanProperty("zuul.haproxy.dump.bytebuf", false); - - OptionalHAProxyMessageDecoder() {} - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception - { - if (msg instanceof ByteBuf) { - try { - ProtocolDetectionResult result = HAProxyMessageDecoder.detectProtocol((ByteBuf) msg); - - // TODO - is it possible that this message could be split over multiple ByteBufS, and therefore this would fail? - if (result.state() == ProtocolDetectionState.DETECTED) { - // Add the actual HAProxy decoder. - // Note that the HAProxyMessageDecoder will remove itself once it has finished decoding the initial ProxyProtocol message(s). - ctx.pipeline().addAfter(NAME, null, new HAProxyMessageDecoder()); - - // Remove this handler, as now no longer needed. - ctx.pipeline().remove(this); - } - } catch (Exception e) { - if (msg != null) { - logger.error("Exception in OptionalHAProxyMessageDecoder {}" + e.getClass().getCanonicalName()); - if (dumpHAProxyByteBuf.get()) { - logger.error("Exception Stack: {}" + e.getStackTrace()); - logger.error("Bytebuf is: {} " + ((ByteBuf) msg).toString(CharsetUtil.US_ASCII)); - } - ((ByteBuf) msg).release(); - } - } - } - super.channelRead(ctx, msg); - } -} diff --git a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandler.java index 6f6b9421..c76eb606 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Netflix, Inc. + * Copyright 2020 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,23 @@ package com.netflix.netty.common.proxyprotocol; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Sets; +import com.netflix.config.DynamicStringListProperty; import com.netflix.netty.common.ssl.SslHandshakeInfo; import com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.ssl.ClientAuth; import io.netty.util.AsciiString; import java.util.Collection; +import java.util.List; /** * Strip out any X-Forwarded-* headers from inbound http requests if connection is not trusted. @@ -36,6 +40,9 @@ @ChannelHandler.Sharable public class StripUntrustedProxyHeadersHandler extends ChannelInboundHandlerAdapter { + private static final DynamicStringListProperty XFF_BLACKLIST = + new DynamicStringListProperty("zuul.proxy.headers.host.blacklist", ""); + public enum AllowWhen { ALWAYS, MUTUAL_SSL_AUTH, @@ -70,10 +77,12 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception case MUTUAL_SSL_AUTH: if (! connectionIsUsingMutualSSLWithAuthEnforced(ctx.channel())) { stripXFFHeaders(req); + } else { + checkBlacklist(req, XFF_BLACKLIST.get()); } break; case ALWAYS: - // Do nothing. + checkBlacklist(req, XFF_BLACKLIST.get()); break; default: // default to not allow. @@ -84,7 +93,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception super.channelRead(ctx, msg); } - private boolean connectionIsUsingMutualSSLWithAuthEnforced(Channel ch) + @VisibleForTesting + boolean connectionIsUsingMutualSSLWithAuthEnforced(Channel ch) { boolean is = false; SslHandshakeInfo sslHandshakeInfo = ch.attr(SslHandshakeInfoHandler.ATTR_SSL_INFO).get(); @@ -96,11 +106,20 @@ private boolean connectionIsUsingMutualSSLWithAuthEnforced(Channel ch) return is; } - private void stripXFFHeaders(HttpRequest req) + @VisibleForTesting + void stripXFFHeaders(HttpRequest req) { HttpHeaders headers = req.headers(); for (AsciiString headerName : HEADERS_TO_STRIP) { headers.remove(headerName); } } + + @VisibleForTesting + void checkBlacklist(HttpRequest req, List blacklist) { + // blacklist headers from + if (blacklist.stream().anyMatch(h -> h.equalsIgnoreCase(req.headers().get(HttpHeaderNames.HOST)))) { + stripXFFHeaders(req); + } + } } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/ssl/ServerSslConfig.java b/zuul-core/src/main/java/com/netflix/netty/common/ssl/ServerSslConfig.java index 3a1b63b1..093c22ef 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/ssl/ServerSslConfig.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/ssl/ServerSslConfig.java @@ -18,21 +18,19 @@ import com.netflix.config.DynamicLongProperty; import io.netty.handler.ssl.ClientAuth; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; import java.io.File; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; /** * User: michaels@netflix.com * Date: 8/16/16 * Time: 2:40 PM */ -public class ServerSslConfig -{ +public class ServerSslConfig { private static final DynamicLongProperty DEFAULT_SESSION_TIMEOUT = new DynamicLongProperty("server.ssl.session.timeout", (18 * 60)); // 18 hours @@ -58,35 +56,25 @@ public class ServerSslConfig private final String clientAuthTrustStorePassword; private final File clientAuthTrustStorePasswordFile; - private final boolean decryptKeyUsingMetatronPolicy; - private final boolean decryptKeyUsingMetatronBundle; - private final byte[] metatronPolicy; - private final long sessionTimeout; private final boolean sessionTicketsEnabled; - public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile) - { - this(protocols, ciphers, certChainFile, keyFile, null, ClientAuth.NONE, null, (File) null, false); + public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile) { + this(protocols, ciphers, certChainFile, keyFile, ClientAuth.NONE, null, (File) null, false); } - public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile, byte[] metatronPolicy, ClientAuth clientAuth) - { - this(protocols, ciphers, certChainFile, keyFile, metatronPolicy, clientAuth, null, (File) null, true); + public ServerSslConfig( + String[] protocols, String[] ciphers, File certChainFile, File keyFile, ClientAuth clientAuth) { + this(protocols, ciphers, certChainFile, keyFile, clientAuth, null, (File) null, true); } - public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile, - byte[] metatronPolicy, - ClientAuth clientAuth, File clientAuthTrustStoreFile, File clientAuthTrustStorePasswordFile, - boolean sessionTicketsEnabled) - { + public ServerSslConfig( + String[] protocols, String[] ciphers, File certChainFile, File keyFile, ClientAuth clientAuth, + File clientAuthTrustStoreFile, File clientAuthTrustStorePasswordFile, boolean sessionTicketsEnabled) { this.protocols = protocols; this.ciphers = ciphers != null ? Arrays.asList(ciphers) : null; this.certChainFile = certChainFile; this.keyFile = keyFile; - this.decryptKeyUsingMetatronPolicy = (metatronPolicy != null); - this.decryptKeyUsingMetatronBundle = false; - this.metatronPolicy = metatronPolicy; this.clientAuth = clientAuth; this.clientAuthTrustStoreFile = clientAuthTrustStoreFile; this.clientAuthTrustStorePassword = null; @@ -95,38 +83,13 @@ public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, this.sessionTicketsEnabled = sessionTicketsEnabled; } - public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile, - byte[] metatronPolicy, - ClientAuth clientAuth, File clientAuthTrustStoreFile, String clientAuthTrustStorePassword, - boolean sessionTicketsEnabled) - { - this.protocols = protocols; - this.ciphers = Arrays.asList(ciphers); - this.certChainFile = certChainFile; - this.keyFile = keyFile; - this.decryptKeyUsingMetatronPolicy = (metatronPolicy != null); - this.decryptKeyUsingMetatronBundle = false; - this.metatronPolicy = metatronPolicy; - this.clientAuth = clientAuth; - this.clientAuthTrustStoreFile = clientAuthTrustStoreFile; - this.clientAuthTrustStorePassword = clientAuthTrustStorePassword; - this.clientAuthTrustStorePasswordFile = null; - this.sessionTimeout = DEFAULT_SESSION_TIMEOUT.get(); - this.sessionTicketsEnabled = sessionTicketsEnabled; - } - - public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile, - boolean metatronBundle, - ClientAuth clientAuth, File clientAuthTrustStoreFile, String clientAuthTrustStorePassword, - boolean sessionTicketsEnabled) - { + public ServerSslConfig( + String[] protocols, String[] ciphers, File certChainFile, File keyFile, ClientAuth clientAuth, + File clientAuthTrustStoreFile, String clientAuthTrustStorePassword, boolean sessionTicketsEnabled) { this.protocols = protocols; this.ciphers = Arrays.asList(ciphers); this.certChainFile = certChainFile; this.keyFile = keyFile; - this.decryptKeyUsingMetatronBundle = metatronBundle; - this.decryptKeyUsingMetatronPolicy = false; - this.metatronPolicy = null; this.clientAuth = clientAuth; this.clientAuthTrustStoreFile = clientAuthTrustStoreFile; this.clientAuthTrustStorePassword = clientAuthTrustStorePassword; @@ -165,21 +128,6 @@ public File getKeyFile() return keyFile; } - public boolean shouldDecryptKeyUsingMetatronPolicy() - { - return decryptKeyUsingMetatronPolicy; - } - - public boolean shouldDecryptKeyUsingMetatronBundle() - { - return decryptKeyUsingMetatronBundle; - } - - public byte[] getMetatronPolicyFile() - { - return metatronPolicy; - } - public ClientAuth getClientAuth() { return clientAuth; @@ -220,8 +168,6 @@ public String toString() ", keyFile=" + keyFile + ", clientAuth=" + clientAuth + ", clientAuthTrustStoreFile=" + clientAuthTrustStoreFile + - ", decryptKeyUsingMetatronPolicy=" + decryptKeyUsingMetatronPolicy + - ", decryptKeyUsingMetatronBundle=" + decryptKeyUsingMetatronBundle + ", sessionTimeout=" + sessionTimeout + ", sessionTicketsEnabled=" + sessionTicketsEnabled + '}'; diff --git a/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java b/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java index 76b117ff..bc342bd4 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java @@ -18,16 +18,14 @@ import io.netty.handler.ssl.ClientAuth; -import javax.security.cert.X509Certificate; +import java.security.cert.X509Certificate; import java.security.cert.Certificate; /** - * User: michaels@netflix.com - * Date: 3/29/16 - * Time: 11:06 AM + * User: michaels@netflix.com Date: 3/29/16 Time: 11:06 AM */ -public class SslHandshakeInfo -{ +public class SslHandshakeInfo { + private final String protocol; private final String cipherSuite; private final ClientAuth clientAuthRequirement; @@ -35,19 +33,9 @@ public class SslHandshakeInfo private final X509Certificate clientCertificate; private final boolean isOfIntermediary; - public SslHandshakeInfo(boolean isOfIntermediary, String protocol, String cipherSuite, Certificate serverCertificate) - { - this.protocol = protocol; - this.cipherSuite = cipherSuite; - this.isOfIntermediary = isOfIntermediary; - this.serverCertificate = serverCertificate; - this.clientAuthRequirement = ClientAuth.NONE; - this.clientCertificate = null; - } - - public SslHandshakeInfo(boolean isOfIntermediary, String protocol, String cipherSuite, ClientAuth clientAuthRequirement, - Certificate serverCertificate, X509Certificate clientCertificate) - { + public SslHandshakeInfo(boolean isOfIntermediary, String protocol, String cipherSuite, + ClientAuth clientAuthRequirement, + Certificate serverCertificate, X509Certificate clientCertificate) { this.protocol = protocol; this.cipherSuite = cipherSuite; this.clientAuthRequirement = clientAuthRequirement; @@ -56,23 +44,19 @@ public SslHandshakeInfo(boolean isOfIntermediary, String protocol, String cipher this.isOfIntermediary = isOfIntermediary; } - public boolean isOfIntermediary() - { + public boolean isOfIntermediary() { return isOfIntermediary; } - public String getProtocol() - { + public String getProtocol() { return protocol; } - public String getCipherSuite() - { + public String getCipherSuite() { return cipherSuite; } - public ClientAuth getClientAuthRequirement() - { + public ClientAuth getClientAuthRequirement() { return clientAuthRequirement; } @@ -81,14 +65,12 @@ public Certificate getServerCertificate() return serverCertificate; } - public X509Certificate getClientCertificate() - { + public X509Certificate getClientCertificate() { return clientCertificate; } @Override - public String toString() - { + public String toString() { return "SslHandshakeInfo{" + "protocol='" + protocol + '\'' + ", cipherSuite='" + cipherSuite + '\'' + diff --git a/zuul-core/src/main/java/com/netflix/netty/common/status/ServerStatusManager.java b/zuul-core/src/main/java/com/netflix/netty/common/status/ServerStatusManager.java index 1fba2a6c..bba8acd3 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/status/ServerStatusManager.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/status/ServerStatusManager.java @@ -18,13 +18,10 @@ import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.appinfo.InstanceInfo; -import com.netflix.discovery.DiscoveryClient; import javax.inject.Inject; import javax.inject.Singleton; -import static com.netflix.appinfo.InstanceInfo.InstanceStatus.UNKNOWN; -import static com.netflix.appinfo.InstanceInfo.InstanceStatus.UP; /** * User: michaels@netflix.com @@ -35,47 +32,13 @@ public class ServerStatusManager { private final ApplicationInfoManager applicationInfoManager; - private final DiscoveryClient discoveryClient; @Inject - public ServerStatusManager(ApplicationInfoManager applicationInfoManager, DiscoveryClient discoveryClient) - { + public ServerStatusManager(ApplicationInfoManager applicationInfoManager) { this.applicationInfoManager = applicationInfoManager; - this.discoveryClient = discoveryClient; - } - - public InstanceInfo.InstanceStatus status() { - - // NOTE: when debugging this locally, found to my surprise that when the instance is maked OUT_OF_SERVICE remotely - // in Discovery, although the StatusChangeEvent does get fired, the _local_ InstanceStatus (ie. - // applicationInfoManager.getInfo().getStatus()) does not get changed to reflect that. - // So that's why I'm doing this little dance here of looking at both remote and local statuses. - - InstanceInfo.InstanceStatus local = localStatus(); - InstanceInfo.InstanceStatus remote = remoteStatus(); - - if (local == UP && remote != UNKNOWN) { - return remote; - } - else { - return local; - } - } - - public InstanceInfo.InstanceStatus localStatus() { - return applicationInfoManager.getInfo().getStatus(); - } - - public InstanceInfo.InstanceStatus remoteStatus() { - return discoveryClient.getInstanceRemoteStatus(); } public void localStatus(InstanceInfo.InstanceStatus status) { applicationInfoManager.setInstanceStatus(status); } - - public int health() { - // TODO - throw new UnsupportedOperationException(); - } } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandler.java b/zuul-core/src/main/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandler.java index d455fd94..73370f9f 100644 --- a/zuul-core/src/main/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandler.java +++ b/zuul-core/src/main/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandler.java @@ -16,6 +16,8 @@ package com.netflix.netty.common.throttle; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Registry; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; import io.netty.channel.Channel; @@ -38,17 +40,18 @@ @ChannelHandler.Sharable public class MaxInboundConnectionsHandler extends ChannelInboundHandlerAdapter { - public static final String CONNECTION_THROTTLED_EVENT = "connection_throttled"; + public static final AttributeKey ATTR_CH_THROTTLED = AttributeKey.newInstance("_channel_throttled"); private static final Logger LOG = LoggerFactory.getLogger(MaxInboundConnectionsHandler.class); - private static final AttributeKey ATTR_CH_THROTTLED = AttributeKey.newInstance("_channel_throttled"); private final static AtomicInteger connections = new AtomicInteger(0); + private final Counter connectionThrottled; private final int maxConnections; - public MaxInboundConnectionsHandler(int maxConnections) + public MaxInboundConnectionsHandler(Registry registry, String metricId, int maxConnections) { this.maxConnections = maxConnections; + this.connectionThrottled = registry.counter("server.connections.throttled", "id", metricId); } @Override @@ -63,7 +66,7 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception channel.attr(ATTR_CH_THROTTLED).set(Boolean.TRUE); CurrentPassport.fromChannel(channel).add(PassportState.SERVER_CH_THROTTLING); channel.close(); - ctx.pipeline().fireUserEventTriggered(CONNECTION_THROTTLED_EVENT); + connectionThrottled.increment(); } } diff --git a/zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionType.java b/zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionType.java new file mode 100644 index 00000000..67566757 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionType.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common.throttle; + +/** + * Indicates a rejection type for DoS protection. While similar, rejection is distinct from throttling in that + * throttling is intended for non-malicious traffic. + */ +public enum RejectionType { + // "It's not you, it's me." + + /** + * Indicates that the request should not be allowed. An HTTP response will be generated as a result. + */ + REJECT, + + /** + * Indicates that the connection should be closed, not allowing the request to proceed. No HTTP response will be + * returned. + */ + CLOSE, + + /** + * Allows the request to proceed, followed by closing the connection. This is typically used in conjunction with + * throttling handling, where the response may need to be handled by the filter chain. It is not expected that the + * request will be proxied. + */ + ALLOW_THEN_CLOSE; +} diff --git a/zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionUtils.java b/zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionUtils.java new file mode 100644 index 00000000..621832e2 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionUtils.java @@ -0,0 +1,270 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common.throttle; + +import static com.netflix.netty.common.proxyprotocol.HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION; +import com.netflix.netty.common.ConnectionCloseChannelAttributes; +import com.netflix.zuul.passport.CurrentPassport; +import com.netflix.zuul.passport.PassportState; +import com.netflix.zuul.stats.status.StatusCategory; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.EmptyHttpHeaders; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.util.ReferenceCountUtil; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * A collection of rejection related utilities useful for failing requests. These are tightly coupled with the channel + * pipeline, but can be called from different handlers. + */ +public final class RejectionUtils { + + // TODO(carl-mastrangelo): add tests for this. + + public static final HttpResponseStatus REJECT_CLOSING_STATUS = new HttpResponseStatus(999, "Closing(Rejection)"); + + /** + * Closes the connection without sending a response, and fires a {@link RequestRejectedEvent} back up the pipeline. + * + * @param nfStatus the status to use for metric reporting + * @param reason the reason for rejecting the request. This is not sent back to the client. + * @param request the request that is being rejected. + * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still + * sent up the pipeline. + */ + public static void rejectByClosingConnection( + ChannelHandlerContext ctx, StatusCategory nfStatus, String reason, HttpRequest request, + @Nullable Integer injectedLatencyMillis) { + if (injectedLatencyMillis != null && injectedLatencyMillis > 0) { + // Delay closing the connection for configured time. + ctx.executor().schedule(() -> { + CurrentPassport.fromChannel(ctx.channel()).add(PassportState.SERVER_CH_REJECTING); + ctx.close(); + }, injectedLatencyMillis, TimeUnit.MILLISECONDS); + } else { + // Close the connection immediately. + CurrentPassport.fromChannel(ctx.channel()).add(PassportState.SERVER_CH_REJECTING); + ctx.close(); + } + + // Notify other handlers that we've rejected this request. + notifyHandlers(ctx, nfStatus, REJECT_CLOSING_STATUS, reason, request); + } + + /** + * Sends a rejection response back to the client, and fires a {@link RequestRejectedEvent} back up the pipeline. + * + * @param ctx the channel handler processing the request + * @param nfStatus the status to use for metric reporting + * @param reason the reason for rejecting the request. This is not sent back to the client. + * @param request the request that is being rejected. + * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still + * sent up the pipeline. + * @param rejectedCode the HTTP code to send back to the client. + * @param rejectedBody the HTTP body to be sent back. It is assumed to be of type text/plain. + * @param rejectionHeaders additional HTTP headers to add to the rejection response + */ + public static void sendRejectionResponse( + ChannelHandlerContext ctx, StatusCategory nfStatus, String reason, HttpRequest request, + @Nullable Integer injectedLatencyMillis, HttpResponseStatus rejectedCode, String rejectedBody, + Map rejectionHeaders) { + boolean shouldClose = closeConnectionAfterReject(ctx.channel()); + // Write out a rejection response message. + FullHttpResponse response = createRejectionResponse(rejectedCode, rejectedBody, shouldClose, rejectionHeaders); + + if (injectedLatencyMillis != null && injectedLatencyMillis > 0) { + // Delay writing the response for configured time. + ctx.executor().schedule(() -> { + CurrentPassport.fromChannel(ctx.channel()).add(PassportState.IN_REQ_REJECTED); + ctx.writeAndFlush(response); + }, injectedLatencyMillis, TimeUnit.MILLISECONDS); + } else { + // Write the response immediately. + CurrentPassport.fromChannel(ctx.channel()).add(PassportState.IN_REQ_REJECTED); + ctx.writeAndFlush(response); + } + + // Notify other handlers that we've rejected this request. + notifyHandlers(ctx, nfStatus, rejectedCode, reason, request); + } + + /** + * Marks the given channel for being closed after the next response. + * + * @param ctx the channel handler processing the request + */ + public static void allowThenClose(ChannelHandlerContext ctx) { + // Just flag this channel to be closed after response complete. + ctx.channel().attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE).set(ctx.newPromise()); + + // And allow this request through without rejecting. + } + + /** + * Throttle either by sending rejection response message, or by closing the connection now, or just drop the + * message. Only call this if ThrottleResult.shouldThrottle() returned {@code true}. + * + * @param ctx the channel handler processing the request + * @param msg the request that is being rejected. + * @param rejectionType the type of rejection + * @param nfStatus the status to use for metric reporting + * @param reason the reason for rejecting the request. This is not sent back to the client. + * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still + * sent up the pipeline. + * @param rejectedCode the HTTP code to send back to the client. + * @param rejectedBody the HTTP body to be sent back. It is assumed to be of type text/plain. + * @param rejectionHeaders additional HTTP headers to add to the rejection response + */ + public static void handleRejection( + ChannelHandlerContext ctx, Object msg, RejectionType rejectionType, StatusCategory nfStatus, String reason, + @Nullable Integer injectedLatencyMillis, HttpResponseStatus rejectedCode, String rejectedBody, + Map rejectionHeaders) + throws Exception { + + boolean shouldDropMessage = false; + if (rejectionType == RejectionType.REJECT || rejectionType == RejectionType.CLOSE) { + shouldDropMessage = true; + } + + boolean shouldRejectNow = false; + if (rejectionType == RejectionType.REJECT && msg instanceof LastHttpContent) { + shouldRejectNow = true; + } else if (rejectionType == RejectionType.CLOSE && msg instanceof HttpRequest) { + shouldRejectNow = true; + } else if (rejectionType == RejectionType.ALLOW_THEN_CLOSE && msg instanceof HttpRequest) { + shouldRejectNow = true; + } + + if (shouldRejectNow) { + // Send a rejection response. + HttpRequest request = msg instanceof HttpRequest ? (HttpRequest) msg : null; + reject(ctx, rejectionType, nfStatus, reason, request, injectedLatencyMillis, rejectedCode, rejectedBody, + rejectionHeaders); + } + + if (shouldDropMessage) { + ReferenceCountUtil.safeRelease(msg); + } else { + ctx.fireChannelRead(msg); + } + } + + + /** + * Switches on the rejection type to decide how to reject the request and or close the conn. + * + * @param ctx the channel handler processing the request + * @param rejectionType the type of rejection + * @param nfStatus the status to use for metric reporting + * @param reason the reason for rejecting the request. This is not sent back to the client. + * @param request the request that is being rejected. + * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still + * sent up the pipeline. + * @param rejectedCode the HTTP code to send back to the client. + * @param rejectedBody the HTTP body to be sent back. It is assumed to be of type text/plain. + */ + public static void reject( + ChannelHandlerContext ctx, RejectionType rejectionType, StatusCategory nfStatus, String reason, + HttpRequest request, @Nullable Integer injectedLatencyMillis, HttpResponseStatus rejectedCode, + String rejectedBody) { + reject(ctx, rejectionType, nfStatus, reason, request, injectedLatencyMillis, rejectedCode, rejectedBody, + Collections.emptyMap()); + } + + /** + * Switches on the rejection type to decide how to reject the request and or close the conn. + * + * @param ctx the channel handler processing the request + * @param rejectionType the type of rejection + * @param nfStatus the status to use for metric reporting + * @param reason the reason for rejecting the request. This is not sent back to the client. + * @param request the request that is being rejected. + * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still + * sent up the pipeline. + * @param rejectedCode the HTTP code to send back to the client. + * @param rejectedBody the HTTP body to be sent back. It is assumed to be of type text/plain. + * @param rejectionHeaders additional HTTP headers to add to the rejection response + */ + public static void reject( + ChannelHandlerContext ctx, RejectionType rejectionType, StatusCategory nfStatus, String reason, + HttpRequest request, @Nullable Integer injectedLatencyMillis, HttpResponseStatus rejectedCode, + String rejectedBody, Map rejectionHeaders) { + switch (rejectionType) { + case REJECT: + sendRejectionResponse( + ctx, nfStatus, reason, request, injectedLatencyMillis, rejectedCode, rejectedBody, + rejectionHeaders); + return; + case CLOSE: + rejectByClosingConnection(ctx, nfStatus, reason, request, injectedLatencyMillis); + return; + case ALLOW_THEN_CLOSE: + allowThenClose(ctx); + return; + } + throw new AssertionError("Bad rejection type: " + rejectionType); + } + + private static void notifyHandlers( + ChannelHandlerContext ctx, StatusCategory nfStatus, HttpResponseStatus status, String reason, + HttpRequest request) { + RequestRejectedEvent event = new RequestRejectedEvent(request, nfStatus, status, reason); + // Send this from the beginning of the pipeline, as it may be sent from the ClientRequestReceiver. + ctx.pipeline().fireUserEventTriggered(event); + } + + private static boolean closeConnectionAfterReject(Channel channel) { + if (channel.hasAttr(ATTR_HAPROXY_VERSION)) { + return HAProxyProtocolVersion.V2 == channel.attr(ATTR_HAPROXY_VERSION).get(); + } else { + return false; + } + } + + private static FullHttpResponse createRejectionResponse( + HttpResponseStatus status, String plaintextMessage, boolean closeConnection, + Map rejectionHeaders) { + ByteBuf body = Unpooled.wrappedBuffer(plaintextMessage.getBytes(StandardCharsets.UTF_8)); + int length = body.readableBytes(); + DefaultHttpHeaders headers = new DefaultHttpHeaders(); + headers.set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=utf-8"); + headers.set(HttpHeaderNames.CONTENT_LENGTH, length); + if (closeConnection) { + headers.set(HttpHeaderNames.CONNECTION, "close"); + } + rejectionHeaders.forEach(headers::add); + + return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, body, headers, EmptyHttpHeaders.INSTANCE); + } + + private RejectionUtils() {} +} diff --git a/zuul-core/src/main/java/com/netflix/netty/common/throttle/RequestRejectedEvent.java b/zuul-core/src/main/java/com/netflix/netty/common/throttle/RequestRejectedEvent.java new file mode 100644 index 00000000..a18d529a --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/netty/common/throttle/RequestRejectedEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common.throttle; + +import com.netflix.zuul.stats.status.StatusCategory; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; + +public final class RequestRejectedEvent { + private final HttpRequest request; + private final StatusCategory nfStatus; + private final HttpResponseStatus httpStatus; + private final String reason; + + public RequestRejectedEvent( + HttpRequest request, StatusCategory nfStatus, HttpResponseStatus httpStatus, String reason) { + this.request = request; + this.nfStatus = nfStatus; + this.httpStatus = httpStatus; + this.reason = reason; + } + + public HttpRequest request() { + return request; + } + + public StatusCategory getNfStatus() { + return nfStatus; + } + + public HttpResponseStatus getHttpStatus() { + return httpStatus; + } + + public String getReason() { + return reason; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/Attrs.java b/zuul-core/src/main/java/com/netflix/zuul/Attrs.java new file mode 100644 index 00000000..ab66ea89 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/Attrs.java @@ -0,0 +1,132 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import javax.annotation.Nullable; + +/** + * A heterogeneous map of attributes. + * + *

Implementation Note: this class is not a proper Map or Collection, in order to encourage callers + * to refer to the keys by their literal name. In the past, finding where a Key was used was difficult, + * so this class is somewhat of an experiment to try to make tracking down usage easier. If it becomes + * too onerous to use this, consider making this class extend AbstractMap. + */ +public final class Attrs { + + final Map, Object> storage = new IdentityHashMap<>(); + + public static Key newKey(String keyName) { + return new Key<>(keyName); + } + + public static final class Key { + + private final String name; + + /** + * Returns the value in the attributes, or {@code null} if absent. + */ + @Nullable + @SuppressWarnings("unchecked") + public T get(Attrs attrs) { + Objects.requireNonNull(attrs, "attrs"); + return (T) attrs.storage.get(this); + } + + /** + * Returns the value in the attributes or {@code defaultValue} if absent. + * @throws NullPointerException if defaultValue is null. + */ + @SuppressWarnings("unchecked") + public T getOrDefault(Attrs attrs, T defaultValue) { + Objects.requireNonNull(attrs, "attrs"); + Objects.requireNonNull(defaultValue, "defaultValue"); + T result = (T) attrs.storage.get(this); + if (result != null) { + return result; + } + return defaultValue; + } + + public void put(Attrs attrs, T value) { + Objects.requireNonNull(attrs, "attrs"); + Objects.requireNonNull(value); + attrs.storage.put(this, value); + } + + public String name() { + return name; + } + + private Key(String name) { + this.name = Objects.requireNonNull(name); + } + + @Override + public String toString() { + return "Key{" + name + '}'; + } + } + + private Attrs() {} + + public static Attrs newInstance() { + return new Attrs(); + } + + public Set> keySet() { + return Collections.unmodifiableSet(new LinkedHashSet<>(storage.keySet())); + } + + public void forEach(BiConsumer, Object> consumer) { + storage.forEach(consumer); + } + + public int size() { + return storage.size(); + } + + @Override + public String toString() { + return "Attrs{" + storage + '}'; + } + + @Override + @VisibleForTesting + public boolean equals(Object other) { + if (!(other instanceof Attrs)) { + return false; + } + Attrs that = (Attrs) other; + return Objects.equals(this.storage, that.storage); + } + + @Override + @VisibleForTesting + public int hashCode() { + return Objects.hash(storage); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/BasicFilterUsageNotifier.java b/zuul-core/src/main/java/com/netflix/zuul/BasicFilterUsageNotifier.java index 81bd0cdd..0f39edb0 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/BasicFilterUsageNotifier.java +++ b/zuul-core/src/main/java/com/netflix/zuul/BasicFilterUsageNotifier.java @@ -16,18 +16,28 @@ package com.netflix.zuul; -import com.netflix.servo.monitor.DynamicCounter; +import com.netflix.spectator.api.Registry; import com.netflix.zuul.filters.ZuulFilter; +import javax.inject.Inject; /** * Publishes a counter metric for each filter on each use. */ public class BasicFilterUsageNotifier implements FilterUsageNotifier { private static final String METRIC_PREFIX = "zuul.filter-"; + private final Registry registry; + + @Inject + BasicFilterUsageNotifier(Registry registry) { + this.registry = registry; + } @Override - public void notify(ZuulFilter filter, ExecutionStatus status) { - DynamicCounter.increment(METRIC_PREFIX + filter.getClass().getSimpleName(), "status", status.name(), "filtertype", filter.filterType().toString()); + public void notify(ZuulFilter filter, ExecutionStatus status) { + registry.counter( + "zuul.filter-" + filter.getClass().getSimpleName(), + "status", status.name(), + "filtertype", filter.filterType().toString()).increment(); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/BasicRequestCompleteHandler.java b/zuul-core/src/main/java/com/netflix/zuul/BasicRequestCompleteHandler.java index d9dd7a12..a3279cbc 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/BasicRequestCompleteHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/BasicRequestCompleteHandler.java @@ -16,13 +16,13 @@ package com.netflix.zuul; -import com.google.inject.Inject; import com.netflix.zuul.message.http.HttpRequestInfo; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.stats.RequestMetricsPublisher; import javax.annotation.Nullable; +import javax.inject.Inject; /** * User: michaels@netflix.com @@ -31,7 +31,8 @@ */ public class BasicRequestCompleteHandler implements RequestCompleteHandler { - @Inject @Nullable + @Inject + @Nullable private RequestMetricsPublisher requestMetricsPublisher; @Override diff --git a/zuul-core/src/main/java/com/netflix/zuul/DynamicCodeCompiler.java b/zuul-core/src/main/java/com/netflix/zuul/DynamicCodeCompiler.java index 6ed3e5f0..7a4aa717 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/DynamicCodeCompiler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/DynamicCodeCompiler.java @@ -26,7 +26,7 @@ * Time: 11:35 AM */ public interface DynamicCodeCompiler { - Class compile(String sCode, String sName) throws Exception; + Class compile(String sCode, String sName) throws Exception; - Class compile(File file) throws Exception; + Class compile(File file) throws Exception; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java b/zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java new file mode 100644 index 00000000..ac5af836 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java @@ -0,0 +1,212 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import com.netflix.zuul.filters.FilterRegistry; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.ZuulFilter; +import java.io.File; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public final class DynamicFilterLoader implements FilterLoader { + private static final Logger LOG = LoggerFactory.getLogger(FilterLoader.class); + + private final ConcurrentMap filterClassLastModified = new ConcurrentHashMap<>(); + private final ConcurrentMap filterClassCode = new ConcurrentHashMap<>(); + private final ConcurrentMap filterCheck = new ConcurrentHashMap<>(); + private final ConcurrentMap>> hashFiltersByType = new ConcurrentHashMap<>(); + private final ConcurrentMap> filtersByNameAndType = new ConcurrentHashMap<>(); + + private final FilterRegistry filterRegistry; + + private final DynamicCodeCompiler compiler; + + private final FilterFactory filterFactory; + + @Inject + public DynamicFilterLoader( + FilterRegistry filterRegistry, + DynamicCodeCompiler compiler, + FilterFactory filterFactory) { + this.filterRegistry = filterRegistry; + this.compiler = compiler; + this.filterFactory = filterFactory; + } + + /** + * Given source and name will compile and store the filter if it detects that the filter code + * has changed or the filter doesn't exist. Otherwise it will return an instance of the + * requested ZuulFilter. + * + * @deprecated it is unclear to me why this method is needed. Nothing seems to use it, and the + * swapping of code seems to happen elsewhere. This will be removed in a later + * Zuul release. + */ + @Deprecated + public ZuulFilter getFilter(String sourceCode, String filterName) throws Exception { + if (filterCheck.get(filterName) == null) { + filterCheck.putIfAbsent(filterName, filterName); + if (!sourceCode.equals(filterClassCode.get(filterName))) { + if (filterRegistry.isMutable()) { + LOG.info("reloading code {}", filterName); + filterRegistry.remove(filterName); + } else { + LOG.warn("Filter registry is not mutable, discarding {}", filterName); + } + } + } + ZuulFilter filter = filterRegistry.get(filterName); + if (filter == null) { + Class clazz = compiler.compile(sourceCode, filterName); + if (!Modifier.isAbstract(clazz.getModifiers())) { + filter = filterFactory.newInstance(clazz); + } + } + return filter; + } + + /** + * @return the total number of Zuul filters + */ + public int filterInstanceMapSize() { + return filterRegistry.size(); + } + + /** + * From a file this will read the ZuulFilter source code, compile it, and add it to the list of current filters + * a true response means that it was successful. + * + * @param file the file to load + * @return true if the filter in file successfully read, compiled, verified and added to Zuul + */ + @Override + public boolean putFilter(File file) { + if (!filterRegistry.isMutable()) { + return false; + } + try { + String sName = file.getAbsolutePath(); + if (filterClassLastModified.get(sName) != null + && (file.lastModified() != filterClassLastModified.get(sName))) { + LOG.debug("reloading filter " + sName); + filterRegistry.remove(sName); + } + ZuulFilter filter = filterRegistry.get(sName); + if (filter == null) { + Class clazz = compiler.compile(file); + if (!Modifier.isAbstract(clazz.getModifiers())) { + filter = filterFactory.newInstance(clazz); + putFilter(sName, filter, file.lastModified()); + return true; + } + } + } catch (Exception e) { + LOG.error("Error loading filter! Continuing. file=" + file, e); + return false; + } + + return false; + } + + private void putFilter(String filterName, ZuulFilter filter, long lastModified) { + if (!filterRegistry.isMutable()) { + LOG.warn("Filter registry is not mutable, discarding {}", filterName); + return; + } + SortedSet> set = hashFiltersByType.get(filter.filterType()); + if (set != null) { + hashFiltersByType.remove(filter.filterType()); //rebuild this list + } + + String nameAndType = filter.filterType() + ":" + filter.filterName(); + filtersByNameAndType.put(nameAndType, filter); + + filterRegistry.put(filterName, filter); + filterClassLastModified.put(filterName, lastModified); + } + + /** + * Load and cache filters by className + * + * @param classNames The class names to load + * @return List of the loaded filters + * @throws Exception If any specified filter fails to load, this will abort. This is a safety mechanism so we can + * prevent running in a partially loaded state. + */ + @Override + public List> putFiltersForClasses(String[] classNames) throws Exception { + List> newFilters = new ArrayList<>(); + for (String className : classNames) { + newFilters.add(putFilterForClassName(className)); + } + return Collections.unmodifiableList(newFilters); + } + + @Override + public ZuulFilter putFilterForClassName(String className) throws Exception { + Class clazz = Class.forName(className); + if (!ZuulFilter.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Specified filter class does not implement ZuulFilter interface!"); + } else { + ZuulFilter filter = filterFactory.newInstance(clazz); + putFilter(className, filter, System.currentTimeMillis()); + return filter; + } + } + + /** + * Returns a list of filters by the filterType specified + */ + @Override + public SortedSet> getFiltersByType(FilterType filterType) { + SortedSet> set = hashFiltersByType.get(filterType); + if (set != null) return set; + + set = new TreeSet<>(FILTER_COMPARATOR); + + for (ZuulFilter filter : filterRegistry.getAllFilters()) { + if (filter.filterType().equals(filterType)) { + set.add(filter); + } + } + + hashFiltersByType.putIfAbsent(filterType, set); + return Collections.unmodifiableSortedSet(set); + } + + @Override + public ZuulFilter getFilterByNameAndType(String name, FilterType type) { + if (name == null || type == null) { + return null; + } + + String nameAndType = type + ":" + name; + return filtersByNameAndType.get(nameAndType); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/Filter.java b/zuul-core/src/main/java/com/netflix/zuul/Filter.java new file mode 100644 index 00000000..5205ad7d --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/Filter.java @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.netflix.zuul.filters.FilterSyncType; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.ZuulFilter; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Identifies a {@link ZuulFilter}. + */ +@Target({TYPE}) +@Retention(RUNTIME) +@Documented +public @interface Filter { + + /** + * The order in which to run. See {@link ZuulFilter#filterOrder()}. + */ + int order(); + + /** + * Indicates the type of this filter. + */ + FilterType type() default FilterType.INBOUND; + + /** + * Indicates if this is a synchronous filter. + */ + FilterSyncType sync() default FilterSyncType.SYNC; + + @Target({PACKAGE}) + @Retention(CLASS) + @Documented + @interface FilterPackageName { + String value(); + } + + /** + * Indicates that the annotated filter should run after another filter in the chain, if the other filter is present. + * In the case of inbound filters, this implies that the annotated filter should have an order greater than the + * filters listed. For outbound filters, the order of this filter should be less than the ones listed. Usage of + * this annotation should be used on homogeneous filter types. Additionally, this should not be applied to endpoint + * filters. + */ + @Target({TYPE}) + @Retention(RUNTIME) + @Documented + @interface ApplyAfter { + Class>[] value(); + } + + /** + * Indicates that the annotated filter should run before another filter in the chain, if the other filter is present. + * In the case of inbound filters, this implies that the annotated filter should have an order less than the + * filters listed. For outbound filters, the order of this filter should be greater than the ones listed. Usage of + * this annotation should be used on homogeneous filter types. Additionally, this should not be applied to endpoint + * filters. + * + *

Prefer to use this {@link ApplyAfter} instead. This annotation is meant in case where it may be infeasible + * to use {@linkplain ApplyAfter}. (such as due to dependency cycles) + * + * @see ApplyAfter + */ + @Target({TYPE}) + @Retention(RUNTIME) + @Documented + @interface ApplyBefore { + Class>[] value(); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/FilterFactory.java b/zuul-core/src/main/java/com/netflix/zuul/FilterFactory.java index cf900d09..bd6e7b22 100755 --- a/zuul-core/src/main/java/com/netflix/zuul/FilterFactory.java +++ b/zuul-core/src/main/java/com/netflix/zuul/FilterFactory.java @@ -29,5 +29,5 @@ public interface FilterFactory { * @return an instance of ZuulFilter * @throws Exception if an error occurs */ - public ZuulFilter newInstance(Class clazz) throws Exception; + ZuulFilter newInstance(Class clazz) throws Exception; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/FilterFileManager.java b/zuul-core/src/main/java/com/netflix/zuul/FilterFileManager.java index a881aa66..1cc2ceef 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/FilterFileManager.java +++ b/zuul-core/src/main/java/com/netflix/zuul/FilterFileManager.java @@ -15,16 +15,8 @@ */ package com.netflix.zuul; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.netflix.config.DynamicIntProperty; -import com.netflix.zuul.groovy.GroovyFileFilter; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.inject.Singleton; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; @@ -35,7 +27,12 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class manages the directory polling for changes and new Groovy filters. @@ -64,11 +61,9 @@ public class FilterFileManager { public FilterFileManager(FilterFileManagerConfig config, FilterLoader filterLoader) { this.config = config; this.filterLoader = filterLoader; - - BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() - .namingPattern("FilterFileManager_ProcessFiles-%d") - .build(); - this.processFilesService = Executors.newFixedThreadPool(FILE_PROCESSOR_THREADS.get(), threadFactory); + ThreadFactory tf = + new ThreadFactoryBuilder().setDaemon(true).setNameFormat("FilterFileManager_ProcessFiles-%d").build(); + this.processFilesService = Executors.newFixedThreadPool(FILE_PROCESSOR_THREADS.get(), tf); } /** @@ -76,7 +71,7 @@ public FilterFileManager(FilterFileManagerConfig config, FilterLoader filterLoad * * @throws Exception */ - @PostConstruct + @Inject public void init() throws Exception { long startTime = System.currentTimeMillis(); @@ -91,7 +86,6 @@ public void init() throws Exception /** * Shuts down the poller */ - @PreDestroy public void shutdown() { stopPoller(); } @@ -102,6 +96,10 @@ void stopPoller() { void startPoller() { poller = new Thread("GroovyFilterFileManagerPoller") { + { + setDaemon(true); + } + public void run() { while (bRunning) { try { @@ -210,10 +208,6 @@ public FilterFileManagerConfig(String[] directories, String[] classNames, int po this.filenameFilter = filenameFilter; } - public FilterFileManagerConfig(String[] directories, String[] classNames, int pollingIntervalSeconds) { - this(directories, classNames, pollingIntervalSeconds, new GroovyFileFilter()); - } - public String[] getDirectories() { return directories; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/FilterLoader.java b/zuul-core/src/main/java/com/netflix/zuul/FilterLoader.java index 85991704..8dec7122 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/FilterLoader.java +++ b/zuul-core/src/main/java/com/netflix/zuul/FilterLoader.java @@ -15,204 +15,54 @@ */ package com.netflix.zuul; -import com.netflix.zuul.filters.*; -import com.netflix.zuul.groovy.GroovyCompiler; -import javax.inject.Inject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static java.util.Objects.requireNonNull; -import javax.inject.Singleton; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.ZuulFilter; import java.io.File; -import java.io.IOException; -import java.lang.reflect.Modifier; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Comparator; +import java.util.List; +import java.util.SortedSet; /** * This class is one of the core classes in Zuul. It compiles, loads from a File, and checks if source code changed. * It also holds ZuulFilters by filterType. - * - * @author Mikey Cohen - * Date: 11/3/11 - * Time: 1:59 PM */ -@Singleton -public class FilterLoader -{ - private static final Logger LOG = LoggerFactory.getLogger(FilterLoader.class); - - private final ConcurrentHashMap filterClassLastModified = new ConcurrentHashMap(); - private final ConcurrentHashMap filterClassCode = new ConcurrentHashMap(); - private final ConcurrentHashMap filterCheck = new ConcurrentHashMap(); - private final ConcurrentHashMap> hashFiltersByType = new ConcurrentHashMap<>(); - private final ConcurrentHashMap filtersByNameAndType = new ConcurrentHashMap<>(); - - private final FilterRegistry filterRegistry; - - private final DynamicCodeCompiler compiler; - - private final FilterFactory filterFactory; - - public FilterLoader() { - this(new FilterRegistry(), new GroovyCompiler(), new DefaultFilterFactory()); - } - - @Inject - public FilterLoader(FilterRegistry filterRegistry, DynamicCodeCompiler compiler, FilterFactory filterFactory) { - this.filterRegistry = filterRegistry; - this.compiler = compiler; - this.filterFactory = filterFactory; - } - - /** - * Given source and name will compile and store the filter if it detects that the filter code has changed or - * the filter doesn't exist. Otherwise it will return an instance of the requested ZuulFilter - * - * @param sCode source code - * @param sName name of the filter - * @return the IZuulFilter - * @throws IllegalAccessException - * @throws InstantiationException - */ - public ZuulFilter getFilter(String sCode, String sName) throws Exception { - - if (filterCheck.get(sName) == null) { - filterCheck.putIfAbsent(sName, sName); - if (!sCode.equals(filterClassCode.get(sName))) { - LOG.info("reloading code " + sName); - filterRegistry.remove(sName); - } - } - ZuulFilter filter = filterRegistry.get(sName); - if (filter == null) { - Class clazz = compiler.compile(sCode, sName); - if (!Modifier.isAbstract(clazz.getModifiers())) { - filter = filterFactory.newInstance(clazz); - } - } - return filter; - - } - - /** - * @return the total number of Zuul filters - */ - public int filterInstanceMapSize() { - return filterRegistry.size(); - } - +public interface FilterLoader { /** * From a file this will read the ZuulFilter source code, compile it, and add it to the list of current filters * a true response means that it was successful. * - * @param file + * @param file the file to load * @return true if the filter in file successfully read, compiled, verified and added to Zuul - * @throws IllegalAccessException - * @throws InstantiationException - * @throws IOException */ - public boolean putFilter(File file) throws Exception - { - try { - String sName = file.getAbsolutePath(); - if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) { - LOG.debug("reloading filter " + sName); - filterRegistry.remove(sName); - } - ZuulFilter filter = filterRegistry.get(sName); - if (filter == null) { - Class clazz = compiler.compile(file); - if (!Modifier.isAbstract(clazz.getModifiers())) { - filter = filterFactory.newInstance(clazz); - putFilter(sName, filter, file.lastModified()); - return true; - } - } - } - catch (Exception e) { - LOG.error("Error loading filter! Continuing. file=" + String.valueOf(file), e); - return false; - } - - return false; - } - - void putFilter(String sName, ZuulFilter filter, long lastModified) - { - List list = hashFiltersByType.get(filter.filterType()); - if (list != null) { - hashFiltersByType.remove(filter.filterType()); //rebuild this list - } - - String nameAndType = filter.filterType() + ":" + filter.filterName(); - filtersByNameAndType.put(nameAndType, filter); - - filterRegistry.put(sName, filter); - filterClassLastModified.put(sName, lastModified); - } + boolean putFilter(File file); /** - * Load and cache filters by className + * Load and cache filters by className. * * @param classNames The class names to load * @return List of the loaded filters * @throws Exception If any specified filter fails to load, this will abort. This is a safety mechanism so we can * prevent running in a partially loaded state. */ - public List putFiltersForClasses(String[] classNames) throws Exception - { - List newFilters = new ArrayList<>(); - for (String className : classNames) - { - newFilters.add(putFilterForClassName(className)); - } - return newFilters; - } + List> putFiltersForClasses(String[] classNames) throws Exception; + - public ZuulFilter putFilterForClassName(String className) throws Exception - { - Class clazz = Class.forName(className); - if (! ZuulFilter.class.isAssignableFrom(clazz)) { - throw new IllegalArgumentException("Specified filter class does not implement ZuulFilter interface!"); - } - else { - ZuulFilter filter = filterFactory.newInstance(clazz); - putFilter(className, filter, System.currentTimeMillis()); - return filter; - } - } + ZuulFilter putFilterForClassName(String className) throws Exception; /** - * Returns a list of filters by the filterType specified + * Returns a sorted set of filters by the filterType specified. */ - public List getFiltersByType(FilterType filterType) { - - List list = hashFiltersByType.get(filterType); - if (list != null) return list; - - list = new ArrayList(); - - Collection filters = filterRegistry.getAllFilters(); - for (Iterator iterator = filters.iterator(); iterator.hasNext(); ) { - ZuulFilter filter = iterator.next(); - if (filter.filterType().equals(filterType)) { - list.add(filter); - } - } - - // Sort by filterOrder. - Collections.sort(list, Comparator.comparingInt(ZuulFilter::filterOrder)); + SortedSet> getFiltersByType(FilterType filterType); - hashFiltersByType.putIfAbsent(filterType, list); - return list; - } + ZuulFilter getFilterByNameAndType(String name, FilterType type); - public ZuulFilter getFilterByNameAndType(String name, FilterType type) - { - if (name == null || type == null) - return null; + Comparator> FILTER_COMPARATOR = + Comparator.>comparingInt(ZuulFilter::filterOrder).thenComparing(ZuulFilter::filterName); - String nameAndType = type.toString() + ":" + name; - return filtersByNameAndType.get(nameAndType); - } + Comparator>> FILTER_CLASS_COMPARATOR = + Comparator.>>comparingInt( + c -> requireNonNull(c.getAnnotation(Filter.class), () -> "missing annotation: " + c).order()) + .thenComparing(Class::getName); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/FilterUsageNotifier.java b/zuul-core/src/main/java/com/netflix/zuul/FilterUsageNotifier.java index ef30a9cc..76f354d8 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/FilterUsageNotifier.java +++ b/zuul-core/src/main/java/com/netflix/zuul/FilterUsageNotifier.java @@ -26,5 +26,5 @@ * Time: 9:55 PM */ public interface FilterUsageNotifier { - public void notify(ZuulFilter filter, ExecutionStatus status); + void notify(ZuulFilter filter, ExecutionStatus status); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/StaticFilterLoader.java b/zuul-core/src/main/java/com/netflix/zuul/StaticFilterLoader.java new file mode 100644 index 00000000..7c6c18d4 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/StaticFilterLoader.java @@ -0,0 +1,158 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import com.google.errorprone.annotations.DoNotCall; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.ZuulFilter; +import com.netflix.zuul.message.ZuulMessage; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import javax.annotation.Nullable; +import javax.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An immutable static collection of filters. + */ +public final class StaticFilterLoader implements FilterLoader { + + private static final Logger logger = LoggerFactory.getLogger(StaticFilterLoader.class); + + public static final String RESOURCE_NAME = "META-INF/zuul/allfilters"; + + private final Map>> filtersByType; + private final Map>> filtersByTypeAndName; + + @Inject + public StaticFilterLoader( + FilterFactory filterFactory, Set>> filterTypes) { + Map>> filtersByType = new EnumMap<>(FilterType.class); + Map>> filtersByName = new EnumMap<>(FilterType.class); + for (Class> clz : filterTypes) { + try { + ZuulFilter f = filterFactory.newInstance(clz); + filtersByType.computeIfAbsent(f.filterType(), k -> new TreeSet<>(FILTER_COMPARATOR)).add(f); + filtersByName.computeIfAbsent(f.filterType(), k -> new HashMap<>()).put(f.filterName(), f); + } catch (RuntimeException | Error e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + for (Entry>> entry : filtersByType.entrySet()) { + entry.setValue(Collections.unmodifiableSortedSet(entry.getValue())); + } + Map>> immutableFiltersByName = new EnumMap<>(FilterType.class); + for (Entry>> entry : filtersByName.entrySet()) { + immutableFiltersByName.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue())); + } + this.filtersByTypeAndName = Collections.unmodifiableMap(immutableFiltersByName); + this.filtersByType = Collections.unmodifiableMap(filtersByType); + } + + public static Set>> loadFilterTypesFromResources(ClassLoader loader) + throws IOException { + Set>> filterTypes = new LinkedHashSet<>(); + for (URL url : Collections.list(loader.getResources(RESOURCE_NAME))) { + try (InputStream is = url.openStream(); + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(isr)) { + String line; + while ((line = br.readLine()) != null) { + String trimmed = line.trim(); + if (!trimmed.isEmpty()) { + Class clz; + try { + clz = Class.forName(trimmed, false, loader); + } catch (ClassNotFoundException e) { + // This can happen if a filter is deleted, but the annotation processor doesn't + // remove it from the list. This is mainly a problem with IntelliJ, which + // forces append only annotation processors. Incremental recompilation drops + // most of the classpath, making the processor unable to reconstruct the filter + // list. To work around this problem, use the stale, cached filter list from + // the initial full compilation and add to it. This makes incremental + // compilation work later, at the cost of polluting the filter list. It's a + // better experience to log a warning (and do a clean build), than to + // mysteriously classes. + + logger.warn("Missing Filter", e); + continue; + } + @SuppressWarnings("unchecked") + Class> filterClz = + (Class>) clz.asSubclass(ZuulFilter.class); + filterTypes.add(filterClz); + } + } + } + } + return Collections.unmodifiableSet(filterTypes); + } + + @Override + @DoNotCall + public boolean putFilter(File file) { + throw new UnsupportedOperationException(); + } + + @Override + @DoNotCall + public List> putFiltersForClasses(String[] classNames) { + throw new UnsupportedOperationException(); + } + + @Override + @DoNotCall + public ZuulFilter putFilterForClassName(String className) { + throw new UnsupportedOperationException(); + } + + @Override + public SortedSet> getFiltersByType(FilterType filterType) { + return filtersByType.get(filterType); + } + + @Override + @Nullable + public ZuulFilter getFilterByNameAndType(String name, FilterType type) { + Map> filtersByName = filtersByTypeAndName.get(type); + if (filtersByName == null) { + return null; + } + return filtersByName.get(name); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/context/CommonContextKeys.java b/zuul-core/src/main/java/com/netflix/zuul/context/CommonContextKeys.java index 711b6579..d4d40c77 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/context/CommonContextKeys.java +++ b/zuul-core/src/main/java/com/netflix/zuul/context/CommonContextKeys.java @@ -44,6 +44,12 @@ public class CommonContextKeys { public static final String ACTUAL_VIP = "origin_vip_actual"; public static final String ORIGIN_VIP_SECURE = "origin_vip_secure"; + /** + * The original client destination address Zuul by a proxy running Proxy Protocol. + * Will only be set if both Zuul and the connected proxy are both using set to use Proxy Protocol. + */ + public static final String PROXY_PROTOCOL_DESTINATION_ADDRESS = "proxy_protocol_destination_address"; + public static final String SSL_HANDSHAKE_INFO = "ssl_handshake_info"; public static final String GZIPPER = "gzipper"; diff --git a/zuul-core/src/main/java/com/netflix/zuul/context/Debug.java b/zuul-core/src/main/java/com/netflix/zuul/context/Debug.java index 89d53817..a8092090 100755 --- a/zuul-core/src/main/java/com/netflix/zuul/context/Debug.java +++ b/zuul-core/src/main/java/com/netflix/zuul/context/Debug.java @@ -16,21 +16,15 @@ package com.netflix.zuul.context; import com.netflix.zuul.message.Header; -import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.ZuulMessage; -import com.netflix.zuul.message.http.*; -import com.netflix.zuul.util.HttpUtils; -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import rx.Observable; - -import java.io.ByteArrayInputStream; -import java.io.IOException; +import com.netflix.zuul.message.http.HttpRequestInfo; +import com.netflix.zuul.message.http.HttpResponseInfo; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.zip.GZIPInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rx.Observable; /** * Simple wrapper class around the RequestContext for setting and managing Request level Debug data. @@ -127,6 +121,10 @@ public static List getRequestDebug(SessionContext ctx) { */ public static void compareContextState(String filterName, SessionContext context, SessionContext copy) { // TODO - only comparing Attributes. Need to compare the messages too. + + // Ensure that the routingDebug property already exists, otherwise we'll have a ConcurrentModificationException below + getRoutingDebug(context); + Iterator it = context.keySet().iterator(); String key = it.next(); while (key != null) { @@ -177,7 +175,7 @@ public static Observable writeDebugResponse(SessionContext context, String prefix = isInbound ? "RESPONSE_INBOUND" : "RESPONSE_OUTBOUND"; String arrow = "<"; - Debug.addRequestDebug(context, String.format("%s:: %s STATUS: %s", prefix, arrow, response.getStatus())); + Debug.addRequestDebug(context, String.format("%s:: %s STATUS: %d ", prefix, arrow, response.getStatus())); obs = Debug.writeDebugMessage(context, response, prefix, arrow); } @@ -210,19 +208,4 @@ public static Observable writeDebugMessage(SessionContext context, Zuul return obs; } - - public static String bodyToText(byte[] bodyBytes, Headers headers) - { - try { - if (HttpUtils.isGzipped(headers)) { - GZIPInputStream gzIn = new GZIPInputStream(new ByteArrayInputStream(bodyBytes)); - bodyBytes = IOUtils.toByteArray(gzIn); - } - return IOUtils.toString(bodyBytes, "UTF-8"); - } - catch (IOException e) { - LOG.error("Error reading message body for debugging.", e); - return "ERROR READING MESSAGE BODY!"; - } - } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/context/SessionContext.java b/zuul-core/src/main/java/com/netflix/zuul/context/SessionContext.java index 84ce9274..3b6a7d23 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/context/SessionContext.java +++ b/zuul-core/src/main/java/com/netflix/zuul/context/SessionContext.java @@ -15,27 +15,25 @@ */ package com.netflix.zuul.context; -/** - * User: Mike Smith - * Date: 4/28/15 - * Time: 6:45 PM - */ import com.netflix.config.DynamicPropertyFactory; import com.netflix.zuul.filters.FilterError; import com.netflix.zuul.message.http.HttpResponseMessage; -import com.netflix.zuul.stats.Timings; -import com.netflix.zuul.util.DeepCopy; - -import java.io.NotSerializableException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Represents the context between client and origin server for the duration of the dedicated connection/session * between them. But we're currently still only modelling single request/response pair per session. * * NOTE: Not threadsafe, and not intended to be used concurrently. + * + * User: Mike Smith + * Date: 4/28/15 + * Time: 6:45 PM */ public class SessionContext extends HashMap implements Cloneable { @@ -51,9 +49,6 @@ public class SessionContext extends HashMap implements Cloneable private boolean debugRequestHeadersOnly = false; private boolean cancelled = false; - private Timings timings = new Timings(); - - private static final String KEY_UUID = "_uuid"; private static final String KEY_VIP = "routeVIP"; private static final String KEY_ENDPOINT = "_endpoint"; @@ -76,8 +71,6 @@ public SessionContext() /** * Makes a copy of the RequestContext. This is used for debugging. - * - * @return */ @Override public SessionContext clone() @@ -93,7 +86,6 @@ public String getString(String key) /** * Convenience method to return a boolean value for a given key * - * @param key * @return true or false depending what was set. default is false */ public boolean getBoolean(String key) { @@ -103,8 +95,6 @@ public boolean getBoolean(String key) { /** * Convenience method to return a boolean value for a given key * - * @param key - * @param defaultResponse * @return true or false depending what was set. default defaultResponse */ public boolean getBoolean(String key, boolean defaultResponse) { @@ -117,8 +107,6 @@ public boolean getBoolean(String key, boolean defaultResponse) { /** * sets a key value to Boolean.TRUE - * - * @param key */ public void set(String key) { put(key, Boolean.TRUE); @@ -127,55 +115,12 @@ public void set(String key) { /** * puts the key, value into the map. a null value will remove the key from the map * - * @param key - * @param value */ public void set(String key, Object value) { if (value != null) put(key, value); else remove(key); } - /** - * Makes a copy of the SessionContext. This is used for debugging. - * - * @return - */ - public SessionContext copy() - { - SessionContext copy = new SessionContext(); - copy.brownoutMode = brownoutMode; - copy.cancelled = cancelled; - copy.shouldStopFilterProcessing = shouldStopFilterProcessing; - copy.shouldSendErrorResponse = shouldSendErrorResponse; - copy.errorResponseSent = errorResponseSent; - copy.debugRouting = debugRouting; - copy.debugRequest = debugRequest; - copy.debugRequestHeadersOnly = debugRequestHeadersOnly; - copy.timings = timings; - - Iterator it = keySet().iterator(); - String key = it.next(); - while (key != null) { - Object orig = get(key); - try { - Object copyValue = DeepCopy.copy(orig); - if (copyValue != null) { - copy.set(key, copyValue); - } else { - copy.set(key, orig); - } - } catch (NotSerializableException e) { - copy.set(key, orig); - } - if (it.hasNext()) { - key = it.next(); - } else { - key = null; - } - } - return copy; - } - public String getUUID() { return getString(KEY_UUID); @@ -195,7 +140,6 @@ public HttpResponseMessage getStaticResponse() { /** * Gets the throwable that will be use in the Error endpoint. * - * @return a set throwable */ public Throwable getError() { return (Throwable) get("_error"); @@ -204,8 +148,6 @@ public Throwable getError() { /** * Sets throwable to use for generating a response in the Error endpoint. - * - * @param th */ public void setError(Throwable th) { put("_error", th); @@ -221,8 +163,6 @@ public void setErrorEndpoint(String name) { /** * sets debugRouting - * - * @param bDebug */ public void setDebugRouting(boolean bDebug) { this.debugRouting = bDebug; @@ -238,11 +178,9 @@ public boolean debugRouting() { /** * sets "debugRequestHeadersOnly" to bHeadersOnly * - * @param bHeadersOnly */ public void setDebugRequestHeadersOnly(boolean bHeadersOnly) { this.debugRequestHeadersOnly = bHeadersOnly; - } /** @@ -254,8 +192,6 @@ public boolean debugRequestHeadersOnly() { /** * sets "debugRequest" - * - * @param bDebug */ public void setDebugRequest(boolean bDebug) { this.debugRequest = bDebug; @@ -318,7 +254,6 @@ public boolean shouldSendErrorResponse() { * Set this to true to indicate that the Error endpoint should be applied after * the end of the current filter processing phase. * - * @param should */ public void setShouldSendErrorResponse(boolean should) { this.shouldSendErrorResponse = should; @@ -338,7 +273,6 @@ public void setErrorResponseSent(boolean should) { * This can be used by filters for flagging if the server is getting overloaded, and then choose * to disable/sample/rate-limit some optional features. * - * @return */ public boolean isInBrownoutMode() { @@ -365,7 +299,6 @@ public boolean shouldStopFilterProcessing() { /** * returns the routeVIP; that is the Eureka "vip" of registered instances * - * @return */ public String getRouteVIP() { return (String) get(KEY_VIP); @@ -400,11 +333,6 @@ public List getFilterErrors() { return (List) get(KEY_FILTER_ERRORS); } - public Timings getTimings() - { - return timings; - } - public void setOriginReportedDuration(int duration) { put("_originReportedDuration", duration); diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilter.java index 406d6ba5..3c471c32 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilter.java @@ -23,8 +23,6 @@ import com.netflix.zuul.netty.SpectatorUtils; import io.netty.handler.codec.http.HttpContent; -import io.perfmark.PerfMark; -import io.perfmark.Tag; import java.util.concurrent.atomic.AtomicInteger; /** @@ -76,26 +74,21 @@ public boolean overrideStopFilterProcessing() /** * The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable - * - * @return */ public String disablePropertyName() { return "zuul." + baseName + ".disable"; } /** - * The name of the Archaius property for this filter's max concurrency. by default it is zuul.[classname].[filtertype].concurrency.limit - * - * @return + * The name of the Archaius property for this filter's max concurrency. by default it is + * zuul.[classname].[filtertype].concurrency.limit */ public String maxConcurrencyPropertyName() { return "zuul." + baseName + ".concurrency.limit"; } /** - * If true, the filter has been disabled by archaius and will not be run - * - * @return + * If true, the filter has been disabled by archaius and will not be run. */ @Override public boolean isDisabled() { diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilterTest.java b/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilterTest.java deleted file mode 100644 index 767e0231..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilterTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.filters; - -import com.netflix.zuul.context.SessionContext; -import com.netflix.zuul.message.Headers; -import com.netflix.zuul.message.http.*; -import org.junit.Before; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.mockito.Mockito.when; - -/** - * A convenience superclass to extend when writing Filter unit-tests. - * - * @author Mike Smith - */ -@RunWith(MockitoJUnitRunner.class) -public abstract class BaseFilterTest -{ - @Mock - protected HttpResponseMessage response; - @Mock - protected HttpRequestMessage request; - @Mock - protected HttpRequestInfo originalRequest; - @Mock - protected HttpResponseInfo originResponse; - - protected SessionContext context; - protected Headers originalRequestHeaders; - protected Headers requestHeaders; - protected HttpQueryParams requestParams; - protected Cookies requestCookies; - protected Headers originResponseHeaders; - protected Headers responseHeaders; - - @Before - public void setup() - { - context = new SessionContext(); - - when(request.getContext()).thenReturn(context); - when(response.getContext()).thenReturn(context); - when(request.getInboundRequest()).thenReturn(originalRequest); - when(response.getOutboundRequest()).thenReturn(request); - when(response.getInboundRequest()).thenReturn(originalRequest); - when(response.getInboundResponse()).thenReturn(originResponse); - - originResponseHeaders = new Headers(); - when(originResponse.getHeaders()).thenReturn(originResponseHeaders); - - originalRequestHeaders = new Headers(); - requestHeaders = new Headers(); - when(request.getHeaders()).thenReturn(requestHeaders); - when(originalRequest.getHeaders()).thenReturn(originalRequestHeaders); - - requestParams = new HttpQueryParams(); - when(request.getQueryParams()).thenReturn(requestParams); - when(originalRequest.getQueryParams()).thenReturn(requestParams); - - requestCookies = new Cookies(); - when(request.parseCookies()).thenReturn(requestCookies); - - responseHeaders = new Headers(); - when(response.getHeaders()).thenReturn(responseHeaders); - } - - protected void setRequestHost(String host) { - when(originalRequest.getOriginalHost()).thenReturn(host); - } -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/BaseSyncFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/BaseSyncFilter.java index 8de5169f..12b7aded 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/BaseSyncFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/BaseSyncFilter.java @@ -30,9 +30,6 @@ public abstract class BaseSyncFilter applyAsync(I input) diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/FilterRegistry.java b/zuul-core/src/main/java/com/netflix/zuul/filters/FilterRegistry.java index a048314f..42e70667 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/FilterRegistry.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/FilterRegistry.java @@ -15,36 +15,37 @@ */ package com.netflix.zuul.filters; - -import javax.inject.Singleton; import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author mhawthorne - */ -@Singleton -public class FilterRegistry -{ - private final ConcurrentHashMap filters = new ConcurrentHashMap(); - - public ZuulFilter remove(String key) { - return this.filters.remove(key); - } - - public ZuulFilter get(String key) { - return this.filters.get(key); - } - - public void put(String key, ZuulFilter filter) { - this.filters.putIfAbsent(key, filter); - } - - public int size() { - return this.filters.size(); - } - - public Collection getAllFilters() { - return this.filters.values(); - } +import javax.annotation.Nullable; + +public interface FilterRegistry { + @Nullable + ZuulFilter get(String key); + + int size(); + + Collection> getAllFilters(); + + /** + * Indicates if this registry can be modified. Implementations should not change the return; + * they return the same value each time. + */ + boolean isMutable(); + + /** + * Removes the filter from the registry, and returns it. Returns {@code null} no such filter + * was found. Callers should check {@link #isMutable()} before calling this method. + * + * @throws IllegalStateException if this registry is not mutable. + */ + @Nullable + ZuulFilter remove(String key); + + /** + * Stores the filter into the registry. If an existing filter was present with the same key, + * it is removed. Callers should check {@link #isMutable()} before calling this method. + * + * @throws IllegalStateException if this registry is not mutable. + */ + void put(String key, ZuulFilter filter); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/MutableFilterRegistry.java b/zuul-core/src/main/java/com/netflix/zuul/filters/MutableFilterRegistry.java new file mode 100644 index 00000000..122745a6 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/MutableFilterRegistry.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Netflix, Inc. + * + * 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 com.netflix.zuul.filters; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import javax.inject.Singleton; + +@Singleton +public final class MutableFilterRegistry implements FilterRegistry { + private final ConcurrentHashMap> filters = new ConcurrentHashMap<>(); + + @Nullable + @Override + public ZuulFilter remove(String key) { + return filters.remove(requireNonNull(key, "key")); + } + + @Override + @Nullable + public ZuulFilter get(String key) { + return filters.get(requireNonNull(key, "key")); + } + + @Override + public void put(String key, ZuulFilter filter) { + filters.putIfAbsent(requireNonNull(key, "key"), requireNonNull(filter, "filter")); + } + + @Override + public int size() { + return filters.size(); + } + + @Override + public Collection> getAllFilters() { + return Collections.unmodifiableList(new ArrayList<>(filters.values())); + } + + @Override + public boolean isMutable() { + return true; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/SyncZuulFilterAdapter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/SyncZuulFilterAdapter.java index 3b78e739..16fe9493 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/SyncZuulFilterAdapter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/SyncZuulFilterAdapter.java @@ -16,11 +16,8 @@ package com.netflix.zuul.filters; -import com.netflix.zuul.exception.ZuulFilterConcurrencyExceededException; import com.netflix.zuul.message.ZuulMessage; import io.netty.handler.codec.http.HttpContent; -import io.perfmark.PerfMark; -import io.perfmark.Tag; import rx.Observable; import static com.netflix.zuul.filters.FilterSyncType.SYNC; diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/TestSyncFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/TestSyncFilter.java deleted file mode 100644 index ca45bf1a..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/TestSyncFilter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.filters; - -import com.netflix.zuul.message.http.HttpRequestMessage; - -/** - * User: michaels@netflix.com - * Date: 5/15/15 - * Time: 10:54 AM - */ -public class TestSyncFilter extends BaseSyncFilter -{ - @Override - public HttpRequestMessage apply(HttpRequestMessage input) { - return input; - } - - @Override - public int filterOrder() { - return 0; - } - - @Override - public FilterType filterType() { - return FilterType.INBOUND; - } - - @Override - public boolean shouldFilter(HttpRequestMessage msg) { - return true; - } -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/ZuulFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/ZuulFilter.java index a00636a8..368720e3 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/ZuulFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/ZuulFilter.java @@ -15,6 +15,7 @@ */ package com.netflix.zuul.filters; +import com.netflix.zuul.Filter; import com.netflix.zuul.exception.ZuulFilterConcurrencyExceededException; import com.netflix.zuul.message.ZuulMessage; import io.netty.handler.codec.http.HttpContent; @@ -34,12 +35,18 @@ public interface ZuulFilter extend String filterName(); /** - * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not + * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not * important for a filter. filterOrders do not need to be sequential. * * @return the int order of a filter */ - int filterOrder(); + default int filterOrder() { + Filter f = getClass().getAnnotation(Filter.class); + if (f != null) { + return f.order(); + } + throw new UnsupportedOperationException("not implemented"); + } /** * to classify a filter by type. Standard types in Zuul are "in" for pre-routing filtering, @@ -47,7 +54,13 @@ public interface ZuulFilter extend * * @return FilterType */ - FilterType filterType(); + default FilterType filterType() { + Filter f = getClass().getAnnotation(Filter.class); + if (f != null) { + return f.type(); + } + throw new UnsupportedOperationException("not implemented"); + } /** * Whether this filter's shouldFilter() method should be checked, and apply() called, even @@ -75,7 +88,13 @@ public interface ZuulFilter extend */ void decrementConcurrency(); - FilterSyncType getSyncType(); + default FilterSyncType getSyncType() { + Filter f = getClass().getAnnotation(Filter.class); + if (f != null) { + return f.sync(); + } + throw new UnsupportedOperationException("not implemented"); + } /** * Choose a default message to use if the applyAsync() method throws an exception. @@ -93,8 +112,6 @@ public interface ZuulFilter extend boolean needsBodyBuffered(I input); /** - * Optionally transform HTTP content chunk received - * @param chunk - * @return + * Optionally transform HTTP content chunk received. */ HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk);} diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java index 871e2dfc..779fed05 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java @@ -20,7 +20,9 @@ import com.netflix.config.CachedDynamicBooleanProperty; import com.netflix.config.CachedDynamicIntProperty; import com.netflix.config.DynamicStringSetProperty; +import com.netflix.zuul.Filter; import com.netflix.zuul.context.CommonContextKeys; +import com.netflix.zuul.filters.FilterType; import com.netflix.zuul.filters.http.HttpOutboundSyncFilter; import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.ZuulMessage; @@ -35,12 +37,14 @@ import io.netty.handler.codec.http.LastHttpContent; /** - * General-purpose filter for gzipping/ungzipping response bodies if requested/needed. + * General-purpose filter for gzipping/ungzipping response bodies if requested/needed. This should be run as late as + * possible to ensure final encoded body length is considered * - * You can just subclass this in your project, and use as-is. + *

You can just subclass this in your project, and use as-is. * * @author Mike Smith */ +@Filter(order = 110, type = FilterType.OUTBOUND) public class GZipResponseFilter extends HttpOutboundSyncFilter { private static DynamicStringSetProperty GZIPPABLE_CONTENT_TYPES = new DynamicStringSetProperty("zuul.gzip.contenttypes", @@ -56,14 +60,6 @@ public class GZipResponseFilter extends HttpOutboundSyncFilter private static final CachedDynamicBooleanProperty ENABLED = new CachedDynamicBooleanProperty("zuul.response.gzip.filter.enabled", true); - @Override - public int filterOrder() { - - // run as late as possible to ensure the - // final encoded body length is considered - return 110; - } - @Override public boolean shouldFilter(HttpResponseMessage response) { if (!ENABLED.get() || !response.hasBody() || response.getContext().isInBrownoutMode()) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/common/SurgicalDebugFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/common/SurgicalDebugFilter.java index b8604fab..231c4cf7 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/common/SurgicalDebugFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/common/SurgicalDebugFilter.java @@ -17,9 +17,11 @@ import com.netflix.config.DynamicBooleanProperty; import com.netflix.config.DynamicStringProperty; +import com.netflix.zuul.Filter; import com.netflix.zuul.constants.ZuulConstants; import com.netflix.zuul.constants.ZuulHeaders; import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.filters.FilterType; import com.netflix.zuul.filters.http.HttpInboundSyncFilter; import com.netflix.zuul.message.http.HttpQueryParams; import com.netflix.zuul.message.http.HttpRequestMessage; @@ -32,6 +34,7 @@ * Date: 6/27/12 * Time: 12:54 PM */ +@Filter(order = 99, type = FilterType.INBOUND) public class SurgicalDebugFilter extends HttpInboundSyncFilter { /** diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/MissingEndpointHandlingFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/MissingEndpointHandlingFilter.java index 7d7a5dca..d190fccc 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/MissingEndpointHandlingFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/MissingEndpointHandlingFilter.java @@ -16,8 +16,10 @@ package com.netflix.zuul.filters.endpoint; +import com.netflix.zuul.Filter; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.exception.ZuulException; +import com.netflix.zuul.filters.FilterType; import com.netflix.zuul.filters.SyncZuulFilterAdapter; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; @@ -28,6 +30,7 @@ /** * Created by saroskar on 2/13/17. */ +@Filter(order = 0, type = FilterType.ENDPOINT) public final class MissingEndpointHandlingFilter extends SyncZuulFilterAdapter { private final String name; @@ -56,5 +59,4 @@ public String filterName() { public HttpResponseMessage getDefaultOutput(final HttpRequestMessage input) { return HttpResponseMessageImpl.defaultErrorResponse(input); } - } diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/ProxyEndpoint.java b/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/ProxyEndpoint.java index 0d474338..e60161e3 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/ProxyEndpoint.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/ProxyEndpoint.java @@ -16,31 +16,45 @@ package com.netflix.zuul.filters.endpoint; +import static com.netflix.zuul.context.CommonContextKeys.ORIGIN_CHANNEL; +import static com.netflix.zuul.netty.server.ClientRequestReceiver.ATTR_ZUUL_RESP; +import static com.netflix.zuul.passport.PassportState.ORIGIN_CONN_ACQUIRE_END; +import static com.netflix.zuul.passport.PassportState.ORIGIN_CONN_ACQUIRE_FAILED; +import static com.netflix.zuul.passport.PassportState.ORIGIN_RETRY_START; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.FAILURE_ORIGIN; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.FAILURE_ORIGIN_THROTTLED; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.SUCCESS; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.SUCCESS_LOCAL_NO_ROUTE; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.SUCCESS_NOT_FOUND; + +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; +import com.google.errorprone.annotations.ForOverride; import com.netflix.client.ClientException; -import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfigKey; -import com.netflix.config.CachedDynamicIntProperty; +import com.netflix.client.config.IClientConfigKey.Keys; +import com.netflix.config.CachedDynamicLongProperty; import com.netflix.config.DynamicBooleanProperty; import com.netflix.config.DynamicIntegerSetProperty; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.reactive.ExecutionContext; -import com.netflix.netty.common.channel.config.CommonChannelConfigKeys; import com.netflix.spectator.api.Counter; +import com.netflix.zuul.Filter; import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.context.Debug; import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.exception.ErrorType; import com.netflix.zuul.exception.OutboundErrorType; import com.netflix.zuul.exception.OutboundException; import com.netflix.zuul.exception.ZuulException; +import com.netflix.zuul.filters.FilterType; import com.netflix.zuul.filters.SyncZuulFilterAdapter; import com.netflix.zuul.message.HeaderName; import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.ZuulMessage; import com.netflix.zuul.message.http.HttpHeaderNames; import com.netflix.zuul.message.http.HttpQueryParams; +import com.netflix.zuul.message.http.HttpRequestInfo; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.message.http.HttpResponseMessageImpl; @@ -54,11 +68,13 @@ import com.netflix.zuul.netty.filter.FilterRunner; import com.netflix.zuul.netty.server.MethodBinding; import com.netflix.zuul.netty.server.OriginResponseReceiver; +import com.netflix.zuul.netty.timeouts.OriginTimeoutManager; import com.netflix.zuul.niws.RequestAttempt; import com.netflix.zuul.niws.RequestAttempts; import com.netflix.zuul.origins.NettyOrigin; import com.netflix.zuul.origins.Origin; import com.netflix.zuul.origins.OriginManager; +import com.netflix.zuul.origins.OriginName; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; import com.netflix.zuul.stats.status.StatusCategory; @@ -79,13 +95,13 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import io.perfmark.PerfMark; +import io.perfmark.TaskCloseable; import java.io.UnsupportedEncodingException; +import java.net.InetAddress; import java.net.URLDecoder; +import java.time.Duration; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -93,37 +109,37 @@ import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.atomic.AtomicReference; - -import static com.netflix.client.config.CommonClientConfigKey.ReadTimeout; -import static com.netflix.zuul.context.CommonContextKeys.ORIGIN_CHANNEL; -import static com.netflix.zuul.netty.server.ClientRequestReceiver.ATTR_ZUUL_RESP; -import static com.netflix.zuul.passport.PassportState.*; -import static com.netflix.zuul.stats.status.ZuulStatusCategory.*; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Not thread safe! New instance of this class is created per HTTP/1.1 request proxied to the origin but NOT for each * attempt/retry. All the retry attempts for a given HTTP/1.1 request proxied share the same EdgeProxyEndpoint instance * Created by saroskar on 5/31/17. */ +@Filter(order = 0, type = FilterType.ENDPOINT) public class ProxyEndpoint extends SyncZuulFilterAdapter implements GenericFutureListener> { private final ChannelHandlerContext channelCtx; private final FilterRunner responseFilters; - protected final AtomicReference chosenServer; - protected final AtomicReference chosenHostAddr; + protected final AtomicReference chosenServer; + protected final AtomicReference chosenHostAddr; /* Individual request related state */ protected final HttpRequestMessage zuulRequest; protected final SessionContext context; + @Nullable protected final NettyOrigin origin; protected final RequestAttempts requestAttempts; protected final CurrentPassport passport; protected final NettyRequestAttemptFactory requestAttemptFactory; + protected final OriginTimeoutManager originTimeoutManager; protected MethodBinding methodBinding; protected HttpResponseMessage zuulResponse; protected boolean startedSendingResponseToClient; - protected Object originalReadTimeout; + protected Duration timeLeftForAttempt; /* Individual retry related state */ private volatile PooledConnection originConn; @@ -134,13 +150,21 @@ public class ProxyEndpoint extends SyncZuulFilterAdapter requestStats = new ArrayList<>(); protected RequestStat currentRequestStat; - private final byte[] sslRetryBodyCache; + private final byte[] retryBodyCache; public static final Set IDEMPOTENT_HTTP_METHODS = Sets.newHashSet("GET", "HEAD", "OPTIONS"); private static final DynamicIntegerSetProperty RETRIABLE_STATUSES_FOR_IDEMPOTENT_METHODS = new DynamicIntegerSetProperty("zuul.retry.allowed.statuses.idempotent", "500"); - private static final DynamicBooleanProperty ENABLE_CACHING_SSL_BODIES = new DynamicBooleanProperty("zuul.cache.ssl.bodies", true); + private static final DynamicBooleanProperty ENABLE_CACHING_BODIES = new DynamicBooleanProperty("zuul.cache.bodies", true); + private static final DynamicBooleanProperty ENABLE_CACHING_PLAINTEXT_BODIES = + new DynamicBooleanProperty("zuul.cache.bodies.plaintext", true); + + /** + * Indicates how long Zuul should remember throttle events for an origin. As of this writing, throttling is used + * to decide to cache request bodies. + */ + private static final CachedDynamicLongProperty THROTTLE_MEMORY_SECONDS = + new CachedDynamicLongProperty("zuul.proxy.throttle_memory_seconds", Duration.ofMinutes(5).getSeconds()); - private static final CachedDynamicIntProperty MAX_OUTBOUND_READ_TIMEOUT = new CachedDynamicIntProperty("zuul.origin.readtimeout.max", 90 * 1000); private static final Set REQUEST_HEADERS_TO_REMOVE = Sets.newHashSet(HttpHeaderNames.CONNECTION, HttpHeaderNames.KEEP_ALIVE); private static final Set RESPONSE_HEADERS_TO_REMOVE = Sets.newHashSet(HttpHeaderNames.CONNECTION, HttpHeaderNames.KEEP_ALIVE); @@ -148,8 +172,7 @@ public class ProxyEndpoint extends SyncZuulFilterAdapter filters, MethodBinding methodBinding) { @@ -164,13 +187,16 @@ public ProxyEndpoint(final HttpRequestMessage inMesg, final ChannelHandlerContex zuulRequest = transformRequest(inMesg); context = zuulRequest.getContext(); origin = getOrigin(zuulRequest); + originTimeoutManager = getTimeoutManager(origin); requestAttempts = RequestAttempts.getFromSessionContext(context); passport = CurrentPassport.fromSessionContext(context); - chosenServer = new AtomicReference<>(); + chosenServer = new AtomicReference<>(DiscoveryResult.EMPTY); chosenHostAddr = new AtomicReference<>(); - this.sslRetryBodyCache = preCacheBodyForRetryingSslRequests(); - this.populatedSslRetryBody = SpectatorUtils.newCounter("zuul.populated.ssl.retry.body", origin == null ? "null" : origin.getVip()); + // This must happen after origin is set, since it depends on it. + this.retryBodyCache = preCacheBodyForRetryingRequests(); + this.populatedRetryBody = SpectatorUtils.newCounter( + "zuul.populated.retry.body", origin == null ? "null" : origin.getName().getTarget()); this.methodBinding = methodBinding; this.requestAttemptFactory = requestAttemptFactory; @@ -267,13 +293,6 @@ public HttpResponseMessage apply(final HttpRequestMessage input) { return null; } - origin.getProxyTiming(zuulRequest).start(); - - // To act the same as Ribbon, we must do this before starting execution (as well as before each attempt). - IClientConfig requestConfig = origin.getExecutionContext(zuulRequest).getRequestConfig(); - originalReadTimeout = requestConfig.getProperty(ReadTimeout, null); - setReadTimeoutOnContext(requestConfig, 1); - origin.onRequestExecutionStart(zuulRequest); proxyRequestToOrigin(); @@ -345,24 +364,33 @@ private void filterResponseChunk(final HttpContent chunk) { private void storeAndLogOriginRequestInfo() { final Map eventProps = context.getEventProperties(); - Map attempToIpAddressMap = (Map) eventProps.get(CommonContextKeys.ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY); - Map attempToChosenHostMap = (Map) eventProps.get(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY); - if (attempToIpAddressMap == null) { - attempToIpAddressMap = new HashMap<>(); + // These two maps appear to be almost the same but are slightly different. Also, the types in the map don't + // match exactly what needs to happen, so this is more of a To-Do. ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY is + // supposed to be the mapping of IP addresses of the server. This is (AFAICT) only used for logging. It is + // an IP address semantically, but a String here. The two should be swapped. + // ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY is almost always an IP address, but may some times be a hostname in + // case the discovery info is not an IP. + Map attemptToIpAddressMap = (Map) eventProps.get(CommonContextKeys.ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY); + Map attemptToChosenHostMap = (Map) eventProps.get(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY); + if (attemptToIpAddressMap == null) { + attemptToIpAddressMap = new HashMap<>(); } - if (attempToChosenHostMap == null) { - attempToChosenHostMap = new HashMap<>(); + if (attemptToChosenHostMap == null) { + attemptToChosenHostMap = new HashMap<>(); } + + // the chosen server can be null in the case of a timeout exception that skips acquiring a new origin connection String ipAddr = origin.getIpAddrFromServer(chosenServer.get()); if (ipAddr != null) { - attempToIpAddressMap.put(attemptNum, ipAddr); - eventProps.put(CommonContextKeys.ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY, attempToIpAddressMap); - context.put(CommonContextKeys.ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY, attempToIpAddressMap); + attemptToIpAddressMap.put(attemptNum, ipAddr); + eventProps.put(CommonContextKeys.ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY, attemptToIpAddressMap); + context.put(CommonContextKeys.ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY, attemptToIpAddressMap); } + if (chosenHostAddr.get() != null) { - attempToChosenHostMap.put(attemptNum, chosenHostAddr.get()); - eventProps.put(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY, attempToChosenHostMap); - context.put(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY, attempToChosenHostMap); + attemptToChosenHostMap.put(attemptNum, chosenHostAddr.get()); + eventProps.put(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY, attemptToChosenHostMap); + context.put(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY, attemptToChosenHostMap); } eventProps.put(CommonContextKeys.ZUUL_ORIGIN_REQUEST_URI, zuulRequest.getPathAndQuery()); @@ -377,8 +405,12 @@ private void proxyRequestToOrigin() { try { attemptNum += 1; - IClientConfig requestConfig = origin.getExecutionContext(zuulRequest).getRequestConfig(); - setReadTimeoutOnContext(requestConfig, attemptNum); + /* + * Before connecting to the origin, we need to compute how much time we have left for this attempt. This + * method is also intended to validate deadline and timeouts boundaries for the request as a whole and could + * throw an exception, skipping the logic below. + */ + timeLeftForAttempt = originTimeoutManager.computeReadTimeout(zuulRequest, attemptNum); currentRequestStat = createRequestStat(); origin.preRequestChecks(zuulRequest); @@ -388,7 +420,8 @@ private void proxyRequestToOrigin() { updateOriginRpsTrackers(origin, attemptNum); // We pass this AtomicReference here and the origin impl will assign the chosen server to it. - promise = origin.connectToOrigin(zuulRequest, channelCtx.channel().eventLoop(), attemptNum, passport, chosenServer, chosenHostAddr); + promise = origin.connectToOrigin( + zuulRequest, channelCtx.channel().eventLoop(), attemptNum, passport, chosenServer, chosenHostAddr); storeAndLogOriginRequestInfo(); currentRequestAttempt = origin.newRequestAttempt(chosenServer.get(), context, attemptNum); @@ -413,74 +446,36 @@ private void proxyRequestToOrigin() { } /** - * Override to track your own request stats - * - * @return + * Override to track your own request stats. */ protected RequestStat createRequestStat() { - BasicRequestStat basicRequestStat = new BasicRequestStat(origin.getName()); + BasicRequestStat basicRequestStat = new BasicRequestStat(); requestStats.add(basicRequestStat); RequestStat.putInSessionContext(basicRequestStat, context); return basicRequestStat; } - private Integer setReadTimeoutOnContext(IClientConfig requestConfig, int attempt) - { - Integer readTimeout = getReadTimeout(requestConfig, attempt); - requestConfig.set(ReadTimeout, readTimeout); - return readTimeout; - } - @Override public void operationComplete(final Future connectResult) { - // MUST run this within bindingcontext because RequestExpiryProcessor (and probably other things) depends on ThreadVariables. + // MUST run this within bindingcontext to support ThreadVariables. try { methodBinding.bind(() -> { + DiscoveryResult server = chosenServer.get(); - Integer readTimeout = null; - Server server = chosenServer.get(); - - // The chosen server would be null if the loadbalancer found no available servers. - if (server != null) { + /** TODO(argha-c): This reliance on mutable update of the `chosenServer` must be improved. + * @see DiscoveryResult.EMPTY indicates that the loadbalancer found no available servers. + */ + if (server != DiscoveryResult.EMPTY) { if (currentRequestStat != null) { currentRequestStat.server(server); } - // Invoke the ribbon execution listeners (including RequestExpiry). - final ExecutionContext executionContext = origin.getExecutionContext(zuulRequest); - IClientConfig requestConfig = executionContext.getRequestConfig(); - try { - readTimeout = requestConfig.get(ReadTimeout); - - origin.onRequestStartWithServer(zuulRequest, server, attemptNum); - - // As the read-timeout can be overridden in the listeners executed from onRequestStartWithServer() above - // check now to see if it was. And if it was, then use that. - Object overriddenReadTimeoutObj = requestConfig.get(IClientConfigKey.Keys.ReadTimeout); - if (overriddenReadTimeoutObj != null && overriddenReadTimeoutObj instanceof Integer) { - int overriddenReadTimeout = (Integer) overriddenReadTimeoutObj; - readTimeout = overriddenReadTimeout; - } - } - catch (Throwable e) { - handleError(e); - return; - } - finally { - // Reset the timeout in overriddenConfig back to what it was before, otherwise it will take - // preference on subsequent retry attempts in RequestExpiryProcessor. - if (originalReadTimeout == null) { - requestConfig.setProperty(ReadTimeout, null); - } - else { - requestConfig.setProperty(ReadTimeout, originalReadTimeout); - } - } + origin.onRequestStartWithServer(zuulRequest, server, attemptNum); } // Handle the connection establishment result. if (connectResult.isSuccess()) { - onOriginConnectSucceeded(connectResult.getNow(), readTimeout); + onOriginConnectSucceeded(connectResult.getNow(), timeLeftForAttempt); } else { onOriginConnectFailed(connectResult.cause()); } @@ -496,7 +491,7 @@ public void operationComplete(final Future connectResult) { } } - private void onOriginConnectSucceeded(PooledConnection conn, int readTimeout) { + private void onOriginConnectSucceeded(PooledConnection conn, Duration readTimeout) { passport.add(ORIGIN_CONN_ACQUIRE_END); if (context.isCancelled()) { @@ -508,41 +503,13 @@ private void onOriginConnectSucceeded(PooledConnection conn, int readTimeout) { } else { // Update the RequestAttempt to reflect the readTimeout chosen. - currentRequestAttempt.setReadTimeout(readTimeout); + currentRequestAttempt.setReadTimeout(readTimeout.toMillis()); // Start sending the request to origin now. writeClientRequestToOrigin(conn, readTimeout); } } - protected Integer getReadTimeout(IClientConfig requestConfig, int attemptNum) { - Integer originTimeout = parseReadTimeout(origin.getClientConfig().getProperty(IClientConfigKey.Keys.ReadTimeout, null)); - Integer requestTimeout = parseReadTimeout(requestConfig.getProperty(IClientConfigKey.Keys.ReadTimeout, null)); - - if (originTimeout == null && requestTimeout == null) { - return MAX_OUTBOUND_READ_TIMEOUT.get(); - } - else if (originTimeout == null || requestTimeout == null) { - return originTimeout == null ? requestTimeout : originTimeout; - } - else { - // return the greater of two timeouts - return originTimeout > requestTimeout ? originTimeout : requestTimeout; - } - } - - private Integer parseReadTimeout(Object p) { - if (p instanceof String && StringUtils.isNotBlank((String)p)) { - return Integer.valueOf((String)p); - } - else if (p instanceof Integer) { - return (Integer) p; - } - else { - return null; - } - } - private void onOriginConnectFailed(Throwable cause) { passport.add(ORIGIN_CONN_ACQUIRE_FAILED); if (! context.isCancelled()) { @@ -550,26 +517,41 @@ private void onOriginConnectFailed(Throwable cause) { } } - private byte[] preCacheBodyForRetryingSslRequests() { + @Nullable + private byte[] preCacheBodyForRetryingRequests() { // Netty SSL handler clears body ByteBufs, so we need to cache the body if we want to retry POSTs - if (ENABLE_CACHING_SSL_BODIES.get() && origin != null && - // only cache requests if already buffered - origin.getClientConfig().get(IClientConfigKey.Keys.IsSecure, false) && zuulRequest.hasCompleteBody()) { - return zuulRequest.getBody(); + // Followup: We expect most origin connections to be secure, so it's okay to unconditionally cache here. + // Additionally, it's risky to assume the plaintext handlers won't clear the body (they do), so just pay the + // cost caching regardless. + if (ENABLE_CACHING_BODIES.get() && origin != null && zuulRequest.hasCompleteBody()) { + // This second check to see if the origin is secure is a kludge to avoid spending too much CPU on + // plaintext requests. Unfortunately, the cost of cahcing the body is non trivial, and as of the + // current implementation, it's only technically required for SSL. See comment above. + if (origin.getClientConfig().get(Keys.IsSecure, false) || ENABLE_CACHING_PLAINTEXT_BODIES.get()) { + ZonedDateTime lastThrottleEvent = origin.stats().lastThrottleEvent(); + if (lastThrottleEvent != null) { + // This is technically the wrong method to call, but the toSeconds() method is only present in JDK9. + long timeSinceLastThrottle = Duration.between(lastThrottleEvent, ZonedDateTime.now()).getSeconds(); + if (timeSinceLastThrottle <= THROTTLE_MEMORY_SECONDS.get()) { + // only cache requests if already buffered + return zuulRequest.getBody(); + } + } + } } return null; } private void repopulateRetryBody() { - // if SSL origin request body is cached and has been cleared by Netty SslHandler, set it from cache // note: it's not null but is empty because the content chunks exist but the actual readable bytes are 0 - if (sslRetryBodyCache != null && attemptNum > 1 && zuulRequest.getBody() != null && zuulRequest.getBody().length == 0) { - zuulRequest.setBody(sslRetryBodyCache); - populatedSslRetryBody.increment(); + if (retryBodyCache != null && attemptNum > 1 + && zuulRequest.getBodyLength() == 0 && zuulRequest.getBody() != null) { + zuulRequest.setBody(retryBodyCache); + populatedRetryBody.increment(); } } - private void writeClientRequestToOrigin(final PooledConnection conn, int readTimeout) { + private void writeClientRequestToOrigin(final PooledConnection conn, Duration readTimeout) { final Channel ch = conn.getChannel(); passport.setOnChannel(ch); @@ -603,7 +585,7 @@ protected OriginResponseReceiver getOriginResponseReceiver() { return new OriginResponseReceiver(this); } - protected void preWriteToOrigin(Server chosenServer, HttpRequestMessage zuulRequest) { + protected void preWriteToOrigin(DiscoveryResult chosenServer, HttpRequestMessage zuulRequest) { // override for custom metrics or processing } @@ -631,8 +613,8 @@ public void errorFromOrigin(final Throwable ex) { if (originConn != null) { // NOTE: if originConn is null, then these stats will have been incremented within PerServerConnectionPool // so don't need to be here. - originConn.getServerStats().incrementSuccessiveConnectionFailureCount(); - originConn.getServerStats().addToFailureCount(); + originConn.getServer().incrementSuccessiveConnectionFailureCount(); + originConn.getServer().addToFailureCount(); originConn.flagShouldClose(); } @@ -679,13 +661,14 @@ private void processErrorFromOrigin(final Throwable ex, final Channel origCh) { postErrorProcessing(ex, zuulCtx, err, chosenServer.get(), attemptNum); final ClientException niwsEx = new ClientException(ClientException.ErrorType.valueOf(err.getClientErrorType().name())); - if (chosenServer.get() != null) { + if (chosenServer.get() != DiscoveryResult.EMPTY) { origin.onRequestExceptionWithServer(zuulRequest, chosenServer.get(), attemptNum, niwsEx); } if ((isBelowRetryLimit()) && (isRetryable(err))) { //retry request with different origin passport.add(ORIGIN_RETRY_START); + origin.adjustRetryPolicyIfNeeded(zuulRequest); proxyRequestToOrigin(); } else { // Record the exception in context. An error filter should later run which can translate this into an @@ -694,7 +677,6 @@ private void processErrorFromOrigin(final Throwable ex, final Channel origCh) { zuulCtx.setShouldSendErrorResponse(true); StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(zuulCtx, err.getStatusCategory()); - origin.getProxyTiming(zuulRequest).end(); origin.recordFinalError(zuulRequest, ex); origin.onRequestExecutionFailed(zuulRequest, chosenServer.get(), attemptNum - 1, niwsEx); @@ -707,7 +689,7 @@ private void processErrorFromOrigin(final Throwable ex, final Channel origCh) { } } - protected void postErrorProcessing(Throwable ex, SessionContext zuulCtx, ErrorType err, Server chosenServer, int attemptNum) { + protected void postErrorProcessing(Throwable ex, SessionContext zuulCtx, ErrorType err, DiscoveryResult chosenServer, int attemptNum) { // override for custom processing } @@ -761,7 +743,9 @@ protected boolean isRequestReplayable() { } public void responseFromOrigin(final HttpResponse originResponse) { - try { + try (TaskCloseable ignore = PerfMark.traceTask("ProxyEndpoint.responseFromOrigin")) { + PerfMark.attachTag("uuid", zuulRequest, r -> r.getContext().getUUID()); + PerfMark.attachTag("path", zuulRequest, HttpRequestInfo::getPath); methodBinding.bind(() -> processResponseFromOrigin(originResponse)); } catch (Exception ex) { unlinkFromOrigin(); @@ -778,10 +762,10 @@ private void processResponseFromOrigin(final HttpResponse originResponse) { } } - protected void handleOriginSuccessResponse(final HttpResponse originResponse, Server chosenServer) { + protected void handleOriginSuccessResponse(final HttpResponse originResponse, DiscoveryResult chosenServer) { origin.recordSuccessResponse(); if (originConn != null) { - originConn.getServerStats().clearSuccessiveConnectionFailureCount(); + originConn.getServer().clearSuccessiveConnectionFailureCount(); } final int respStatus = originResponse.status().code(); long duration = 0; @@ -843,7 +827,6 @@ private HttpResponseMessage buildZuulHttpResponse(final HttpResponse httpRespons // Collect some info about the received response. origin.recordFinalResponse(zuulResponse); origin.recordFinalError(zuulRequest, ex); - origin.getProxyTiming(zuulRequest).end(); zuulCtx.set(CommonContextKeys.STATUS_CATGEORY, statusCategory); zuulCtx.setError(ex); zuulCtx.put("origin_http_status", Integer.toString(respStatus)); @@ -856,7 +839,7 @@ private HttpResponseMessage transformResponse(HttpResponseMessage resp) { return resp; } - protected void handleOriginNonSuccessResponse(final HttpResponse originResponse, Server chosenServer) { + protected void handleOriginNonSuccessResponse(final HttpResponse originResponse, DiscoveryResult chosenServer) { final int respStatus = originResponse.status().code(); OutboundException obe; StatusCategory statusCategory; @@ -867,9 +850,11 @@ protected void handleOriginNonSuccessResponse(final HttpResponse originResponse, statusCategory = FAILURE_ORIGIN_THROTTLED; niwsErrorType = ClientException.ErrorType.SERVER_THROTTLED; obe = new OutboundException(OutboundErrorType.SERVICE_UNAVAILABLE, requestAttempts); + // TODO(carl-mastrangelo): pass in the clock for testing. + origin.stats().lastThrottleEvent(ZonedDateTime.now()); if (originConn != null) { - originConn.getServerStats().incrementSuccessiveConnectionFailureCount(); - originConn.getServerStats().addToFailureCount(); + originConn.getServer().incrementSuccessiveConnectionFailureCount(); + originConn.getServer().addToFailureCount(); originConn.flagShouldClose(); } if (currentRequestStat != null) { @@ -908,6 +893,7 @@ protected void handleOriginNonSuccessResponse(final HttpResponse originResponse, unlinkFromOrigin(); //retry request with different origin passport.add(ORIGIN_RETRY_START); + origin.adjustRetryPolicyIfNeeded(zuulRequest); proxyRequestToOrigin(); } else { SessionContext zuulCtx = context; @@ -1017,10 +1003,8 @@ private static HttpRequestMessage massageRequestURI(HttpRequestMessage request) * * Note: this method gets called in the constructor so if overloading it or any methods called within, you cannot * rely on your own constructor parameters. - * - * @param request - * @return */ + @Nullable protected NettyOrigin getOrigin(HttpRequestMessage request) { SessionContext context = request.getContext(); OriginManager originManager = (OriginManager) context.get(CommonContextKeys.ORIGIN_MANAGER); @@ -1035,7 +1019,7 @@ protected NettyOrigin getOrigin(HttpRequestMessage request) { } String primaryRoute = context.getRouteVIP(); - if (StringUtils.isEmpty(primaryRoute)) { + if (Strings.isNullOrEmpty(primaryRoute)) { // If no vip selected, leave origin null, then later the handleNoOriginSelected() method will be invoked. return null; } @@ -1046,17 +1030,15 @@ protected NettyOrigin getOrigin(HttpRequestMessage request) { String restClientName = useFullName ? restClientVIP : VipUtils.getVIPPrefix(restClientVIP); NettyOrigin origin = null; - if (restClientName != null) { + // allow implementors to override the origin with custom injection logic + OriginName overrideOriginName = injectCustomOriginName(request); + if (overrideOriginName != null) { + // Use the custom vip instead if one has been provided. + origin = getOrCreateOrigin(originManager, overrideOriginName, request.reconstructURI(), context); + } else if (restClientName != null) { // This is the normal flow - that a RoutingFilter has assigned a route - origin = getOrCreateOrigin(originManager, restClientName, restClientVIP, request.reconstructURI(), useFullName, context); - } - - // Use the custom vip instead if one has been provided. - Pair customVip = injectCustomVip(request); - if (customVip != null) { - restClientVIP = customVip.getLeft(); - restClientName = customVip.getRight(); - origin = getOrCreateOrigin(originManager, restClientName, restClientVIP, request.reconstructURI(), useFullName, context); + OriginName originName = OriginName.fromVip(restClientVIP, restClientName); + origin = getOrCreateOrigin(originManager, originName, request.reconstructURI(), context); } verifyOrigin(context, request, restClientName, origin); @@ -1076,20 +1058,23 @@ protected NettyOrigin getOrigin(HttpRequestMessage request) { * Note: this method gets called in the constructor so if overloading it or any methods called within, you cannot * rely on your own constructor parameters. * - * @param request - * @return + * @return {@code null} if unused. */ - protected Pair injectCustomVip(HttpRequestMessage request) { + @Nullable + protected OriginName injectCustomOriginName(HttpRequestMessage request) { // override for custom vip injection return null; } - private NettyOrigin getOrCreateOrigin(OriginManager originManager, String name, String vip, String uri, boolean useFullVipName, SessionContext ctx) { - NettyOrigin origin = originManager.getOrigin(name, vip, uri, ctx); + private NettyOrigin getOrCreateOrigin( + OriginManager originManager, OriginName originName, String uri, SessionContext ctx) { + NettyOrigin origin = originManager.getOrigin(originName, uri, ctx); if (origin == null) { - // If no pre-registered and configured RestClient found for this VIP, then register one using default NIWS properties. - LOG.warn("Attempting to register RestClient for client that has not been configured. restClientName={}, vip={}, uri={}", name, vip, uri); - origin = originManager.createOrigin(name, vip, uri, useFullVipName, ctx); + // If no pre-registered and configured RestClient found for this VIP, then register one using default NIWS + // properties. + LOG.warn("Attempting to register RestClient for client that has not been configured. originName={}, uri={}", + originName, uri); + origin = originManager.createOrigin(originName, uri, ctx); } return origin; } @@ -1107,8 +1092,13 @@ private void verifyOrigin(SessionContext context, HttpRequestMessage request, St } } + @ForOverride protected void originNotFound(SessionContext context, String causeName) { // override for metrics or custom processing } + @ForOverride + protected OriginTimeoutManager getTimeoutManager(NettyOrigin origin) { + return new OriginTimeoutManager(origin); + } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/passport/InboundPassportStampingFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/passport/InboundPassportStampingFilter.java index 291c41d7..870f21e1 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/passport/InboundPassportStampingFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/passport/InboundPassportStampingFilter.java @@ -16,6 +16,7 @@ package com.netflix.zuul.filters.passport; +import com.netflix.zuul.Filter; import com.netflix.zuul.filters.FilterType; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.passport.PassportState; @@ -25,6 +26,7 @@ /** * Created by saroskar on 3/14/17. */ +@Filter(order = 0, type = INBOUND) public final class InboundPassportStampingFilter extends PassportStampingFilter { public InboundPassportStampingFilter(PassportState stamp) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/filters/passport/OutboundPassportStampingFilter.java b/zuul-core/src/main/java/com/netflix/zuul/filters/passport/OutboundPassportStampingFilter.java index d3cff91b..776657e2 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/filters/passport/OutboundPassportStampingFilter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/filters/passport/OutboundPassportStampingFilter.java @@ -16,6 +16,7 @@ package com.netflix.zuul.filters.passport; +import com.netflix.zuul.Filter; import com.netflix.zuul.filters.FilterType; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.passport.PassportState; @@ -25,6 +26,7 @@ /** * Created by saroskar on 3/14/17. */ +@Filter(order = 0, type = OUTBOUND) public final class OutboundPassportStampingFilter extends PassportStampingFilter { public OutboundPassportStampingFilter(PassportState stamp) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyCompatability.groovy b/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyCompatability.groovy deleted file mode 100755 index 813a9c5d..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyCompatability.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.groovy - -import com.netflix.zuul.context.SessionContext -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.runners.MockitoJUnitRunner - -import static org.junit.Assert.assertEquals -import static org.junit.Assert.assertNotNull - -/** - * Unit test class to verify groovy compatibility with RequestContext - * Created by IntelliJ IDEA. - * User: mcohen - * Date: 1/3/12 - * Time: 4:55 PM - */ -class GroovyCompatability { - - - @RunWith(MockitoJUnitRunner.class) - public static class TestUnit { - - @Test - public void testRequestContext() { - SessionContext context = new SessionContext() - - context.test = "moo" - assertNotNull(context.test) - assertEquals(context.test, "moo") - assertNotNull(context.get("test")) - assertEquals(context.get("test"), "moo") - - context.set("test", "ik") - assertEquals(context.get("test"), "ik") - assertEquals(context.test, "ik") - assertNotNull(context) - assertEquals(context.test, "ik") - - } - - } - -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/logging/FilteredPatternLayout.java b/zuul-core/src/main/java/com/netflix/zuul/logging/FilteredPatternLayout.java deleted file mode 100644 index 9985a8f8..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/logging/FilteredPatternLayout.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.logging; - -import org.apache.log4j.PatternLayout; -import org.apache.log4j.spi.LoggingEvent; -import org.apache.log4j.spi.ThrowableInformation; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - - -/** - * A modified copy of FilteredPatternLayout. - * - * An extension of org.apache.log4j.PatternLayout which strips out from stack traces a list of configured - * entries. Sample configuration: - * - *

- *  <appender name="console" class="org.apache.log4j.ConsoleAppender">
- *      <layout class="it.openutils.log4j.FilteredPatternLayout">
- *          <param name="ConversionPattern" value="%-5p  %c %F(%M:%L) %d{dd.MM.yyyy HH:mm:ss}  %m%n" />
- *          <param name="Filter" value="org.apache.catalina" />
- *          <param name="Filter" value="sun.reflect" />
- *          <param name="Filter" value="javax.servlet.http" />
- *      </layout>
- *  </appender>
- * 
- * - * @author Fabrizio Giustina - * @author Michael Smith - * - */ -public class FilteredPatternLayout extends PatternLayout -{ - - /** - * Holds the list of filtered frames. - */ - private Set filteredFrames = new HashSet(); - - private String header; - - private String footer; - - /** - * Line separator for stacktrace frames. - */ - private static String lineSeparator = "\n"; - - static - { - try - { - lineSeparator = System.getProperty("line.separator"); - } - catch (SecurityException ex) - { - // ignore - } - } - - private static final String FILTERED_LINE_INDICATOR = "\t... filtered lines = "; - - - /** - * Returns the header. - * @return the header - */ - @Override - public String getHeader() - { - return header; - } - - /** - * Sets the header. - * @param header the header to set - */ - public void setHeader(String header) - { - this.header = header; - } - - /** - * Returns the footer. - * @return the footer - */ - @Override - public String getFooter() - { - return footer; - } - - /** - * Sets the footer. - * @param footer the footer to set - */ - public void setFooter(String footer) - { - this.footer = footer; - } - - /** - * @see org.apache.log4j.Layout#ignoresThrowable() - */ - @Override - public boolean ignoresThrowable() - { - return false; - } - - /** - * @see PatternLayout#format(LoggingEvent) - */ - @Override - public String format(LoggingEvent event) - { - String result = super.format(event); - - ThrowableInformation throwableInformation = event.getThrowableInformation(); - - if (throwableInformation != null) - { - result += getFilteredStacktrace(throwableInformation); - } - - return result; - } - - /** - * Adds new filtered frames. Any stack frame starting with "at " + filter will not be - * written to the log. - * @param filters a comma-delimited list of class names or package names to be filtered - */ - public void setFilters(String filters) - { - for (String filter : filters.split(",")) { - filteredFrames.add("at " + filter.trim()); - } - } - - - private String getFilteredStacktrace(ThrowableInformation throwableInformation) - { - StringBuffer buffer = new StringBuffer(); - - String[] s = throwableInformation.getThrowableStrRep(); - - boolean previousLineWasAMatch = false; - int consecutiveFilteredCount = 0; - for (int j = 0; j < s.length; j++) - { - String string = s[j]; - boolean shouldAppend = true; - - if (startsWithAFilteredPAttern(string)) { - shouldAppend = false; - previousLineWasAMatch = true; - consecutiveFilteredCount++; - } - else { - appendFilteredLineIndicator(buffer, previousLineWasAMatch, consecutiveFilteredCount); - consecutiveFilteredCount = 0; - previousLineWasAMatch = false; - } - - if (shouldAppend) { - buffer.append(string); - buffer.append(lineSeparator); - } - } - - // In case consecutive filtered lines run to end of trace. - appendFilteredLineIndicator(buffer, previousLineWasAMatch, consecutiveFilteredCount); - - return buffer.toString(); - } - - private void appendFilteredLineIndicator(StringBuffer buffer, boolean previousLineWasAMatch, int consecutiveFilteredCount) - { - // For the last consecutive filtered line, append some indication that lines have been filtered. - if (previousLineWasAMatch) { - buffer.append(FILTERED_LINE_INDICATOR).append(consecutiveFilteredCount); - buffer.append(lineSeparator); - } - } - - /** - * Check if the given string starts with any of the filtered patterns. - * @param string checked String - * @return true if the begininning of the string matches a filtered pattern, false - * otherwise - */ - private boolean startsWithAFilteredPAttern(String string) - { - Iterator iterator = filteredFrames.iterator(); - while (iterator.hasNext()) - { - if (string.trim().startsWith(iterator.next())) - { - return true; - } - } - return false; - } - -} \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/Header.java b/zuul-core/src/main/java/com/netflix/zuul/message/Header.java index 5de9e188..f589617c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/Header.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/Header.java @@ -17,12 +17,9 @@ package com.netflix.zuul.message; /** - * User: Mike Smith - * Date: 7/29/15 - * Time: 1:06 PM + * Represents a single header from a {@link Headers} object. */ -public class Header implements Cloneable -{ +public final class Header { private final HeaderName name; private final String value; @@ -33,45 +30,44 @@ public Header(HeaderName name, String value) this.value = value; } - public String getKey() - { + public String getKey() { return name.getName(); } - public HeaderName getName() - { + public HeaderName getName() { return name; } - public String getValue() - { + public String getValue() { return value; } @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } Header header = (Header) o; - if (!name.equals(header.name)) return false; + if (!name.equals(header.name)) { + return false; + } return !(value != null ? !value.equals(header.value) : header.value != null); - } @Override - public int hashCode() - { + public int hashCode() { int result = name.hashCode(); result = 31 * result + (value != null ? value.hashCode() : 0); return result; } @Override - public String toString() - { + public String toString() { return String.format("%s: %s", name, value); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/HeaderName.java b/zuul-core/src/main/java/com/netflix/zuul/message/HeaderName.java index 1beed0a3..8728c0bf 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/HeaderName.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/HeaderName.java @@ -16,7 +16,7 @@ package com.netflix.zuul.message; -import com.netflix.config.DynamicPropertyFactory; +import java.util.Locale; /** * Immutable, case-insensitive wrapper around Header name. @@ -25,58 +25,60 @@ * Date: 7/29/15 * Time: 1:07 PM */ -public class HeaderName -{ - private static final boolean SHOULD_INTERN = - DynamicPropertyFactory.getInstance().getBooleanProperty( - "com.netflix.zuul.message.HeaderName.shouldIntern", true).get(); - +public final class HeaderName { private final String name; private final String normalised; + private final int hashCode; - public HeaderName(String name) - { - if (name == null) throw new NullPointerException("HeaderName cannot be null!"); - this.name = SHOULD_INTERN ? name.intern() : name; - this.normalised = SHOULD_INTERN ? name.toLowerCase().intern() : name.toLowerCase(); + public HeaderName(String name) { + if (name == null) { + throw new NullPointerException("HeaderName cannot be null!"); + } + this.name = name; + this.normalised = normalize(name); + this.hashCode = this.normalised.hashCode(); } - public String getName() - { + HeaderName(String name, String normalised) { + this.name = name; + this.normalised = normalised; + this.hashCode = normalised.hashCode(); + } + + /** + * Gets the original, non-normalized name for this header. + */ + public String getName() { return name; } - public String getNormalised() - { + public String getNormalised() { return normalised; } - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - HeaderName that = (HeaderName) o; + static String normalize(String s) { + return s.toLowerCase(Locale.ROOT); + } - // Ignore case when comparing. - if (SHOULD_INTERN) { - return normalised == that.normalised; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - else { - return normalised.equals(that.normalised); + if (!(o instanceof HeaderName)) { + return false; } + HeaderName that = (HeaderName) o; + return this.normalised.equals(that.normalised); } @Override - public int hashCode() - { - return normalised.hashCode(); + public int hashCode() { + return hashCode; } @Override - public String toString() - { + public String toString() { return name; } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/Headers.java b/zuul-core/src/main/java/com/netflix/zuul/message/Headers.java index 7346ede3..cb919649 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/Headers.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/Headers.java @@ -15,19 +15,24 @@ */ package com.netflix.zuul.message; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.ListMultimap; -import com.netflix.zuul.message.http.HttpHeaderNames; +import static java.util.Objects.requireNonNull; + +import com.google.common.annotations.VisibleForTesting; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Spectator; +import com.netflix.zuul.exception.ZuulException; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Predicate; -import java.util.stream.Collectors; - -import static com.netflix.zuul.util.HttpUtils.stripMaliciousHeaderChars; +import javax.annotation.Nullable; /** * An abstraction over a collection of http headers. Allows multiple headers with same name, and header names are @@ -35,60 +40,57 @@ * * There are methods for getting and setting headers by String AND by HeaderName. When possible, use the HeaderName * variants and cache the HeaderName instances somewhere, to avoid case-insensitive String comparisons. - * - * User: michaels@netflix.com - * Date: 2/20/15 - * Time: 3:13 PM */ -public class Headers implements Cloneable -{ - private final ListMultimap delegate; - private final boolean immutable; +public final class Headers { + private static final int ABSENT = -1; - public Headers() - { - delegate = ArrayListMultimap.create(); - immutable = false; - } + private final List originalNames; + private final List names; + private final List values; - private Headers(ListMultimap delegate) - { - this.delegate = delegate; - immutable = ImmutableListMultimap.class.isAssignableFrom(delegate.getClass()); + private static final Counter invalidHeaderCounter = Spectator.globalRegistry().counter("zuul.header.invalid.char"); + + public static Headers copyOf(Headers original) { + return new Headers(requireNonNull(original, "original")); } - protected HeaderName getHeaderName(String name) - { - return HttpHeaderNames.get(name); + public Headers() { + originalNames = new ArrayList<>(); + names = new ArrayList<>(); + values = new ArrayList<>(); } - private boolean delegatePut(HeaderName hn, String value) { - return delegate.put(hn, stripMaliciousHeaderChars(value)); + private Headers(Headers original) { + originalNames = new ArrayList<>(original.originalNames); + names = new ArrayList<>(original.names); + values = new ArrayList<>(original.values); } - private void delegatePutAll(Headers headers) { - // enforce using above delegatePut method, for stripping malicious characters - headers.delegate.entries().forEach(entry -> delegatePut(entry.getKey(), entry.getValue())); + /** + * Get the first value found for this key even if there are multiple. If none, then + * return {@code null}. + */ + @Nullable + public String getFirst(String headerName) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + return getFirstNormal(normalName); } /** * Get the first value found for this key even if there are multiple. If none, then - * return null. - * - * @param name - * @return - */ - public String getFirst(String name) - { - HeaderName hn = getHeaderName(name); - return getFirst(hn); - } - public String getFirst(HeaderName hn) - { - List values = delegate.get(hn); - if (values != null) { - if (values.size() > 0) { - return values.get(0); + * return {@code null}. + */ + @Nullable + public String getFirst(HeaderName headerName) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + return getFirstNormal(normalName); + } + + @Nullable + private String getFirstNormal(String name) { + for (int i = 0; i < size(); i++) { + if (name(i).equals(name)) { + return value(i); } } return null; @@ -97,178 +99,581 @@ public String getFirst(HeaderName hn) /** * Get the first value found for this key even if there are multiple. If none, then * return the specified defaultValue. - * - * @param name - * @return - */ - public String getFirst(String name, String defaultValue) - { - String value = getFirst(name); - if (value == null) { - value = defaultValue; + */ + public String getFirst(String headerName, String defaultValue) { + requireNonNull(defaultValue, "defaultValue"); + String value = getFirst(headerName); + if (value != null) { + return value; } - return value; + return defaultValue; } - public String getFirst(HeaderName hn, String defaultValue) - { - String value = getFirst(hn); - if (value == null) { - value = defaultValue; + + /** + * Get the first value found for this key even if there are multiple. If none, then + * return the specified defaultValue. + */ + public String getFirst(HeaderName headerName, String defaultValue) { + requireNonNull(defaultValue, "defaultValue"); + String value = getFirst(headerName); + if (value != null) { + return value; } - return value; + return defaultValue; + } + + /** + * Returns all header values associated with the name. + */ + public List getAll(String headerName) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + return getAllNormal(normalName); } - public List get(String name) - { - HeaderName hn = getHeaderName(name); - return get(hn); + /** + * Returns all header values associated with the name. + */ + public List getAll(HeaderName headerName) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + return getAllNormal(normalName); + } + + private List getAllNormal(String normalName) { + List results = null; + for (int i = 0; i < size(); i++) { + if (name(i).equals(normalName)) { + if (results == null) { + results = new ArrayList<>(1); + } + results.add(value(i)); + } + } + if (results == null) { + return Collections.emptyList(); + } else { + return Collections.unmodifiableList(results); + } } - public List get(HeaderName hn) - { - return delegate.get(hn); + + /** + * Iterates over the header entries with the given consumer. The first argument will be the normalised header + * name as returned by {@link HeaderName#getNormalised()}. The second argument will be the value. Do not modify + * the headers during iteration. + */ + public void forEachNormalised(BiConsumer entryConsumer) { + for (int i = 0; i < size(); i++) { + entryConsumer.accept(name(i), value(i)); + } } /** * Replace any/all entries with this key, with this single entry. * - * If value is null, then not added, but any existing header of same name is removed. + * If value is {@code null}, then not added, but any existing header of same name is removed. + */ + public void set(String headerName, @Nullable String value) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + setNormal(headerName, normalName, value); + } + + /** + * Replace any/all entries with this key, with this single entry. + * + * If value is {@code null}, then not added, but any existing header of same name is removed. + */ + public void set(HeaderName headerName, String value) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + setNormal(headerName.getName(), normalName, value); + } + + /** + * Replace any/all entries with this key, with this single entry and validate. + * + * If value is {@code null}, then not added, but any existing header of same name is removed. + * + * @throws ZuulException on invalid name or value + */ + public void setAndValidate(String headerName, @Nullable String value) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + setNormal(validateField(headerName), validateField(normalName), validateField(value)); + } + + /** + * Replace any/all entries with this key, with this single entry if the key and entry are valid. + * + * If value is {@code null}, then not added, but any existing header of same name is removed. + */ + public void setIfValid(HeaderName headerName, String value) { + requireNonNull(headerName, "headerName"); + if (isValid(headerName.getName()) && isValid(value)) { + String normalName = headerName.getNormalised(); + setNormal(headerName.getName(), normalName, value); + } + } + + /** + * Replace any/all entries with this key, with this single entry if the key and entry are valid. + * + * If value is {@code null}, then not added, but any existing header of same name is removed. + */ + public void setIfValid(String headerName, @Nullable String value) { + requireNonNull(headerName, "headerName"); + if (isValid(headerName) && isValid(value)) { + String normalName = HeaderName.normalize(headerName); + setNormal(headerName, normalName, value); + } + } + + /** + * Replace any/all entries with this key, with this single entry and validate. + * + * If value is {@code null}, then not added, but any existing header of same name is removed. * - * @param name - * @param value + * @throws ZuulException on invalid name or value */ - public void set(String name, String value) - { - HeaderName hn = getHeaderName(name); - set(hn, value); + public void setAndValidate(HeaderName headerName, String value) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + setNormal(validateField(headerName.getName()), validateField(normalName), validateField(value)); } - public void set(HeaderName hn, String value) - { - delegate.removeAll(hn); + + private void setNormal(String originalName, String normalName, @Nullable String value) { + int i = findNormal(normalName); + if (i == ABSENT) { + if (value != null) { + addNormal(originalName, normalName, value); + } + return; + } if (value != null) { - delegatePut(hn, value); + value(i, value); + originalName(i, originalName); + i++; } + clearMatchingStartingAt(i, normalName, /* removed= */ null); } - public boolean setIfAbsent(String name, String value) - { - HeaderName hn = getHeaderName(name); - return setIfAbsent(hn, value); + /** + * Returns the first index entry that has a matching name. Returns {@link #ABSENT} if absent. + */ + private int findNormal(String normalName) { + for (int i = 0; i < size(); i++) { + if (name(i).equals(normalName)) { + return i; + } + } + return -1; } - public boolean setIfAbsent(HeaderName hn, String value) - { - boolean did = false; - if (! contains(hn)) { - set(hn, value); - did = true; + + /** + * Removes entries that match the name, starting at the given index. + */ + private void clearMatchingStartingAt(int i, String normalName, @Nullable Collection removed) { + // This works by having separate read and write indexes, that iterate along the list. + // Values that don't match are moved to the front, leaving garbage values in place. + // At the end, all values at and values are garbage and are removed. + int w = i; + for (int r = i; r < size(); r++) { + if (!name(r).equals(normalName)) { + originalName(w, originalName(r)); + name(w, name(r)); + value(w, value(r)); + w++; + } else if (removed != null) { + removed.add(value(r)); + } } - return did; + truncate(w); } - public void add(String name, String value) - { - HeaderName hn = getHeaderName(name); - add(hn, value); + /** + * Adds the name and value to the headers, except if the name is already present. Unlike + * {@link #set(String, String)}, this method does not accept a {@code null} value. + * + * @return if the value was successfully added. + */ + public boolean setIfAbsent(String headerName, String value) { + requireNonNull(value, "value"); + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + return setIfAbsentNormal(headerName, normalName, value); } - public void add(HeaderName hn, String value) - { - delegatePut(hn, value); + + /** + * Adds the name and value to the headers, except if the name is already present. Unlike + * {@link #set(HeaderName, String)}, this method does not accept a {@code null} value. + * + * @return if the value was successfully added. + */ + public boolean setIfAbsent(HeaderName headerName, String value) { + requireNonNull(value, "value"); + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + return setIfAbsentNormal(headerName.getName(), normalName, value); } - public void putAll(Headers headers) - { - delegatePutAll(headers); + private boolean setIfAbsentNormal(String originalName, String normalName, String value) { + int i = findNormal(normalName); + if (i != ABSENT) { + return false; + } + addNormal(originalName, normalName, value); + return true; } - public List remove(String name) - { - HeaderName hn = getHeaderName(name); - return remove(hn); + /** + * Validates and adds the name and value to the headers, except if the name is already present. Unlike + * {@link #set(String, String)}, this method does not accept a {@code null} value. + * + * @return if the value was successfully added. + */ + public boolean setIfAbsentAndValid(String headerName, String value) { + requireNonNull(value, "value"); + requireNonNull(headerName, "headerName"); + if (isValid(headerName) && isValid(value)) { + String normalName = HeaderName.normalize(headerName); + return setIfAbsentNormal(headerName, normalName, value); + } + return false; } - public List remove(HeaderName hn) - { - return delegate.removeAll(hn); + + /** + * Validates and adds the name and value to the headers, except if the name is already present. Unlike + * {@link #set(HeaderName, String)}, this method does not accept a {@code null} value. + * + * @return if the value was successfully added. + */ + public boolean setIfAbsentAndValid(HeaderName headerName, String value) { + requireNonNull(value, "value"); + requireNonNull(headerName, "headerName"); + if (isValid(headerName.getName()) && isValid((value))) { + String normalName = headerName.getNormalised(); + return setIfAbsentNormal(headerName.getName(), normalName, value); + } + return false; } - public boolean removeIf(Predicate> filter) { - return delegate.entries().removeIf(filter); + /** + * Adds the name and value to the headers. + */ + public void add(String headerName, String value) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + requireNonNull(value, "value"); + addNormal(headerName, normalName, value); } - public Collection
entries() - { - return delegate.entries() - .stream() - .map(entry -> new Header(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()); + /** + * Adds the name and value to the headers. + */ + public void add(HeaderName headerName, String value) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + requireNonNull(value, "value"); + addNormal(headerName.getName(), normalName, value); } - public Set keySet() - { - return delegate.keySet(); + /** + * Adds the name and value to the headers and validate. + * + * @throws ZuulException on invalid name or value + */ + public void addAndValidate(String headerName, String value) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + requireNonNull(value, "value"); + addNormal(validateField(headerName), validateField(normalName), validateField(value)); } - public boolean contains(String name) - { - return contains(getHeaderName(name)); + /** + * Adds the name and value to the headers and validate + * + * @throws ZuulException on invalid name or value + */ + public void addAndValidate(HeaderName headerName, String value) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + requireNonNull(value, "value"); + addNormal(validateField(headerName.getName()), validateField(normalName), validateField(value)); } - public boolean contains(HeaderName hn) - { - return delegate.containsKey(hn); + + /** + * Adds the name and value to the headers if valid + */ + public void addIfValid(String headerName, String value) { + requireNonNull(headerName, "headerName"); + requireNonNull(value, "value"); + if (isValid(headerName) && isValid(value)) { + String normalName = HeaderName.normalize(headerName); + addNormal(headerName, normalName, value); + } } - public boolean contains(String name, String value) - { - HeaderName hn = getHeaderName(name); - return contains(hn, value); + /** + * Adds the name and value to the headers if valid + */ + public void addIfValid(HeaderName headerName, String value) { + requireNonNull(headerName, "headerName"); + requireNonNull(value, "value"); + if (isValid(headerName.getName()) && isValid(value)) { + String normalName = headerName.getNormalised(); + addNormal(headerName.getName(), normalName, value); + } } - public boolean contains(HeaderName hn, String value) - { - return delegate.containsEntry(hn, value); + + /** + * Adds all the headers into this headers object. + */ + public void putAll(Headers headers) { + for (int i = 0; i < headers.size(); i++) { + addNormal(headers.originalName(i), headers.name(i), headers.value(i)); + } } - public int size() - { - return delegate.size(); + /** + * Removes the header entries that match the given header name, and returns them as a list. + */ + public List remove(String headerName) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + return removeNormal(normalName); } - @Override - public Headers clone() - { - Headers copy = new Headers(); - copy.delegatePutAll(this); - return copy; + /** + * Removes the header entries that match the given header name, and returns them as a list. + */ + public List remove(HeaderName headerName) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + return removeNormal(normalName); + } + + private List removeNormal(String normalName) { + List removed = new ArrayList<>(); + clearMatchingStartingAt(0, normalName, removed); + return Collections.unmodifiableList(removed); + } + + /** + * Removes all header entries that match the given predicate. Do not access the header list from inside the + * {@link Predicate#test} body. + * + * @return if any elements were removed. + */ + public boolean removeIf(Predicate> filter) { + requireNonNull(filter, "filter"); + boolean removed = false; + int w = 0; + for (int r = 0; r < size(); r++) { + if (filter.test(new SimpleImmutableEntry<>(new HeaderName(originalName(r), name(r)), value(r)))) { + removed = true; + } else { + originalName(w, originalName(r)); + name(w, name(r)); + value(w, value(r)); + w++; + } + } + truncate(w); + return removed; + } + + /** + * Returns the collection of headers. + */ + public Collection
entries() { + List
entries = new ArrayList<>(size()); + for (int i = 0; i < size(); i++) { + entries.add(new Header(new HeaderName(originalName(i), name(i)), value(i))); + } + return Collections.unmodifiableList(entries); + } + + /** + * Returns a set of header names found in this headers object. If there are duplicate header names, the first + * one present takes precedence. + */ + public Set keySet() { + Set headerNames = new LinkedHashSet<>(size()); + for (int i = 0 ; i < size(); i++) { + HeaderName headerName = new HeaderName(originalName(i), name(i)); + // We actually do need to check contains before adding to the set because the original name may change. + // In this case, the first name wins. + if (!headerNames.contains(headerName)) { + headerNames.add(headerName); + } + } + return Collections.unmodifiableSet(headerNames); } - public Headers immutableCopy() - { - return new Headers(ImmutableListMultimap.copyOf(delegate)); + /** + * Returns if there is a header entry that matches the given name. + */ + public boolean contains(String headerName) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + return findNormal(normalName) != ABSENT; } - public boolean isImmutable() - { - return immutable; + /** + * Returns if there is a header entry that matches the given name. + */ + public boolean contains(HeaderName headerName) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + return findNormal(normalName) != ABSENT; } + /** + * Returns if there is a header entry that matches the given name and value. + */ + public boolean contains(String headerName, String value) { + String normalName = HeaderName.normalize(requireNonNull(headerName, "headerName")); + requireNonNull(value, "value"); + return containsNormal(normalName, value); + } + + /** + * Returns if there is a header entry that matches the given name and value. + */ + public boolean contains(HeaderName headerName, String value) { + String normalName = requireNonNull(headerName, "headerName").getNormalised(); + requireNonNull(value, "value"); + return containsNormal(normalName, value); + } + + private boolean containsNormal(String normalName, String value) { + for (int i = 0; i < size(); i++) { + if (name(i).equals(normalName) && value(i).equals(value)) { + return true; + } + } + return false; + } + + /** + * Returns the number of header entries. + */ + public int size() { + return names.size(); + } + + /** + * This method should only be used for testing, as it is expensive to call. + */ @Override - public int hashCode() - { - return super.hashCode(); + @VisibleForTesting + public int hashCode() { + return asMap().hashCode(); } + /** + * Equality on headers is not clearly defined, but this method makes an attempt to do so. This method should + * only be used for testing, as it is expensive to call. Two headers object are considered equal if they have + * the same, normalized header names, and have the corresponding header values in the same order. + */ @Override - public boolean equals(Object obj) - { - if (obj == null) - return false; - if (! (obj instanceof Headers)) + @VisibleForTesting + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Headers)) { return false; + } + Headers other = (Headers) obj; - Headers h2 = (Headers) obj; - return Iterables.elementsEqual(delegate.entries(), h2.delegate.entries()); + return asMap().equals(other.asMap()); } + private Map> asMap() { + Map> map = new LinkedHashMap<>(size()); + for (int i = 0; i < size(); i++) { + map.computeIfAbsent(name(i), k -> new ArrayList<>(1)).add(value(i)); + } + // Return an unwrapped collection since it should not ever be returned on the API. + return map; + } + + /** + * This is used for debugging. It is fairly expensive to construct, so don't call it on a hot path. + */ @Override - public String toString() - { - return delegate.toString(); + public String toString() { + return asMap().toString(); + } + + private String originalName(int i) { + return originalNames.get(i); + } + + private void originalName(int i, String originalName) { + originalNames.set(i, originalName); + } + + private String name(int i) { + return names.get(i); + } + + private void name(int i, String name) { + names.set(i, name); + } + + private String value(int i) { + return values.get(i); + } + + private void value(int i, String val) { + values.set(i, val); + } + + private void addNormal(String originalName, String normalName, String value) { + originalNames.add(originalName); + names.add(normalName); + values.add(value); + } + + /** + * Removes all elements at and after the given index. + */ + private void truncate(int i) { + for (int k = size() - 1; k >= i; k--) { + originalNames.remove(k); + names.remove(k); + values.remove(k); + } + } + + /** + * Checks if the given value is compliant with our RFC 7230 based check + */ + private static boolean isValid(@Nullable String value) { + if (value == null || findInvalid(value) == ABSENT) { + return true; + } + invalidHeaderCounter.increment(); + return false; + } + + /** + * Checks if the input value is compliant with our RFC 7230 based check + * Returns input value if valid, raises ZuulException otherwise + */ + private static String validateField(@Nullable String value) { + if (value != null) { + int pos = findInvalid(value); + if (pos != ABSENT) { + invalidHeaderCounter.increment(); + throw new ZuulException("Invalid header field: char " + (int) value.charAt(pos) + " in string " + value + + " does not comply with RFC 7230"); + } + } + return value; + } + + /** + * Validated the input value based on RFC 7230 but more lenient. + * Currently, only ASCII control characters are considered invalid. + * + * Returns the index of first invalid character. Returns {@link #ABSENT} if absent. + */ + private static int findInvalid(String value) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + // ASCII non-control characters, per RFC 7230 but slightly more lenient + if (c < 31 || c == 127) { + return i; + } + } + return ABSENT; } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessage.java b/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessage.java index 0d8ed910..15f5699e 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessage.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessage.java @@ -112,7 +112,7 @@ public interface ZuulMessage extends Cloneable { /** * Passes the body content chunks through the given filter, and sets them back into this message. */ - void runBufferedBodyContentThroughFilter(ZuulFilter filter); + void runBufferedBodyContentThroughFilter(ZuulFilter filter); /** * Clears the content chunks of this body, calling {@code release()} in the process. Users SHOULD call this method diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessageImpl.java b/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessageImpl.java index 44dfab2a..bf97503f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessageImpl.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessageImpl.java @@ -198,7 +198,7 @@ public void disposeBufferedBody() { } @Override - public void runBufferedBodyContentThroughFilter(ZuulFilter filter) { + public void runBufferedBodyContentThroughFilter(ZuulFilter filter) { //Loop optimized for the common case: Most filters' processContentChunk() return // original chunk passed in as is without any processing for (int i=0; i < bodyChunks.size(); i++) { @@ -217,7 +217,7 @@ public void runBufferedBodyContentThroughFilter(ZuulFilter filter) { @Override public ZuulMessage clone() { - final ZuulMessageImpl copy = new ZuulMessageImpl(context.clone(), headers.clone()); + final ZuulMessageImpl copy = new ZuulMessageImpl(context.clone(), Headers.copyOf(headers)); this.bodyChunks.forEach(chunk -> { chunk.retain(); copy.bufferBodyContents(chunk); @@ -227,12 +227,9 @@ public ZuulMessage clone() { /** * Override this in more specific subclasses to add request/response info for logging purposes. - * - * @return */ @Override - public String getInfoForLogging() - { + public String getInfoForLogging() { return "ZuulMessage"; } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpHeaderNames.java b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpHeaderNames.java index b3833c36..e6ab57ff 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpHeaderNames.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpHeaderNames.java @@ -30,7 +30,7 @@ * Date: 8/5/15 * Time: 12:33 PM */ -public class HttpHeaderNames +public final class HttpHeaderNames { private static final DynamicIntProperty MAX_CACHE_SIZE = DynamicPropertyFactory.getInstance().getIntProperty("com.netflix.zuul.message.http.HttpHeaderNames.maxCacheSize", 30); diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpQueryParams.java b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpQueryParams.java index 10b93482..d14522f2 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpQueryParams.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpQueryParams.java @@ -15,16 +15,21 @@ */ package com.netflix.zuul.message.http; +import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.ListMultimap; -import org.apache.commons.lang3.StringUtils; - import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; /** * User: michaels @@ -78,7 +83,7 @@ public static HttpQueryParams parse(String queryString) { queryParams.add(name, value); // respect trailing equals for key-only params - if (s.endsWith("=") && StringUtils.isBlank(value)) { + if (s.endsWith("=") && value.isEmpty()) { queryParams.setTrailingEquals(name, true); } } @@ -103,9 +108,6 @@ else if (s.length() > 0) { /** * Get the first value found for this key even if there are multiple. If none, then * return null. - * - * @param name - * @return */ public String getFirst(String name) { @@ -128,6 +130,14 @@ public boolean contains(String name) return delegate.containsKey(name); } + /** + * Per https://tools.ietf.org/html/rfc7230#page-19, query params are to be treated as case sensitive. + * However, as an utility, this exists to allow us to do a case insensitive match on demand. + */ + public boolean containsIgnoreCase(String name) { + return delegate.containsKey(name) || delegate.containsKey(name.toLowerCase(Locale.ROOT)); + } + public boolean contains(String name, String value) { return delegate.containsEntry(name, value); @@ -135,9 +145,6 @@ public boolean contains(String name, String value) /** * Replace any/all entries with this key, with this single entry. - * - * @param name - * @param value */ public void set(String name, String value) { @@ -175,7 +182,7 @@ public String toEncodedString() try { for (Map.Entry entry : entries()) { sb.append(URLEncoder.encode(entry.getKey(), "UTF-8")); - if (StringUtils.isNotEmpty(entry.getValue())) { + if (!Strings.isNullOrEmpty(entry.getValue())) { sb.append('='); sb.append(URLEncoder.encode(entry.getValue(), "UTF-8")); } @@ -203,7 +210,7 @@ public String toString() StringBuilder sb = new StringBuilder(); for (Map.Entry entry : entries()) { sb.append(entry.getKey()); - if (StringUtils.isNotEmpty(entry.getValue())) { + if (!Strings.isNullOrEmpty(entry.getValue())) { sb.append('='); sb.append(entry.getValue()); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestMessageImpl.java b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestMessageImpl.java index 046c5ec5..28bff583 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestMessageImpl.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestMessageImpl.java @@ -32,6 +32,8 @@ import io.netty.handler.codec.http.HttpContent; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; @@ -69,7 +71,6 @@ public class HttpRequestMessageImpl implements HttpRequestMessage } } - private static final Pattern PTN_COLON = Pattern.compile(":"); private static final String URI_SCHEME_SEP = "://"; private static final String URI_SCHEME_HTTP = "http"; private static final String URI_SCHEME_HTTPS = "https"; @@ -358,7 +359,7 @@ public Cookies parseCookies() public Cookies reParseCookies() { Cookies cookies = new Cookies(); - for (String aCookieHeader : getHeaders().get(HttpHeaderNames.COOKIE)) + for (String aCookieHeader : getHeaders().getAll(HttpHeaderNames.COOKIE)) { try { if (CLEAN_COOKIES.get()) { @@ -402,7 +403,7 @@ public ZuulMessage clone() { HttpRequestMessageImpl clone = new HttpRequestMessageImpl(message.getContext().clone(), protocol, method, path, - queryParams.clone(), message.getHeaders().clone(), clientIp, scheme, + queryParams.clone(), Headers.copyOf(message.getHeaders()), clientIp, scheme, port, serverName, clientRemoteAddress, immutable); if (getInboundRequest() != null) { clone.inboundRequest = (HttpRequestInfo) getInboundRequest().clone(); @@ -412,10 +413,10 @@ public ZuulMessage clone() protected HttpRequestInfo copyRequestInfo() { - // Unlike clone(), we create immutable copies of the Headers and HttpQueryParams here. + HttpRequestMessageImpl req = new HttpRequestMessageImpl(message.getContext(), protocol, method, path, - queryParams.immutableCopy(), message.getHeaders().immutableCopy(), clientIp, scheme, + queryParams.immutableCopy(), Headers.copyOf(message.getHeaders()), clientIp, scheme, port, serverName, clientRemoteAddress, true); req.setHasBody(hasBody()); return req; @@ -471,25 +472,31 @@ protected String generateInfoForLogging() * * The Host header may contain port, but in this method we strip it out for consistency - use the * getOriginalPort method for that. - * - * @return */ @Override - public String getOriginalHost() - { - String host = getHeaders().getFirst(HttpHeaderNames.X_FORWARDED_HOST); - if (host == null) { - host = getHeaders().getFirst(HttpHeaderNames.HOST); - if (host != null) { - // Host header may have a trailing port. Strip that out if it does. - host = PTN_COLON.split(host)[0]; - } + public String getOriginalHost() { + try { + return getOriginalHost(getHeaders(), getServerName()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } - if (host == null) { - host = getServerName(); + @VisibleForTesting + static String getOriginalHost(Headers headers, String serverName) throws URISyntaxException { + String xForwardedHost = headers.getFirst(HttpHeaderNames.X_FORWARDED_HOST); + if (xForwardedHost != null) { + return xForwardedHost; + } + String host = headers.getFirst(HttpHeaderNames.HOST); + if (host != null) { + URI uri = new URI(/* scheme= */ null, host, /* path= */ null, /* query= */ null, /* fragment= */ null); + if (uri.getHost() == null) { + throw new URISyntaxException(host, "Bad host name"); } + return uri.getHost(); } - return host; + return serverName; } @Override @@ -513,30 +520,32 @@ public String getOriginalProtocol() } @Override - public int getOriginalPort() - { - int port; - String portStr = getHeaders().getFirst(HttpHeaderNames.X_FORWARDED_PORT); - if (portStr == null) { - // Check if port was specified on a Host header. - String hostHeader = getHeaders().getFirst(HttpHeaderNames.HOST); - if (hostHeader != null) { - String[] hostParts = PTN_COLON.split(hostHeader); - if (hostParts.length == 2) { - port = Integer.parseInt(hostParts[1]); - } - else { - port = getPort(); - } - } - else { - port = getPort(); - } + public int getOriginalPort() { + try { + return getOriginalPort(getContext(), getHeaders(), getPort()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); } - else { - port = Integer.parseInt(portStr); + } + + @VisibleForTesting + static int getOriginalPort(SessionContext context, Headers headers, int serverPort) throws URISyntaxException { + if (context.containsKey(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS)) { + return ((InetSocketAddress) context.get(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS)).getPort(); } - return port; + String portStr = headers.getFirst(HttpHeaderNames.X_FORWARDED_PORT); + if (portStr != null && !portStr.isEmpty()) { + return Integer.parseInt(portStr); + } + // Check if port was specified on a Host header. + String host = headers.getFirst(HttpHeaderNames.HOST); + if (host != null) { + URI uri = new URI(/* scheme= */ null, host, /* path= */ null, /* query= */ null, /* fragment= */ null); + if (uri.getPort() != -1) { + return uri.getPort(); + } + } + return serverPort; } @Override @@ -576,7 +585,7 @@ protected String _reconstructURI() String scheme = getOriginalScheme().toLowerCase(); uri.append(scheme); - uri.append(URI_SCHEME_SEP).append(getOriginalHost()); + uri.append(URI_SCHEME_SEP).append(getOriginalHost(getHeaders(), getServerName())); int port = getOriginalPort(); if ((URI_SCHEME_HTTP.equals(scheme) && 80 == port) @@ -590,6 +599,11 @@ protected String _reconstructURI() return uri.toString(); } + catch (URISyntaxException e) { + // This is not really so bad, just debug log it and move on. + LOG.debug("Error reconstructing request URI!", e); + return ""; + } catch (Exception e) { LOG.error("Error reconstructing request URI!", e); return ""; diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseMessageImpl.java b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseMessageImpl.java index 89ac667c..693fd368 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseMessageImpl.java +++ b/zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseMessageImpl.java @@ -191,7 +191,7 @@ public Cookies parseSetCookieHeader(String setCookieValue) public boolean hasSetCookieWithName(String cookieName) { boolean has = false; - for (String setCookieValue : getHeaders().get(HttpHeaderNames.SET_COOKIE)) { + for (String setCookieValue : getHeaders().getAll(HttpHeaderNames.SET_COOKIE)) { for (Cookie cookie : CookieDecoder.decode(setCookieValue)) { if (cookie.getName().equalsIgnoreCase(cookieName)) { has = true; @@ -251,7 +251,7 @@ public ZuulMessage clone() { // TODO - not sure if should be cloning the outbound request object here or not.... HttpResponseMessageImpl clone = new HttpResponseMessageImpl(getContext().clone(), - getHeaders().clone(), + Headers.copyOf(getHeaders()), getOutboundRequest(), getStatus()); if (getInboundResponse() != null) { clone.inboundResponse = (HttpResponseInfo) getInboundResponse().clone(); @@ -261,10 +261,12 @@ public ZuulMessage clone() protected HttpResponseInfo copyResponseInfo() { - // Unlike clone(), we create immutable copies of the Headers here. - HttpResponseMessageImpl response = new HttpResponseMessageImpl(getContext(), - getHeaders().immutableCopy(), - getOutboundRequest(), getStatus()); + HttpResponseMessageImpl response = + new HttpResponseMessageImpl( + getContext(), + Headers.copyOf(getHeaders()), + getOutboundRequest(), + getStatus()); response.setHasBody(hasBody()); return response; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/message/util/HttpRequestBuilder.java b/zuul-core/src/main/java/com/netflix/zuul/message/util/HttpRequestBuilder.java new file mode 100644 index 00000000..94f3a055 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/message/util/HttpRequestBuilder.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.message.util; + +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.message.Headers; +import com.netflix.zuul.message.http.HttpQueryParams; +import com.netflix.zuul.message.http.HttpRequestMessage; +import com.netflix.zuul.message.http.HttpRequestMessageImpl; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; +import java.util.Objects; + +/** + * Builder for a zuul http request. *exclusively* for use in unit tests. + * + * For default values initialized in the constructor: + *
+ * {@code new HttpRequestBuilder(context).withDefaults();}
+ *
+ * + * For overrides : + *
+ * {@code new HttpRequestBuilder(context).withHeaders(httpHeaders).withQueryParams(requestParams).build();}
+ * 
+ * @author Argha C + * @since 5/11/21 + */ +public final class HttpRequestBuilder { + private SessionContext sessionContext; + private String protocol; + private String method; + private String path; + private HttpQueryParams queryParams; + private Headers headers; + private String clientIp; + private String scheme; + private int port; + private String serverName; + private boolean isBuilt; + + public HttpRequestBuilder(SessionContext context) { + sessionContext = Objects.requireNonNull(context); + protocol = HttpVersion.HTTP_1_1.text(); + method = "get"; + path = "/"; + queryParams = new HttpQueryParams(); + headers = new Headers(); + clientIp = "::1"; + scheme = "https"; + port = 443; + isBuilt = false; + } + + /** + * Builds a request with basic defaults + * + * @return `HttpRequestMessage` + */ + public HttpRequestMessage withDefaults() { + return build(); + } + + public HttpRequestBuilder withHost(String hostName) { + serverName = Objects.requireNonNull(hostName); + return this; + } + + public HttpRequestBuilder withHeaders(Headers requestHeaders) { + headers = Objects.requireNonNull(requestHeaders); + return this; + } + + public HttpRequestBuilder withQueryParams(HttpQueryParams requestParams) { + this.queryParams = Objects.requireNonNull(requestParams); + return this; + } + + public HttpRequestBuilder withMethod(HttpMethod httpMethod) { + method = Objects.requireNonNull(httpMethod).name(); + return this; + } + + public HttpRequestBuilder withUri(String uri) { + path = Objects.requireNonNull(uri); + return this; + } + + /** + * Used to build a request with overriden values + * + * @return `HttpRequestMessage` + */ + public HttpRequestMessage build() { + if (isBuilt) { + throw new IllegalStateException("Builder must only be invoked once!"); + } + isBuilt = true; + return new HttpRequestMessageImpl(sessionContext, protocol, method, path, queryParams, headers, clientIp, scheme, port, + serverName); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnCounter.java b/zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnCounter.java new file mode 100644 index 00000000..183ad89a --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnCounter.java @@ -0,0 +1,151 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.monitoring; + +import com.netflix.spectator.api.Gauge; +import com.netflix.spectator.api.Id; +import com.netflix.spectator.api.Registry; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A counter for connection stats. Not thread-safe. + */ +public final class ConnCounter { + + private static final Logger logger = LoggerFactory.getLogger(ConnCounter.class); + + private static final AttributeKey CONN_COUNTER = AttributeKey.newInstance("zuul.conncounter"); + + private static final int LOCK_COUNT = 256; + private static final int LOCK_MASK = LOCK_COUNT - 1; + + private static final Attrs EMPTY = Attrs.newInstance(); + + /** + * An array of locks to guard the gauges. This is the same as Guava's Striped, but avoids the dep. + *

+ * This can be removed after https://github.com/Netflix/spectator/issues/862 is fixed. + */ + private static final Object[] locks = new Object[LOCK_COUNT]; + + static { + assert (LOCK_COUNT & LOCK_MASK) == 0; + for (int i = 0; i < locks.length; i++) { + locks[i] = new Object(); + } + } + + private final Registry registry; + private final Channel chan; + private final Id metricBase; + + private String lastCountKey; + + private final Map counts = new HashMap<>(); + + private ConnCounter(Registry registry, Channel chan, Id metricBase) { + this.registry = Objects.requireNonNull(registry); + this.chan = Objects.requireNonNull(chan); + this.metricBase = Objects.requireNonNull(metricBase); + } + + public static ConnCounter install(Channel chan, Registry registry, Id metricBase) { + ConnCounter counter = new ConnCounter(registry, chan, metricBase); + if (!chan.attr(CONN_COUNTER).compareAndSet(null, counter)) { + throw new IllegalStateException("pre-existing counter already present"); + } + return counter; + } + + public static ConnCounter from(Channel chan) { + Objects.requireNonNull(chan); + ConnCounter counter = chan.attr(CONN_COUNTER).get(); + if (counter != null) { + return counter; + } + if (chan.parent() != null && (counter = chan.parent().attr(CONN_COUNTER).get()) != null) { + return counter; + } + throw new IllegalStateException("no counter on channel"); + } + + public void increment(String event) { + increment(event, EMPTY); + } + + public void increment(String event, Attrs extraDimensions) { + Objects.requireNonNull(event); + Objects.requireNonNull(extraDimensions); + if (counts.containsKey(event)) { + // TODO(carl-mastrangelo): make this throw IllegalStateException after verifying this doesn't happen. + logger.warn("Duplicate conn counter increment {}", event); + return; + } + Attrs connDims = chan.attr(Server.CONN_DIMENSIONS).get(); + Map dimTags = new HashMap<>(connDims.size() + extraDimensions.size()); + + connDims.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v))); + extraDimensions.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v))); + + dimTags.put("from", lastCountKey != null ? lastCountKey : "nascent"); + lastCountKey = event; + Id id = registry.createId(metricBase.name() + '.' + event).withTags(metricBase.tags()).withTags(dimTags); + Gauge gauge = registry.gauge(id); + + synchronized (getLock(id)) { + double current = gauge.value(); + gauge.set(Double.isNaN(current) ? 1 : current + 1); + } + counts.put(event, gauge); + } + + public double getCurrentActiveConns() { + return counts.containsKey("active") ? counts.get("active").value() : 0.0; + } + + public void decrement(String event) { + Objects.requireNonNull(event); + Gauge gauge = counts.remove(event); + if (gauge == null) { + // TODO(carl-mastrangelo): make this throw IllegalStateException after verifying this doesn't happen. + logger.warn("Missing conn counter increment {}", event); + return; + } + synchronized (getLock(gauge.id())) { + // Noop gauges break this assertion in tests, but the type is package private. Check to make sure + // the gauge has a value, or by implementation cannot have a value. + assert !Double.isNaN(gauge.value()) + || gauge.getClass().getName().equals("com.netflix.spectator.api.NoopGauge"); + gauge.set(gauge.value() - 1); + } + } + + // This is here to pick the correct lock stripe. This avoids multiple threads synchronizing on the + // same lock in the common case. This can go away once there is an atomic gauge update implemented + // in spectator. + private static Object getLock(Id id) { + return locks[id.hashCode() & LOCK_MASK]; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnTimer.java b/zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnTimer.java new file mode 100644 index 00000000..d9fd739f --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnTimer.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.monitoring; + +import com.netflix.config.DynamicBooleanProperty; +import com.netflix.spectator.api.Id; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.histogram.PercentileTimer; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.channel.Channel; +import io.netty.util.AttributeKey; +import java.time.Duration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; + +/** + * A timer for connection stats. Not thread-safe. + */ +public final class ConnTimer { + + private static final DynamicBooleanProperty PRECISE_TIMING = + new DynamicBooleanProperty("zuul.conn.precise_timing", false); + + private static final AttributeKey CONN_TIMER = AttributeKey.newInstance("zuul.conntimer"); + + private static final Duration MIN_CONN_TIMING = Duration.ofNanos(1024); + private static final Duration MAX_CONN_TIMING = Duration.ofDays(366); + + private static final Attrs EMPTY = Attrs.newInstance(); + + private final Registry registry; + private final Channel chan; + // TODO(carl-mastrangelo): make this changable. + private final Id metricBase; + @Nullable + private final Id preciseMetricBase; + + private final Map timings = new LinkedHashMap<>(); + + private ConnTimer(Registry registry, Channel chan, Id metricBase) { + this.registry = Objects.requireNonNull(registry); + this.chan = Objects.requireNonNull(chan); + this.metricBase = Objects.requireNonNull(metricBase); + if (PRECISE_TIMING.get()) { + preciseMetricBase = registry.createId(metricBase.name() + ".pct").withTags(metricBase.tags()); + } else { + preciseMetricBase = null; + } + } + + public static ConnTimer install(Channel chan, Registry registry, Id metricBase) { + ConnTimer timer = new ConnTimer(registry, chan, metricBase); + if (!chan.attr(CONN_TIMER).compareAndSet(null, timer)) { + throw new IllegalStateException("pre-existing timer already present"); + } + return timer; + } + + public static ConnTimer from(Channel chan) { + Objects.requireNonNull(chan); + ConnTimer timer = chan.attr(CONN_TIMER).get(); + if (timer != null) { + return timer; + } + if (chan.parent() != null && (timer = chan.parent().attr(CONN_TIMER).get()) != null) { + return timer; + } + throw new IllegalStateException("no timer on channel"); + } + + public void record(Long now, String event) { + record(now, event, EMPTY); + } + + public void record(Long now, String event, Attrs extraDimensions) { + if (timings.containsKey(event)) { + return; + } + Objects.requireNonNull(now); + Objects.requireNonNull(event); + Objects.requireNonNull(extraDimensions); + + Attrs connDims = chan.attr(Server.CONN_DIMENSIONS).get(); + Map dimTags = new HashMap<>(connDims.size() + extraDimensions.size()); + + connDims.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v))); + extraDimensions.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v))); + + // Note: this is effectively O(n^2) because it will be called for each event in the connection + // setup. It should be bounded to at most 10 or so. + timings.forEach((from, stamp) -> { + long durationNanos = now - stamp; + if (durationNanos == 0) { + // This may happen if an event is double listed, or if the timer is not accurate enough to record + // it. + return; + } + registry.timer(buildId(metricBase, from, event, dimTags)) + .record(durationNanos, TimeUnit.NANOSECONDS); + if (preciseMetricBase != null) { + PercentileTimer.builder(registry) + .withId(buildId(preciseMetricBase, from, event, dimTags)) + .withRange(MIN_CONN_TIMING, MAX_CONN_TIMING) + .build() + .record(durationNanos, TimeUnit.NANOSECONDS); + } + }); + timings.put(event, now); + } + + private Id buildId(Id base, String from, String to, Map tags) { + return registry.createId(metricBase.name() + '.' + from + '-' + to).withTags(base.tags()).withTags(tags); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/monitoring/CounterFactory.java b/zuul-core/src/main/java/com/netflix/zuul/monitoring/CounterFactory.java deleted file mode 100644 index 3d9e83c0..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/monitoring/CounterFactory.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.monitoring; - -/** - * Abstraction layer to provide counter based monitoring. - * - * @author mhawthorne - */ -public abstract class CounterFactory { - - private static CounterFactory INSTANCE; - - /** - * Pass in a CounterFactory Instance. This must be done to use Zuul as Zuul uses several internal counters - * - * @param f a CounterFactory value - */ - public static final void initialize(CounterFactory f) { - INSTANCE = f; - } - - /** - * return the singleton CounterFactory instance. - * - * @return a CounterFactory value - */ - public static final CounterFactory instance() { - if(INSTANCE == null) throw new IllegalStateException(String.format("%s not initialized", CounterFactory.class.getSimpleName())); - return INSTANCE; - } - - /** - * Increments the counter of the given name - * - * @param name a String value - */ - public abstract void increment(String name); - -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/monitoring/MonitoringHelper.java b/zuul-core/src/main/java/com/netflix/zuul/monitoring/MonitoringHelper.java index 81f5c4ef..12b2653f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/monitoring/MonitoringHelper.java +++ b/zuul-core/src/main/java/com/netflix/zuul/monitoring/MonitoringHelper.java @@ -22,15 +22,9 @@ public class MonitoringHelper { public static final void initMocks() { - CounterFactory.initialize(new CounterFactoryImpl()); TracerFactory.initialize(new TracerFactoryImpl()); } - private static final class CounterFactoryImpl extends CounterFactory { - @Override - public void increment(String name) {} - } - private static final class TracerFactoryImpl extends TracerFactory { @Override public Tracer startMicroTracer(String name) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/BasicRequestStat.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/BasicRequestStat.java index 872656df..10d2b38f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/BasicRequestStat.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/BasicRequestStat.java @@ -17,7 +17,7 @@ package com.netflix.zuul.netty.connectionpool; import com.google.common.base.Stopwatch; -import com.netflix.loadbalancer.Server; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.exception.ErrorType; import com.netflix.zuul.exception.OutboundErrorType; @@ -32,13 +32,13 @@ public class BasicRequestStat implements RequestStat { private volatile boolean isFinished; private volatile Stopwatch stopwatch; - public BasicRequestStat(String clientName) { + public BasicRequestStat() { this.isFinished = false; this.stopwatch = Stopwatch.createStarted(); } @Override - public RequestStat server(Server server) { + public RequestStat server(DiscoveryResult server) { return this; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientChannelManager.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientChannelManager.java index a430522e..e188eef7 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientChannelManager.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientChannelManager.java @@ -17,11 +17,12 @@ package com.netflix.zuul.netty.connectionpool; -import com.netflix.loadbalancer.Server; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.passport.CurrentPassport; import io.netty.channel.EventLoop; import io.netty.util.concurrent.Promise; +import java.net.InetAddress; import java.util.concurrent.atomic.AtomicReference; /** @@ -43,9 +44,12 @@ public interface ClientChannelManager Promise acquire(EventLoop eventLoop); - Promise acquire(EventLoop eventLoop, Object key, String httpMethod, String uri, int retryNum, - CurrentPassport passport, AtomicReference selectedServer, - AtomicReference selectedHostAddr); + Promise acquire( + EventLoop eventLoop, + Object key, + CurrentPassport passport, + AtomicReference selectedServer, + AtomicReference selectedHostAddr); boolean isCold(); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientTimeoutHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientTimeoutHandler.java index b63c2e7e..ca02939a 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientTimeoutHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientTimeoutHandler.java @@ -22,6 +22,7 @@ import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.AttributeKey; +import java.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +35,7 @@ public final class ClientTimeoutHandler { private static final Logger LOG = LoggerFactory.getLogger(ClientTimeoutHandler.class); - public static final AttributeKey ORIGIN_RESPONSE_READ_TIMEOUT = AttributeKey.newInstance("originResponseReadTimeout"); + public static final AttributeKey ORIGIN_RESPONSE_READ_TIMEOUT = AttributeKey.newInstance("originResponseReadTimeout"); public static final class InboundHandler extends ChannelInboundHandlerAdapter { @Override @@ -55,10 +56,10 @@ public static final class OutboundHandler extends ChannelOutboundHandlerAdapter @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { try { - final Integer timeout = ctx.channel().attr(ORIGIN_RESPONSE_READ_TIMEOUT).get(); + final Duration timeout = ctx.channel().attr(ORIGIN_RESPONSE_READ_TIMEOUT).get(); if (timeout != null && msg instanceof LastHttpContent) { promise.addListener(e -> { - LOG.debug("[{}] Adding read timeout handler: {}", ctx.channel().id(), timeout); + LOG.debug("[{}] Adding read timeout handler: {}", ctx.channel().id(), timeout.toMillis()); PooledConnection.getFromChannel(ctx.channel()).startReadTimeoutHandler(timeout); }); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfig.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfig.java index 8df0c499..ea466ab2 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfig.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfig.java @@ -16,13 +16,15 @@ package com.netflix.zuul.netty.connectionpool; +import com.netflix.zuul.origins.OriginName; + /** * Created by saroskar on 3/24/16. */ public interface ConnectionPoolConfig { /* Origin name from connection pool */ - String getOriginName(); + OriginName getOriginName(); /* Max number of requests per connection before it needs to be recycled */ int getMaxRequestsPerConnection(); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfigImpl.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfigImpl.java index 0b597ac0..fa31db5b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfigImpl.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfigImpl.java @@ -20,6 +20,8 @@ import com.netflix.client.config.IClientConfigKey; import com.netflix.config.CachedDynamicBooleanProperty; import com.netflix.config.CachedDynamicIntProperty; +import com.netflix.zuul.origins.OriginName; +import java.util.Objects; /** * Created by saroskar on 3/24/16. @@ -31,7 +33,7 @@ public class ConnectionPoolConfigImpl implements ConnectionPoolConfig { private static final int DEFAULT_IDLE_TIMEOUT = 60000; private static final int DEFAULT_MAX_CONNS_PER_HOST = 50; - private final String originName; + private final OriginName originName; private final IClientConfig clientConfig; private final CachedDynamicIntProperty MAX_REQUESTS_PER_CONNECTION; @@ -44,24 +46,31 @@ public class ConnectionPoolConfigImpl implements ConnectionPoolConfig { private final CachedDynamicBooleanProperty AUTO_READ; - public ConnectionPoolConfigImpl(final String originName, IClientConfig clientConfig) { - this.originName = originName; + public ConnectionPoolConfigImpl(final OriginName originName, IClientConfig clientConfig) { + this.originName = Objects.requireNonNull(originName, "originName"); + String niwsClientName = originName.getNiwsClientName(); this.clientConfig = clientConfig; - this.MAX_REQUESTS_PER_CONNECTION = new CachedDynamicIntProperty(originName+".netty.client.maxRequestsPerConnection", 1000); + this.MAX_REQUESTS_PER_CONNECTION = + new CachedDynamicIntProperty(niwsClientName + ".netty.client.maxRequestsPerConnection", 1000); // NOTE that the each eventloop has it's own connection pool per host, and this is applied per event-loop. - this.PER_SERVER_WATERLINE = new CachedDynamicIntProperty(originName+".netty.client.perServerWaterline", 4); + this.PER_SERVER_WATERLINE = + new CachedDynamicIntProperty(niwsClientName + ".netty.client.perServerWaterline", 4); - this.SOCKET_KEEP_ALIVE = new CachedDynamicBooleanProperty(originName+".netty.client.TcpKeepAlive", false); - this.TCP_NO_DELAY = new CachedDynamicBooleanProperty(originName+".netty.client.TcpNoDelay", false); - this.WRITE_BUFFER_HIGH_WATER_MARK = new CachedDynamicIntProperty(originName+".netty.client.WriteBufferHighWaterMark", 32 * 1024); - this.WRITE_BUFFER_LOW_WATER_MARK = new CachedDynamicIntProperty(originName+".netty.client.WriteBufferLowWaterMark", 8 * 1024); - this.AUTO_READ = new CachedDynamicBooleanProperty(originName+".netty.client.AutoRead", false); + this.SOCKET_KEEP_ALIVE = + new CachedDynamicBooleanProperty(niwsClientName + ".netty.client.TcpKeepAlive", false); + this.TCP_NO_DELAY = + new CachedDynamicBooleanProperty(niwsClientName + ".netty.client.TcpNoDelay", false); + this.WRITE_BUFFER_HIGH_WATER_MARK = + new CachedDynamicIntProperty(niwsClientName + ".netty.client.WriteBufferHighWaterMark", 32 * 1024); + this.WRITE_BUFFER_LOW_WATER_MARK = + new CachedDynamicIntProperty(niwsClientName + ".netty.client.WriteBufferLowWaterMark", 8 * 1024); + this.AUTO_READ = new CachedDynamicBooleanProperty(niwsClientName + ".netty.client.AutoRead", false); } @Override - public String getOriginName() { + public OriginName getOriginName() { return originName; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolHandler.java index a675d858..922e3886 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolHandler.java @@ -19,6 +19,7 @@ import com.netflix.spectator.api.Counter; import com.netflix.zuul.netty.ChannelUtils; import com.netflix.zuul.netty.SpectatorUtils; +import com.netflix.zuul.origins.OriginName; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -45,17 +46,19 @@ public class ConnectionPoolHandler extends ChannelDuplexHandler public static final String METRIC_PREFIX = "connectionpool"; - private final String originName; + private final OriginName originName; private final Counter idleCounter; private final Counter inactiveCounter; private final Counter errorCounter; - public ConnectionPoolHandler(String originName) { - if (originName == null) throw new IllegalArgumentException("Null originName passed to constructor!"); + public ConnectionPoolHandler(OriginName originName) { + if (originName == null) { + throw new IllegalArgumentException("Null originName passed to constructor!"); + } this.originName = originName; - this.idleCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_idle", originName); - this.inactiveCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_inactive", originName); - this.errorCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_error", originName); + this.idleCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_idle", originName.getMetricId()); + this.inactiveCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_inactive", originName.getMetricId()); + this.errorCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_error", originName.getMetricId()); } @Override @@ -122,10 +125,8 @@ private void closeConnection(ChannelHandlerContext ctx, String msg) { PooledConnection conn = PooledConnection.getFromChannel(ctx.channel()); if (conn != null) { if (LOG.isDebugEnabled()) { - msg = msg + " Closing the PooledConnection and releasing." - + " ASG: " + String.valueOf(conn.getServerKey().getASGName() - + ", host=" + String.valueOf(conn.getServerKey().getHostName())); - LOG.debug(msg); + msg = msg + " Closing the PooledConnection and releasing. conn={}"; + LOG.debug(msg, conn); } flagCloseAndReleaseConnection(conn); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManager.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManager.java index 7bd0f41f..11b92f33 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManager.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManager.java @@ -16,25 +16,22 @@ package com.netflix.zuul.netty.connectionpool; -import static com.netflix.client.config.CommonClientConfigKey.NFLoadBalancerClassName; - -import com.google.common.base.Throwables; -import com.google.common.collect.Sets; -import com.netflix.appinfo.InstanceInfo; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.net.InetAddresses; import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.DynamicServerListLoadBalancer; -import com.netflix.loadbalancer.LoadBalancerStats; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.ServerStats; -import com.netflix.loadbalancer.ZoneAwareLoadBalancer; -import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; import com.netflix.spectator.api.histogram.PercentileTimer; +import com.netflix.zuul.discovery.DiscoveryResult; +import com.netflix.zuul.discovery.DynamicServerResolver; +import com.netflix.zuul.discovery.ResolverResult; import com.netflix.zuul.exception.OutboundErrorType; +import com.netflix.zuul.resolver.Resolver; +import com.netflix.zuul.resolver.ResolverListener; import com.netflix.zuul.netty.SpectatorUtils; import com.netflix.zuul.netty.insights.PassportStateHttpClientHandler; import com.netflix.zuul.netty.server.OriginResponseReceiver; +import com.netflix.zuul.origins.OriginName; import com.netflix.zuul.passport.CurrentPassport; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; @@ -43,14 +40,16 @@ import io.netty.channel.EventLoop; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.concurrent.Promise; -import java.lang.reflect.InvocationTargetException; -import java.util.HashSet; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.List; -import java.util.Set; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,13 +63,12 @@ public class DefaultClientChannelManager implements ClientChannelManager { public static final String METRIC_PREFIX = "connectionpool"; - private final DynamicServerListLoadBalancer loadBalancer; + private final Resolver dynamicServerResolver; private final ConnectionPoolConfig connPoolConfig; private final IClientConfig clientConfig; private final Registry spectatorRegistry; - /* DeploymentContextBasedVIP for which to maintain this connection pool */ - private final String vip; + private final OriginName originName; private static final Throwable SHUTTING_DOWN_ERR = new IllegalStateException("ConnectionPool is shutting down now."); private volatile boolean shuttingDown = false; @@ -91,41 +89,73 @@ public class DefaultClientChannelManager implements ClientChannelManager { private final AtomicInteger connsInPool; private final AtomicInteger connsInUse; - private final ConcurrentHashMap perServerPools; + private final ConcurrentHashMap perServerPools; private NettyClientConnectionFactory clientConnFactory; private OriginChannelInitializer channelInitializer; public static final String IDLE_STATE_HANDLER_NAME = "idleStateHandler"; - public DefaultClientChannelManager(String originName, String vip, IClientConfig clientConfig, Registry spectatorRegistry) { - this.loadBalancer = createLoadBalancer(clientConfig); + public DefaultClientChannelManager( + OriginName originName, IClientConfig clientConfig, Registry spectatorRegistry) { + this.originName = Objects.requireNonNull(originName, "originName"); + this.dynamicServerResolver = new DynamicServerResolver(clientConfig, new ServerPoolListener()); + + String metricId = originName.getMetricId(); - this.vip = vip; this.clientConfig = clientConfig; this.spectatorRegistry = spectatorRegistry; this.perServerPools = new ConcurrentHashMap<>(200); - // Setup a listener for Discovery serverlist changes. - this.loadBalancer.addServerListChangeListener(this::removeMissingServerConnectionPools); + this.connPoolConfig = new ConnectionPoolConfigImpl(originName, this.clientConfig); + + this.createNewConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create", metricId); + this.createConnSucceededCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create_success", metricId); + this.createConnFailedCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create_fail", metricId); + + this.closeConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_close", metricId); + this.requestConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_request", metricId); + this.reuseConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_reuse", metricId); + this.releaseConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_release", metricId); + this.alreadyClosedCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_alreadyClosed", metricId); + this.connTakenFromPoolIsNotOpen = SpectatorUtils.newCounter(METRIC_PREFIX + "_fromPoolIsClosed", metricId); + this.maxConnsPerHostExceededCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_maxConnsPerHostExceeded", metricId); + this.closeWrtBusyConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_closeWrtBusyConnCounter", metricId); + this.connEstablishTimer = PercentileTimer.get(spectatorRegistry, spectatorRegistry.createId(METRIC_PREFIX + "_createTiming", "id", metricId)); + this.connsInPool = SpectatorUtils.newGauge(METRIC_PREFIX + "_inPool", metricId, new AtomicInteger()); + this.connsInUse = SpectatorUtils.newGauge(METRIC_PREFIX + "_inUse", metricId, new AtomicInteger()); + } + + @VisibleForTesting + public DefaultClientChannelManager( + OriginName originName, IClientConfig clientConfig, + Resolver resolver, Registry spectatorRegistry) { + this.originName = Objects.requireNonNull(originName, "originName"); + this.dynamicServerResolver = resolver; + + String metricId = originName.getMetricId(); + + this.clientConfig = clientConfig; + this.spectatorRegistry = spectatorRegistry; + this.perServerPools = new ConcurrentHashMap<>(200); this.connPoolConfig = new ConnectionPoolConfigImpl(originName, this.clientConfig); - this.createNewConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create", originName); - this.createConnSucceededCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create_success", originName); - this.createConnFailedCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create_fail", originName); - - this.closeConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_close", originName); - this.requestConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_request", originName); - this.reuseConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_reuse", originName); - this.releaseConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_release", originName); - this.alreadyClosedCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_alreadyClosed", originName); - this.connTakenFromPoolIsNotOpen = SpectatorUtils.newCounter(METRIC_PREFIX + "_fromPoolIsClosed", originName); - this.maxConnsPerHostExceededCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_maxConnsPerHostExceeded", originName); - this.closeWrtBusyConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_closeWrtBusyConnCounter", originName); - this.connEstablishTimer = PercentileTimer.get(spectatorRegistry, spectatorRegistry.createId(METRIC_PREFIX + "_createTiming", "id", originName)); - this.connsInPool = SpectatorUtils.newGauge(METRIC_PREFIX + "_inPool", originName, new AtomicInteger()); - this.connsInUse = SpectatorUtils.newGauge(METRIC_PREFIX + "_inUse", originName, new AtomicInteger()); + this.createNewConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create", metricId); + this.createConnSucceededCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create_success", metricId); + this.createConnFailedCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_create_fail", metricId); + + this.closeConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_close", metricId); + this.requestConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_request", metricId); + this.reuseConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_reuse", metricId); + this.releaseConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_release", metricId); + this.alreadyClosedCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_alreadyClosed", metricId); + this.connTakenFromPoolIsNotOpen = SpectatorUtils.newCounter(METRIC_PREFIX + "_fromPoolIsClosed", metricId); + this.maxConnsPerHostExceededCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_maxConnsPerHostExceeded", metricId); + this.closeWrtBusyConnCounter = SpectatorUtils.newCounter(METRIC_PREFIX + "_closeWrtBusyConnCounter", metricId); + this.connEstablishTimer = PercentileTimer.get(spectatorRegistry, spectatorRegistry.createId(METRIC_PREFIX + "_createTiming", "id", metricId)); + this.connsInPool = SpectatorUtils.newGauge(METRIC_PREFIX + "_inPool", metricId, new AtomicInteger()); + this.connsInUse = SpectatorUtils.newGauge(METRIC_PREFIX + "_inUse", metricId, new AtomicInteger()); } @Override @@ -146,41 +176,6 @@ protected NettyClientConnectionFactory createNettyClientConnectionFactory(Connec return new NettyClientConnectionFactory(connPoolConfig, clientConnInitializer); } - protected DynamicServerListLoadBalancer createLoadBalancer(IClientConfig clientConfig) { - // Create and configure a loadbalancer for this vip. - String loadBalancerClassName = clientConfig.get(NFLoadBalancerClassName, ZoneAwareLoadBalancer.class.getName()); - - DynamicServerListLoadBalancer lb; - try { - Class clazz = Class.forName(loadBalancerClassName); - lb = clazz.asSubclass(DynamicServerListLoadBalancer.class).getConstructor().newInstance(); - lb.initWithNiwsConfig(clientConfig); - } catch (Exception e) { - Throwables.throwIfUnchecked(e); - throw new IllegalStateException("Could not instantiate LoadBalancer " + loadBalancerClassName, e); - } - - return lb; - } - - protected void removeMissingServerConnectionPools(List oldList, List newList) { - Set oldSet = new HashSet<>(oldList); - Set newSet = new HashSet<>(newList); - Set removedSet = Sets.difference(oldSet, newSet); - - if (!removedSet.isEmpty()) { - LOG.debug("Removing connection pools for missing servers. vip = " + this.vip - + ". " + removedSet.size() + " servers gone."); - - for (Server s : removedSet) { - IConnectionPool pool = perServerPools.remove(s); - if (pool != null) { - pool.shutdown(); - } - } - } - } - @Override public ConnectionPoolConfig getConfig() { return connPoolConfig; @@ -188,7 +183,7 @@ public ConnectionPoolConfig getConfig() { @Override public boolean isAvailable() { - return !loadBalancer.getReachableServers().isEmpty(); + return dynamicServerResolver.hasServers(); } @Override @@ -205,7 +200,7 @@ public int getInflightRequestsCount() { public void shutdown() { this.shuttingDown = true; - loadBalancer.shutdown(); + dynamicServerResolver.shutdown(); for (IConnectionPool pool : perServerPools.values()) { pool.shutdown(); @@ -219,9 +214,9 @@ public boolean release(final PooledConnection conn) { releaseConnCounter.increment(); connsInUse.decrementAndGet(); - final ServerStats stats = conn.getServerStats(); - stats.decrementActiveRequestsCount(); - stats.incrementNumRequests(); + final DiscoveryResult discoveryResult = conn.getServer(); + discoveryResult.decrementActiveRequestsCount(); + discoveryResult.incrementNumRequests(); if (shuttingDown) { return false; @@ -237,7 +232,7 @@ public boolean release(final PooledConnection conn) { conn.setInPool(false); conn.close(); } - else if (stats.isCircuitBreakerTripped()) { + else if (discoveryResult.isCircuitBreakerTripped()) { // Don't put conns for currently circuit-tripped servers back into the pool. conn.setInPool(false); conn.close(); @@ -253,7 +248,7 @@ else if (!conn.isActive()) { releaseHandlers(conn); // Attempt to return connection to the pool. - IConnectionPool pool = perServerPools.get(conn.getServer()); + IConnectionPool pool = perServerPools.get(discoveryResult); if (pool != null) { released = pool.release(conn); } @@ -312,20 +307,16 @@ public boolean remove(PooledConnection conn) { @Override public Promise acquire(final EventLoop eventLoop) { - return acquire(eventLoop, null, null, null, 1, CurrentPassport.create(), - new AtomicReference<>(), new AtomicReference<>()); + return acquire(eventLoop, null, CurrentPassport.create(), new AtomicReference<>(), new AtomicReference<>()); } @Override - public Promise acquire(final EventLoop eventLoop, final Object key, final String httpMethod, - final String uri, final int attemptNum, final CurrentPassport passport, - final AtomicReference selectedServer, - final AtomicReference selectedHostAdddr) - { - - if (attemptNum < 1) { - throw new IllegalArgumentException("attemptNum must be greater than zero"); - } + public Promise acquire( + EventLoop eventLoop, + @Nullable Object key, + CurrentPassport passport, + AtomicReference selectedServer, + AtomicReference selectedHostAddr) { if (shuttingDown) { Promise promise = eventLoop.newPromise(); @@ -334,58 +325,50 @@ public Promise acquire(final EventLoop eventLoop, final Object } // Choose the next load-balanced server. - final Server chosenServer = loadBalancer.chooseServer(key); - if (chosenServer == null) { + final DiscoveryResult chosenServer = dynamicServerResolver.resolve(key); + + //(argha-c): Always ensure the selected server is updated, since the call chain relies on this mutation. + selectedServer.set(chosenServer); + if (chosenServer == DiscoveryResult.EMPTY) { Promise promise = eventLoop.newPromise(); promise.setFailure(new OriginConnectException("No servers available", OutboundErrorType.NO_AVAILABLE_SERVERS)); return promise; } - final InstanceInfo instanceInfo = chosenServer instanceof DiscoveryEnabledServer ? - ((DiscoveryEnabledServer) chosenServer).getInstanceInfo() : - // create mock instance info for non-discovery instances - new InstanceInfo(chosenServer.getId(), null, null, chosenServer.getHost(), chosenServer.getId(), - null, null, null, null, null, null, null, null, 0, null, null, null, null, null, null, null, null, null, null, null, null); - - selectedServer.set(chosenServer); // Now get the connection-pool for this server. IConnectionPool pool = perServerPools.computeIfAbsent(chosenServer, s -> { - // Get the stats from LB for this server. - LoadBalancerStats lbStats = loadBalancer.getLoadBalancerStats(); - ServerStats stats = lbStats.getSingleServerStat(chosenServer); - + SocketAddress finalServerAddr = pickAddress(chosenServer); final ClientChannelManager clientChannelMgr = this; - PooledConnectionFactory pcf = createPooledConnectionFactory(chosenServer, instanceInfo, stats, clientChannelMgr, closeConnCounter, closeWrtBusyConnCounter); + PooledConnectionFactory pcf = createPooledConnectionFactory(chosenServer, clientChannelMgr, closeConnCounter, closeWrtBusyConnCounter); // Create a new pool for this server. - return createConnectionPool(chosenServer, stats, instanceInfo, clientConnFactory, pcf, connPoolConfig, + return createConnectionPool(chosenServer, finalServerAddr, clientConnFactory, pcf, connPoolConfig, clientConfig, createNewConnCounter, createConnSucceededCounter, createConnFailedCounter, requestConnCounter, reuseConnCounter, connTakenFromPoolIsNotOpen, maxConnsPerHostExceededCounter, connEstablishTimer, connsInPool, connsInUse); }); - return pool.acquire(eventLoop, null, httpMethod, uri, attemptNum, passport, selectedHostAdddr); + return pool.acquire(eventLoop, passport, selectedHostAddr); } - protected PooledConnectionFactory createPooledConnectionFactory(Server chosenServer, InstanceInfo instanceInfo, ServerStats stats, ClientChannelManager clientChannelMgr, - Counter closeConnCounter, Counter closeWrtBusyConnCounter) { - return ch -> new PooledConnection(ch, chosenServer, clientChannelMgr, instanceInfo, stats, closeConnCounter, closeWrtBusyConnCounter); + protected PooledConnectionFactory createPooledConnectionFactory( + DiscoveryResult chosenServer, ClientChannelManager clientChannelMgr, Counter closeConnCounter, + Counter closeWrtBusyConnCounter) { + return ch -> new PooledConnection(ch, chosenServer, clientChannelMgr, closeConnCounter, closeWrtBusyConnCounter); } - protected IConnectionPool createConnectionPool(Server chosenServer, ServerStats stats, InstanceInfo instanceInfo, - NettyClientConnectionFactory clientConnFactory, PooledConnectionFactory pcf, - ConnectionPoolConfig connPoolConfig, IClientConfig clientConfig, - Counter createNewConnCounter, Counter createConnSucceededCounter, - Counter createConnFailedCounter, Counter requestConnCounter, - Counter reuseConnCounter, Counter connTakenFromPoolIsNotOpen, - Counter maxConnsPerHostExceededCounter, PercentileTimer connEstablishTimer, - AtomicInteger connsInPool, AtomicInteger connsInUse) { + protected IConnectionPool createConnectionPool( + DiscoveryResult discoveryResult, SocketAddress serverAddr, + NettyClientConnectionFactory clientConnFactory, PooledConnectionFactory pcf, + ConnectionPoolConfig connPoolConfig, IClientConfig clientConfig, Counter createNewConnCounter, + Counter createConnSucceededCounter, Counter createConnFailedCounter, Counter requestConnCounter, + Counter reuseConnCounter, Counter connTakenFromPoolIsNotOpen, Counter maxConnsPerHostExceededCounter, + PercentileTimer connEstablishTimer, AtomicInteger connsInPool, AtomicInteger connsInUse) { return new PerServerConnectionPool( - chosenServer, - stats, - instanceInfo, + discoveryResult, + serverAddr, clientConnFactory, pcf, connPoolConfig, @@ -403,6 +386,24 @@ protected IConnectionPool createConnectionPool(Server chosenServer, ServerStats ); } + final class ServerPoolListener implements ResolverListener { + + @Override + public void onChange(List removedSet) { + if (!removedSet.isEmpty()) { + LOG.debug("Removing connection pools for missing servers. name = {}. {} servers gone.", originName, + removedSet.size()); + for (DiscoveryResult s : removedSet) { + IConnectionPool pool = perServerPools.remove(s); + if (pool != null) { + pool.shutdown(); + } + } + } + } + + } + @Override public int getConnsInPool() { return connsInPool.get(); @@ -413,16 +414,41 @@ public int getConnsInUse() { return connsInUse.get(); } - // This is just used for information in the RestClient 'bridge'. - public DynamicServerListLoadBalancer getLoadBalancer() { - return this.loadBalancer; + protected ConcurrentHashMap getPerServerPools() { + return perServerPools; } - public IClientConfig getClientConfig() { - return this.loadBalancer.getClientConfig(); + @VisibleForTesting + static SocketAddress pickAddressInternal(ResolverResult chosenServer, @Nullable OriginName originName) { + String rawHost; + int port; + rawHost = chosenServer.getHost(); + port = chosenServer.getPort(); + InetSocketAddress serverAddr; + try { + InetAddress ipAddr = InetAddresses.forString(rawHost); + serverAddr = new InetSocketAddress(ipAddr, port); + } catch (IllegalArgumentException e1) { + LOG.warn("NettyClientConnectionFactory got an unresolved address, addr: {}", rawHost); + Counter unresolvedDiscoveryHost = SpectatorUtils.newCounter( + "unresolvedDiscoveryHost", + originName == null ? "unknownOrigin" : originName.getTarget()); + unresolvedDiscoveryHost.increment(); + try { + serverAddr = new InetSocketAddress(rawHost, port); + } catch (RuntimeException e2) { + e1.addSuppressed(e2); + throw e1; + } + } + + return serverAddr; } - protected ConcurrentHashMap getPerServerPools() { - return perServerPools; + /** + * Given a server chosen from the load balancer, pick the appropriate address to connect to. + */ + protected SocketAddress pickAddress(DiscoveryResult chosenServer) { + return pickAddressInternal(chosenServer, connPoolConfig.getOriginName()); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultOriginChannelInitializer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultOriginChannelInitializer.java index 71610dc3..b16b69ef 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultOriginChannelInitializer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultOriginChannelInitializer.java @@ -47,10 +47,10 @@ public class DefaultOriginChannelInitializer extends OriginChannelInitializer { public DefaultOriginChannelInitializer(ConnectionPoolConfig connPoolConfig, Registry spectatorRegistry) { this.connectionPoolConfig = connPoolConfig; - final String originName = connectionPoolConfig.getOriginName(); - this.connectionPoolHandler = new ConnectionPoolHandler(originName); - this.httpMetricsHandler = new HttpMetricsChannelHandler(spectatorRegistry, "client", originName); - this.nettyLogger = new LoggingHandler("zuul.origin.nettylog." + originName, LogLevel.INFO); + String niwsClientName = connectionPoolConfig.getOriginName().getNiwsClientName(); + this.connectionPoolHandler = new ConnectionPoolHandler(connectionPoolConfig.getOriginName()); + this.httpMetricsHandler = new HttpMetricsChannelHandler(spectatorRegistry, "client", niwsClientName); + this.nettyLogger = new LoggingHandler("zuul.origin.nettylog." + niwsClientName, LogLevel.INFO); this.sslContext = getClientSslContext(spectatorRegistry); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/IConnectionPool.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/IConnectionPool.java index 132d0985..3980a9b7 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/IConnectionPool.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/IConnectionPool.java @@ -20,6 +20,7 @@ import io.netty.channel.EventLoop; import io.netty.util.concurrent.Promise; +import java.net.InetAddress; import java.util.concurrent.atomic.AtomicReference; /** @@ -29,8 +30,8 @@ */ public interface IConnectionPool { - Promise acquire(EventLoop eventLoop, Object key, String httpMethod, String uri, - int retryNum, CurrentPassport passport, AtomicReference selectedHostAddr); + Promise acquire( + EventLoop eventLoop, CurrentPassport passport, AtomicReference selectedHostAddr); boolean release(PooledConnection conn); boolean remove(PooledConnection conn); void shutdown(); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/NettyClientConnectionFactory.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/NettyClientConnectionFactory.java index 1a8d73d8..3648c5d4 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/NettyClientConnectionFactory.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/NettyClientConnectionFactory.java @@ -16,8 +16,6 @@ package com.netflix.zuul.netty.connectionpool; -import com.netflix.spectator.api.Counter; -import com.netflix.zuul.netty.SpectatorUtils; import com.netflix.zuul.netty.server.Server; import com.netflix.zuul.passport.CurrentPassport; import io.netty.bootstrap.Bootstrap; @@ -26,37 +24,29 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoop; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Objects; /** * Created by saroskar on 3/16/16. */ -public class NettyClientConnectionFactory { +public final class NettyClientConnectionFactory { private final ConnectionPoolConfig connPoolConfig; private final ChannelInitializer channelInitializer; - private final Counter unresolvedDiscoveryHost; - - private static final Logger LOGGER = LoggerFactory.getLogger(NettyClientConnectionFactory.class); - NettyClientConnectionFactory(final ConnectionPoolConfig connPoolConfig, final ChannelInitializer channelInitializer) { this.connPoolConfig = connPoolConfig; this.channelInitializer = channelInitializer; - this.unresolvedDiscoveryHost = SpectatorUtils.newCounter("unresolvedDiscoveryHost", - connPoolConfig.getOriginName() == null ? "unknownOrigin" : connPoolConfig.getOriginName()); } - public ChannelFuture connect(final EventLoop eventLoop, String host, final int port, CurrentPassport passport) { - InetSocketAddress socketAddress = new InetSocketAddress(host, port); - if (socketAddress.isUnresolved()) { - LOGGER.warn("NettyClientConnectionFactory got an unresolved address, host: {}, port: {}", host, port); - unresolvedDiscoveryHost.increment(); + public ChannelFuture connect(final EventLoop eventLoop, SocketAddress socketAddress, CurrentPassport passport) { + Objects.requireNonNull(socketAddress, "socketAddress"); + if (socketAddress instanceof InetSocketAddress) { + // This should be checked by the ClientConnectionManager + assert !((InetSocketAddress) socketAddress).isUnresolved() : socketAddress; } - final Bootstrap bootstrap = new Bootstrap() .channel(Server.defaultOutboundChannelType.get()) .handler(channelInitializer) @@ -73,5 +63,4 @@ public ChannelFuture connect(final EventLoop eventLoop, String host, final int p .remoteAddress(socketAddress); return bootstrap.connect(); } - } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PerServerConnectionPool.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PerServerConnectionPool.java index fd78b1b3..420fca3a 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PerServerConnectionPool.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PerServerConnectionPool.java @@ -16,30 +16,28 @@ package com.netflix.zuul.netty.connectionpool; -import com.google.common.base.Strings; -import com.netflix.appinfo.InstanceInfo; import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.ServerStats; -import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Timer; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.exception.OutboundErrorType; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; -import com.netflix.zuul.stats.Timing; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoop; import io.netty.util.concurrent.Promise; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Deque; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * User: michaels@netflix.com @@ -48,17 +46,18 @@ */ public class PerServerConnectionPool implements IConnectionPool { - private ConcurrentHashMap> connectionsPerEventLoop = new ConcurrentHashMap<>(); + private static final Logger LOG = LoggerFactory.getLogger(PerServerConnectionPool.class); + + private final ConcurrentHashMap> connectionsPerEventLoop = + new ConcurrentHashMap<>(); - private final Server server; - private final ServerStats stats; - private final InstanceInfo instanceInfo; + private final DiscoveryResult server; + private final SocketAddress serverAddr; private final NettyClientConnectionFactory connectionFactory; private final PooledConnectionFactory pooledConnectionFactory; private final ConnectionPoolConfig config; private final IClientConfig niwsClientConfig; - private final Counter createNewConnCounter; private final Counter createConnSucceededCounter; private final Counter createConnFailedCounter; @@ -77,26 +76,25 @@ public class PerServerConnectionPool implements IConnectionPool */ private final AtomicInteger connCreationsInProgress; - private static final Logger LOG = LoggerFactory.getLogger(PerServerConnectionPool.class); - - - public PerServerConnectionPool(Server server, ServerStats stats, InstanceInfo instanceInfo, - NettyClientConnectionFactory connectionFactory, - PooledConnectionFactory pooledConnectionFactory, - ConnectionPoolConfig config, - IClientConfig niwsClientConfig, - Counter createNewConnCounter, - Counter createConnSucceededCounter, - Counter createConnFailedCounter, - Counter requestConnCounter, Counter reuseConnCounter, - Counter connTakenFromPoolIsNotOpen, - Counter maxConnsPerHostExceededCounter, - Timer connEstablishTimer, - AtomicInteger connsInPool, AtomicInteger connsInUse) - { + public PerServerConnectionPool( + DiscoveryResult server, + SocketAddress serverAddr, + NettyClientConnectionFactory connectionFactory, + PooledConnectionFactory pooledConnectionFactory, + ConnectionPoolConfig config, + IClientConfig niwsClientConfig, + Counter createNewConnCounter, + Counter createConnSucceededCounter, + Counter createConnFailedCounter, + Counter requestConnCounter, Counter reuseConnCounter, + Counter connTakenFromPoolIsNotOpen, + Counter maxConnsPerHostExceededCounter, + Timer connEstablishTimer, + AtomicInteger connsInPool, + AtomicInteger connsInUse) { this.server = server; - this.stats = stats; - this.instanceInfo = instanceInfo; + // Note: child classes can sometimes connect to different addresses than + this.serverAddr = Objects.requireNonNull(serverAddr, "serverAddr"); this.connectionFactory = connectionFactory; this.pooledConnectionFactory = pooledConnectionFactory; this.config = config; @@ -126,21 +124,14 @@ public IClientConfig getNiwsClientConfig() return niwsClientConfig; } - public Server getServer() - { - return server; - } - @Override public boolean isAvailable() { return true; } - /** function to run when a connection is acquired before returning it to caller. */ - private void onAcquire(final PooledConnection conn, String httpMethod, String uriStr, - int attemptNum, CurrentPassport passport) + private void onAcquire(final PooledConnection conn, CurrentPassport passport) { passport.setOnChannel(conn.getChannel()); removeIdleStateHandler(conn); @@ -154,12 +145,10 @@ protected void removeIdleStateHandler(PooledConnection conn) { } @Override - public Promise acquire(EventLoop eventLoop, Object key, String httpMethod, String uri, - int attemptNum, CurrentPassport passport, - AtomicReference selectedHostAddr) - { + public Promise acquire( + EventLoop eventLoop, CurrentPassport passport, AtomicReference selectedHostAddr) { requestConnCounter.increment(); - stats.incrementActiveRequestsCount(); + server.incrementActiveRequestsCount(); Promise promise = eventLoop.newPromise(); @@ -170,13 +159,13 @@ public Promise acquire(EventLoop eventLoop, Object key, String conn.startRequestTimer(); conn.incrementUsageCount(); conn.getChannel().read(); - onAcquire(conn, httpMethod, uri, attemptNum, passport); + onAcquire(conn, passport); initPooledConnection(conn, promise); - selectedHostAddr.set(getHostFromServer(conn.getServer())); + selectedHostAddr.set(getSelectedHostString(serverAddr)); } else { // connection pool empty, create new connection using client connection factory. - tryMakingNewConnection(eventLoop, promise, httpMethod, uri, attemptNum, passport, selectedHostAddr); + tryMakingNewConnection(eventLoop, promise, passport, selectedHostAddr); } return promise; @@ -228,13 +217,12 @@ protected Deque getPoolForEventLoop(EventLoop eventLoop) return pool; } - protected void tryMakingNewConnection(final EventLoop eventLoop, final Promise promise, - final String httpMethod, final String uri, final int attemptNum, - final CurrentPassport passport, final AtomicReference selectedHostAddr) - { + protected void tryMakingNewConnection( + EventLoop eventLoop, Promise promise, CurrentPassport passport, + AtomicReference selectedHostAddr) { // Enforce MaxConnectionsPerHost config. int maxConnectionsPerHost = config.maxConnectionsPerHost(); - int openAndOpeningConnectionCount = stats.getOpenConnectionsCount() + connCreationsInProgress.get(); + int openAndOpeningConnectionCount = server.getOpenConnectionsCount() + connCreationsInProgress.get(); if (maxConnectionsPerHost != -1 && openAndOpeningConnectionCount >= maxConnectionsPerHost) { maxConnsPerHostExceededCounter.increment(); promise.setFailure(new OriginConnectException( @@ -243,33 +231,28 @@ protected void tryMakingNewConnection(final EventLoop eventLoop, final Promise

callerPromise, - final String httpMethod, - final String uri, - final int attemptNum, - final CurrentPassport passport) - { + protected void handleConnectCompletion( + ChannelFuture cf, Promise callerPromise, CurrentPassport passport) { connCreationsInProgress.decrementAndGet(); if (cf.isSuccess()) { passport.add(PassportState.ORIGIN_CH_CONNECTED); - stats.incrementOpenConnectionsCount(); + server.incrementOpenConnectionsCount(); createConnSucceededCounter.increment(); connsInUse.incrementAndGet(); - createConnection(cf, callerPromise, httpMethod, uri, attemptNum, passport); + createConnection(cf, callerPromise, passport); } else { - stats.incrementSuccessiveConnectionFailureCount(); - stats.addToFailureCount(); - stats.decrementActiveRequestsCount(); + server.incrementSuccessiveConnectionFailureCount(); + server.addToFailureCount(); + server.decrementActiveRequestsCount(); createConnFailedCounter.increment(); callerPromise.setFailure(new OriginConnectException(cf.cause().getMessage(), OutboundErrorType.CONNECT_ERROR)); } } - protected void createConnection(ChannelFuture cf, Promise callerPromise, String httpMethod, String uri, - int attemptNum, CurrentPassport passport) { + protected void createConnection( + ChannelFuture cf, Promise callerPromise, CurrentPassport passport) { final PooledConnection conn = pooledConnectionFactory.create(cf.channel()); conn.incrementUsageCount(); conn.startRequestTimer(); conn.getChannel().read(); - onAcquire(conn, httpMethod, uri, attemptNum, passport); + onAcquire(conn, passport); callerPromise.setSuccess(conn); } @@ -446,4 +392,14 @@ public int getConnsInUse() { return connsInUse.get(); } + @Nullable + private static InetAddress getSelectedHostString(SocketAddress addr) { + if (addr instanceof InetSocketAddress) { + return ((InetSocketAddress) addr).getAddress(); + } else { + // If it's some other kind of address, just set it to empty + return null; + } + } + } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PooledConnection.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PooledConnection.java index ab610d39..a271f47e 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PooledConnection.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PooledConnection.java @@ -16,16 +16,15 @@ package com.netflix.zuul.netty.connectionpool; -import com.netflix.appinfo.InstanceInfo; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.ServerStats; import com.netflix.spectator.api.Counter; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.passport.CurrentPassport; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelPipeline; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.util.AttributeKey; +import java.time.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,11 +38,9 @@ public class PooledConnection { protected static final AttributeKey CHANNEL_ATTR = AttributeKey.newInstance("_pooled_connection"); public static final String READ_TIMEOUT_HANDLER_NAME = "readTimeoutHandler"; - private final Server server; + private final DiscoveryResult server; private final Channel channel; private final ClientChannelManager channelManager; - private final InstanceInfo serverKey; - private final ServerStats serverStats; private final long creationTS; private final Counter closeConnCounter; private final Counter closeWrtBusyConnCounter; @@ -72,17 +69,13 @@ public enum ConnectionState { private boolean shouldClose = false; private boolean released = false; - public PooledConnection(final Channel channel, final Server server, final ClientChannelManager channelManager, - final InstanceInfo serverKey, - final ServerStats serverStats, + public PooledConnection(final Channel channel, final DiscoveryResult server, final ClientChannelManager channelManager, final Counter closeConnCounter, final Counter closeWrtBusyConnCounter) { this.channel = channel; this.server = server; this.channelManager = channelManager; - this.serverKey = serverKey; - this.serverStats = serverStats; this.creationTS = System.currentTimeMillis(); this.closeConnCounter = closeConnCounter; this.closeWrtBusyConnCounter = closeWrtBusyConnCounter; @@ -113,7 +106,7 @@ public ConnectionPoolConfig getConfig() return this.channelManager.getConfig(); } - public Server getServer() + public DiscoveryResult getServer() { return server; } @@ -122,10 +115,6 @@ public Channel getChannel() { return channel; } - public InstanceInfo getServerKey() { - return serverKey; - } - public long getUsageCount() { return usageCount; @@ -144,17 +133,13 @@ public long getAgeInMillis() { return System.currentTimeMillis() - creationTS; } - public ServerStats getServerStats() { - return serverStats; - } - public void startRequestTimer() { reqStartTime = System.nanoTime(); } public long stopRequestTimer() { final long responseTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - reqStartTime); - serverStats.noteResponseTime(responseTime); + server.noteResponseTime(responseTime); return responseTime; } @@ -183,16 +168,14 @@ public void flagShouldClose() } public ChannelFuture close() { - final ServerStats stats = getServerStats(); - stats.decrementOpenConnectionsCount(); + server.decrementOpenConnectionsCount(); closeConnCounter.increment(); return channel.close(); } public void updateServerStats() { - final ServerStats stats = getServerStats(); - stats.decrementOpenConnectionsCount(); - stats.close(); + server.decrementOpenConnectionsCount(); + server.stopPublishingStats(); } public ChannelFuture closeAndRemoveFromPool() @@ -241,9 +224,10 @@ private void removeHandlerFromPipeline(String handlerName, ChannelPipeline pipel } } - public void startReadTimeoutHandler(int readTimeout) + public void startReadTimeoutHandler(Duration readTimeout) { - channel.pipeline().addBefore("originNettyLogger", READ_TIMEOUT_HANDLER_NAME, new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS)); + channel.pipeline().addBefore("originNettyLogger", READ_TIMEOUT_HANDLER_NAME, + new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS)); } @@ -252,7 +236,6 @@ public String toString() { return "PooledConnection{" + "channel=" + channel + - ", serverKey=" + serverKey + ", usageCount=" + usageCount + '}'; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/RequestStat.java b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/RequestStat.java index 46f3dc66..d9d64d66 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/RequestStat.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/RequestStat.java @@ -16,7 +16,7 @@ package com.netflix.zuul.netty.connectionpool; -import com.netflix.loadbalancer.Server; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.exception.ErrorType; @@ -41,7 +41,7 @@ static RequestStat getFromSessionContext(SessionContext context) return (RequestStat) context.get(SESSION_CONTEXT_KEY); } - RequestStat server(Server server); + RequestStat server(DiscoveryResult server); boolean isFinished(); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/BaseZuulFilterRunner.java b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/BaseZuulFilterRunner.java index 116a7051..96ac421b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/BaseZuulFilterRunner.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/BaseZuulFilterRunner.java @@ -36,14 +36,11 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpContent; import io.perfmark.Link; -import io.perfmark.PerfMark; +import io.perfmark.TaskCloseable; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.atomic.AtomicReferenceArray; -import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observer; -import rx.functions.Action; import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; @@ -60,6 +57,10 @@ import static com.netflix.zuul.context.CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT; import static com.netflix.zuul.filters.FilterType.ENDPOINT; import static com.netflix.zuul.filters.FilterType.INBOUND; +import static io.perfmark.PerfMark.attachTag; +import static io.perfmark.PerfMark.linkIn; +import static io.perfmark.PerfMark.linkOut; +import static io.perfmark.PerfMark.traceTask; /** * Subclasses of this class are supposed to be thread safe and hence should not have any non final member variables @@ -73,7 +74,7 @@ public abstract class BaseZuulFilterRunner s.getClass().getSimpleName() + ".invokeNextStageChunk")){ addPerfMarkTags(zuulMesg); nextStage.filter(zuulMesg, chunk); - } finally { - PerfMark.stopTask(getClass().getName(), "invokeNextStageChunk"); } } else { //Next stage is Netty channel handler - PerfMark.startTask(getClass().getName(), "fireChannelReadChunk"); - try { + try (TaskCloseable ignored = + traceTask(this, s -> s.getClass().getSimpleName() + ".fireChannelReadChunk")) { addPerfMarkTags(zuulMesg); getChannelHandlerContext(zuulMesg).fireChannelRead(chunk); - } finally { - PerfMark.stopTask(getClass().getName(), "fireChannelReadChunk"); } } } protected final void invokeNextStage(final O zuulMesg) { if (nextStage != null) { - PerfMark.startTask(getClass().getName(), "invokeNextStage"); - try { + try (TaskCloseable ignored = + traceTask(this, s -> s.getClass().getSimpleName() + ".invokeNextStage")) { addPerfMarkTags(zuulMesg); nextStage.filter(zuulMesg); - } finally { - PerfMark.stopTask(getClass().getName(), "invokeNextStage"); } } else { //Next stage is Netty channel handler - PerfMark.startTask(getClass().getName(), "fireChannelRead"); - try { + try (TaskCloseable ignored = + traceTask(this, s -> s.getClass().getSimpleName() + ".fireChannelRead")) { addPerfMarkTags(zuulMesg); getChannelHandlerContext(zuulMesg).fireChannelRead(zuulMesg); - } finally { - PerfMark.stopTask(getClass().getName(), "fireChannelRead"); } } } @@ -166,14 +159,15 @@ protected final void addPerfMarkTags(ZuulMessage inMesg) { req = (HttpRequestInfo) inMesg; } if (inMesg instanceof HttpResponseMessage) { - req = ((HttpResponseMessage) inMesg).getOutboundRequest(); - PerfMark.attachTag("statuscode", ((HttpResponseMessage) inMesg).getStatus()); + HttpResponseMessage msg = (HttpResponseMessage) inMesg; + req = msg.getOutboundRequest(); + attachTag("statuscode", msg.getStatus()); } if (req != null) { - PerfMark.attachTag("path", req.getPath()); - PerfMark.attachTag("originalhost", req.getOriginalHost()); + attachTag("path", req, HttpRequestInfo::getPath); + attachTag("originalhost", req, HttpRequestInfo::getOriginalHost); } - PerfMark.attachTag("uuid", inMesg.getContext().getUUID()); + attachTag("uuid", inMesg, m -> m.getContext().getUUID()); } protected final O filter(final ZuulFilter filter, final I inMesg) { @@ -181,8 +175,7 @@ protected final O filter(final ZuulFilter filter, final I inMesg) { final ZuulMessage snapshot = inMesg.getContext().debugRouting() ? inMesg.clone() : null; FilterChainResumer resumer = null; - PerfMark.startTask(filter.filterName(), "filter"); - try { + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".filter")) { addPerfMarkTags(inMesg); ExecutionStatus filterRunStatus = null; if (filter.filterType() == INBOUND && inMesg.getContext().shouldSendErrorResponse()) { @@ -190,13 +183,11 @@ protected final O filter(final ZuulFilter filter, final I inMesg) { filterRunStatus = SKIPPED; } - PerfMark.startTask(filter.filterName(), "shouldSkipFilter"); - try { + ; + try (TaskCloseable ignored2 = traceTask(filter, f -> f.filterName() + ".shouldSkipFilter")){ if (shouldSkipFilter(inMesg, filter)) { filterRunStatus = SKIPPED; } - } finally { - PerfMark.stopTask(filter.filterName(), "shouldSkipFilter"); } if (filter.isDisabled()) { @@ -210,7 +201,7 @@ protected final O filter(final ZuulFilter filter, final I inMesg) { if (!isMessageBodyReadyForFilter(filter, inMesg)) { setFilterAwaitingBody(inMesg, true); - LOG.debug("Filter {} waiting for body, UUID {}", filter.filterName(), inMesg.getContext().getUUID()); + logger.debug("Filter {} waiting for body, UUID {}", filter.filterName(), inMesg.getContext().getUUID()); return null; //wait for whole body to be buffered } setFilterAwaitingBody(inMesg, false); @@ -225,30 +216,24 @@ protected final O filter(final ZuulFilter filter, final I inMesg) { if (filter.getSyncType() == FilterSyncType.SYNC) { final SyncZuulFilter syncFilter = (SyncZuulFilter) filter; final O outMesg; - PerfMark.startTask(filter.filterName(), "apply"); - try { + try (TaskCloseable ignored2 = traceTask(filter, f -> f.filterName() + ".apply")) { addPerfMarkTags(inMesg); outMesg = syncFilter.apply(inMesg); - } finally { - PerfMark.stopTask(filter.filterName(), "apply"); } recordFilterCompletion(SUCCESS, filter, startTime, inMesg, snapshot); return (outMesg != null) ? outMesg : filter.getDefaultOutput(inMesg); } // async filter - PerfMark.startTask(filter.filterName(), "applyAsync"); - try { - final Link nettyToSchedulerLink = PerfMark.linkOut(); + try (TaskCloseable ignored2 = traceTask(filter, f -> f.filterName() + ".applyAsync")){ + final Link nettyToSchedulerLink = linkOut(); filter.incrementConcurrency(); resumer = new FilterChainResumer(inMesg, filter, snapshot, startTime); filter.applyAsync(inMesg) .doOnSubscribe(() -> { - PerfMark.startTask(filter.filterName(), "onSubscribeAsync"); - try { - PerfMark.linkIn(nettyToSchedulerLink); - } finally { - PerfMark.stopTask(filter.filterName(), "onSubscribeAsync"); + try (TaskCloseable ignored3 = + traceTask(filter, f -> f.filterName() + ".onSubscribeAsync")) { + linkIn(nettyToSchedulerLink); } }) .doOnNext(resumer.onNextStarted(nettyToSchedulerLink)) @@ -257,8 +242,6 @@ protected final O filter(final ZuulFilter filter, final I inMesg) { .observeOn(Schedulers.from(getChannelHandlerContext(inMesg).executor())) .doOnUnsubscribe(resumer::decrementConcurrency) .subscribe(resumer); - } finally { - PerfMark.stopTask(filter.filterName(), "applyAsync"); } return null; //wait for the async filter to finish @@ -271,8 +254,6 @@ protected final O filter(final ZuulFilter filter, final I inMesg) { outMesg.finishBufferedBodyIfIncomplete(); recordFilterCompletion(FAILED, filter, startTime, inMesg, snapshot); return outMesg; - } finally { - PerfMark.stopTask(filter.filterName(), "filter"); } } @@ -315,10 +296,10 @@ protected void recordFilterError(final I inMesg, final ZuulFilter filter, final String errorMsg = "Filter Exception: filter=" + filter.filterName() + ", request-info=" + inMesg.getInfoForLogging() + ", msg=" + String.valueOf(t.getMessage()); if (t instanceof ZuulException && !((ZuulException) t).shouldLogAsError()) { - LOG.warn(errorMsg); + logger.warn(errorMsg); } else { - LOG.error(errorMsg, t); + logger.error(errorMsg, t); } // Store this filter error for possible future use. But we still continue with next filter in the chain. @@ -337,16 +318,20 @@ protected void recordFilterCompletion(final ExecutionStatus status, final ZuulFi final long execTimeNs = System.nanoTime() - startTime; final long execTimeMs = execTimeNs / 1_000_000L; if (execTimeMs >= FILTER_EXCESSIVE_EXEC_TIME.get()) { - LOG.warn("Filter {} took {} ms to complete! status = {}", filter.filterName(), execTimeMs, status.name()); + logger.warn("Filter {} took {} ms to complete! status = {}", filter.filterName(), execTimeMs, status.name()); } // Record the execution summary in context. switch (status) { case FAILED: - zuulCtx.addFilterExecutionSummary(filter.filterName(), FAILED.name(), execTimeMs); + if (logger.isDebugEnabled()) { + zuulCtx.addFilterExecutionSummary(filter.filterName(), FAILED.name(), execTimeMs); + } break; case SUCCESS: - zuulCtx.addFilterExecutionSummary(filter.filterName(), SUCCESS.name(), execTimeMs); + if (logger.isDebugEnabled()) { + zuulCtx.addFilterExecutionSummary(filter.filterName(), SUCCESS.name(), execTimeMs); + } if (startSnapshot != null) { //debugRouting == true Debug.addRoutingDebug(zuulCtx, "Filter {" + filter.filterName() + " TYPE:" + filter.filterType().toString() @@ -358,7 +343,7 @@ protected void recordFilterCompletion(final ExecutionStatus status, final ZuulFi break; } - LOG.debug("Filter {} completed with status {}, UUID {}", filter.filterName(), status.name(), + logger.debug("Filter {} completed with status {}, UUID {}", filter.filterName(), status.name(), zuulMesg.getContext().getUUID()); // Notify configured listener. usageNotifier.notify(filter, status); @@ -376,7 +361,7 @@ else if (zuulMesg instanceof HttpResponseMessage) { final String path = (zuulReq != null) ? zuulReq.getPathAndQuery() : "-"; final String method = (zuulReq != null) ? zuulReq.getMethod() : "-"; final String errMesg = "Error with filter: " + filterName + ", path: " + path + ", method: " + method; - LOG.error(errMesg, ex); + logger.error(errMesg, ex); getChannelHandlerContext(zuulMesg).fireExceptionCaught(ex); } @@ -423,34 +408,25 @@ void decrementConcurrency() { @Override public void onNext(O outMesg) { - boolean stopped = false; - PerfMark.startTask(filter.filterName(), "onNextAsync"); - try { - PerfMark.linkIn(onNextLinkOut.get()); + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".onNextAsync")) { + linkIn(onNextLinkOut.get()); addPerfMarkTags(inMesg); recordFilterCompletion(SUCCESS, filter, startTime, inMesg, snapshot); if (outMesg == null) { outMesg = filter.getDefaultOutput(inMesg); } - stopped = true; - PerfMark.stopTask(filter.filterName(), "onNextAsync"); resumeInBindingContext(outMesg, filter.filterName()); } catch (Exception e) { decrementConcurrency(); handleException(inMesg, filter.filterName(), e); - } finally { - if (!stopped) { - PerfMark.stopTask(filter.filterName(), "onNextAsync"); - } } } @Override public void onError(Throwable ex) { - PerfMark.startTask(filter.filterName(), "onErrorAsync"); - try { - PerfMark.linkIn(onErrorLinkOut.get()); + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".onErrorAsync")) { + linkIn(onErrorLinkOut.get()); decrementConcurrency(); recordFilterCompletion(FAILED, filter, startTime, inMesg, snapshot); final O outMesg = handleFilterException(inMesg, filter, ex); @@ -458,51 +434,41 @@ public void onError(Throwable ex) { } catch (Exception e) { handleException(inMesg, filter.filterName(), e); - } finally { - PerfMark.stopTask(filter.filterName(), "onErrorAsync"); } + } } @Override public void onCompleted() { - PerfMark.startTask(filter.filterName(), "onCompletedAsync"); - try { - PerfMark.linkIn(onCompletedLinkOut.get( )); + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".onCompletedAsync")) { + linkIn(onCompletedLinkOut.get()); decrementConcurrency(); - } finally { - PerfMark.stopTask(filter.filterName(), "onCompletedAsync"); } } private Action1 onNextStarted(Link onNextLinkIn) { return o -> { - PerfMark.startTask(filter.filterName(), "onNext"); - try { - PerfMark.linkIn(onNextLinkIn); - onNextLinkOut.compareAndSet(null, PerfMark.linkOut()); - } finally { - PerfMark.stopTask(filter.filterName(), "onNext"); } + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".onNext")) { + linkIn(onNextLinkIn); + onNextLinkOut.compareAndSet(null, linkOut()); + } }; } private Action1 onErrorStarted(Link onErrorLinkIn) { return t -> { - PerfMark.startTask(filter.filterName(), "onError"); - try { - PerfMark.linkIn(onErrorLinkIn); - onErrorLinkOut.compareAndSet(null, PerfMark.linkOut()); - } finally { - PerfMark.stopTask(filter.filterName(), "onError"); } + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".onError")) { + linkIn(onErrorLinkIn); + onErrorLinkOut.compareAndSet(null, linkOut()); + } }; } private Action0 onCompletedStarted(Link onCompletedLinkIn) { return () -> { - PerfMark.startTask(filter.filterName(), "onCompleted"); - try { - PerfMark.linkIn(onCompletedLinkIn); - onCompletedLinkOut.compareAndSet(null, PerfMark.linkOut()); - } finally { - PerfMark.stopTask(filter.filterName(), "onCompleted"); } + try (TaskCloseable ignored = traceTask(filter, f -> f.filterName() + ".onCompleted")) { + linkIn(onCompletedLinkIn); + onCompletedLinkOut.compareAndSet(null, linkOut()); + } }; } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulEndPointRunner.java b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulEndPointRunner.java index 28e3148f..2eb3fdbe 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulEndPointRunner.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulEndPointRunner.java @@ -35,6 +35,7 @@ import com.netflix.zuul.netty.server.MethodBinding; import io.netty.handler.codec.http.HttpContent; import io.perfmark.PerfMark; +import io.perfmark.TaskCloseable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,8 +85,7 @@ public void filter(final HttpRequestMessage zuulReq) { } final String endpointName = getEndPointName(zuulReq.getContext()); - PerfMark.startTask(getClass().getName(), "filter"); - try { + try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + ".filter")) { Preconditions.checkNotNull(zuulReq, "input message"); addPerfMarkTags(zuulReq); @@ -102,21 +102,16 @@ public void filter(final HttpRequestMessage zuulReq) { } catch (Exception ex) { handleException(zuulReq, endpointName, ex); - } finally { - PerfMark.stopTask(getClass().getName(), "filter"); } } @Override protected void resume(final HttpResponseMessage zuulMesg) { - PerfMark.startTask(getClass().getSimpleName(), "resume"); - try { + try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + ".resume")) { if (zuulMesg.getContext().isCancelled()) { return; } invokeNextStage(zuulMesg); - } finally { - PerfMark.stopTask(getClass().getSimpleName(), "resume"); } } @@ -128,8 +123,7 @@ public void filter(final HttpRequestMessage zuulReq, final HttpContent chunk) { } String endpointName = "-"; - PerfMark.startTask(getClass().getName(), "filterChunk"); - try { + try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + ".filterChunk")) { addPerfMarkTags(zuulReq); ZuulFilter endpoint = Preconditions.checkNotNull( getEndpoint(zuulReq), "endpoint"); @@ -153,8 +147,6 @@ public void filter(final HttpRequestMessage zuulReq, final HttpContent chunk) { } catch (Exception ex) { handleException(zuulReq, endpointName, ex); - } finally { - PerfMark.stopTask(getClass().getName(), "filterChunk"); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainHandler.java index b2759825..3f0ed080 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainHandler.java @@ -108,10 +108,6 @@ else if (evt instanceof RequestCancelledEvent) { fireEndpointFinish(true); ctx.close(); } - else if (evt instanceof HttpLifecycleChannelHandler.RejectedPipeliningEvent) { - sendResponse(FAILURE_CLIENT_PIPELINE_REJECT, 400, ctx); - } - super.userEventTriggered(ctx, evt); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainRunner.java b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainRunner.java index 88cee8ff..29833fff 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainRunner.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainRunner.java @@ -27,6 +27,7 @@ import io.netty.handler.codec.http.HttpContent; import io.perfmark.PerfMark; +import io.perfmark.TaskCloseable; import javax.annotation.concurrent.ThreadSafe; import java.util.concurrent.atomic.AtomicInteger; @@ -50,24 +51,18 @@ public ZuulFilterChainRunner(ZuulFilter[] zuulFilters, FilterUsageNotifier @Override public void filter(final T inMesg) { - PerfMark.startTask(getClass().getSimpleName(), "filter"); - try { + try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + ".filter")) { addPerfMarkTags(inMesg); runFilters(inMesg, initRunningFilterIndex(inMesg)); - } finally { - PerfMark.stopTask(getClass().getSimpleName(), "filter"); } } @Override protected void resume(final T inMesg) { - PerfMark.startTask(getClass().getSimpleName(), "resume"); - try { + try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + ".resume")) { final AtomicInteger runningFilterIdx = getRunningFilterIndex(inMesg); runningFilterIdx.incrementAndGet(); runFilters(inMesg, runningFilterIdx); - } finally { - PerfMark.stopTask(getClass().getSimpleName(), "resume"); } } @@ -100,8 +95,7 @@ private final void runFilters(final T mesg, final AtomicInteger runningFilterIdx @Override public void filter(T inMesg, HttpContent chunk) { String filterName = "-"; - PerfMark.startTask(getClass().getName(), "filterChunk"); - try { + try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + ".filterChunk")) { addPerfMarkTags(inMesg); Preconditions.checkNotNull(inMesg, "input message"); @@ -159,9 +153,6 @@ public void filter(T inMesg, HttpContent chunk) { } catch (Exception ex) { handleException(inMesg, filterName, ex); - } finally { - PerfMark.stopTask(getClass().getName(), "filterChunk"); } } - } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportLoggingHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportLoggingHandler.java index 44ce15a4..6444cb9c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportLoggingHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportLoggingHandler.java @@ -17,11 +17,14 @@ package com.netflix.zuul.netty.insights; import com.netflix.config.CachedDynamicLongProperty; +import com.netflix.netty.common.HttpLifecycleChannelHandler; +import com.netflix.netty.common.metrics.HttpMetricsChannelHandler; import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; +import com.netflix.zuul.monitoring.ConnCounter; import com.netflix.zuul.netty.ChannelUtils; import com.netflix.zuul.netty.server.ClientRequestReceiver; import com.netflix.zuul.niws.RequestAttempts; @@ -33,9 +36,6 @@ import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import com.netflix.netty.common.HttpLifecycleChannelHandler; -import com.netflix.netty.common.metrics.HttpMetricsChannelHandler; -import com.netflix.netty.common.metrics.ServerChannelMetrics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,7 +92,7 @@ private void logPassport(Channel channel) // Do some debug logging of the Passport. if (LOG.isDebugEnabled()) { LOG.debug("State after complete. " - + ", current-server-conns = " + ServerChannelMetrics.currentConnectionCountFromChannel(channel) + + ", current-server-conns = " + ConnCounter.from(channel).getCurrentActiveConns() + ", current-http-reqs = " + HttpMetricsChannelHandler.getInflightRequestCountFromChannel(channel) + ", status = " + (response == null ? getRequestId(channel, ctx) : response.getStatus()) + ", nfstatus = " + String.valueOf(StatusCategoryUtils.getStatusCategory(ctx)) diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateServerHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/insights/ServerStateHandler.java similarity index 61% rename from zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateServerHandler.java rename to zuul-core/src/main/java/com/netflix/zuul/netty/insights/ServerStateHandler.java index 9185c963..37cf164e 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateServerHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/insights/ServerStateHandler.java @@ -16,12 +16,10 @@ package com.netflix.zuul.netty.insights; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Registry; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; -import com.netflix.spectator.api.Registry; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelOutboundHandlerAdapter; @@ -33,62 +31,57 @@ /** - * User: Mike Smith - * Date: 9/24/16 - * Time: 2:41 PM + * User: Mike Smith Date: 9/24/16 Time: 2:41 PM */ -public final class PassportStateServerHandler { - private static final Logger LOG = LoggerFactory.getLogger(PassportStateServerHandler.class); +public final class ServerStateHandler { - private static Registry registry; + private static final Logger logger = LoggerFactory.getLogger(ServerStateHandler.class); - private static CurrentPassport passport(ChannelHandlerContext ctx) - { + private static CurrentPassport passport(ChannelHandlerContext ctx) { return CurrentPassport.fromChannel(ctx.channel()); } - public static void setRegistry(Registry registry) { - checkNotNull(registry, "registry"); - checkState( - PassportStateServerHandler.registry == null || PassportStateServerHandler.registry == registry, - "registry already set"); - PassportStateServerHandler.registry = registry; - } + public static final class InboundHandler extends ChannelInboundHandlerAdapter { - protected static void incrementExceptionCounter(Throwable throwable, String handler) { - checkState(PassportStateServerHandler.registry != null, "registry not set"); - registry.counter("server.connection.exception", - "handler", handler, - "id", throwable.getClass().getSimpleName()) - .increment(); - } + private final Registry registry; + private final Counter totalConnections; + private final Counter connectionClosed; + private final Counter connectionErrors; + + public InboundHandler(Registry registry, String metricId) { + this.registry = registry; + this.totalConnections = registry.counter("server.connections.connect", "id", metricId); + this.connectionClosed = registry.counter("server.connections.close", "id", metricId); + this.connectionErrors = registry.counter("server.connections.errors", "id", metricId); + } - public static final class InboundHandler extends ChannelInboundHandlerAdapter - { @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception - { + public void channelActive(ChannelHandlerContext ctx) throws Exception { + totalConnections.increment(); passport(ctx).add(PassportState.SERVER_CH_ACTIVE); + super.channelActive(ctx); } @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception - { + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + connectionClosed.increment(); passport(ctx).add(PassportState.SERVER_CH_INACTIVE); + super.channelInactive(ctx); } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception - { + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + connectionErrors.increment(); + registry.counter("server.connection.exception.inbound", + "handler", "ServerStateHandler.InboundHandler", + "id", cause.getClass().getSimpleName()) + .increment(); passport(ctx).add(PassportState.SERVER_CH_EXCEPTION); - if (cause instanceof Errors.NativeIoException) { - LOG.debug("PassportStateServerHandler Inbound NativeIoException " + cause); - incrementExceptionCounter(cause, "PassportStateServerHandler.Inbound"); - } else { - super.exceptionCaught(ctx, cause); - } + logger.info("Connection error on Inbound: {} ", cause); + + super.exceptionCaught(ctx, cause); } @Override @@ -104,29 +97,36 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } } - public static final class OutboundHandler extends ChannelOutboundHandlerAdapter - { + public static final class OutboundHandler extends ChannelOutboundHandlerAdapter { + + private final Registry registry; + + public OutboundHandler(Registry registry) { + this.registry = registry; + } + @Override - public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception - { + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { passport(ctx).add(PassportState.SERVER_CH_CLOSE); super.close(ctx, promise); } @Override - public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception - { + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { passport(ctx).add(PassportState.SERVER_CH_DISCONNECT); super.disconnect(ctx, promise); } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception - { + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { passport(ctx).add(PassportState.SERVER_CH_EXCEPTION); if (cause instanceof Errors.NativeIoException) { - LOG.debug("PassportStateServerHandler Outbound NativeIoException " + cause); - incrementExceptionCounter(cause, "PassportStateServerHandler.Outbound"); + logger.debug("PassportStateServerHandler Outbound NativeIoException " + cause); + registry.counter("server.connection.exception.outbound", + "handler", "ServerStateHandler.OutboundHandler", + "id", cause.getClass().getSimpleName()) + .increment(); + } else { super.exceptionCaught(ctx, cause); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseServerStartup.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseServerStartup.java index 1578c314..51dbba3c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseServerStartup.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseServerStartup.java @@ -30,9 +30,7 @@ import com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler; import com.netflix.netty.common.ssl.ServerSslConfig; import com.netflix.netty.common.status.ServerStatusManager; -import com.netflix.servo.DefaultMonitorRegistry; -import com.netflix.servo.monitor.BasicCounter; -import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; import com.netflix.zuul.FilterLoader; import com.netflix.zuul.FilterUsageNotifier; @@ -43,24 +41,23 @@ import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.ssl.SslContext; -import io.netty.util.DomainNameMapping; +import io.netty.util.AsyncMapping; import io.netty.util.concurrent.GlobalEventExecutor; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.Map; import javax.annotation.Nullable; +import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import java.util.Map; - public abstract class BaseServerStartup { protected static final Logger LOG = LoggerFactory.getLogger(BaseServerStartup.class); protected final ServerStatusManager serverStatusManager; protected final Registry registry; + @SuppressWarnings("unused") // force initialization protected final DirectMemoryMonitor directMemoryMonitor; protected final EventLoopGroupMetrics eventLoopGroupMetrics; protected final EurekaClient discoveryClient; @@ -71,7 +68,7 @@ public abstract class BaseServerStartup protected final FilterLoader filterLoader; protected final FilterUsageNotifier usageNotifier; - private Map> addrsToChannelInitializers; + private Map> addrsToChannelInitializers; private ClientConnectionsShutdown clientConnectionsShutdown; private Server server; @@ -102,7 +99,7 @@ public Server server() return server; } - @PostConstruct + @Inject public void init() throws Exception { ChannelGroup clientChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @@ -111,9 +108,8 @@ public void init() throws Exception addrsToChannelInitializers = chooseAddrsAndChannels(clientChannels); - directMemoryMonitor.init(); - server = new Server( + registry, serverStatusManager, addrsToChannelInitializers, clientConnectionsShutdown, @@ -131,7 +127,7 @@ protected Map choosePortsAndChannels(ChannelGroup c } @ForOverride - protected Map> chooseAddrsAndChannels(ChannelGroup clientChannels) { + protected Map> chooseAddrsAndChannels(ChannelGroup clientChannels) { @SuppressWarnings("unchecked") // Channel init map has the wrong generics and we can't fix without api breakage. Map> portMap = (Map>) (Map) choosePortsAndChannels(clientChannels); @@ -157,8 +153,7 @@ protected void addChannelDependencies( channelDeps.set(ZuulDependencyKeys.sessionCtxDecorator, sessionCtxDecorator); channelDeps.set(ZuulDependencyKeys.requestCompleteHandler, reqCompleteHandler); - final BasicCounter httpRequestReadTimeoutCounter = new BasicCounter(MonitorConfig.builder("server.http.request.read.timeout").build()); - DefaultMonitorRegistry.getInstance().register(httpRequestReadTimeoutCounter); + final Counter httpRequestReadTimeoutCounter = registry.counter("server.http.request.read.timeout"); channelDeps.set(ZuulDependencyKeys.httpRequestReadTimeoutCounter, httpRequestReadTimeoutCounter); channelDeps.set(ZuulDependencyKeys.filterLoader, filterLoader); channelDeps.set(ZuulDependencyKeys.filterUsageNotifier, usageNotifier); @@ -173,11 +168,6 @@ protected void addChannelDependencies( * First looks for a property specific to the named listen address of the form - * "server.${addrName}.${propertySuffix}". If none found, then looks for a server-wide property of the form - * "server.${propertySuffix}". If that is also not found, then returns the specified default value. - * - * @param listenAddressName - * @param propertySuffix - * @param defaultValue - * @return */ public static int chooseIntChannelProperty(String listenAddressName, String propertySuffix, int defaultValue) { String globalPropertyName = "server." + propertySuffix; @@ -302,10 +292,10 @@ protected void logPortConfigured(int port, ServerSslConfig serverSslConfig) { // TODO(carl-mastrangelo): remove this after 2.1.7 /** - * Use {@link #logAddrConfigured(SocketAddress, DomainNameMapping)} instead. + * Use {@link #logAddrConfigured(SocketAddress, AsyncMapping)} instead. */ @Deprecated - protected void logPortConfigured(int port, DomainNameMapping sniMapping) { + protected void logPortConfigured(int port, AsyncMapping sniMapping) { logAddrConfigured(new InetSocketAddress(port), sniMapping); } @@ -321,11 +311,16 @@ protected final void logAddrConfigured(SocketAddress socketAddress, @Nullable Se LOG.info(msg); } - protected final void logAddrConfigured(SocketAddress socketAddress, @Nullable DomainNameMapping sniMapping) { + protected final void logAddrConfigured( + SocketAddress socketAddress, @Nullable AsyncMapping sniMapping) { String msg = "Configured address: " + socketAddress; if (sniMapping != null) { - msg = msg + " with SNI config: " + sniMapping.asMap(); + msg = msg + " with SNI config: " + sniMapping; } LOG.info(msg); } + + protected final void logSecureAddrConfigured(SocketAddress socketAddress, @Nullable Object securityConfig) { + LOG.info("Configured address: {} with security config {}", socketAddress, securityConfig); + } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializer.java index dd18941a..c6e4c8ed 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializer.java @@ -16,6 +16,11 @@ package com.netflix.zuul.netty.server; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.netflix.zuul.passport.PassportState.FILTERS_INBOUND_END; +import static com.netflix.zuul.passport.PassportState.FILTERS_INBOUND_START; +import static com.netflix.zuul.passport.PassportState.FILTERS_OUTBOUND_END; +import static com.netflix.zuul.passport.PassportState.FILTERS_OUTBOUND_START; import com.netflix.config.CachedDynamicIntProperty; import com.netflix.netty.common.CloseOnIdleStateHandler; import com.netflix.netty.common.Http1ConnectionCloseHandler; @@ -31,12 +36,10 @@ import com.netflix.netty.common.metrics.HttpBodySizeRecordingChannelHandler; import com.netflix.netty.common.metrics.HttpMetricsChannelHandler; import com.netflix.netty.common.metrics.PerEventLoopMetricsChannelHandler; -import com.netflix.netty.common.metrics.ServerChannelMetrics; import com.netflix.netty.common.proxyprotocol.ElbProxyProtocolChannelHandler; import com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler; -import com.netflix.netty.common.status.ServerStatusManager; import com.netflix.netty.common.throttle.MaxInboundConnectionsHandler; -import com.netflix.servo.monitor.BasicCounter; +import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; import com.netflix.zuul.FilterLoader; import com.netflix.zuul.FilterUsageNotifier; @@ -54,7 +57,7 @@ import com.netflix.zuul.netty.filter.ZuulFilterChainRunner; import com.netflix.zuul.netty.insights.PassportLoggingHandler; import com.netflix.zuul.netty.insights.PassportStateHttpServerHandler; -import com.netflix.zuul.netty.insights.PassportStateServerHandler; +import com.netflix.zuul.netty.insights.ServerStateHandler; import com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; @@ -66,13 +69,9 @@ import io.netty.handler.logging.LoggingHandler; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.AttributeKey; - -import java.util.List; +import java.util.SortedSet; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.netflix.zuul.passport.PassportState.*; - /** * User: Mike Smith * Date: 3/5/16 @@ -102,6 +101,7 @@ public abstract class BaseZuulChannelInitializer extends ChannelInitializer ZuulFilterChainRunner(filters, filterUsageNotifier, filterRunner); } - public ZuulFilter [] getFilters(final ZuulFilter start, final ZuulFilter stop) { - final List zuulFilters = filterLoader.getFiltersByType(start.filterType()); - final ZuulFilter[] filters = new ZuulFilter[zuulFilters.size() + 2]; + @SuppressWarnings("unchecked") // For the conversion from getFiltersByType. It's not safe, sorry. + public ZuulFilter [] getFilters(ZuulFilter start, ZuulFilter stop) { + final SortedSet> zuulFilters = filterLoader.getFiltersByType(start.filterType()); + final ZuulFilter[] filters = new ZuulFilter[zuulFilters.size() + 2]; filters[0] = start; - for (int i=1, j=0; i < filters.length && j < zuulFilters.size(); i++,j++) { - filters[i] = zuulFilters.get(j); + int i = 1; + for (ZuulFilter filter : zuulFilters) { + // TODO(carl-mastrangelo): find some way to make this cast not needed. + filters[i++] = (ZuulFilter) filter; } filters[filters.length -1] = stop; return filters; diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientRequestReceiver.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientRequestReceiver.java index 66e96f0c..b03e76b9 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientRequestReceiver.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientRequestReceiver.java @@ -16,10 +16,20 @@ package com.netflix.zuul.netty.server; +import static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent; +import static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason; +import static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason.SESSION_COMPLETE; +import static com.netflix.zuul.netty.server.http2.Http2OrHttpHandler.PROTOCOL_NAME; + +import com.netflix.netty.common.HttpLifecycleChannelHandler; +import com.netflix.netty.common.SourceAddressChannelHandler; +import com.netflix.netty.common.ssl.SslHandshakeInfo; +import com.netflix.netty.common.throttle.RejectionUtils; +import com.netflix.spectator.api.Spectator; +import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.context.Debug; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.context.SessionContextDecorator; -import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.exception.ZuulException; import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.http.HttpQueryParams; @@ -30,7 +40,6 @@ import com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler; import com.netflix.zuul.passport.CurrentPassport; import com.netflix.zuul.passport.PassportState; -import com.netflix.zuul.stats.status.StatusCategory; import com.netflix.zuul.stats.status.StatusCategoryUtils; import com.netflix.zuul.stats.status.ZuulStatusCategory; import com.netflix.zuul.util.HttpUtils; @@ -41,24 +50,28 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.channel.unix.Errors; -import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.haproxy.HAProxyMessage; -import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultLastHttpContent; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; -import com.netflix.netty.common.SourceAddressChannelHandler; -import com.netflix.netty.common.ssl.SslHandshakeInfo; +import io.perfmark.PerfMark; +import io.perfmark.TaskCloseable; +import java.net.InetSocketAddress; import java.net.SocketAddress; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.List; import java.util.Map; - -import static com.netflix.zuul.netty.server.http2.Http2OrHttpHandler.PROTOCOL_NAME; -import static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent; -import static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason; -import static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason.SESSION_COMPLETE; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -98,7 +111,12 @@ public static boolean isLastContentReceivedForChannel(Channel ch) { @Override public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { + try (TaskCloseable ignore = PerfMark.traceTask("CRR.channelRead")) { + channelReadInternal(ctx, msg); + } + } + private void channelReadInternal(final ChannelHandlerContext ctx, Object msg) throws Exception { // Flag that we have now received the LastContent for this request from the client. // This is needed for ClientResponseReceiver to know whether it's yet safe to start writing // a response to the client channel. @@ -110,23 +128,25 @@ public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exce clientRequest = (HttpRequest) msg; zuulRequest = buildZuulHttpRequest(clientRequest, ctx); - handleExpect100Continue(ctx, clientRequest); // Handle invalid HTTP requests. if (clientRequest.decoderResult().isFailure()) { - String errorMsg = "Invalid http request. " - + "clientRequest = " + clientRequest.toString() - + ", uri = " + String.valueOf(clientRequest.uri()) - + ", info = " + ChannelUtils.channelInfoForLogging(ctx.channel()); - String causeMsg = String.valueOf(clientRequest.decoderResult().cause()); - final ZuulException ze = new ZuulException(errorMsg, causeMsg, true); - ze.setStatusCode(400); - - StatusCategoryUtils.setStatusCategory(zuulRequest.getContext(), + LOG.warn( + "Invalid http request. clientRequest = {} , uri = {}, info = {}", + clientRequest.toString(), + clientRequest.uri(), + ChannelUtils.channelInfoForLogging(ctx.channel()), + clientRequest.decoderResult().cause()); + StatusCategoryUtils.setStatusCategory( + zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST); - - zuulRequest.getContext().setError(ze); - zuulRequest.getContext().setShouldSendErrorResponse(true); + RejectionUtils.rejectByClosingConnection( + ctx, + ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST, + "decodefailure", + clientRequest, + /* injectedLatencyMillis= */ null); + return; } else if (zuulRequest.hasBody() && zuulRequest.getBodyLength() > zuulRequest.getMaxBodySize()) { String errorMsg = "Request too large. " + "clientRequest = " + clientRequest.toString() @@ -141,6 +161,9 @@ public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exce zuulRequest.getContext().setShouldSendErrorResponse(true); } + handleExpect100Continue(ctx, clientRequest); + + //Send the request down the filter pipeline ctx.fireChannelRead(zuulRequest); } @@ -177,11 +200,15 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } } - if (reason == CompleteReason.INACTIVE) { + if (reason == CompleteReason.INACTIVE && zuulRequest != null) { // Client closed connection prematurely. StatusCategoryUtils.setStatusCategory(zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_CANCELLED); } + if (reason == CompleteReason.PIPELINE_REJECT && zuulRequest != null) { + StatusCategoryUtils.setStatusCategory(zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_PIPELINE_REJECT); + } + if (reason != SESSION_COMPLETE && zuulRequest != null) { final SessionContext zuulCtx = zuulRequest.getContext(); if (clientRequest != null) { @@ -190,7 +217,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc // of response to the channel, which causes this CompleteEvent to fire before we have cleaned up state. But // thats ok, so don't log in that case. if (! "HTTP/2".equals(zuulRequest.getProtocol())) { - LOG.info("Client {} request UUID {} to {} completed with reason = {}, {}", clientRequest.method(), + LOG.debug("Client {} request UUID {} to {} completed with reason = {}, {}", clientRequest.method(), zuulCtx.getUUID(), clientRequest.uri(), reason.name(), ChannelUtils.channelInfoForLogging(ctx.channel())); } } @@ -202,13 +229,19 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc } } + if (zuulRequest == null) { + Spectator.globalRegistry() + .counter("zuul.client.complete.null", "reason", String.valueOf(reason)) + .increment(); + } + clientRequest = null; zuulRequest = null; } super.userEventTriggered(ctx, evt); - if (evt instanceof CompleteEvent) { + if (evt instanceof CompleteEvent) { final Channel channel = ctx.channel(); channel.attr(ATTR_ZUUL_REQ).set(null); channel.attr(ATTR_ZUUL_RESP).set(null); @@ -222,10 +255,11 @@ private static void dumpDebugInfo(final List debugInfo) { private void handleExpect100Continue(ChannelHandlerContext ctx, HttpRequest req) { if (HttpUtil.is100ContinueExpected(req)) { + PerfMark.event("CRR.handleExpect100Continue"); final ChannelFuture f = ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)); f.addListener((s) -> { if (! s.isSuccess()) { - throw new ZuulException( s.cause(), "Failed while writing 100-continue response", true); + throw new ZuulException(s.cause(), "Failed while writing 100-continue response", true); } }); // Remove the Expect: 100-Continue header from request as we don't want to proxy it downstream. @@ -235,7 +269,9 @@ private void handleExpect100Continue(ChannelHandlerContext ctx, HttpRequest req) } // Build a ZuulMessage from the netty request. - private HttpRequestMessage buildZuulHttpRequest(final HttpRequest nativeRequest, final ChannelHandlerContext clientCtx) { + private HttpRequestMessage buildZuulHttpRequest( + final HttpRequest nativeRequest, final ChannelHandlerContext clientCtx) { + PerfMark.attachTag("path", nativeRequest, HttpRequest::uri); // Setup the context for this request. final SessionContext context; if (decorator != null) { // Optionally decorate the context. @@ -243,6 +279,8 @@ private HttpRequestMessage buildZuulHttpRequest(final HttpRequest nativeRequest, // Store the netty channel in SessionContext. tempContext.set(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, clientCtx); context = decorator.decorate(tempContext); + // We expect the UUID is present after decoration + PerfMark.attachTag("uuid", context, SessionContext::getUUID); } else { context = new SessionContext(); @@ -256,6 +294,11 @@ private HttpRequestMessage buildZuulHttpRequest(final HttpRequest nativeRequest, final int port = channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).get(); final String serverName = channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_ADDRESS).get(); final SocketAddress clientDestinationAddress = channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get(); + final InetSocketAddress proxyProtocolDestinationAddress = + channel.attr(SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS).get(); + if (proxyProtocolDestinationAddress != null) { + context.set(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS, proxyProtocolDestinationAddress); + } // Store info about the SSL handshake if applicable, and choose the http scheme. String scheme = SCHEME_HTTP; @@ -337,26 +380,28 @@ public static HttpQueryParams copyQueryParams(final HttpRequest nativeRequest) { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof HttpResponse) { - promise.addListener((future) -> { - if (! future.isSuccess()) { - fireWriteError("response headers", future.cause(), ctx); - } - }); - super.write(ctx, msg, promise); - } - else if (msg instanceof HttpContent) { - promise.addListener((future) -> { - if (! future.isSuccess()) { - fireWriteError("response content", future.cause(), ctx); - } - }); - super.write(ctx, msg, promise); - } - else { - //should never happen - ReferenceCountUtil.release(msg); - throw new ZuulException("Attempt to write invalid content type to client: "+msg.getClass().getSimpleName(), true); + try (TaskCloseable ignored = PerfMark.traceTask("CRR.write")) { + if (msg instanceof HttpResponse) { + promise.addListener((future) -> { + if (! future.isSuccess()) { + fireWriteError("response headers", future.cause(), ctx); + } + }); + super.write(ctx, msg, promise); + } + else if (msg instanceof HttpContent) { + promise.addListener((future) -> { + if (! future.isSuccess()) { + fireWriteError("response content", future.cause(), ctx); + } + }); + super.write(ctx, msg, promise); + } + else { + //should never happen + ReferenceCountUtil.release(msg); + throw new ZuulException("Attempt to write invalid content type to client: "+msg.getClass().getSimpleName(), true); + } } } @@ -366,7 +411,7 @@ private void fireWriteError(String requestPart, Throwable cause, ChannelHandlerC if (cause instanceof java.nio.channels.ClosedChannelException || cause instanceof Errors.NativeIoException) { - LOG.info(errMesg + " - client connection is closed."); + LOG.debug(errMesg + " - client connection is closed."); if (zuulRequest != null) { zuulRequest.getContext().cancel(); StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_CANCELLED); @@ -378,4 +423,4 @@ private void fireWriteError(String requestPart, Throwable cause, ChannelHandlerC } } -} \ No newline at end of file +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientResponseWriter.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientResponseWriter.java index 7678df42..228f847f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientResponseWriter.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientResponseWriter.java @@ -247,7 +247,7 @@ else if (evt instanceof CompleteEvent) { } else { if (isHandlingRequest) { - LOG.warn("Received complete event while still handling the request. With reason: " + reason.name() + " -- " + + LOG.debug("Received complete event while still handling the request. With reason: " + reason.name() + " -- " + ChannelUtils.channelInfoForLogging(ctx.channel())); } ctx.close(); @@ -259,7 +259,7 @@ else if (evt instanceof IdleStateEvent) { LOG.debug("Received IdleStateEvent."); } else { - LOG.info("ClientResponseWriter Received event {}", evt); + LOG.debug("ClientResponseWriter Received event {}", evt); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/DirectMemoryMonitor.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/DirectMemoryMonitor.java index 0602edf3..065886be 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/DirectMemoryMonitor.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/DirectMemoryMonitor.java @@ -16,22 +16,20 @@ package com.netflix.zuul.netty.server; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.netflix.config.DynamicIntProperty; -import com.netflix.servo.DefaultMonitorRegistry; -import com.netflix.servo.monitor.LongGauge; -import com.netflix.servo.monitor.MonitorConfig; -import io.netty.util.internal.PlatformDependent; -import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.inject.Singleton; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.patterns.PolledMeter; import java.lang.reflect.Field; +import java.time.Duration; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * User: michaels@netflix.com @@ -39,8 +37,7 @@ * Time: 10:23 AM */ @Singleton -public final class DirectMemoryMonitor -{ +public final class DirectMemoryMonitor { private static final Logger LOG = LoggerFactory.getLogger(DirectMemoryMonitor.class); private static final String PROP_PREFIX = "zuul.directmemory"; private static final DynamicIntProperty TASK_DELAY_PROP = new DynamicIntProperty(PROP_PREFIX + ".task.delay", 10); @@ -89,65 +86,45 @@ public final class DirectMemoryMonitor reservedMemoryGetter = reservedMemory; } - private final LongGauge reservedMemoryGauge = - new LongGauge(MonitorConfig.builder(PROP_PREFIX + ".reserved").build()); - private final LongGauge maxMemoryGauge = new LongGauge(MonitorConfig.builder(PROP_PREFIX + ".max").build()); - // TODO(carl-mastrangelo): this should be passed in as a dependency, so it can be shutdown and waited on for // termination. - private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1); + private final ScheduledExecutorService service = + Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryBuilder().setDaemon(true).setNameFormat("dmm-%d").build()); - public DirectMemoryMonitor() { - DefaultMonitorRegistry.getInstance().register(reservedMemoryGauge); - DefaultMonitorRegistry.getInstance().register(maxMemoryGauge); - } - - @PostConstruct - public void init() { - if (directMemoryLimitGetter == null || reservedMemoryGetter == null) { - return; + @Inject + public DirectMemoryMonitor(Registry registry) { + if (reservedMemoryGetter != null) { + PolledMeter.using(registry) + .withName(PROP_PREFIX + ".reserved") + .withDelay(Duration.ofSeconds(TASK_DELAY_PROP.get())) + .scheduleOn(service) + .monitorValue(DirectMemoryMonitor.class, DirectMemoryMonitor::getReservedMemory); } - service.scheduleWithFixedDelay(new Task(), TASK_DELAY_PROP.get(), TASK_DELAY_PROP.get(), TimeUnit.SECONDS); - } - - public void stop() { - service.shutdown(); - } - - final class Task implements Runnable { - @Override - public void run() - { - try { - Current current = measure(); - if (current != null) { - LOG.debug("reservedMemory={}, maxMemory={}", current.reservedMemory, current.maxMemory); - reservedMemoryGauge.set(current.reservedMemory); - maxMemoryGauge.set(current.maxMemory); - } - } - catch (Throwable t) { - LOG.warn("Error in DirectMemoryMonitor task.", t); - } + if (directMemoryLimitGetter != null) { + PolledMeter.using(registry) + .withName(PROP_PREFIX + ".max") + .withDelay(Duration.ofSeconds(TASK_DELAY_PROP.get())) + .scheduleOn(service) + .monitorValue(DirectMemoryMonitor.class, DirectMemoryMonitor::getMaxMemory); } + } - private Current measure() { - try { - Current current = new Current(); - current.maxMemory = directMemoryLimitGetter.get(); - current.reservedMemory = reservedMemoryGetter.get(); - return current; - } - catch (Exception e) { - LOG.warn("Error measuring direct memory.", e); - return null; - } + private static double getReservedMemory(Object discard) { + try { + return reservedMemoryGetter.get(); + } catch (RuntimeException | Error e) { + LOG.warn("Error in DirectMemoryMonitor task.", e); } - + return -1; } - private static final class Current { - long maxMemory; - long reservedMemory; + private static double getMaxMemory(Object discard) { + try { + return directMemoryLimitGetter.get(); + } catch (RuntimeException | Error e) { + LOG.warn("Error in DirectMemoryMonitor task.", e); + } + return -1; } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/NamedSocketAddress.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/NamedSocketAddress.java new file mode 100644 index 00000000..8b9e8835 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/NamedSocketAddress.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.server; + +import java.net.SocketAddress; +import java.util.Objects; +import javax.annotation.CheckReturnValue; + +public final class NamedSocketAddress extends SocketAddress { + + private final String name; + private final SocketAddress delegate; + + public NamedSocketAddress(String name, SocketAddress delegate) { + this.name = Objects.requireNonNull(name); + this.delegate = Objects.requireNonNull(delegate); + } + + public String name() { + return name; + } + + public SocketAddress unwrap() { + return delegate; + } + + @CheckReturnValue + public NamedSocketAddress withNewSocket(SocketAddress delegate) { + return new NamedSocketAddress(this.name, delegate); + } + + @Override + public String toString() { + return "NamedSocketAddress{" + + "name='" + name + '\'' + + ", delegate=" + delegate + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NamedSocketAddress that = (NamedSocketAddress) o; + return Objects.equals(name, that.name) && Objects.equals(delegate, that.delegate); + } + + @Override + public int hashCode() { + return Objects.hash(name, delegate); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/OriginResponseReceiver.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/OriginResponseReceiver.java index 052e47f2..2d75f721 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/OriginResponseReceiver.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/OriginResponseReceiver.java @@ -34,9 +34,13 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.ssl.SslHandshakeCompletionEvent; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; +import io.perfmark.PerfMark; +import io.perfmark.TaskCloseable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +60,7 @@ public class OriginResponseReceiver extends ChannelDuplexHandler { private volatile ProxyEndpoint edgeProxy; private static final Logger LOG = LoggerFactory.getLogger(OriginResponseReceiver.class); + private static final AttributeKey SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE = AttributeKey.newInstance("_ssl_handshake_from_origin_throwable"); public static final String CHANNEL_HANDLER_NAME = "_origin_response_receiver"; public OriginResponseReceiver(final ProxyEndpoint edgeProxy) { @@ -66,8 +71,15 @@ public void unlinkFromClientRequest() { edgeProxy = null; } + @Override - public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { + public final void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { + try (TaskCloseable a = PerfMark.traceTask("ORR.channelRead")) { + channelReadInternal(ctx, msg); + } + } + + private void channelReadInternal(final ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpResponse) { if (edgeProxy != null) { edgeProxy.responseFromOrigin((HttpResponse) msg); @@ -115,6 +127,10 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc postCompleteHook(ctx, evt); } } + else if (evt instanceof SslHandshakeCompletionEvent && !((SslHandshakeCompletionEvent) evt).isSuccess()) { + Throwable cause = ((SslHandshakeCompletionEvent) evt).cause(); + ctx.channel().attr(SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE).set(cause); + } else if (evt instanceof IdleStateEvent) { if (edgeProxy != null) { LOG.error("Origin request received IDLE event: {}", ChannelUtils.channelInfoForLogging(ctx.channel())); @@ -173,7 +189,13 @@ private static String pathAndQueryString(HttpRequestMessage request) { } @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + try (TaskCloseable ignore = PerfMark.traceTask("ORR.writeInternal")) { + writeInternal(ctx, msg, promise); + } + } + + private void writeInternal(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (!ctx.channel().isActive()) { ReferenceCountUtil.release(msg); return; @@ -182,7 +204,15 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) if (msg instanceof HttpRequestMessage) { promise.addListener((future) -> { if (!future.isSuccess()) { - fireWriteError("request headers", future.cause(), ctx); + Throwable cause = ctx.channel().attr(SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE).get(); + if (cause != null) { + // Set the specific SSL handshake error if the handlers have already caught them + ctx.channel().attr(SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE).set(null); + fireWriteError("request headers", cause, ctx); + LOG.debug("SSLException is overridden by SSLHandshakeException caught in handler level. Original SSL exception message: ", future.cause()); + } else { + fireWriteError("request headers", future.cause(), ctx); + } } }); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/Server.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/Server.java index 1f71d000..887a89a2 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/Server.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/Server.java @@ -16,6 +16,8 @@ package com.netflix.zuul.netty.server; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.VisibleForTesting; import com.netflix.appinfo.InstanceInfo; import com.netflix.config.DynamicBooleanProperty; @@ -23,9 +25,20 @@ import com.netflix.netty.common.LeastConnsEventLoopChooserFactory; import com.netflix.netty.common.metrics.EventLoopGroupMetrics; import com.netflix.netty.common.status.ServerStatusManager; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Spectator; +import com.netflix.spectator.api.patterns.PolledMeter; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.monitoring.ConnCounter; +import com.netflix.zuul.monitoring.ConnTimer; import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufAllocatorMetric; +import io.netty.buffer.ByteBufAllocatorMetricProvider; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.DefaultSelectStrategyFactory; @@ -43,31 +56,27 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.AttributeKey; import io.netty.util.concurrent.DefaultEventExecutorChooserFactory; import io.netty.util.concurrent.EventExecutor; import io.netty.util.concurrent.EventExecutorChooserFactory; import io.netty.util.concurrent.ThreadPerTaskExecutor; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; - -import static com.google.common.base.Preconditions.checkNotNull; +import java.util.concurrent.atomic.AtomicReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -99,13 +108,21 @@ public class Server private static final DynamicBooleanProperty USE_LEASTCONNS_FOR_EVENTLOOPS = new DynamicBooleanProperty("zuul.server.eventloops.use_leastconns", false); + private static final DynamicBooleanProperty MANUAL_DISCOVERY_STATUS = + new DynamicBooleanProperty("zuul.server.netty.manual.discovery.status", true); + private final EventLoopGroupMetrics eventLoopGroupMetrics; private final Thread jvmShutdownHook = new Thread(this::stop, "Zuul-JVM-shutdown-hook"); + private final Registry registry; private ServerGroup serverGroup; private final ClientConnectionsShutdown clientConnectionsShutdown; private final ServerStatusManager serverStatusManager; - private final Map> addressesToInitializers; + private final Map> addressesToInitializers; + /** + * Unlike the above, the socket addresses in this map are the *bound* addresses, rather than the requested ones. + */ + private final Map addressesToChannels = new LinkedHashMap<>(); private final EventLoopConfig eventLoopConfig; /** @@ -116,7 +133,8 @@ public class Server public static final AtomicReference> defaultOutboundChannelType = new AtomicReference<>(); /** - * Use {@link #Server(ServerStatusManager, Map, ClientConnectionsShutdown, EventLoopGroupMetrics, EventLoopConfig)} + * Use {@link #Server(Registry, ServerStatusManager, Map, ClientConnectionsShutdown, EventLoopGroupMetrics, + * EventLoopConfig)} * instead. */ @Deprecated @@ -128,7 +146,8 @@ public Server(Map portsToChannelInitializers, Serve } /** - * Use {@link #Server(ServerStatusManager, Map, ClientConnectionsShutdown, EventLoopGroupMetrics, EventLoopConfig)} + * Use {@link #Server(Registry, ServerStatusManager, Map, ClientConnectionsShutdown, EventLoopGroupMetrics, + * EventLoopConfig)} * instead. */ @SuppressWarnings("unchecked") // Channel init map has the wrong generics and we can't fix without api breakage. @@ -136,15 +155,16 @@ public Server(Map portsToChannelInitializers, Serve public Server(Map portsToChannelInitializers, ServerStatusManager serverStatusManager, ClientConnectionsShutdown clientConnectionsShutdown, EventLoopGroupMetrics eventLoopGroupMetrics, EventLoopConfig eventLoopConfig) { - this(serverStatusManager, + this(Spectator.globalRegistry(), serverStatusManager, convertPortMap((Map>) (Map) portsToChannelInitializers), clientConnectionsShutdown, eventLoopGroupMetrics, eventLoopConfig); } - public Server(ServerStatusManager serverStatusManager, - Map> addressesToInitializers, + public Server(Registry registry, ServerStatusManager serverStatusManager, + Map> addressesToInitializers, ClientConnectionsShutdown clientConnectionsShutdown, EventLoopGroupMetrics eventLoopGroupMetrics, EventLoopConfig eventLoopConfig) { + this.registry = Objects.requireNonNull(registry); this.addressesToInitializers = Collections.unmodifiableMap(new LinkedHashMap<>(addressesToInitializers)); this.serverStatusManager = checkNotNull(serverStatusManager, "serverStatusManager"); this.clientConnectionsShutdown = checkNotNull(clientConnectionsShutdown, "clientConnectionsShutdown"); @@ -152,8 +172,7 @@ public Server(ServerStatusManager serverStatusManager, this.eventLoopGroupMetrics = checkNotNull(eventLoopGroupMetrics, "eventLoopGroupMetrics"); } - public void stop() - { + public void stop() { LOG.info("Shutting down Zuul."); serverGroup.stop(); @@ -167,7 +186,7 @@ public void stop() LOG.info("Completed zuul shutdown."); } - public void start(boolean sync) + public void start() { serverGroup = new ServerGroup( "Salamander", eventLoopConfig.acceptorCount(), eventLoopConfig.eventLoopCount(), eventLoopGroupMetrics); @@ -176,32 +195,31 @@ public void start(boolean sync) List allBindFutures = new ArrayList<>(addressesToInitializers.size()); // Setup each of the channel initializers on requested ports. - for (Map.Entry> entry + for (Map.Entry> entry : addressesToInitializers.entrySet()) { - ChannelFuture nettyServerFuture = setupServerBootstrap(entry.getKey(), entry.getValue()); - serverGroup.addListeningServer(nettyServerFuture.channel()); + NamedSocketAddress requestedNamedAddr = entry.getKey(); + ChannelFuture nettyServerFuture = setupServerBootstrap(requestedNamedAddr, entry.getValue()); + Channel chan = nettyServerFuture.channel(); + addressesToChannels.put(requestedNamedAddr.withNewSocket(chan.localAddress()), chan); allBindFutures.add(nettyServerFuture); } - - // Once all server bootstraps are successfully initialized, then bind to each port. - for (ChannelFuture f : allBindFutures) { - // Wait until the server socket is closed. - ChannelFuture cf = f.channel().closeFuture(); - if (sync) { - cf.sync(); - } - } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } - public final List getListeningAddresses() { + public final void awaitTermination() throws InterruptedException { + for (Channel chan : addressesToChannels.values()) { + chan.closeFuture().sync(); + } + } + + public final List getListeningAddresses() { if (serverGroup == null) { throw new IllegalStateException("Server has not been started"); } - return serverGroup.getListeningAddresses(); + return Collections.unmodifiableList(new ArrayList<>(addressesToChannels.keySet())); } @VisibleForTesting @@ -222,12 +240,12 @@ public void gracefullyShutdownConnections() } private ChannelFuture setupServerBootstrap( - SocketAddress listenAddress, ChannelInitializer channelInitializer) throws InterruptedException { + NamedSocketAddress listenAddress, ChannelInitializer channelInitializer) throws InterruptedException { ServerBootstrap serverBootstrap = new ServerBootstrap().group(serverGroup.clientToProxyBossPool, serverGroup.clientToProxyWorkerPool); // Choose socket options. - Map channelOptions = new HashMap<>(); + Map, Object> channelOptions = new HashMap<>(); channelOptions.put(ChannelOption.SO_BACKLOG, 128); channelOptions.put(ChannelOption.SO_LINGER, -1); channelOptions.put(ChannelOption.TCP_NODELAY, true); @@ -237,24 +255,44 @@ private ChannelFuture setupServerBootstrap( serverBootstrap.channel(serverGroup.channelType); // Apply socket options. - for (Map.Entry optionEntry : channelOptions.entrySet()) { - serverBootstrap = serverBootstrap.option(optionEntry.getKey(), optionEntry.getValue()); + for (Map.Entry, ?> optionEntry : channelOptions.entrySet()) { + serverBootstrap = serverBootstrap.option((ChannelOption) optionEntry.getKey(), optionEntry.getValue()); } // Apply transport specific socket options. - for (Map.Entry optionEntry : serverGroup.transportChannelOptions.entrySet()) { - serverBootstrap = serverBootstrap.option(optionEntry.getKey(), optionEntry.getValue()); + for (Map.Entry, ?> optionEntry : serverGroup.transportChannelOptions.entrySet()) { + serverBootstrap = serverBootstrap.option((ChannelOption) optionEntry.getKey(), optionEntry.getValue()); } + serverBootstrap.handler(new NewConnHandler()); serverBootstrap.childHandler(channelInitializer); serverBootstrap.validate(); LOG.info("Binding to : " + listenAddress); - // Flag status as UP just before binding to the port. - serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP); + if (MANUAL_DISCOVERY_STATUS.get()) { + // Flag status as UP just before binding to the port. + serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP); + } // Bind and start to accept incoming connections. - return serverBootstrap.bind(listenAddress).sync(); + ChannelFuture bindFuture = serverBootstrap.bind(listenAddress.unwrap()); + + ByteBufAllocator alloc = bindFuture.channel().alloc(); + if (alloc instanceof ByteBufAllocatorMetricProvider) { + ByteBufAllocatorMetric metrics = ((ByteBufAllocatorMetricProvider) alloc).metric(); + PolledMeter.using(registry).withId(registry.createId("zuul.nettybuffermem.live", "type", "heap")) + .monitorValue(metrics, ByteBufAllocatorMetric::usedHeapMemory); + PolledMeter.using(registry).withId(registry.createId("zuul.nettybuffermem.live", "type", "direct")) + .monitorValue(metrics, ByteBufAllocatorMetric::usedDirectMemory); + } + + try { + return bindFuture.sync(); + } catch (Exception e) { + // sync() sneakily throws a checked Exception, but doesn't declare it. This can happen if there is a bind + // failure, which is typically an IOException. Just chain it and rethrow. + throw new RuntimeException("Failed to bind on addr " + listenAddress, e); + } } /** @@ -279,16 +317,10 @@ private final class ServerGroup private EventLoopGroup clientToProxyBossPool; private EventLoopGroup clientToProxyWorkerPool; private Class channelType; - private Map transportChannelOptions; + private Map, ?> transportChannelOptions; private volatile boolean stopped = false; - private final Set nettyServers = new LinkedHashSet<>(); - - void addListeningServer(Channel channel) { - nettyServers.add(channel); - } - private ServerGroup(String name, int acceptorThreads, int workerThreads, EventLoopGroupMetrics eventLoopGroupMetrics) { this.name = name; this.acceptorThreads = acceptorThreads; @@ -304,17 +336,6 @@ public void uncaughtException(final Thread t, final Throwable e) { Runtime.getRuntime().addShutdownHook(jvmShutdownHook); } - synchronized List getListeningAddresses() { - if (stopped) { - return Collections.emptyList(); - } - List listeningAddresses = new ArrayList<>(nettyServers.size()); - for (Channel nettyServer : nettyServers) { - listeningAddresses.add(nettyServer.localAddress()); - } - return Collections.unmodifiableList(listeningAddresses); - } - private void initializeTransport() { // TODO - try our own impl of ChooserFactory that load-balances across the eventloops using leastconns algo? @@ -328,7 +349,7 @@ private void initializeTransport() ThreadFactory workerThreadFactory = new CategorizedThreadFactory(name + "-ClientToZuulWorker"); Executor workerExecutor = new ThreadPerTaskExecutor(workerThreadFactory); - Map extraOptions = new HashMap<>(); + Map, Object> extraOptions = new HashMap<>(); boolean useNio = FORCE_NIO.get(); if (!useNio && epollIsAvailable()) { channelType = EpollServerSocketChannel.class; @@ -383,13 +404,11 @@ synchronized private void stop() return; } - // TODO(carl-mastrangelo): shutdown the netty servers accepting new connections. - nettyServers.clear(); - - // Flag status as down. - // TODO - is this _only_ changing the local status? And therefore should we also implement a HealthCheckHandler - // that we can flag to return DOWN here (would that then update Discovery? or still be a delay?) - serverStatusManager.localStatus(InstanceInfo.InstanceStatus.DOWN); + if (MANUAL_DISCOVERY_STATUS.get()) { + // Flag status as down. + // that we can flag to return DOWN here (would that then update Discovery? or still be a delay?) + serverStatusManager.localStatus(InstanceInfo.InstanceStatus.DOWN); + } // Shutdown each of the client connections (blocks until complete). // NOTE: ClientConnectionsShutdown can also be configured to gracefully close connections when the @@ -417,6 +436,7 @@ synchronized private void stop() } catch (IllegalStateException e) { // This can happen if the VM is already shutting down LOG.debug("Failed to remove shutdown hook", e); + throw e; } stopped = true; @@ -424,12 +444,34 @@ synchronized private void stop() } } - static Map> convertPortMap( + /** + * Keys should be a short string usable in metrics. + */ + public static final AttributeKey CONN_DIMENSIONS = AttributeKey.newInstance("zuulconndimensions"); + + private final class NewConnHandler extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Long now = System.nanoTime(); + final Channel child = (Channel) msg; + child.attr(CONN_DIMENSIONS).set(Attrs.newInstance()); + ConnTimer timer = ConnTimer.install(child, registry, registry.createId("zuul.conn.client.timing")); + timer.record(now, "ACCEPT"); + ConnCounter.install(child, registry, registry.createId("zuul.conn.client.current")); + super.channelRead(ctx, msg); + } + } + + static Map> convertPortMap( Map> portsToChannelInitializers) { - Map> addrsToInitializers = + Map> addrsToInitializers = new LinkedHashMap<>(portsToChannelInitializers.size()); - for (Map.Entry> portToInitializer : portsToChannelInitializers.entrySet()){ - addrsToInitializers.put(new InetSocketAddress(portToInitializer.getKey()), portToInitializer.getValue()); + for (Map.Entry> portToInitializer : portsToChannelInitializers.entrySet()) { + int portNumber = portToInitializer.getKey(); + addrsToInitializers.put( + new NamedSocketAddress("port" + portNumber, new InetSocketAddress(portNumber)), + portToInitializer.getValue()); } return Collections.unmodifiableMap(addrsToInitializers); } @@ -467,4 +509,4 @@ private static boolean kqueueIsAvailable() { } return available; } -} \ No newline at end of file +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ZuulDependencyKeys.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ZuulDependencyKeys.java index e563d5a6..fce76ea8 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ZuulDependencyKeys.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ZuulDependencyKeys.java @@ -22,7 +22,7 @@ import com.netflix.netty.common.channel.config.ChannelConfigKey; import com.netflix.netty.common.metrics.EventLoopGroupMetrics; import com.netflix.netty.common.status.ServerStatusManager; -import com.netflix.servo.monitor.BasicCounter; +import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; import com.netflix.zuul.FilterLoader; import com.netflix.zuul.FilterUsageNotifier; @@ -45,7 +45,7 @@ public class ZuulDependencyKeys { public static final ChannelConfigKey registry = new ChannelConfigKey<>("registry"); public static final ChannelConfigKey sessionCtxDecorator = new ChannelConfigKey<>("sessionCtxDecorator"); public static final ChannelConfigKey requestCompleteHandler = new ChannelConfigKey<>("requestCompleteHandler"); - public static final ChannelConfigKey httpRequestReadTimeoutCounter = new ChannelConfigKey<>("httpRequestReadTimeoutCounter"); + public static final ChannelConfigKey httpRequestReadTimeoutCounter = new ChannelConfigKey<>("httpRequestReadTimeoutCounter"); public static final ChannelConfigKey filterLoader = new ChannelConfigKey<>("filterLoader"); public static final ChannelConfigKey filterUsageNotifier = new ChannelConfigKey<>("filterUsageNotifier"); public static final ChannelConfigKey discoveryClient = new ChannelConfigKey<>("discoveryClient"); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2Configuration.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2Configuration.java index 74d97b56..072e3598 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2Configuration.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2Configuration.java @@ -16,56 +16,33 @@ package com.netflix.zuul.netty.server.http2; -import com.netflix.config.DynamicBooleanProperty; import com.netflix.zuul.netty.ssl.SslContextFactory; import io.netty.handler.ssl.ApplicationProtocolConfig; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; - import javax.net.ssl.SSLException; public class Http2Configuration { - - private static final DynamicBooleanProperty HTTP2_DISABLED = - new DynamicBooleanProperty("zuul.server.http2.disabled", false); - - - /** - * Use {@link #configureSSL(SslContextFactory, String)} instead. - */ - @Deprecated - public static SslContext configureSSL(SslContextFactory sslContextFactory, int port) { - return configureSSL(sslContextFactory, String.valueOf(port)); - } - + public static SslContext configureSSL(SslContextFactory sslContextFactory, String metricId) { SslContextBuilder builder = sslContextFactory.createBuilderForServer(); - String[] supportedProtocol; - if (HTTP2_DISABLED.get()) { - supportedProtocol = new String[]{ApplicationProtocolNames.HTTP_1_1}; - } - else { - supportedProtocol = new String[]{ApplicationProtocolNames.HTTP_2, - ApplicationProtocolNames.HTTP_1_1}; - } - + String[] supportedProtocols = new String[]{ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1}; ApplicationProtocolConfig apn = new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, - supportedProtocol); + supportedProtocols); final SslContext sslContext; try { sslContext = builder .applicationProtocolConfig(apn) .build(); - } - catch (SSLException e) { + } catch (SSLException e) { throw new RuntimeException("Error configuring SslContext with ALPN!", e); } @@ -77,4 +54,28 @@ public static SslContext configureSSL(SslContextFactory sslContextFactory, Strin return sslContext; } + + /** + * This is meant to be use in cases where the server wishes not to advertise h2 as part of ALPN. + */ + public static SslContext configureSSLWithH2Disabled(SslContextFactory sslContextFactory, String host) { + + ApplicationProtocolConfig apn = new ApplicationProtocolConfig( + ApplicationProtocolConfig.Protocol.ALPN, + // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers. + ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, + // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers. + ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + ApplicationProtocolNames.HTTP_1_1); + final SslContext sslContext; + try { + sslContext = sslContextFactory.createBuilderForServer().applicationProtocolConfig(apn).build(); + } catch (SSLException e) { + throw new RuntimeException("Error configuring SslContext with ALPN!", e); + } + + sslContextFactory.enableSessionTickets(sslContext); + sslContextFactory.configureOpenSslStatsMetrics(sslContext, host); + return sslContext; + } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandler.java new file mode 100644 index 00000000..4e37d87e --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.server.http2; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http2.DefaultHttp2ResetFrame; +import io.netty.handler.codec.http2.Http2Error; +import java.util.List; + +/** + * This class is only suitable for use on HTTP/2 child channels. + */ +public final class Http2ContentLengthEnforcingHandler extends ChannelInboundHandlerAdapter { + private static final long UNSET_CONTENT_LENGTH = -1; + + private long expectedContentLength = UNSET_CONTENT_LENGTH; + + private long seenContentLength; + + /** + * This checks that the content length does what it says, preventing a client from causing Zuul to misinterpret the + * request. Because this class is meant to work in an HTTP/2 setting, the content length and transfer encoding + * checks are more semantics. In particular, this checks: + *

    + *
  • No duplicate Content length
  • + *
  • Content Length (if present) must always be greater than or equal to how much content has been seen
  • + *
  • Content Length (if present) must always be equal to how much content has been seen by the end
  • + *
  • Content Length cannot be present along with chunked transfer encoding.
  • + *
+ */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof HttpRequest) { + HttpRequest req = (HttpRequest) msg; + List lengthHeaders = req.headers().getAll(HttpHeaderNames.CONTENT_LENGTH); + if (lengthHeaders.size() > 1) { + ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR)); + return; + } else if (lengthHeaders.size() == 1) { + expectedContentLength = Long.parseLong(lengthHeaders.get(0)); + if (expectedContentLength < 0) { + // TODO(carl-mastrangelo): this is not right, but meh. Fix this to return a proper 400. + ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR)); + return; + } + } + if (hasContentLength() && HttpUtil.isTransferEncodingChunked(req)) { + // TODO(carl-mastrangelo): this is not right, but meh. Fix this to return a proper 400. + ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR)); + return; + } + } + if (msg instanceof HttpContent) { + ByteBuf content = ((HttpContent) msg).content(); + incrementSeenContent(content.readableBytes()); + if (hasContentLength() && seenContentLength > expectedContentLength) { + // TODO(carl-mastrangelo): this is not right, but meh. Fix this to return a proper 400. + ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR)); + return; + } + } + if (msg instanceof LastHttpContent) { + if (hasContentLength() && seenContentLength != expectedContentLength) { + // TODO(carl-mastrangelo): this is not right, but meh. Fix this to return a proper 400. + ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR)); + return; + } + } + super.channelRead(ctx, msg); + } + + private boolean hasContentLength() { + return expectedContentLength != UNSET_CONTENT_LENGTH; + } + + private void incrementSeenContent(int length) { + seenContentLength = Math.addExact(seenContentLength, length); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java index 833d6e57..7208da52 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java @@ -16,25 +16,27 @@ package com.netflix.zuul.netty.server.http2; +import static com.netflix.zuul.netty.server.BaseZuulChannelInitializer.HTTP_CODEC_HANDLER_NAME; + import com.netflix.netty.common.channel.config.ChannelConfig; import com.netflix.netty.common.channel.config.CommonChannelConfigKeys; import com.netflix.netty.common.http2.DynamicHttp2FrameLogger; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController; +import io.netty.handler.codec.http2.Http2Connection; import io.netty.handler.codec.http2.Http2FrameCodec; import io.netty.handler.codec.http2.Http2FrameCodecBuilder; import io.netty.handler.codec.http2.Http2MultiplexHandler; import io.netty.handler.codec.http2.Http2Settings; +import io.netty.handler.codec.http2.UniformStreamByteDistributor; import io.netty.handler.logging.LogLevel; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; import io.netty.util.AttributeKey; - import java.util.function.Consumer; -import static com.netflix.zuul.netty.server.BaseZuulChannelInitializer.HTTP_CODEC_HANDLER_NAME; - /** * Http2 Or Http Handler * @@ -95,6 +97,10 @@ private void configureHttp2(ChannelPipeline pipeline) { .initialSettings(settings) .validateHeaders(true) .build(); + Http2Connection conn = frameCodec.connection(); + // Use the uniform byte distributor until https://github.com/netty/netty/issues/10525 is fixed. + conn.remote().flowController( + new DefaultHttp2RemoteFlowController(conn, new UniformStreamByteDistributor(conn))); Http2MultiplexHandler multiplexHandler = new Http2MultiplexHandler(http2StreamHandler); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamInitializer.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamInitializer.java index a1691829..85c72930 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamInitializer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamInitializer.java @@ -16,9 +16,12 @@ package com.netflix.zuul.netty.server.http2; +import static com.netflix.zuul.netty.server.http2.Http2OrHttpHandler.PROTOCOL_NAME; import com.netflix.netty.common.Http2ConnectionCloseHandler; import com.netflix.netty.common.Http2ConnectionExpiryHandler; +import com.netflix.netty.common.SourceAddressChannelHandler; import com.netflix.netty.common.metrics.Http2MetricsChannelHandlers; +import com.netflix.netty.common.proxyprotocol.HAProxyMessageChannelHandler; import com.netflix.zuul.netty.server.BaseZuulChannelInitializer; import com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler; import io.netty.channel.Channel; @@ -28,13 +31,8 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec; import io.netty.util.AttributeKey; -import com.netflix.netty.common.SourceAddressChannelHandler; -import com.netflix.netty.common.proxyprotocol.ElbProxyProtocolChannelHandler; - import java.util.function.Consumer; -import static com.netflix.zuul.netty.server.http2.Http2OrHttpHandler.PROTOCOL_NAME; - /** * TODO - can this be done when we create the Http2StreamChannelBootstrap instead now? */ @@ -87,6 +85,7 @@ protected void addHttp2StreamSpecificHandlers(ChannelPipeline pipeline) pipeline.addLast("h2_downgrader", new Http2StreamFrameToHttpObjectCodec(true)); pipeline.addLast(http2StreamErrorHandler); pipeline.addLast(http2StreamHeaderCleaner); + pipeline.addLast(new Http2ContentLengthEnforcingHandler()); } protected void copyAttrsFromParentChannel(Channel parent, Channel child) @@ -94,17 +93,16 @@ protected void copyAttrsFromParentChannel(Channel parent, Channel child) AttributeKey[] attributesToCopy = { SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS, SourceAddressChannelHandler.ATTR_LOCAL_INET_ADDR, - SourceAddressChannelHandler.ATTR_LOCAL_PORT, SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS, SourceAddressChannelHandler.ATTR_SOURCE_INET_ADDR, - SourceAddressChannelHandler.ATTR_SOURCE_PORT, SourceAddressChannelHandler.ATTR_SERVER_LOCAL_ADDRESS, SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT, + SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS, PROTOCOL_NAME, SslHandshakeInfoHandler.ATTR_SSL_INFO, - ElbProxyProtocolChannelHandler.ATTR_HAPROXY_MESSAGE, - ElbProxyProtocolChannelHandler.ATTR_HAPROXY_VERSION, + HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE, + HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION, BaseZuulChannelInitializer.ATTR_CHANNEL_CONFIG }; diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushConnectionRegistry.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushConnectionRegistry.java index 030f440d..8a36d148 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushConnectionRegistry.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushConnectionRegistry.java @@ -15,13 +15,13 @@ */ package com.netflix.zuul.netty.server.push; -import com.google.inject.Inject; -import com.google.inject.Singleton; import java.security.SecureRandom; import java.util.Base64; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import javax.inject.Inject; +import javax.inject.Singleton; /** * Maintains client identity to web socket or SSE channel mapping. diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageFactory.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageFactory.java index 4b9b09e3..b4130b04 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageFactory.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageFactory.java @@ -40,7 +40,6 @@ public final void sendErrorAndClose(ChannelHandlerContext ctx, int statusCode, S /** * Message server sends to the client just before it force closes connection from its side - * @return */ protected abstract Object serverClosingConnectionMessage(int statusCode, String reasonText); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageSender.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageSender.java index ee61410b..5c8bfb90 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageSender.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageSender.java @@ -15,18 +15,34 @@ */ package com.netflix.zuul.netty.server.push; +import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; +import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; +import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; +import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED; +import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; +import static io.netty.handler.codec.http.HttpResponseStatus.NO_CONTENT; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + import com.google.common.base.Strings; -import com.google.inject.Inject; -import com.google.inject.Singleton; import io.netty.buffer.ByteBuf; -import io.netty.channel.*; -import io.netty.handler.codec.http.*; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpUtil; +import javax.inject.Inject; +import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; - /** * Serves "/push" URL that is used by the backend to POST push messages to a given Zuul instance. This URL handler * MUST BE accessible ONLY from RFC 1918 private internal network space (10.0.0.0 or 172.16.0.0) to guarantee that diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushProtocol.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushProtocol.java index cafd6d8e..7804d5b0 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushProtocol.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushProtocol.java @@ -134,7 +134,6 @@ public final void sendErrorAndClose(ChannelHandlerContext ctx, int statusCode, S public abstract Object goAwayMessage(); /** * Message server sends to the client just before it force closes connection from its side - * @return */ public abstract Object serverClosingConnectionMessage(int statusCode, String reasonText); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java index 1e4eb8f5..b2ab429c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java @@ -33,33 +33,29 @@ import io.netty.util.AttributeKey; import com.netflix.netty.common.SourceAddressChannelHandler; import com.netflix.netty.common.ssl.SslHandshakeInfo; -import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; -import javax.security.cert.X509Certificate; +import java.security.cert.X509Certificate; import java.nio.channels.ClosedChannelException; import java.security.cert.Certificate; /** * Stores info about the client and server's SSL certificates in the context, after a successful handshake. - * - * User: michaels@netflix.com - * Date: 3/29/16 - * Time: 10:48 AM + *

+ * User: michaels@netflix.com Date: 3/29/16 Time: 10:48 AM */ -public class SslHandshakeInfoHandler extends ChannelInboundHandlerAdapter -{ +public class SslHandshakeInfoHandler extends ChannelInboundHandlerAdapter { + public static final AttributeKey ATTR_SSL_INFO = AttributeKey.newInstance("_ssl_handshake_info"); - private static final Logger LOG = LoggerFactory.getLogger(SslHandshakeInfoHandler.class); + private static final Logger logger = LoggerFactory.getLogger(SslHandshakeInfoHandler.class); private final Registry spectatorRegistry; private final boolean isSSlFromIntermediary; - public SslHandshakeInfoHandler(@Nullable Registry spectatorRegistry, boolean isSSlFromIntermediary) - { + public SslHandshakeInfoHandler(Registry spectatorRegistry, boolean isSSlFromIntermediary) { this.spectatorRegistry = checkNotNull(spectatorRegistry); this.isSSlFromIntermediary = isSSlFromIntermediary; } @@ -71,8 +67,7 @@ public SslHandshakeInfoHandler(@Nullable Registry spectatorRegistry, boolean isS } @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception - { + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof SslHandshakeCompletionEvent) { try { SslHandshakeCompletionEvent sslEvent = (SslHandshakeCompletionEvent) evt; @@ -89,124 +84,91 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc X509Certificate peerCert = null; if ((clientAuth == ClientAuth.REQUIRE || clientAuth == ClientAuth.OPTIONAL) - && session.getPeerCertificateChain() != null && session.getPeerCertificateChain().length > 0) { - peerCert = session.getPeerCertificateChain()[0]; + && session.getPeerCertificates() != null + && session.getPeerCertificates().length > 0) { + peerCert = (X509Certificate) session.getPeerCertificates()[0]; } if (session.getLocalCertificates() != null && session.getLocalCertificates().length > 0) { serverCert = session.getLocalCertificates()[0]; } - SslHandshakeInfo info = new SslHandshakeInfo(isSSlFromIntermediary, session.getProtocol(), session.getCipherSuite(), clientAuth, serverCert, peerCert); + SslHandshakeInfo info = new SslHandshakeInfo(isSSlFromIntermediary, session.getProtocol(), + session.getCipherSuite(), clientAuth, serverCert, peerCert); ctx.channel().attr(ATTR_SSL_INFO).set(info); // Metrics. incrementCounters(sslEvent, info); - if (LOG.isDebugEnabled()) { - LOG.debug("Successful SSL Handshake: " + String.valueOf(info)); - } - else if (LOG.isInfoEnabled()) { - LOG.info("Successful SSL Handshake: protocol={}, ciphersuite={}, has_client_cert={}", info.getProtocol(), info.getCipherSuite(), info.getClientCertificate() != null); - } - } - else { + logger.debug("Successful SSL Handshake: {}", info); + } else { String clientIP = ctx.channel().attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get(); Throwable cause = sslEvent.cause(); PassportState passportState = CurrentPassport.fromChannel(ctx.channel()).getState(); if (cause instanceof ClosedChannelException && - (PassportState.SERVER_CH_INACTIVE.equals(passportState) || PassportState.SERVER_CH_IDLE_TIMEOUT.equals(passportState))) { + (PassportState.SERVER_CH_INACTIVE.equals(passportState) + || PassportState.SERVER_CH_IDLE_TIMEOUT.equals(passportState))) { // Either client closed the connection without/before having completed a handshake, or // the connection idle timed-out before handshake. // NOTE: we were seeing a lot of these in prod and can repro by just telnetting to port and then closing terminal // without sending anything. // So don't treat these as SSL handshake failures. - LOG.info("Client closed connection or it idle timed-out without doing an ssl handshake. " - + ", client_ip = " + String.valueOf(clientIP) + logger.debug("Client closed connection or it idle timed-out without doing an ssl handshake. " + + ", client_ip = " + clientIP + ", channel_info = " + ChannelUtils.channelInfoForLogging(ctx.channel())); - } - else if (cause instanceof SSLException && "handshake timed out".equals(cause.getMessage())) { - LOG.info("Client timed-out doing the ssl handshake. " - + ", client_ip = " + String.valueOf(clientIP) + } else if (cause instanceof SSLException && cause.getMessage().contains("handshake timed out")) { + logger.debug("Client timed-out doing the ssl handshake. " + + ", client_ip = " + clientIP + ", channel_info = " + ChannelUtils.channelInfoForLogging(ctx.channel())); - } - else if (cause instanceof SSLException + } else if (cause instanceof SSLException && cause.getMessage().contains("failure when writing TLS control frames")) { // This can happen if the ClientHello is sent followed by a RST packet, before we can respond. - LOG.info("Client terminated handshake early." + logger.debug("Client terminated handshake early." + ", client_ip = " + clientIP + ", channel_info = " + ChannelUtils.channelInfoForLogging(ctx.channel())); - } - else { - String msg = "Unsuccessful SSL Handshake: " + String.valueOf(sslEvent) - + ", client_ip = " + String.valueOf(clientIP) + } else { + String msg = "Unsuccessful SSL Handshake: " + sslEvent + + ", client_ip = " + clientIP + ", channel_info = " + ChannelUtils.channelInfoForLogging(ctx.channel()) - + ", error = " + String.valueOf(cause); - if (cause != null && cause instanceof ClosedChannelException) { - LOG.warn(msg); - } - else { - LOG.warn(msg, cause); + + ", error = " + cause; + if (cause instanceof ClosedChannelException) { + logger.debug(msg); + } else { + logger.debug(msg, cause); } incrementCounters(sslEvent, null); } - - // ### TESTING - -// SslHandler sslhandler = null; -// try { -// sslhandler = (SslHandler) ctx.channel().pipeline().get("ssl"); -// if (sslhandler != null) { -// SSLSession session = sslhandler.engine().getSession(); -// -// LOG.warn("SSL Handshake failure. id = " + String.valueOf(session.getId()) -// + ", protocol = " + String.valueOf(session.getProtocol()) -// + ", ciphersuite = " + String.valueOf(session.getCipherSuite())); -// } -// } -// catch (Exception e) { -// e.printStackTrace(); -// } - } - } - catch (Throwable e) { - LOG.warn("Error getting the SSL handshake info.", e); - } - finally { + } catch (Throwable e) { + logger.warn("Error getting the SSL handshake info.", e); + } finally { // Now remove this handler from the pipeline as no longer needed once the ssl handshake has completed. ctx.pipeline().remove(this); } - } - else if (evt instanceof SslCloseCompletionEvent) { + } else if (evt instanceof SslCloseCompletionEvent) { // TODO - increment a separate metric for this event? - } - else if (evt instanceof SniCompletionEvent) { - LOG.debug("SNI Parsing Complete: " + evt.toString()); + } else if (evt instanceof SniCompletionEvent) { + logger.debug("SNI Parsing Complete: {}", evt); SniCompletionEvent sniCompletionEvent = (SniCompletionEvent) evt; if (sniCompletionEvent.isSuccess()) { spectatorRegistry.counter("zuul.sni.parse.success").increment(); - } - else { + } else { Throwable cause = sniCompletionEvent.cause(); - spectatorRegistry.counter("zuul.sni.parse.failure", cause != null ? cause.getMessage() : "UNKNOWN").increment(); + spectatorRegistry.counter("zuul.sni.parse.failure", cause != null ? cause.getMessage() : "UNKNOWN") + .increment(); } } - super.userEventTriggered(ctx, evt); } - private ClientAuth whichClientAuthEnum(SslHandler sslhandler) - { + private ClientAuth whichClientAuthEnum(SslHandler sslhandler) { ClientAuth clientAuth; if (sslhandler.engine().getNeedClientAuth()) { clientAuth = ClientAuth.REQUIRE; - } - else if (sslhandler.engine().getWantClientAuth()) { + } else if (sslhandler.engine().getWantClientAuth()) { clientAuth = ClientAuth.OPTIONAL; - } - else { + } else { clientAuth = ClientAuth.NONE; } return clientAuth; @@ -228,18 +190,17 @@ private void incrementCounters( "protocol", String.valueOf(proto), "ciphersuite", String.valueOf(ciphsuite), "clientauth", String.valueOf(handshakeInfo.getClientAuthRequirement()) - ) + ) .increment(); - } - else { + } else { spectatorRegistry.counter("server.ssl.handshake", "success", String.valueOf(sslHandshakeCompletionEvent.isSuccess()), "failure_cause", String.valueOf(sslHandshakeCompletionEvent.cause()) - ) + ) .increment(); } } catch (Exception e) { - LOG.error("Error incrememting counters for SSL handshake!", e); + logger.error("Error incrememting counters for SSL handshake!", e); } } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/ssl/BaseSslContextFactory.java b/zuul-core/src/main/java/com/netflix/zuul/netty/ssl/BaseSslContextFactory.java index 0bbfa43a..af5dea87 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/netty/ssl/BaseSslContextFactory.java +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/ssl/BaseSslContextFactory.java @@ -16,9 +16,12 @@ package com.netflix.zuul.netty.ssl; +import com.google.errorprone.annotations.ForOverride; import com.netflix.config.DynamicBooleanProperty; +import com.netflix.netty.common.ssl.ServerSslConfig; import com.netflix.spectator.api.Id; import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.patterns.PolledMeter; import io.netty.handler.ssl.CipherSuiteFilter; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.OpenSsl; @@ -28,14 +31,11 @@ import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslProvider; import io.netty.handler.ssl.SupportedCipherSuiteFilter; -import com.netflix.netty.common.ssl.ServerSslConfig; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -45,9 +45,10 @@ import java.util.Base64; import java.util.Enumeration; import java.util.List; +import java.util.Objects; import java.util.function.ToDoubleFunction; - -import static org.apache.commons.collections.CollectionUtils.isNotEmpty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * User: michaels@netflix.com @@ -57,7 +58,8 @@ public class BaseSslContextFactory implements SslContextFactory { private static final Logger LOG = LoggerFactory.getLogger(BaseSslContextFactory.class); - private static final DynamicBooleanProperty ALLOW_USE_OPENSSL = new DynamicBooleanProperty("zuul.ssl.openssl.allow", true); + private static final DynamicBooleanProperty ALLOW_USE_OPENSSL = + new DynamicBooleanProperty("zuul.ssl.openssl.allow", true); static { // Install BouncyCastle provider. @@ -68,8 +70,8 @@ public class BaseSslContextFactory implements SslContextFactory { protected final ServerSslConfig serverSslConfig; public BaseSslContextFactory(Registry spectatorRegistry, ServerSslConfig serverSslConfig) { - this.spectatorRegistry = spectatorRegistry; - this.serverSslConfig = serverSslConfig; + this.spectatorRegistry = Objects.requireNonNull(spectatorRegistry); + this.serverSslConfig = Objects.requireNonNull(serverSslConfig); } @Override @@ -78,17 +80,14 @@ public SslContextBuilder createBuilderForServer() { ArrayList trustedCerts = getTrustedX509Certificates(); SslProvider sslProvider = chooseSslProvider(); - LOG.warn("Using SslProvider of type - " + sslProvider.name() + " for certChainFile - " + serverSslConfig.getCertChainFile()); + LOG.debug("Using SslProvider of type {}", sslProvider.name()); - InputStream certChainInput = new FileInputStream(serverSslConfig.getCertChainFile()); - InputStream keyInput = getKeyInputStream(); - - SslContextBuilder builder = SslContextBuilder.forServer(certChainInput, keyInput) + SslContextBuilder builder = newBuilderForServer() .ciphers(getCiphers(), getCiphersFilter()) .sessionTimeout(serverSslConfig.getSessionTimeout()) .sslProvider(sslProvider); - if (serverSslConfig.getClientAuth() != null && isNotEmpty(trustedCerts)) { + if (serverSslConfig.getClientAuth() != null && trustedCerts != null && !trustedCerts.isEmpty()) { builder = builder .trustManager(trustedCerts.toArray(new X509Certificate[0])) .clientAuth(serverSslConfig.getClientAuth()); @@ -101,6 +100,19 @@ public SslContextBuilder createBuilderForServer() { } } + /** + * This function is meant to call the correct overload of {@code SslContextBuilder.forServer()}. It should not + * apply any other customization. + */ + @ForOverride + protected SslContextBuilder newBuilderForServer() throws IOException { + LOG.debug("Using certChainFile {}", serverSslConfig.getCertChainFile()); + try (InputStream keyInput = getKeyInputStream(); + InputStream certChainInput = new FileInputStream(serverSslConfig.getCertChainFile())) { + return SslContextBuilder.forServer(certChainInput, keyInput); + } + } + @Override public void enableSessionTickets(SslContext sslContext) { // TODO @@ -130,9 +142,11 @@ public void configureOpenSslStatsMetrics(SslContext sslContext, String sslContex } } - private void openSslStatGauge(OpenSslSessionStats stats, String sslContextId, String statName, ToDoubleFunction value) { + private void openSslStatGauge( + OpenSslSessionStats stats, String sslContextId, String statName, + ToDoubleFunction value) { Id id = spectatorRegistry.createId("server.ssl.stats", "id", sslContextId, "stat", statName); - spectatorRegistry.gauge(id, stats, value); + PolledMeter.using(spectatorRegistry).withId(id).monitorValue(stats, value); LOG.debug("Registered spectator gauge - " + id.name()); } @@ -168,7 +182,7 @@ protected CipherSuiteFilter getCiphersFilter() { protected ArrayList getTrustedX509Certificates() throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException { ArrayList trustedCerts = new ArrayList<>(); - // Add the certifcates from the JKS truststore - ie. the CA's of the client cert that peer Zuul's will use. + // Add the certificates from the JKS truststore - ie. the CA's of the client cert that peer Zuul's will use. if (serverSslConfig.getClientAuth() == ClientAuth.REQUIRE || serverSslConfig.getClientAuth() == ClientAuth.OPTIONAL) { // Get the encrypted bytes of the truststore password. byte[] trustStorePwdBytes; @@ -176,7 +190,7 @@ protected ArrayList getTrustedX509Certificates() throws Certifi trustStorePwdBytes = Base64.getDecoder().decode(serverSslConfig.getClientAuthTrustStorePassword()); } else if (serverSslConfig.getClientAuthTrustStorePasswordFile() != null) { - trustStorePwdBytes = FileUtils.readFileToByteArray(serverSslConfig.getClientAuthTrustStorePasswordFile()); + trustStorePwdBytes = Files.readAllBytes(serverSslConfig.getClientAuthTrustStorePasswordFile().toPath()); } else { throw new IllegalArgumentException("Must specify either ClientAuthTrustStorePassword or ClientAuthTrustStorePasswordFile!"); @@ -207,8 +221,6 @@ else if (serverSslConfig.getClientAuthTrustStorePasswordFile() != null) { /** * Can be overridden to implement your own decryption scheme. * - * @param trustStorePwdBytes - * @return */ protected String getTruststorePassword(byte[] trustStorePwdBytes) { return new String(trustStorePwdBytes).trim(); @@ -216,9 +228,6 @@ protected String getTruststorePassword(byte[] trustStorePwdBytes) { /** * Can be overridden to implement your own decryption scheme. - * - * @return - * @throws IOException */ protected InputStream getKeyInputStream() throws IOException { return new FileInputStream(serverSslConfig.getKeyFile()); diff --git a/zuul-core/src/main/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManager.java b/zuul-core/src/main/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManager.java new file mode 100644 index 00000000..9a5530da --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManager.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.timeouts; + +import com.google.common.annotations.VisibleForTesting; +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.client.config.IClientConfig; +import com.netflix.config.DynamicLongProperty; +import com.netflix.zuul.context.CommonContextKeys; +import com.netflix.zuul.message.http.HttpRequestMessage; +import com.netflix.zuul.origins.NettyOrigin; +import java.time.Duration; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; + +/** + * Origin Timeout Manager + * + * @author Arthur Gonigberg + * @since February 24, 2021 + */ +public class OriginTimeoutManager { + + private final NettyOrigin origin; + + public OriginTimeoutManager(NettyOrigin origin) { + this.origin = Objects.requireNonNull(origin); + } + + @VisibleForTesting + static final DynamicLongProperty MAX_OUTBOUND_READ_TIMEOUT_MS = + new DynamicLongProperty("zuul.origin.readtimeout.max", Duration.ofSeconds(90).toMillis()); + + /** + * Derives the read timeout from the configuration. This implementation prefers the longer of either the origin + * timeout or the request timeout. + *

+ * This method can also be used to validate timeout and deadline boundaries and throw exceptions as needed. If + * extending this method to do validation, you should extend {@link com.netflix.zuul.exception.OutboundException} + * and set the appropriate {@link com.netflix.zuul.exception.ErrorType}. + * + * @param request the request. + * @param attemptNum the attempt number, starting at 1. + */ + public Duration computeReadTimeout(HttpRequestMessage request, int attemptNum) { + IClientConfig clientConfig = getRequestClientConfig(request); + Long originTimeout = getOriginReadTimeout(); + Long requestTimeout = getRequestReadTimeout(clientConfig); + + long computedTimeout; + if (originTimeout == null && requestTimeout == null) { + computedTimeout = MAX_OUTBOUND_READ_TIMEOUT_MS.get(); + } else if (originTimeout == null || requestTimeout == null) { + computedTimeout = originTimeout == null ? requestTimeout : originTimeout; + } else { + // return the stricter (i.e. lower) of the two timeouts + computedTimeout = Math.min(originTimeout, requestTimeout); + } + + // enforce max timeout upperbound + return Duration.ofMillis(Math.min(computedTimeout, MAX_OUTBOUND_READ_TIMEOUT_MS.get())); + } + + /** + * This method will create a new client config or retrieve the existing one from the current request. + * + * @param zuulRequest - the request + * @return the config + */ + protected IClientConfig getRequestClientConfig(HttpRequestMessage zuulRequest) { + IClientConfig overriddenClientConfig = + (IClientConfig) zuulRequest.getContext().get(CommonContextKeys.REST_CLIENT_CONFIG); + if (overriddenClientConfig == null) { + overriddenClientConfig = new DefaultClientConfigImpl(); + zuulRequest.getContext().put(CommonContextKeys.REST_CLIENT_CONFIG, overriddenClientConfig); + } + + return overriddenClientConfig; + } + + /** + * This method makes the assumption that the timeout is a numeric value + */ + @Nullable + private Long getRequestReadTimeout(IClientConfig clientConfig) { + return Optional.ofNullable(clientConfig.get(CommonClientConfigKey.ReadTimeout)) + .map(Long::valueOf) + .orElse(null); + } + + /** + * This method makes the assumption that the timeout is a numeric value + */ + @Nullable + private Long getOriginReadTimeout() { + return Optional.ofNullable(origin.getClientConfig().get(CommonClientConfigKey.ReadTimeout)) + .map(Long::valueOf) + .orElse(null); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempt.java b/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempt.java index 9c20e53f..5a7a413a 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempt.java +++ b/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempt.java @@ -19,16 +19,19 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.base.Throwables; import com.netflix.appinfo.AmazonInfo; import com.netflix.appinfo.InstanceInfo; import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfigKey; -import com.netflix.loadbalancer.Server; -import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.exception.OutboundException; +import com.netflix.zuul.discovery.SimpleMetaInfo; import com.netflix.zuul.netty.connectionpool.OriginConnectException; import io.netty.handler.timeout.ReadTimeoutException; +import javax.net.ssl.SSLHandshakeException; + /** * User: michaels@netflix.com * Date: 9/2/14 @@ -41,6 +44,7 @@ public class RequestAttempt private int attempt; private int status; private long duration; + private String cause; private String error; private String exceptionType; private String app; @@ -51,7 +55,7 @@ public class RequestAttempt private String vip; private String region; private String availabilityZone; - private int readTimeout; + private long readTimeout; private int connectTimeout; private int maxRetries; @@ -95,36 +99,28 @@ public RequestAttempt(int attemptNumber, InstanceInfo server, String targetVip, this.maxRetries = maxRetries; } - public RequestAttempt(final Server server, final IClientConfig clientConfig, int attemptNumber, int readTimeout) { + public RequestAttempt(final DiscoveryResult server, final IClientConfig clientConfig, int attemptNumber, int readTimeout) { this.status = -1; this.attempt = attemptNumber; this.readTimeout = readTimeout; - if (server != null) { + if (server != null && server != DiscoveryResult.EMPTY) { this.host = server.getHost(); this.port = server.getPort(); this.availabilityZone = server.getZone(); - if (server instanceof DiscoveryEnabledServer) { - InstanceInfo instanceInfo = ((DiscoveryEnabledServer) server).getInstanceInfo(); - this.app = instanceInfo.getAppName().toLowerCase(); - this.asg = instanceInfo.getASGName(); - this.instanceId = instanceInfo.getInstanceId(); - this.host = instanceInfo.getHostName(); - this.port = instanceInfo.getPort(); + if (server.isDiscoveryEnabled()) { + this.app = server.getAppName().toLowerCase(); + this.asg = server.getASGName(); + this.instanceId = server.getServerId(); + this.host = server.getHost(); + this.port = server.getPort(); + this.vip = server.getTarget(); + this.availabilityZone = server.getAvailabilityZone(); - if (server.getPort() == instanceInfo.getSecurePort()) { - this.vip = instanceInfo.getSecureVipAddress(); - } - else { - this.vip = instanceInfo.getVIPAddress(); - } - if (instanceInfo.getDataCenterInfo() instanceof AmazonInfo) { - this.availabilityZone = ((AmazonInfo) instanceInfo.getDataCenterInfo()).getMetadata().get("availability-zone"); - } } else { - final Server.MetaInfo metaInfo = server.getMetaInfo(); + SimpleMetaInfo metaInfo = server.getMetaInfo(); if (metaInfo != null) { this.asg = metaInfo.getServerGroup(); this.vip = metaInfo.getServiceIdForDiscovery(); @@ -213,7 +209,7 @@ public String getExceptionType() return exceptionType; } - public int getReadTimeout() + public long getReadTimeout() { return readTimeout; } @@ -283,7 +279,7 @@ public void setAvailabilityZone(String availabilityZone) this.availabilityZone = availabilityZone; } - public void setReadTimeout(int readTimeout) + public void setReadTimeout(long readTimeout) { this.readTimeout = readTimeout; } @@ -321,9 +317,15 @@ else if (t instanceof OutboundException) { error = obe.getOutboundErrorType().toString(); exceptionType = OutboundException.class.getSimpleName(); } + else if (t instanceof SSLHandshakeException) { + error = t.getMessage(); + exceptionType = t.getClass().getSimpleName(); + cause = t.getCause().getMessage(); + } else { error = t.getMessage(); exceptionType = t.getClass().getSimpleName(); + cause = Throwables.getStackTraceAsString(t); } } } @@ -352,6 +354,7 @@ public ObjectNode toJsonNode() root.put("attempt", attempt); putNullableAttribute(root, "error", error); + putNullableAttribute(root, "cause", cause); putNullableAttribute(root, "exceptionType", exceptionType); putNullableAttribute(root, "region", region); putNullableAttribute(root, "asg", asg); @@ -373,4 +376,4 @@ private static ObjectNode putNullableAttribute(ObjectNode node, String name, Str } return node; } -} \ No newline at end of file +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempts.java b/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempts.java index 3e0b78e0..67d0f5b0 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempts.java +++ b/zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempts.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.context.SessionContext; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +36,6 @@ public class RequestAttempts extends ArrayList { private static final Logger LOG = LoggerFactory.getLogger(RequestAttempts.class); - private static ThreadLocal threadLocal = new ThreadLocal<>().withInitial(() -> new RequestAttempts()); private static final ObjectMapper JACKSON_MAPPER = new ObjectMapper(); public RequestAttempts() @@ -43,6 +43,7 @@ public RequestAttempts() super(); } + @Nullable public RequestAttempt getFinalAttempt() { if (size() > 0) { @@ -58,25 +59,6 @@ public static RequestAttempts getFromSessionContext(SessionContext ctx) return (RequestAttempts) ctx.get(CommonContextKeys.REQUEST_ATTEMPTS); } - /** - * This is only intended for use when running on a blocking server (ie. tomcat). - * @return - */ - public static RequestAttempts getThreadLocalInstance() - { - return threadLocal.get(); - } - - public static void setThreadLocalInstance(RequestAttempts instance) - { - threadLocal.set(instance); - } - - public static void removeThreadLocalInstance() - { - threadLocal.remove(); - } - public static RequestAttempts parse(String attemptsJson) throws IOException { return JACKSON_MAPPER.readValue(attemptsJson, RequestAttempts.class); diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOrigin.java b/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOrigin.java index 844d6047..a5bd7b5f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOrigin.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOrigin.java @@ -16,16 +16,18 @@ package com.netflix.zuul.origins; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.FAILURE_ORIGIN; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.FAILURE_ORIGIN_THROTTLED; +import static com.netflix.zuul.stats.status.ZuulStatusCategory.SUCCESS; + import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.IClientConfig; import com.netflix.config.CachedDynamicBooleanProperty; import com.netflix.config.CachedDynamicIntProperty; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.reactive.ExecutionContext; -import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; import com.netflix.spectator.api.Counter; import com.netflix.spectator.api.Registry; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.exception.ErrorType; @@ -38,20 +40,16 @@ import com.netflix.zuul.netty.connectionpool.PooledConnection; import com.netflix.zuul.niws.RequestAttempt; import com.netflix.zuul.passport.CurrentPassport; -import com.netflix.zuul.stats.Timing; import com.netflix.zuul.stats.status.StatusCategory; import com.netflix.zuul.stats.status.StatusCategoryUtils; import io.netty.channel.EventLoop; import io.netty.util.concurrent.Promise; -import org.apache.commons.lang3.StringUtils; - +import java.net.InetAddress; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import static com.netflix.zuul.stats.status.ZuulStatusCategory.FAILURE_ORIGIN; -import static com.netflix.zuul.stats.status.ZuulStatusCategory.FAILURE_ORIGIN_THROTTLED; -import static com.netflix.zuul.stats.status.ZuulStatusCategory.SUCCESS; - /** * Netty Origin basic implementation that can be used for most apps, with the more complex methods having no-op * implementations. @@ -61,49 +59,50 @@ */ public class BasicNettyOrigin implements NettyOrigin { - private final String name; - private final String vip; + private final OriginName originName; private final Registry registry; private final IClientConfig config; private final ClientChannelManager clientChannelManager; private final NettyRequestAttemptFactory requestAttemptFactory; + private final OriginStats stats = new OriginStats(); private final AtomicInteger concurrentRequests; private final Counter rejectedRequests; private final CachedDynamicIntProperty concurrencyMax; private final CachedDynamicBooleanProperty concurrencyProtectionEnabled; - public BasicNettyOrigin(String name, String vip, Registry registry) { - this.name = name; - this.vip = vip; + public BasicNettyOrigin(OriginName originName, Registry registry) { + this.originName = Objects.requireNonNull(originName, "originName"); this.registry = registry; - this.config = setupClientConfig(name); - this.clientChannelManager = new DefaultClientChannelManager(name, vip, config, registry); + this.config = setupClientConfig(originName); + this.clientChannelManager = new DefaultClientChannelManager(originName, config, registry); this.clientChannelManager.init(); this.requestAttemptFactory = new NettyRequestAttemptFactory(); - this.concurrentRequests = SpectatorUtils.newGauge("zuul.origin.concurrent.requests", name, new AtomicInteger(0)); - this.rejectedRequests = SpectatorUtils.newCounter("zuul.origin.rejected.requests", name); - this.concurrencyMax = new CachedDynamicIntProperty("zuul.origin." + name + ".concurrency.max.requests", 200); - this.concurrencyProtectionEnabled = new CachedDynamicBooleanProperty("zuul.origin." + name + ".concurrency.protect.enabled", true); + String niwsClientName = getName().getNiwsClientName(); + this.concurrentRequests = + SpectatorUtils.newGauge( + "zuul.origin.concurrent.requests", niwsClientName, new AtomicInteger(0)); + this.rejectedRequests = + SpectatorUtils.newCounter("zuul.origin.rejected.requests", niwsClientName); + this.concurrencyMax = + new CachedDynamicIntProperty("zuul.origin." + niwsClientName + ".concurrency.max.requests", 200); + this.concurrencyProtectionEnabled = + new CachedDynamicBooleanProperty("zuul.origin." + niwsClientName + ".concurrency.protect.enabled", true); } - protected IClientConfig setupClientConfig(String name) { + protected IClientConfig setupClientConfig(OriginName originName) { // Get the NIWS properties for this Origin. - IClientConfig niwsClientConfig = DefaultClientConfigImpl.getClientConfigWithDefaultValues(name); - niwsClientConfig.set(CommonClientConfigKey.ClientClassName, name); - niwsClientConfig.loadProperties(name); + IClientConfig niwsClientConfig = + DefaultClientConfigImpl.getClientConfigWithDefaultValues(originName.getNiwsClientName()); + niwsClientConfig.set(CommonClientConfigKey.ClientClassName, originName.getNiwsClientName()); + niwsClientConfig.loadProperties(originName.getNiwsClientName()); return niwsClientConfig; } @Override - public String getName() { - return name; - } - - @Override - public String getVip() { - return vip; + public OriginName getName() { + return originName; } @Override @@ -117,40 +116,25 @@ public boolean isCold() { } @Override - public Promise connectToOrigin(HttpRequestMessage zuulReq, EventLoop eventLoop, int attemptNumber, - CurrentPassport passport, AtomicReference chosenServer, - AtomicReference chosenHostAddr) { - return clientChannelManager.acquire(eventLoop, null, zuulReq.getMethod().toUpperCase(), - zuulReq.getPath(), attemptNumber, passport, chosenServer, chosenHostAddr); - } - - @Override - public Timing getProxyTiming(HttpRequestMessage zuulReq) { - return new Timing(name); + public Promise connectToOrigin( + HttpRequestMessage zuulReq, EventLoop eventLoop, int attemptNumber, CurrentPassport passport, + AtomicReference chosenServer, AtomicReference chosenHostAddr) { + return clientChannelManager.acquire(eventLoop, null, passport, chosenServer, chosenHostAddr); } - @Override public int getMaxRetriesForRequest(SessionContext context) { return config.get(CommonClientConfigKey.MaxAutoRetriesNextServer, 0); } @Override - public RequestAttempt newRequestAttempt(Server server, SessionContext zuulCtx, int attemptNum) { + public RequestAttempt newRequestAttempt(DiscoveryResult server, SessionContext zuulCtx, int attemptNum) { return new RequestAttempt(server, config, attemptNum, config.get(CommonClientConfigKey.ReadTimeout)); } @Override - public String getIpAddrFromServer(Server server) { - if (server instanceof DiscoveryEnabledServer) { - DiscoveryEnabledServer discoveryServer = (DiscoveryEnabledServer) server; - if (discoveryServer.getInstanceInfo() != null) { - String ip = discoveryServer.getInstanceInfo().getIPAddr(); - if (StringUtils.isNotBlank(ip)) { - return ip; - } - } - } - return null; + public String getIpAddrFromServer(DiscoveryResult discoveryResult) { + final Optional ipAddr = discoveryResult.getIPAddr(); + return ipAddr.isPresent() ? ipAddr.get() : null; } @Override @@ -163,26 +147,6 @@ public Registry getSpectatorRegistry() { return registry; } - @Override - public ExecutionContext getExecutionContext(HttpRequestMessage zuulRequest) { - ExecutionContext execCtx = (ExecutionContext) zuulRequest.getContext().get(CommonContextKeys.REST_EXECUTION_CONTEXT); - if (execCtx == null) { - IClientConfig overriddenClientConfig = (IClientConfig) zuulRequest.getContext().get(CommonContextKeys.REST_CLIENT_CONFIG); - if (overriddenClientConfig == null) { - overriddenClientConfig = new DefaultClientConfigImpl(); - zuulRequest.getContext().put(CommonContextKeys.REST_CLIENT_CONFIG, overriddenClientConfig); - } - - final ExecutionContext context = new ExecutionContext<>(zuulRequest, overriddenClientConfig, this.config, null); - context.put("vip", getVip()); - context.put("clientName", getName()); - - zuulRequest.getContext().set(CommonContextKeys.REST_EXECUTION_CONTEXT, context); - execCtx = context; - } - return execCtx; - } - @Override public void recordFinalError(HttpRequestMessage requestMsg, Throwable throwable) { if (throwable == null) { @@ -239,6 +203,11 @@ public void recordProxyRequestEnd() { concurrentRequests.decrementAndGet(); } + @Override + public final OriginStats stats() { + return stats; + } + /* Not required for basic operation */ @Override @@ -256,19 +225,19 @@ public void onRequestExecutionStart(HttpRequestMessage zuulReq) { } @Override - public void onRequestStartWithServer(HttpRequestMessage zuulReq, Server originServer, int attemptNum) { + public void onRequestStartWithServer(HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum) { } @Override - public void onRequestExceptionWithServer(HttpRequestMessage zuulReq, Server originServer, int attemptNum, Throwable t) { + public void onRequestExceptionWithServer(HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum, Throwable t) { } @Override - public void onRequestExecutionSuccess(HttpRequestMessage zuulReq, HttpResponseMessage zuulResp, Server originServer, int attemptNum) { + public void onRequestExecutionSuccess(HttpRequestMessage zuulReq, HttpResponseMessage zuulResp, DiscoveryResult discoveryResult, int attemptNum) { } @Override - public void onRequestExecutionFailed(HttpRequestMessage zuulReq, Server originServer, int attemptNum, Throwable t) { + public void onRequestExecutionFailed(HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum, Throwable t) { } @Override diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOriginManager.java b/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOriginManager.java index b231d6f6..2ababc88 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOriginManager.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOriginManager.java @@ -34,7 +34,7 @@ public class BasicNettyOriginManager implements OriginManager { private final Registry registry; - private final ConcurrentHashMap originMappings; + private final ConcurrentHashMap originMappings; @Inject public BasicNettyOriginManager(Registry registry) { @@ -43,12 +43,13 @@ public BasicNettyOriginManager(Registry registry) { } @Override - public BasicNettyOrigin getOrigin(String name, String vip, String uri, SessionContext ctx) { - return originMappings.computeIfAbsent(name, n -> createOrigin(name, vip, uri, false, ctx)); + public BasicNettyOrigin getOrigin(OriginName originName, String uri, SessionContext ctx) { + return originMappings.computeIfAbsent(originName, n -> createOrigin(originName, uri, ctx)); } @Override - public BasicNettyOrigin createOrigin(String name, String vip, String uri, boolean useFullVipName, SessionContext ctx) { - return new BasicNettyOrigin(name, vip, registry); + public BasicNettyOrigin createOrigin( + OriginName originName, String uri, SessionContext ctx) { + return new BasicNettyOrigin(originName, registry); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/InstrumentedOrigin.java b/zuul-core/src/main/java/com/netflix/zuul/origins/InstrumentedOrigin.java index e2ad4ab0..f87bd274 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/InstrumentedOrigin.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/InstrumentedOrigin.java @@ -17,6 +17,7 @@ package com.netflix.zuul.origins; import com.netflix.zuul.message.http.HttpRequestMessage; +import javax.annotation.Nullable; /** * User: michaels@netflix.com @@ -36,4 +37,13 @@ public interface InstrumentedOrigin extends Origin { void recordSuccessResponse(); void recordProxyRequestEnd(); + + /** + * Returns the mutable origin stats for this origin. Unlike the other methods in this interface, + * External callers are expected to update these numbers, rather than this object itself. + * @return + */ + default OriginStats stats() { + throw new UnsupportedOperationException(); + } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/NettyOrigin.java b/zuul-core/src/main/java/com/netflix/zuul/origins/NettyOrigin.java index aa1e8510..666b17b3 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/NettyOrigin.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/NettyOrigin.java @@ -17,19 +17,18 @@ package com.netflix.zuul.origins; import com.netflix.client.config.IClientConfig; -import com.netflix.loadbalancer.Server; -import com.netflix.loadbalancer.reactive.ExecutionContext; import com.netflix.spectator.api.Registry; +import com.netflix.zuul.discovery.DiscoveryResult; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.netty.connectionpool.PooledConnection; import com.netflix.zuul.niws.RequestAttempt; import com.netflix.zuul.passport.CurrentPassport; -import com.netflix.zuul.stats.Timing; import io.netty.channel.EventLoop; import io.netty.util.concurrent.Promise; +import java.net.InetAddress; import java.util.concurrent.atomic.AtomicReference; /** @@ -42,37 +41,33 @@ public interface NettyOrigin extends InstrumentedOrigin { Promise connectToOrigin(final HttpRequestMessage zuulReq, EventLoop eventLoop, int attemptNumber, CurrentPassport passport, - AtomicReference chosenServer, - AtomicReference chosenHostAddr); - - Timing getProxyTiming(HttpRequestMessage zuulReq); + AtomicReference chosenServer, + AtomicReference chosenHostAddr); int getMaxRetriesForRequest(SessionContext context); void onRequestExecutionStart(final HttpRequestMessage zuulReq); - void onRequestStartWithServer(final HttpRequestMessage zuulReq, final Server originServer, int attemptNum); + void onRequestStartWithServer(final HttpRequestMessage zuulReq, final DiscoveryResult discoveryResult, int attemptNum); - void onRequestExceptionWithServer(final HttpRequestMessage zuulReq, final Server originServer, + void onRequestExceptionWithServer(final HttpRequestMessage zuulReq, final DiscoveryResult discoveryResult, final int attemptNum, Throwable t); void onRequestExecutionSuccess(final HttpRequestMessage zuulReq, final HttpResponseMessage zuulResp, - final Server originServer, final int attemptNum); + final DiscoveryResult discoveryResult, final int attemptNum); - void onRequestExecutionFailed(final HttpRequestMessage zuulReq, final Server originServer, + void onRequestExecutionFailed(final HttpRequestMessage zuulReq, final DiscoveryResult discoveryResult, final int attemptNum, Throwable t); void recordFinalError(final HttpRequestMessage requestMsg, final Throwable throwable); void recordFinalResponse(final HttpResponseMessage resp); - RequestAttempt newRequestAttempt(final Server server, final SessionContext zuulCtx, int attemptNum); + RequestAttempt newRequestAttempt(final DiscoveryResult server, final SessionContext zuulCtx, int attemptNum); - String getIpAddrFromServer(Server server); + String getIpAddrFromServer(DiscoveryResult server); IClientConfig getClientConfig(); Registry getSpectatorRegistry(); - - ExecutionContext getExecutionContext(HttpRequestMessage zuulRequest); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/Origin.java b/zuul-core/src/main/java/com/netflix/zuul/origins/Origin.java index 2e73c9ac..6927978f 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/Origin.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/Origin.java @@ -21,8 +21,7 @@ * Time: 3:14 PM */ public interface Origin { - String getName(); - String getVip(); + OriginName getName(); boolean isAvailable(); boolean isCold(); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginConcurrencyExceededException.java b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginConcurrencyExceededException.java index 33e6f655..1c9c2a73 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginConcurrencyExceededException.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginConcurrencyExceededException.java @@ -21,7 +21,7 @@ public class OriginConcurrencyExceededException extends OriginThrottledException { - public OriginConcurrencyExceededException(String originName) + public OriginConcurrencyExceededException(OriginName originName) { super(originName, "Max concurrent requests on origin exceeded", ZuulStatusCategory.FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginManager.java b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginManager.java index c50e5d41..f3b0817b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginManager.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginManager.java @@ -24,7 +24,7 @@ */ public interface OriginManager { - T getOrigin(String name, String vip, String uri, SessionContext ctx); + T getOrigin(OriginName originName, String uri, SessionContext ctx); - T createOrigin(String name, String vip, String uri, boolean useFullVipName, SessionContext ctx); + T createOrigin(OriginName originName, String uri, SessionContext ctx); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginName.java b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginName.java new file mode 100644 index 00000000..ca6f2b1d --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginName.java @@ -0,0 +1,132 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.origins; + +import com.netflix.zuul.util.VipUtils; +import java.util.Locale; +import java.util.Objects; + +public final class OriginName { + /** + * The NIWS client name of the origin. This is typically used in metrics and for configuration of NIWS + * {@link com.netflix.client.config.IClientConfig} objects. + */ + private final String niwsClientName; + + /** + * This should not be used in {@link #equals} or {@link #hashCode} as it is already covered by + * {@link #niwsClientName}. + */ + private final String metricId; + + /** + * The target to connect to, used for name resolution. This is typically the VIP. + */ + private final String target; + /** + * The authority of this origin. Usually this is the Application name of origin. It is primarily + * used for establishing a secure connection, as well as logging. + */ + private final String authority; + + /** + * @deprecated use {@link #fromVipAndApp(String, String)} + */ + @Deprecated + public static OriginName fromVip(String vip) { + return fromVipAndApp(vip, VipUtils.extractUntrustedAppNameFromVIP(vip)); + } + + /** + * @deprecated use {@link #fromVipAndApp(String, String, String)} + */ + @Deprecated + public static OriginName fromVip(String vip, String niwsClientName) { + return fromVipAndApp(vip, VipUtils.extractUntrustedAppNameFromVIP(vip), niwsClientName); + } + + public static OriginName fromVipAndApp(String vip, String appName) { + return fromVipAndApp(vip, appName, vip); + } + + public static OriginName fromVipAndApp(String vip, String appName, String niwsClientName) { + return new OriginName(vip, appName, niwsClientName); + } + + private OriginName(String target, String authority, String niwsClientName) { + this.target = Objects.requireNonNull(target, "target"); + this.authority = Objects.requireNonNull(authority, "authority"); + this.niwsClientName = Objects.requireNonNull(niwsClientName, "niwsClientName"); + this.metricId = niwsClientName.toLowerCase(Locale.ROOT); + } + + /** + * This is typically the VIP for the given Origin. + */ + public String getTarget() { + return target; + } + + /** + * Returns the niwsClientName. This is normally used for interaction with NIWS, and should be used without prior + * knowledge that the value will be used in NIWS libraries. + */ + public String getNiwsClientName() { + return niwsClientName; + } + + /** + * Returns the identifier for this this metric name. This may be different than any of the other + * fields; currently it is equivalent to the lowercased {@link #getNiwsClientName()}. + */ + public String getMetricId() { + return metricId; + } + + /** + * Returns the Authority of this origin. This is used for establishing secure connections. May be absent + * if the authority is not trusted. + */ + public String getAuthority() { + return authority; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof OriginName)) { + return false; + } + OriginName that = (OriginName) o; + return Objects.equals(niwsClientName, that.niwsClientName) + && Objects.equals(target, that.target) + && Objects.equals(authority, that.authority); + } + + @Override + public int hashCode() { + return Objects.hash(niwsClientName, target, authority); + } + + @Override + public String toString() { + return "OriginName{" + + "niwsClientName='" + niwsClientName + '\'' + + ", target='" + target + '\'' + + ", authority='" + authority + '\'' + + '}'; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginStats.java b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginStats.java new file mode 100644 index 00000000..3a331480 --- /dev/null +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginStats.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.origins; + +import java.time.ZonedDateTime; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Information about an {@link Origin} to be used for proxying. + */ +@ThreadSafe +public final class OriginStats { + + /** + * Represents the last time a Server in this Origin was throttled. + */ + private final AtomicReference lastThrottleEvent = new AtomicReference<>(); + + + /** + * Gets the last time a Server in this Origin was throttled. Returns {@code null} if there was + * no throttling. This class does not define what throttling is; see + * {@link com.netflix.zuul.filters.endpoint.ProxyEndpoint}. + */ + @Nullable + public ZonedDateTime lastThrottleEvent() { + return lastThrottleEvent.get(); + } + + /** + * Sets the last throttle event, if it is after the existing last throttle event. + */ + public void lastThrottleEvent(ZonedDateTime lastThrottleEvent) { + Objects.requireNonNull(lastThrottleEvent); + ZonedDateTime existing; + do { + existing = this.lastThrottleEvent.get(); + if (existing != null && lastThrottleEvent.compareTo(existing) <= 0) { + break; + } + } while (!this.lastThrottleEvent.compareAndSet(existing, lastThrottleEvent)); + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginThrottledException.java b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginThrottledException.java index 2a33eba4..8b24a5d4 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/origins/OriginThrottledException.java +++ b/zuul-core/src/main/java/com/netflix/zuul/origins/OriginThrottledException.java @@ -18,23 +18,23 @@ import com.netflix.zuul.exception.ZuulException; import com.netflix.zuul.stats.status.StatusCategory; +import java.util.Objects; public abstract class OriginThrottledException extends ZuulException { - private final String originName; + private final OriginName originName; private final StatusCategory statusCategory; - public OriginThrottledException(String originName, String msg, StatusCategory statusCategory) + public OriginThrottledException(OriginName originName, String msg, StatusCategory statusCategory) { // Ensure this exception does not fill its stacktrace as causes too much load. super(msg + ", origin=" + originName, true); - this.originName = originName; + this.originName = Objects.requireNonNull(originName, "originName"); this.statusCategory = statusCategory; this.setStatusCode(503); } - public String getOriginName() - { + public OriginName getOriginName() { return originName; } diff --git a/zuul-core/src/main/java/com/netflix/zuul/passport/CurrentPassport.java b/zuul-core/src/main/java/com/netflix/zuul/passport/CurrentPassport.java index bdb9b0fb..8334266c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/passport/CurrentPassport.java +++ b/zuul-core/src/main/java/com/netflix/zuul/passport/CurrentPassport.java @@ -20,26 +20,33 @@ import com.google.common.base.Ticker; import com.google.common.collect.Sets; import com.netflix.config.CachedDynamicBooleanProperty; -import com.netflix.servo.DefaultMonitorRegistry; -import com.netflix.servo.monitor.BasicCounter; -import com.netflix.servo.monitor.MonitorConfig; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Spectator; import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.context.SessionContext; import io.netty.channel.Channel; import io.netty.util.AttributeKey; import java.util.ArrayList; +import java.util.Arrays; +import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class CurrentPassport { + private static final Logger logger = LoggerFactory.getLogger(CurrentPassport.class); + private static final CachedDynamicBooleanProperty COUNT_STATES = new CachedDynamicBooleanProperty( "zuul.passport.count.enabled", false); @@ -61,6 +68,40 @@ public class CurrentPassport private final HashSet statesAdded; private final long creationTimeSinceEpochMs; + private final IntrospectiveReentrantLock historyLock = new IntrospectiveReentrantLock(); + private final Unlocker unlocker = new Unlocker(); + private final class Unlocker implements AutoCloseable { + + @Override + public void close() { + historyLock.unlock(); + } + } + private final static class IntrospectiveReentrantLock extends ReentrantLock { + + @Override + protected Thread getOwner() { + return super.getOwner(); + } + } + + private Unlocker lock() { + boolean locked = false; + if ((historyLock.isLocked() && !historyLock.isHeldByCurrentThread()) || !(locked = historyLock.tryLock())) { + Thread owner = historyLock.getOwner(); + String ownerStack = String.valueOf(owner != null ? Arrays.asList(owner.getStackTrace()) : historyLock); + logger.warn( + "CurrentPassport already locked!, other={}, self={}", + ownerStack, + Thread.currentThread(), + new ConcurrentModificationException()); + } + if (!locked) { + historyLock.lock(); + } + return unlocker; + } + CurrentPassport() { this(SYSTEM_TICKER); @@ -119,14 +160,19 @@ public static void clearFromChannel(Channel ch) { ch.attr(CHANNEL_ATTR).set(null); } - public PassportState getState() - { - return history.peekLast().getState(); + public PassportState getState() { + try (Unlocker ignored = lock()){ + return history.peekLast().getState(); + } } + @VisibleForTesting public LinkedList getHistory() { - return history; + try (Unlocker ignored = lock()) { + // best effort, but doesn't actually protect anything + return history; + } } public void add(PassportState state) @@ -137,8 +183,10 @@ public void add(PassportState state) return; } } - - history.addLast(new PassportItem(state, now())); + + try (Unlocker ignored = lock()) { + history.addLast(new PassportItem(state, now())); + } statesAdded.add(state); } @@ -152,9 +200,11 @@ public void addIfNotAlready(PassportState state) public long calculateTimeBetweenFirstAnd(PassportState endState) { long startTime = firstTime(); - for (PassportItem item : history) { - if (item.getState() == endState) { - return item.getTime() - startTime; + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (item.getState() == endState) { + return item.getTime() - startTime; + } } } return now() - startTime; @@ -165,7 +215,9 @@ public long calculateTimeBetweenFirstAnd(PassportState endState) */ public long firstTime() { - return history.getFirst().getTime(); + try (Unlocker ignored = lock()) { + return history.getFirst().getTime(); + } } public long creationTimeSinceEpochMs() @@ -198,26 +250,31 @@ public long calculateTimeBetweenButIfNoEndThenUseNow(StartAndEnd sae) public StartAndEnd findStartAndEndStates(PassportState startState, PassportState endState) { StartAndEnd sae = new StartAndEnd(); - for (PassportItem item : history) { - if (item.getState() == startState) { - sae.startTime = item.getTime(); - } - else if (item.getState() == endState) { - sae.endTime = item.getTime(); + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (item.getState() == startState) { + sae.startTime = item.getTime(); + } + else if (item.getState() == endState) { + sae.endTime = item.getTime(); + } } } + return sae; } public StartAndEnd findFirstStartAndLastEndStates(PassportState startState, PassportState endState) { StartAndEnd sae = new StartAndEnd(); - for (PassportItem item : history) { - if (sae.startNotFound() && item.getState() == startState) { - sae.startTime = item.getTime(); - } - else if (item.getState() == endState) { - sae.endTime = item.getTime(); + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (sae.startNotFound() && item.getState() == startState) { + sae.startTime = item.getTime(); + } + else if (item.getState() == endState) { + sae.endTime = item.getTime(); + } } } return sae; @@ -226,12 +283,13 @@ else if (item.getState() == endState) { public StartAndEnd findLastStartAndFirstEndStates(PassportState startState, PassportState endState) { StartAndEnd sae = new StartAndEnd(); - for (PassportItem item : history) { - if (item.getState() == startState) { - sae.startTime = item.getTime(); - } - else if (sae.endNotFound() && item.getState() == endState) { - sae.endTime = item.getTime(); + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (item.getState() == startState) { + sae.startTime = item.getTime(); + } else if (sae.endNotFound() && item.getState() == endState) { + sae.endTime = item.getTime(); + } } } return sae; @@ -243,19 +301,20 @@ public List findEachPairOf(PassportState startState, PassportState StartAndEnd currentPair = null; - for (PassportItem item : history) { + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { - if (item.getState() == startState) { - if (currentPair == null) { - currentPair = new StartAndEnd(); - currentPair.startTime = item.getTime(); - } - } - else if (item.getState() == endState) { - if (currentPair != null) { - currentPair.endTime = item.getTime(); - items.add(currentPair); - currentPair = null; + if (item.getState() == startState) { + if (currentPair == null) { + currentPair = new StartAndEnd(); + currentPair.startTime = item.getTime(); + } + } else if (item.getState() == endState) { + if (currentPair != null) { + currentPair.endTime = item.getTime(); + items.add(currentPair); + currentPair = null; + } } } } @@ -265,9 +324,11 @@ else if (item.getState() == endState) { public PassportItem findState(PassportState state) { - for (PassportItem item : history) { - if (item.getState() == state) { - return item; + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (item.getState() == state) { + return item; + } } } return null; @@ -275,11 +336,13 @@ public PassportItem findState(PassportState state) public PassportItem findStateBackwards(PassportState state) { - Iterator itr = history.descendingIterator(); - while (itr.hasNext()) { - PassportItem item = (PassportItem) itr.next(); - if (item.getState() == state) { - return item; + try (Unlocker ignored = lock()) { + Iterator itr = history.descendingIterator(); + while (itr.hasNext()) { + PassportItem item = (PassportItem) itr.next(); + if (item.getState() == state) { + return item; + } } } return null; @@ -288,9 +351,11 @@ public PassportItem findStateBackwards(PassportState state) public List findStates(PassportState state) { ArrayList items = new ArrayList<>(); - for (PassportItem item : history) { - if (item.getState() == state) { - items.add(item); + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (item.getState() == state) { + items.add(item); + } } } return items; @@ -300,9 +365,11 @@ public List findTimes(PassportState state) { long startTick = firstTime(); ArrayList items = new ArrayList<>(); - for (PassportItem item : history) { - if (item.getState() == state) { - items.add(item.getTime() - startTick); + try (Unlocker ignored = lock()) { + for (PassportItem item : history) { + if (item.getState() == state) { + items.add(item.getTime() - startTick); + } } } return items; @@ -323,23 +390,26 @@ private long now() @Override public String toString() { - long startTime = history.size() > 0 ? firstTime() : 0; - long now = now(); - - StringBuilder sb = new StringBuilder(); - sb.append("CurrentPassport {"); - sb.append("start_ms=").append(creationTimeSinceEpochMs()).append(", "); - - sb.append('['); - for (PassportItem item : history) { - sb.append('+').append(item.getTime() - startTime).append('=').append(item.getState().name()).append(", "); + try (Unlocker ignored = lock()) { + long startTime = history.size() > 0 ? firstTime() : 0; + long now = now(); + + StringBuilder sb = new StringBuilder(); + sb.append("CurrentPassport {"); + sb.append("start_ms=").append(creationTimeSinceEpochMs()).append(", "); + + sb.append('['); + for (PassportItem item : history) { + sb.append('+').append(item.getTime() - startTime).append('=').append(item.getState().name()) + .append(", "); + } + sb.append('+').append(now - startTime).append('=').append("NOW"); + sb.append(']'); + + sb.append('}'); + + return sb.toString(); } - sb.append('+').append(now - startTime).append('=').append("NOW"); - sb.append(']'); - - sb.append('}'); - - return sb.toString(); } @VisibleForTesting @@ -353,19 +423,20 @@ public static CurrentPassport parseFromToString(String text) String[] stateStrs = m.group(1).split(", "); MockTicker ticker = new MockTicker(); passport = new CurrentPassport(ticker); - for (String stateStr : stateStrs) { - Matcher stateMatch = ptnState.matcher(stateStr); - if (stateMatch.matches()) { - String stateName = stateMatch.group(2); - if (stateName.equals("NOW")) { - long startTime = passport.getHistory().size() > 0 ? passport.firstTime() : 0; - long now = Long.valueOf(stateMatch.group(1)) + startTime; - ticker.setNow(now); - } - else { - PassportState state = PassportState.valueOf(stateName); - PassportItem item = new PassportItem(state, Long.valueOf(stateMatch.group(1))); - passport.getHistory().add(item); + try (Unlocker ignored = passport.lock()) { + for (String stateStr : stateStrs) { + Matcher stateMatch = ptnState.matcher(stateStr); + if (stateMatch.matches()) { + String stateName = stateMatch.group(2); + if (stateName.equals("NOW")) { + long startTime = passport.history.size() > 0 ? passport.firstTime() : 0; + long now = Long.valueOf(stateMatch.group(1)) + startTime; + ticker.setNow(now); + } else { + PassportState state = PassportState.valueOf(stateName); + PassportItem item = new PassportItem(state, Long.valueOf(stateMatch.group(1))); + passport.history.add(item); + } } } } @@ -395,23 +466,21 @@ public void setNow(long now) class CountingCurrentPassport extends CurrentPassport { - private final static BasicCounter IN_REQ_HEADERS_RECEIVED_CNT = createCounter("in_req_hdrs_rec"); - private final static BasicCounter IN_REQ_LAST_CONTENT_RECEIVED_CNT = createCounter("in_req_last_cont_rec"); + private final static Counter IN_REQ_HEADERS_RECEIVED_CNT = createCounter("in_req_hdrs_rec"); + private final static Counter IN_REQ_LAST_CONTENT_RECEIVED_CNT = createCounter("in_req_last_cont_rec"); - private final static BasicCounter IN_RESP_HEADERS_RECEIVED_CNT = createCounter("in_resp_hdrs_rec"); - private final static BasicCounter IN_RESP_LAST_CONTENT_RECEIVED_CNT = createCounter("in_resp_last_cont_rec"); + private final static Counter IN_RESP_HEADERS_RECEIVED_CNT = createCounter("in_resp_hdrs_rec"); + private final static Counter IN_RESP_LAST_CONTENT_RECEIVED_CNT = createCounter("in_resp_last_cont_rec"); - private final static BasicCounter OUT_REQ_HEADERS_SENT_CNT = createCounter("out_req_hdrs_sent"); - private final static BasicCounter OUT_REQ_LAST_CONTENT_SENT_CNT = createCounter("out_req_last_cont_sent"); + private final static Counter OUT_REQ_HEADERS_SENT_CNT = createCounter("out_req_hdrs_sent"); + private final static Counter OUT_REQ_LAST_CONTENT_SENT_CNT = createCounter("out_req_last_cont_sent"); - private final static BasicCounter OUT_RESP_HEADERS_SENT_CNT = createCounter("out_resp_hdrs_sent"); - private final static BasicCounter OUT_RESP_LAST_CONTENT_SENT_CNT = createCounter("out_resp_last_cont_sent"); + private final static Counter OUT_RESP_HEADERS_SENT_CNT = createCounter("out_resp_hdrs_sent"); + private final static Counter OUT_RESP_LAST_CONTENT_SENT_CNT = createCounter("out_resp_last_cont_sent"); - private static BasicCounter createCounter(String name) + private static Counter createCounter(String name) { - BasicCounter counter = new BasicCounter(MonitorConfig.builder("zuul.passport." + name).build()); - DefaultMonitorRegistry.getInstance().register(counter); - return counter; + return Spectator.globalRegistry().counter("zuul.passport." + name); } public CountingCurrentPassport() diff --git a/zuul-core/src/main/java/com/netflix/zuul/plugins/Counter.java b/zuul-core/src/main/java/com/netflix/zuul/plugins/Counter.java deleted file mode 100644 index d640dd7c..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/plugins/Counter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.plugins; - -import com.netflix.servo.DefaultMonitorRegistry; -import com.netflix.servo.monitor.BasicCounter; -import com.netflix.servo.monitor.MonitorConfig; -import com.netflix.servo.tag.InjectableTag; -import com.netflix.servo.tag.Tag; -import com.netflix.zuul.monitoring.CounterFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * Plugin to hook up a Servo counter to the CounterFactory - * @author Mikey Cohen - * Date: 4/10/13 - * Time: 4:50 PM - */ -public class Counter extends CounterFactory { - final static ConcurrentMap map = new ConcurrentHashMap(); - final Object lock = new Object(); - - @Override - public void increment(String name) { - BasicCounter counter = getCounter(name); - counter.increment(); - } - - private BasicCounter getCounter(String name) { - BasicCounter counter = map.get(name); - if (counter == null) { - synchronized (lock) { - counter = map.get(name); - if (counter != null) { - return counter; - } - - List tags = new ArrayList(2); - tags.add(InjectableTag.HOSTNAME); - tags.add(InjectableTag.IP); - counter = new BasicCounter(MonitorConfig.builder(name).withTags(tags).build()); - map.putIfAbsent(name, counter); - DefaultMonitorRegistry.getInstance().register(counter); - } - } - return counter; - } -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/plugins/MetricPoller.java b/zuul-core/src/main/java/com/netflix/zuul/plugins/MetricPoller.java deleted file mode 100644 index 4efc5e44..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/plugins/MetricPoller.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.plugins; - -import com.netflix.servo.publish.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -/** - * Sample poller to poll metrics using Servo's metric publication - * @author Mikey Cohen - * Date: 4/18/13 - * Time: 9:20 AM - */ -public class MetricPoller { - - private static Logger LOG = LoggerFactory.getLogger(MetricPoller.class); - - final static PollScheduler scheduler = PollScheduler.getInstance(); - - public static void startPoller(){ - scheduler.start(); - final int heartbeatInterval = 1200; - - final File metricsDir; - try { - metricsDir = File.createTempFile("zuul-servo-metrics-", ""); - metricsDir.delete(); - metricsDir.mkdir(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - LOG.debug("created metrics dir " + metricsDir.getAbsolutePath()); - - MetricObserver transform = new CounterToRateMetricTransform( - new FileMetricObserver("ZuulMetrics", metricsDir), - heartbeatInterval, TimeUnit.SECONDS); - - PollRunnable task = new PollRunnable( - new MonitorRegistryMetricPoller(), - BasicMetricFilter.MATCH_ALL, - transform); - - final int samplingInterval = 10; - scheduler.addPoller(task, samplingInterval, TimeUnit.SECONDS); - - } - -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/plugins/Tracer.java b/zuul-core/src/main/java/com/netflix/zuul/plugins/Tracer.java index 13b37581..bb50cfbe 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/plugins/Tracer.java +++ b/zuul-core/src/main/java/com/netflix/zuul/plugins/Tracer.java @@ -15,15 +15,10 @@ */ package com.netflix.zuul.plugins; -import com.netflix.servo.monitor.DynamicTimer; -import com.netflix.servo.monitor.MonitorConfig; -import com.netflix.servo.monitor.Stopwatch; -import com.netflix.servo.tag.InjectableTag; -import com.netflix.servo.tag.Tag; +import com.netflix.spectator.api.Spectator; import com.netflix.zuul.monitoring.TracerFactory; - -import java.util.ArrayList; -import java.util.List; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.concurrent.TimeUnit; /** @@ -35,37 +30,47 @@ */ public class Tracer extends TracerFactory { - static List tags = new ArrayList(2); - - static { - tags.add(InjectableTag.HOSTNAME); - tags.add(InjectableTag.IP); - } - @Override public com.netflix.zuul.monitoring.Tracer startMicroTracer(String name) { - return new ServoTracer(name); + return new SpectatorTracer(name); } - class ServoTracer implements com.netflix.zuul.monitoring.Tracer { + class SpectatorTracer implements com.netflix.zuul.monitoring.Tracer { - final MonitorConfig config; - final Stopwatch stopwatch; + private String name; + private final long start; - private ServoTracer(String name) { - config = MonitorConfig.builder(name).withTags(tags).build(); - stopwatch = DynamicTimer.start(config, TimeUnit.MICROSECONDS); + private SpectatorTracer(String name) { + this.name = name; + start = System.nanoTime(); } @Override public void stopAndLog() { - DynamicTimer.record(config, stopwatch.getDuration()); + Spectator.globalRegistry().timer(name, "hostname", getHostName(), "ip", getIp()) + .record(System.nanoTime() - start, TimeUnit.NANOSECONDS); } @Override public void setName(String name) { + this.name = name; + } + } + + private static String getHostName() { + return (loadAddress() != null) ? loadAddress().getHostName() : "unkownHost"; + } + + private static String getIp() { + return (loadAddress() != null) ? loadAddress().getHostAddress() : "unknownHost"; + } + private static InetAddress loadAddress() { + try { + return InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + return null; } } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/BasicRequestMetricsPublisher.java b/zuul-core/src/main/java/com/netflix/zuul/stats/BasicRequestMetricsPublisher.java index 2b8cef18..d05ac1d1 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/BasicRequestMetricsPublisher.java +++ b/zuul-core/src/main/java/com/netflix/zuul/stats/BasicRequestMetricsPublisher.java @@ -16,8 +16,6 @@ package com.netflix.zuul.stats; -import com.netflix.servo.monitor.DynamicTimer; -import com.netflix.servo.monitor.MonitorConfig; import com.netflix.zuul.context.SessionContext; /** @@ -28,37 +26,8 @@ public class BasicRequestMetricsPublisher implements RequestMetricsPublisher { @Override - public void collectAndPublish(SessionContext context) - { - // Request timings. - long totalRequestTime = context.getTimings().getRequest().getDuration(); - long requestProxyTime = context.getTimings().getRequestProxy().getDuration(); - int originReportedDuration = context.getOriginReportedDuration(); + public void collectAndPublish(SessionContext context) { + // Record metrics here. - // Approximation of time spent just within Zuul's own processing of the request. - long totalInternalTime = totalRequestTime - requestProxyTime; - - // Approximation of time added to request by addition of Zuul+NIWS - // (ie. the time added compared to if ELB sent request direct to Origin). - // if -1, means we don't have that metric. - long totalTimeAddedToOrigin = -1; - if (originReportedDuration > -1) { - totalTimeAddedToOrigin = totalRequestTime - originReportedDuration; - } - - // Publish - final String METRIC_TIMINGS_REQ_PREFIX = "zuul.timings.request."; - recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "total", totalRequestTime); - recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "proxy", requestProxyTime); - recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "internal", totalInternalTime); - recordRequestTiming(METRIC_TIMINGS_REQ_PREFIX + "added", totalTimeAddedToOrigin); - } - - private void recordRequestTiming(String name, long timeNs) - { - long timeMs = timeNs / 1000000; - if(timeMs > -1) { - DynamicTimer.record(MonitorConfig.builder(name).build(), timeMs); - } } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/ErrorStatsData.java b/zuul-core/src/main/java/com/netflix/zuul/stats/ErrorStatsData.java index 99c06c83..167a0621 100755 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/ErrorStatsData.java +++ b/zuul-core/src/main/java/com/netflix/zuul/stats/ErrorStatsData.java @@ -15,12 +15,9 @@ */ package com.netflix.zuul.stats; -import com.netflix.servo.annotations.DataSourceType; -import com.netflix.servo.annotations.Monitor; -import com.netflix.servo.annotations.MonitorTags; -import com.netflix.servo.tag.BasicTag; -import com.netflix.servo.tag.BasicTagList; -import com.netflix.servo.tag.TagList; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Spectator; +import com.netflix.spectator.api.patterns.PolledMeter; import com.netflix.zuul.stats.monitoring.NamedCount; import java.util.concurrent.atomic.AtomicLong; @@ -32,19 +29,11 @@ * Date: 2/23/12 * Time: 4:16 PM */ -public class ErrorStatsData implements NamedCount -{ +public class ErrorStatsData implements NamedCount { + private final String id; - - String id; - - @MonitorTags - TagList tagList; - - String error_cause; - - @Monitor(name = "count", type = DataSourceType.COUNTER) - AtomicLong count = new AtomicLong(); + private final String errorCause; + private final AtomicLong count = new AtomicLong(); /** * create a counter by route and cause of error @@ -57,11 +46,11 @@ public ErrorStatsData(String route, String cause) { } id = route + "_" + cause; - this.error_cause = cause; - tagList = BasicTagList.of(new BasicTag("ID", id)); - - - + this.errorCause = cause; + Registry registry = Spectator.globalRegistry(); + PolledMeter.using(registry) + .withId(registry.createId("zuul.ErrorStatsData", "ID", id)) + .monitorValue(this, ErrorStatsData::getCount); } @Override @@ -71,13 +60,13 @@ public boolean equals(Object o) { ErrorStatsData that = (ErrorStatsData) o; - return !(error_cause != null ? !error_cause.equals(that.error_cause) : that.error_cause != null); + return !(errorCause != null ? !errorCause.equals(that.errorCause) : that.errorCause != null); } @Override public int hashCode() { - return error_cause != null ? error_cause.hashCode() : 0; + return errorCause != null ? errorCause.hashCode() : 0; } /** @@ -93,7 +82,7 @@ public String getName() { } @Override - public long getCount() { + public long getCount() { return count.get(); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/NamedCountingMonitor.java b/zuul-core/src/main/java/com/netflix/zuul/stats/NamedCountingMonitor.java index 91efe7da..fa18374b 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/NamedCountingMonitor.java +++ b/zuul-core/src/main/java/com/netflix/zuul/stats/NamedCountingMonitor.java @@ -15,11 +15,9 @@ */ package com.netflix.zuul.stats; -import com.netflix.servo.annotations.DataSourceType; -import com.netflix.servo.annotations.Monitor; -import com.netflix.servo.annotations.MonitorTags; -import com.netflix.servo.tag.BasicTagList; -import com.netflix.servo.tag.TagList; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Spectator; +import com.netflix.spectator.api.patterns.PolledMeter; import com.netflix.zuul.stats.monitoring.MonitorRegistry; import com.netflix.zuul.stats.monitoring.NamedCount; @@ -30,25 +28,23 @@ * * @author mhawthorne */ -public class NamedCountingMonitor implements NamedCount{ +public class NamedCountingMonitor implements NamedCount { private final String name; - @MonitorTags - TagList tagList; - - @Monitor(name = "count", type = DataSourceType.COUNTER) private final AtomicLong count = new AtomicLong(); public NamedCountingMonitor(String name) { this.name = name; - tagList = BasicTagList.of("ID", name); + Registry registry = Spectator.globalRegistry(); + PolledMeter.using(registry) + .withId(registry.createId("zuul.ErrorStatsData", "ID", name)) + .monitorValue(this, NamedCountingMonitor::getCount); } /** - * reguisters this objects - * @return + * registers this objects */ public NamedCountingMonitor register() { MonitorRegistry.getInstance().registerObject(this); @@ -57,7 +53,6 @@ public NamedCountingMonitor register() { /** * increments the counter - * @return */ public long increment() { return this.count.incrementAndGet(); @@ -69,7 +64,6 @@ public String getName() { } /** - * * @return the current count */ public long getCount() { diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/RouteStatusCodeMonitor.java b/zuul-core/src/main/java/com/netflix/zuul/stats/RouteStatusCodeMonitor.java index ae559ee5..55543ef2 100755 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/RouteStatusCodeMonitor.java +++ b/zuul-core/src/main/java/com/netflix/zuul/stats/RouteStatusCodeMonitor.java @@ -16,15 +16,13 @@ package com.netflix.zuul.stats; import com.google.common.annotations.VisibleForTesting; -import com.netflix.servo.annotations.DataSourceType; -import com.netflix.servo.annotations.Monitor; -import com.netflix.servo.annotations.MonitorTags; -import com.netflix.servo.tag.BasicTag; -import com.netflix.servo.tag.BasicTagList; -import com.netflix.servo.tag.TagList; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Spectator; +import com.netflix.spectator.api.patterns.PolledMeter; import com.netflix.zuul.stats.monitoring.NamedCount; - +import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; +import javax.annotation.Nullable; /** * counter for per route/status code counting @@ -33,27 +31,24 @@ * Time: 3:04 PM */ public class RouteStatusCodeMonitor implements NamedCount { - - @MonitorTags - TagList tagList; - - - String route_code; - - String route; - int status_code; - - @Monitor(name = "count", type = DataSourceType.COUNTER) + private final String routeCode; @VisibleForTesting - final AtomicLong count = new AtomicLong(); + final String route; + private final int statusCode; + private final AtomicLong count = new AtomicLong(); - public RouteStatusCodeMonitor(String route, int status_code) { - if(route == null) route = ""; + public RouteStatusCodeMonitor(@Nullable String route, int statusCode) { + if (route == null) { + route = ""; + } this.route = route; - this.status_code = status_code; - route_code = route + "_" + status_code; - tagList = BasicTagList.of(new BasicTag("ID", route_code)); + this.statusCode = statusCode; + this.routeCode = route + "_" + statusCode; + Registry registry = Spectator.globalRegistry(); + PolledMeter.using(registry) + .withId(registry.createId("zuul.RouteStatusCodeMonitor", "ID", routeCode)) + .monitorValue(this, RouteStatusCodeMonitor::getCount); } @Override @@ -63,8 +58,12 @@ public boolean equals(Object o) { RouteStatusCodeMonitor statsData = (RouteStatusCodeMonitor) o; - if (status_code != statsData.status_code) return false; - if (route != null ? !route.equals(statsData.route) : statsData.route != null) return false; + if (statusCode != statsData.statusCode) { + return false; + } + if (!Objects.equals(route, statsData.route)) { + return false; + } return true; } @@ -72,13 +71,13 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = route != null ? route.hashCode() : 0; - result = 31 * result + status_code; + result = 31 * result + statusCode; return result; } @Override public String getName() { - return route_code; + return routeCode; } public long getCount() { diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/Timing.java b/zuul-core/src/main/java/com/netflix/zuul/stats/Timing.java deleted file mode 100644 index 6c421622..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/Timing.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.stats; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Generic timing helper, in nanoseconds. - * - * User: michaels@netflix.com - * Date: 12/13/13 - * Time: 5:05 PM - */ -public class Timing -{ - private static final Logger LOG = LoggerFactory.getLogger(Timing.class); - - private String name; - private long startTime = 0; - private long endTime = 0; - private long duration = 0; - - public Timing(String name) { - this.name = name; - } - - public void start() { - this.startTime = System.nanoTime(); - } - - public void end() { - this.endTime = System.nanoTime(); - this.duration = endTime - startTime; - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Timing: name=%s, duration=%s", name, duration)); - } - } - - public String getName() { - return name; - } - - public long getStartTime() { - return startTime; - } - - public long getEndTime() { - return endTime; - } - - public long getDuration() { - return duration; - } - - @Override - public String toString() - { - return Long.toString(duration); - } -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/Timings.java b/zuul-core/src/main/java/com/netflix/zuul/stats/Timings.java deleted file mode 100644 index 21c571a7..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/Timings.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.stats; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * User: michaels@netflix.com - * Date: 6/15/15 - * Time: 4:16 PM - */ -public class Timings -{ - private final Timing request = new Timing("_requestTiming"); - private final Timing requestProxy = new Timing("_requestProxyTiming"); - private final Timing requestBodyRead = new Timing("_requestBodyReadTiming"); - private final Timing responseBodyRead = new Timing("_responseBodyReadTiming"); - private final Timing requestBodyWrite = new Timing("_requestBodyWriteTiming"); - private final Timing responseBodyWrite = new Timing("_responseBodyWriteTiming"); - - protected final ConcurrentHashMap additionalTimings = new ConcurrentHashMap<>(); - - public Timing get(String name) - { - return additionalTimings.computeIfAbsent(name, (newName) -> new Timing(newName)); - } - - /* Following are some standard Zuul timings: */ - - public Timing getRequest() - { - return request; - } - public Timing getRequestProxy() - { - return requestProxy; - } - public Timing getRequestBodyRead() - { - return requestBodyRead; - } - public Timing getResponseBodyRead() - { - return responseBodyRead; - } - public Timing getRequestBodyWrite() - { - return requestBodyWrite; - } - public Timing getResponseBodyWrite() - { - return responseBodyWrite; - } -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/ZuulEvent.java b/zuul-core/src/main/java/com/netflix/zuul/stats/ZuulEvent.java deleted file mode 100644 index 2b46f833..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/ZuulEvent.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.stats; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * An event that can be published to an NFEventPublisher and consumed by an NFEventListener, - * defined by String key-value pairs. - * - * @author mhawthorne - */ -public class ZuulEvent { - - - private static final Logger LOG = LoggerFactory.getLogger(ZuulEvent.class); - - - private static final String TYPE_KEY = "type"; - - private final JSONObject json; - - public ZuulEvent() { - this(new JSONObject()); - } - - public ZuulEvent(JSONObject json) { - this.json = json; - } - - @Deprecated - public ZuulEvent setAttribute(String key, String value) { - return this.set(key, value); - } - - public ZuulEvent set(String key, Object value) { - try { - this.json.put(key, value); - } catch (JSONException e) { - throw new IllegalStateException(e); - } - return this; - } - - public Object get(String key) { - if(!this.has(key)) { - return null; - } - - try { - return this.json.get(key); - } catch (JSONException e) { - LOG.debug(e.getMessage(),e); - return null; - } - } - - public boolean has(String key) { - return this.json.has(key); - } - - public Iterator keys() { - return this.json.keys(); - } - - public JSONObject toJson() { - return this.json; - } - - public Map toMap() { - final Map m = new HashMap(); - for(final Iterator i = this.keys(); i.hasNext();) { - final String key = i.next(); - final Object val = this.get(key); - if (val != null) m.put(key, val); - } - return m; - } - - public Map toStringMap() { - final Map m = new HashMap(); - for(final Iterator i = this.keys(); i.hasNext();) { - final String key = i.next(); - final Object val = this.get(key); - if (val != null) m.put(key, val.toString()); - } - return m; - } - - @Override - public String toString() { - return String.format("%s%s", this.getClass().getSimpleName(), this.json); - } - -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/NamedCount.java b/zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/NamedCount.java index 3cc87c8c..c389971a 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/NamedCount.java +++ b/zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/NamedCount.java @@ -22,8 +22,6 @@ * Time: 4:33 PM */ public interface NamedCount { - - public String getName(); - public long getCount(); - + String getName(); + long getCount(); } diff --git a/zuul-core/src/main/java/com/netflix/zuul/util/DeepCopy.java b/zuul-core/src/main/java/com/netflix/zuul/util/DeepCopy.java deleted file mode 100755 index d86abd1d..00000000 --- a/zuul-core/src/main/java/com/netflix/zuul/util/DeepCopy.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * 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 com.netflix.zuul.util; - -import java.io.*; - -/** - * Deep copy of an Object. The Object must be Serializable - * @author Mikey Cohen - * Date: 1/31/12 - * Time: 11:54 AM - */ -public class DeepCopy { - /** - * Returns a copy of the object, or null if the object cannot - * be serialized. - * @param orig an Object value - * @return a deep copy of that Object - * @exception NotSerializableException if an error occurs - */ - public static Object copy(Object orig) throws NotSerializableException { - Object obj = null; - try { - // Write the object out to a byte array - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(orig); - out.flush(); - out.close(); - - // Make an input stream from the byte array and read - // a copy of the object back in. - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - obj = in.readObject(); - } catch (NotSerializableException e) { - throw e; - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException cnfe) { - cnfe.printStackTrace(); - } - return obj; - } -} diff --git a/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java b/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java index 183a371c..9f7a13b3 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java +++ b/zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java @@ -15,6 +15,7 @@ */ package com.netflix.zuul.util; +import com.google.common.base.Strings; import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.ZuulMessage; import com.netflix.zuul.message.http.HttpHeaderNames; @@ -22,7 +23,7 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http2.Http2StreamChannel; -import org.apache.commons.lang3.StringUtils; +import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,10 +103,15 @@ public static boolean acceptsGzip(Headers headers) { * @param input - decoded header string * @return - clean header string */ - public static String stripMaliciousHeaderChars(String input) - { + public static String stripMaliciousHeaderChars(@Nullable String input) { + if (input == null) { + return null; + } + // TODO(carl-mastrangelo): implement this more efficiently. for (char c : MALICIOUS_HEADER_CHARS) { - input = StringUtils.remove(input, c); + if (input.indexOf(c) != -1) { + input = input.replace(Character.toString(c), ""); + } } return input; } @@ -120,13 +126,13 @@ public static boolean hasNonZeroContentLengthHeader(ZuulMessage msg) public static Integer getContentLengthIfPresent(ZuulMessage msg) { final String contentLengthValue = msg.getHeaders().getFirst(com.netflix.zuul.message.http.HttpHeaderNames.CONTENT_LENGTH); - if (StringUtils.isNotEmpty(contentLengthValue) && StringUtils.isNumeric(contentLengthValue)) { + if (!Strings.isNullOrEmpty(contentLengthValue)) { try { return Integer.valueOf(contentLengthValue); } catch (NumberFormatException e) { LOG.info("Invalid Content-Length header value on request. " + - "value = " + String.valueOf(contentLengthValue)); + "value = {}", contentLengthValue, e); } } return null; @@ -147,7 +153,7 @@ public static boolean hasChunkedTransferEncodingHeader(ZuulMessage msg) { boolean isChunked = false; String teValue = msg.getHeaders().getFirst(com.netflix.zuul.message.http.HttpHeaderNames.TRANSFER_ENCODING); - if (StringUtils.isNotEmpty(teValue)) { + if (!Strings.isNullOrEmpty(teValue)) { isChunked = "chunked".equals(teValue.toLowerCase()); } return isChunked; diff --git a/zuul-core/src/main/java/com/netflix/zuul/util/VipUtils.java b/zuul-core/src/main/java/com/netflix/zuul/util/VipUtils.java index 2ff6f646..1caab2b3 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/util/VipUtils.java +++ b/zuul-core/src/main/java/com/netflix/zuul/util/VipUtils.java @@ -16,17 +16,40 @@ package com.netflix.zuul.util; -public class VipUtils +public final class VipUtils { - public static String getVIPPrefix(String vipAddress) - { - String vipHost = vipAddress.split(":")[0]; - return vipHost.split("\\.")[0]; + public static String getVIPPrefix(String vipAddress) { + for (int i = 0; i < vipAddress.length(); i++) { + char c = vipAddress.charAt(i); + if (c == '.' || c == ':') { + return vipAddress.substring(0, i); + } + } + return vipAddress; } - public static String extractAppNameFromVIP(String vipAddress) - { + /** + * Use {@link #extractUntrustedAppNameFromVIP} instead. + */ + @Deprecated + public static String extractAppNameFromVIP(String vipAddress) { String vipPrefix = getVIPPrefix(vipAddress); return vipPrefix.split("-")[0]; } + + /** + * Attempts to derive an app name from the VIP. Because the VIP is an arbitrary collection of characters, the + * value is just a best guess and not suitable for security purposes. + */ + public static String extractUntrustedAppNameFromVIP(String vipAddress) { + for (int i = 0; i < vipAddress.length(); i++) { + char c = vipAddress.charAt(i); + if (c == '-' || c == '.' || c == ':') { + return vipAddress.substring(0, i); + } + } + return vipAddress; + } + + private VipUtils() {} } diff --git a/zuul-core/src/test/java/com/netflix/netty/common/CloseOnIdleStateHandlerTest.java b/zuul-core/src/test/java/com/netflix/netty/common/CloseOnIdleStateHandlerTest.java new file mode 100644 index 00000000..92fb14c6 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/netty/common/CloseOnIdleStateHandlerTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common; + +import static io.netty.handler.timeout.IdleStateEvent.ALL_IDLE_STATE_EVENT; +import static org.junit.Assert.assertEquals; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.Id; +import com.netflix.spectator.api.Registry; +import com.netflix.zuul.netty.server.http2.DummyChannelHandler; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CloseOnIdleStateHandlerTest { + + private Registry registry = new DefaultRegistry(); + private Id counterId; + private final String listener = "test-idle-state"; + + @Before + public void setup() { + counterId = registry.createId("server.connections.idle.timeout").withTags("id", listener); + } + + @Test + public void incrementCounterOnIdleStateEvent() { + final EmbeddedChannel channel = new EmbeddedChannel(); + channel.pipeline().addLast(new DummyChannelHandler()); + channel.pipeline().addLast(new CloseOnIdleStateHandler(registry, listener)); + + channel.pipeline().context(DummyChannelHandler.class).fireUserEventTriggered(ALL_IDLE_STATE_EVENT); + + final Counter idleTimeouts = (Counter) registry.get(counterId); + assertEquals(1, idleTimeouts.count()); + } +} diff --git a/zuul-core/src/test/java/com/netflix/netty/common/HttpServerLifecycleChannelHandlerTest.java b/zuul-core/src/test/java/com/netflix/netty/common/HttpServerLifecycleChannelHandlerTest.java new file mode 100644 index 00000000..61ea7365 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/netty/common/HttpServerLifecycleChannelHandlerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.netty.common; + +import com.google.common.truth.Truth; +import com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent; +import com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason; +import com.netflix.netty.common.HttpLifecycleChannelHandler.State; +import com.netflix.netty.common.HttpServerLifecycleChannelHandler.HttpServerLifecycleOutboundChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Test; + +public class HttpServerLifecycleChannelHandlerTest { + + final class AssertReasonHandler extends ChannelInboundHandlerAdapter { + + CompleteEvent completeEvent; + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + assert evt instanceof CompleteEvent; + this.completeEvent = (CompleteEvent) evt; + } + + public CompleteEvent getCompleteEvent() { + return completeEvent; + } + } + + @Test + public void completionEventReasonIsUpdatedOnPipelineReject() { + + final EmbeddedChannel channel = new EmbeddedChannel(new HttpServerLifecycleOutboundChannelHandler()); + final AssertReasonHandler reasonHandler = new AssertReasonHandler(); + channel.pipeline().addLast(reasonHandler); + + channel.attr(HttpLifecycleChannelHandler.ATTR_STATE).set(State.STARTED); + // emulate pipeline rejection + channel.attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT).set(Boolean.TRUE); + // Fire close + channel.pipeline().close(); + + Truth.assertThat(reasonHandler.getCompleteEvent().getReason()).isEqualTo(CompleteReason.PIPELINE_REJECT); + } + + @Test + public void completionEventReasonIsCloseByDefault() { + + final EmbeddedChannel channel = new EmbeddedChannel(new HttpServerLifecycleOutboundChannelHandler()); + final AssertReasonHandler reasonHandler = new AssertReasonHandler(); + channel.pipeline().addLast(reasonHandler); + + channel.attr(HttpLifecycleChannelHandler.ATTR_STATE).set(State.STARTED); + // Fire close + channel.pipeline().close(); + + Truth.assertThat(reasonHandler.getCompleteEvent().getReason()).isEqualTo(CompleteReason.CLOSE); + } +} diff --git a/zuul-core/src/test/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetectorTest.java b/zuul-core/src/test/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetectorTest.java index eb319d24..291ed114 100644 --- a/zuul-core/src/test/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetectorTest.java +++ b/zuul-core/src/test/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetectorTest.java @@ -22,7 +22,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class InstrumentedResourceLeakDetectorTest { diff --git a/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java new file mode 100644 index 00000000..dbfbc692 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java @@ -0,0 +1,266 @@ +/* + * Copyright 2019 Netflix, Inc. + * + * 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 com.netflix.netty.common.proxyprotocol; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.google.common.net.InetAddresses; +import com.netflix.netty.common.SourceAddressChannelHandler; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.Registry; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.haproxy.HAProxyMessage; +import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ElbProxyProtocolChannelHandlerTest { + + @Mock + private Registry registry; + @Mock + private Counter counter; + + @Before + public void setup() { + when(registry.counter("zuul.hapm.failure")).thenReturn(counter); + } + + @Test + public void noProxy() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, false)); + ByteBuf buf = Unpooled.wrappedBuffer( + "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object dropped = channel.readInbound(); + assertEquals(dropped, buf); + buf.release(); + + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); + assertNull(channel.pipeline().context("HAProxyMessageChannelHandler")); + assertNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION).get()); + assertNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); + } + + @Test + public void extraDataForwarded() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + ByteBuf buf = Unpooled.wrappedBuffer( + "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\nPOTATO".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object msg = channel.readInbound(); + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); + + ByteBuf readBuf = (ByteBuf) msg; + assertEquals("POTATO", new String(ByteBufUtil.getBytes(readBuf), StandardCharsets.US_ASCII)); + readBuf.release(); + } + + @Test + public void passThrough_ProxyProtocolEnabled_nonProxyBytes() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + //Note that the bytes aren't prefixed by PROXY, as required by the spec + ByteBuf buf = Unpooled.wrappedBuffer( + "TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object dropped = channel.readInbound(); + assertEquals(dropped, buf); + buf.release(); + + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); + assertNull(channel.pipeline().context("HAProxyMessageChannelHandler")); + assertNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION).get()); + assertNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); + assertNull(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); + } + + @Test + public void incrementCounterWhenPPEnabledButNonHAPMMessage() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + //Note that the bytes aren't prefixed by PROXY, as required by the spec + ByteBuf buf = Unpooled.wrappedBuffer( + "TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object dropped = channel.readInbound(); + assertEquals(dropped, buf); + buf.release(); + + verify(counter, times(1)).increment(); + } + + @Ignore + @Test + public void detectsSplitPpv1Message() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + ByteBuf buf1 = Unpooled.wrappedBuffer( + "PROXY TCP4".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf1); + ByteBuf buf2 = Unpooled.wrappedBuffer( + "192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf2); + + Object msg = channel.readInbound(); + assertTrue(msg instanceof HAProxyMessage); + buf1.release(); + buf2.release(); + ((HAProxyMessage) msg).release(); + + // The handler should remove itself. + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.class)); + } + + @Test + public void negotiateProxy_ppv1_ipv4() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + ByteBuf buf = Unpooled.wrappedBuffer( + "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object dropped = channel.readInbound(); + assertNull(dropped); + + // The handler should remove itself. + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); + assertNull(channel.pipeline().context(HAProxyMessageChannelHandler.class)); + assertEquals(HAProxyProtocolVersion.V1, channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION).get()); + // TODO(carl-mastrangelo): this check is in place, but it should be removed. The message is not properly GC'd + // in later versions of netty. + assertNotNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get()); + assertEquals("124.123.111.111", channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); + assertEquals(new InetSocketAddress(InetAddresses.forString("124.123.111.111"), 443), + channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); + assertEquals("192.168.0.1", channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); + assertEquals(new InetSocketAddress(InetAddresses.forString("192.168.0.1"), 10008), + channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); + } + + @Test + public void negotiateProxy_ppv1_ipv6() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + ByteBuf buf = Unpooled.wrappedBuffer( + "PROXY TCP6 ::1 ::2 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object dropped = channel.readInbound(); + assertNull(dropped); + + // The handler should remove itself. + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); + assertEquals( + HAProxyProtocolVersion.V1, channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION).get()); + // TODO(carl-mastrangelo): this check is in place, but it should be removed. The message is not properly GC'd + // in later versions of netty. + assertNotNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get()); + assertEquals("::2", channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); + assertEquals( + new InetSocketAddress(InetAddresses.forString("::2"), 443), + channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); + assertEquals("::1", channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); + assertEquals( + new InetSocketAddress(InetAddresses.forString("::1"), 10008), + channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); + } + + @Test + public void negotiateProxy_ppv2_ipv4() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + channel.pipeline() + .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true)); + ByteBuf buf = Unpooled.wrappedBuffer( + new byte[]{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A, 0x21, 0x11, 0x00, + 0x0C, (byte) 0xC0, (byte) 0xA8, 0x00, 0x01, 0x7C, 0x7B, 0x6F, 0x6F, 0x27, 0x18, 0x01, + (byte) 0xbb}); + channel.writeInbound(buf); + + Object dropped = channel.readInbound(); + assertNull(dropped); + + // The handler should remove itself. + assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); + assertEquals( + HAProxyProtocolVersion.V2, channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION).get()); + // TODO(carl-mastrangelo): this check is in place, but it should be removed. The message is not properly GC'd + // in later versions of netty. + assertNotNull(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get()); + assertEquals("124.123.111.111", channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); + assertEquals( + new InetSocketAddress(InetAddresses.forString("124.123.111.111"), 443), + channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); + assertEquals("192.168.0.1", channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); + assertEquals( + new InetSocketAddress(InetAddresses.forString("192.168.0.1"), 10008), + channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); + } +} diff --git a/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandlerTest.java b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandlerTest.java new file mode 100644 index 00000000..ee2409f7 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandlerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common.proxyprotocol; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import com.netflix.netty.common.SourceAddressChannelHandler; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class HAProxyMessageChannelHandlerTest { + + @Test + public void setClientDestPortForHAPM() { + EmbeddedChannel channel = new EmbeddedChannel(); + // This is normally done by Server. + channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance()); + // This is to emulate `ElbProxyProtocolChannelHandler` + channel.pipeline() + .addLast(HAProxyMessageDecoder.class.getSimpleName(), new HAProxyMessageDecoder()) + .addLast(HAProxyMessageChannelHandler.class.getSimpleName(), new HAProxyMessageChannelHandler()); + + ByteBuf buf = Unpooled.wrappedBuffer( + "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); + channel.writeInbound(buf); + + Object result = channel.readInbound(); + assertNull(result); + + InetSocketAddress destAddress = channel + .attr(SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS).get(); + + InetSocketAddress srcAddress = (InetSocketAddress) channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR) + .get(); + + assertEquals("124.123.111.111", destAddress.getHostString()); + assertEquals(443, destAddress.getPort()); + + assertEquals("192.168.0.1", srcAddress.getHostString()); + assertEquals(10008, srcAddress.getPort()); + + Attrs attrs = channel.attr(Server.CONN_DIMENSIONS).get(); + Integer port = HAProxyMessageChannelHandler.HAPM_DEST_PORT.get(attrs); + assertEquals(443, port.intValue()); + } +} \ No newline at end of file diff --git a/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/OptionalHAProxyMessageDecoderTest.java b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/OptionalHAProxyMessageDecoderTest.java deleted file mode 100644 index eebf3fc7..00000000 --- a/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/OptionalHAProxyMessageDecoderTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2019 Netflix, Inc. - * - * 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 com.netflix.netty.common.proxyprotocol; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.handler.codec.haproxy.HAProxyMessage; -import io.netty.util.ReferenceCounted; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -/** - * Unit tests for {@link OptionalHAProxyMessageDecoder}. - */ -@RunWith(JUnit4.class) -public class OptionalHAProxyMessageDecoderTest { - @Test - public void detectsPpv1Message() { - OptionalHAProxyMessageDecoder decoder = new OptionalHAProxyMessageDecoder(); - EmbeddedChannel channel = new EmbeddedChannel(); - channel.pipeline().addLast(OptionalHAProxyMessageDecoder.NAME, decoder); - - ByteBuf buf = Unpooled.wrappedBuffer( - "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf); - - Object msg = channel.readInbound(); - HAProxyMessage hapm = (HAProxyMessage) msg; - - // The handler should remove itself. - assertNull(channel.pipeline().context(OptionalHAProxyMessageDecoder.NAME)); - - // TODO(carl-mastrangelo): make this always happen once netty has been upgraded. - if (hapm instanceof ReferenceCounted) { - hapm.release(); - } - } - - @Test - @Ignore // TODO(carl-mastrangelo): reenable this. - public void detectsSplitPpv1Message() { - OptionalHAProxyMessageDecoder decoder = new OptionalHAProxyMessageDecoder(); - EmbeddedChannel channel = new EmbeddedChannel(); - channel.pipeline().addLast(OptionalHAProxyMessageDecoder.NAME, decoder); - - ByteBuf buf1 = Unpooled.wrappedBuffer( - "PROXY TCP4".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf1); - ByteBuf buf2 = Unpooled.wrappedBuffer( - "192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf2); - - Object msg = channel.readInbound(); - HAProxyMessage hapm = (HAProxyMessage) msg; - - // The handler should remove itself. - assertNull(channel.pipeline().context(OptionalHAProxyMessageDecoder.NAME)); - - // TODO(carl-mastrangelo): make this always happen once netty has been upgraded. - if (hapm instanceof ReferenceCounted) { - hapm.release(); - } - } - - @Test - public void extraDataForwarded() { - OptionalHAProxyMessageDecoder decoder = new OptionalHAProxyMessageDecoder(); - EmbeddedChannel channel = new EmbeddedChannel(); - channel.pipeline().addLast(OptionalHAProxyMessageDecoder.NAME, decoder); - - ByteBuf buf = Unpooled.wrappedBuffer( - "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\nPOTATO".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf); - - Object msg = channel.readInbound(); - HAProxyMessage hapm = (HAProxyMessage) msg; - - // The handler should remove itself. - assertNull(channel.pipeline().context(OptionalHAProxyMessageDecoder.NAME)); - - // TODO(carl-mastrangelo): make this always happen once netty has been upgraded. - if (hapm instanceof ReferenceCounted) { - hapm.release(); - } - - Object msg2 = channel.readInbound(); - ByteBuf readBuf = (ByteBuf) msg2; - assertEquals("POTATO", new String(ByteBufUtil.getBytes(readBuf), StandardCharsets.US_ASCII)); - readBuf.release(); - } - - @Test - public void ignoresNonPpMessage() { - OptionalHAProxyMessageDecoder decoder = new OptionalHAProxyMessageDecoder(); - EmbeddedChannel channel = new EmbeddedChannel(); - channel.pipeline().addLast(OptionalHAProxyMessageDecoder.NAME, decoder); - - ByteBuf buf = Unpooled.wrappedBuffer( - "BOGUS TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf); - - Object msg = channel.readInbound(); - ByteBuf readBuf = (ByteBuf) msg; - readBuf.release(); - - // TODO(carl-mastrangelo): this is wrong, it should remove itself. Change it. - assertNotNull(channel.pipeline().context(OptionalHAProxyMessageDecoder.NAME)); - } -} diff --git a/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandlerTest.java b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandlerTest.java new file mode 100644 index 00000000..79a1ec6a --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandlerTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common.proxyprotocol; + +import static com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler.ATTR_SSL_INFO; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableList; +import com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler.AllowWhen; +import com.netflix.netty.common.ssl.SslHandshakeInfo; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.ssl.ClientAuth; +import io.netty.util.AttributeKey; +import io.netty.util.DefaultAttributeMap; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Strip Untrusted Proxy Headers Handler Test + * + * @author Arthur Gonigberg + * @since May 27, 2020 + */ +@RunWith(MockitoJUnitRunner.class) +public class StripUntrustedProxyHeadersHandlerTest { + + @Mock + private ChannelHandlerContext channelHandlerContext; + @Mock + private HttpRequest msg; + private HttpHeaders headers; + @Mock + private Channel channel; + @Mock + private SslHandshakeInfo sslHandshakeInfo; + + + @Before + public void before() { + when(channelHandlerContext.channel()).thenReturn(channel); + + DefaultAttributeMap attributeMap = new DefaultAttributeMap(); + attributeMap.attr(ATTR_SSL_INFO).set(sslHandshakeInfo); + when(channel.attr(any())).thenAnswer(arg -> attributeMap.attr((AttributeKey) arg.getArguments()[0])); + + headers = new DefaultHttpHeaders(); + when(msg.headers()).thenReturn(headers); + headers.add(HttpHeaderNames.HOST, "netflix.com"); + } + + @Test + public void allow_never() throws Exception { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.NEVER); + + stripHandler.channelRead(channelHandlerContext, msg); + + verify(stripHandler).stripXFFHeaders(any()); + } + + @Test + public void allow_always() throws Exception { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.ALWAYS); + + stripHandler.channelRead(channelHandlerContext, msg); + + verify(stripHandler, never()).stripXFFHeaders(any()); + verify(stripHandler).checkBlacklist(any(), any()); + } + + @Test + public void allow_mtls_noCert() throws Exception { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH); + + stripHandler.channelRead(channelHandlerContext, msg); + + verify(stripHandler).stripXFFHeaders(any()); + } + + @Test + public void allow_mtls_cert() throws Exception { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH); + when(sslHandshakeInfo.getClientAuthRequirement()).thenReturn(ClientAuth.REQUIRE); + + stripHandler.channelRead(channelHandlerContext, msg); + + verify(stripHandler, never()).stripXFFHeaders(any()); + verify(stripHandler).checkBlacklist(any(), any()); + } + + @Test + public void blacklist_noMatch() { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH); + + stripHandler.checkBlacklist(msg, ImmutableList.of("netflix.net")); + + verify(stripHandler, never()).stripXFFHeaders(any()); + } + + @Test + public void blacklist_match() { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH); + + stripHandler.checkBlacklist(msg, ImmutableList.of("netflix.com")); + + verify(stripHandler).stripXFFHeaders(any()); + } + + @Test + public void blacklist_match_casing() { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH); + + stripHandler.checkBlacklist(msg, ImmutableList.of("NeTfLiX.cOm")); + + verify(stripHandler).stripXFFHeaders(any()); + } + + @Test + public void strip_match() { + StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH); + + headers.add("x-forwarded-for", "abcd"); + stripHandler.stripXFFHeaders(msg); + + assertFalse(headers.contains("x-forwarded-for")); + } + + private StripUntrustedProxyHeadersHandler getHandler(AllowWhen allowWhen) { + return spy(new StripUntrustedProxyHeadersHandler(allowWhen)); + } + +} \ No newline at end of file diff --git a/zuul-core/src/test/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandlerTest.java b/zuul-core/src/test/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandlerTest.java new file mode 100644 index 00000000..d561d94f --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandlerTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.netty.common.throttle; + +import static com.netflix.netty.common.throttle.MaxInboundConnectionsHandler.ATTR_CH_THROTTLED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.Id; +import com.netflix.spectator.api.Registry; +import com.netflix.zuul.netty.server.http2.DummyChannelHandler; +import com.netflix.zuul.passport.CurrentPassport; +import com.netflix.zuul.passport.PassportState; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MaxInboundConnectionsHandlerTest { + + private Registry registry = new DefaultRegistry(); + private String listener = "test-throttled"; + private Id counterId; + + @Before + public void setup() { + counterId = registry.createId("server.connections.throttled").withTags("id", listener); + } + + @Test + public void verifyPassportStateAndAttrs() { + + final EmbeddedChannel channel = new EmbeddedChannel(); + channel.pipeline().addLast(new DummyChannelHandler()); + channel.pipeline().addLast(new MaxInboundConnectionsHandler(registry, listener, 1)); + + // Fire twice to increment current conns. count + channel.pipeline().context(DummyChannelHandler.class).fireChannelActive(); + channel.pipeline().context(DummyChannelHandler.class).fireChannelActive(); + + final Counter throttledCount = (Counter) registry.get(counterId); + + assertEquals(1, throttledCount.count()); + assertEquals(PassportState.SERVER_CH_THROTTLING, CurrentPassport.fromChannel(channel).getState()); + assertTrue(channel.attr(ATTR_CH_THROTTLED).get()); + + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/AttrsTest.java b/zuul-core/src/test/java/com/netflix/zuul/AttrsTest.java new file mode 100644 index 00000000..12078982 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/AttrsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +import com.google.common.truth.Truth; +import com.netflix.zuul.Attrs.Key; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AttrsTest { + @Test + public void keysAreUnique() { + Attrs attrs = Attrs.newInstance(); + Key key1 = Attrs.newKey("foo"); + key1.put(attrs, "bar"); + Key key2 = Attrs.newKey("foo"); + key2.put(attrs, "baz"); + + Truth.assertThat(attrs.keySet()).containsExactly(key1, key2); + } + + @Test + public void newKeyFailsOnNull() { + assertThrows(NullPointerException.class, () -> Attrs.newKey(null)); + } + + @Test + public void attrsPutFailsOnNull() { + Attrs attrs = Attrs.newInstance(); + Key key = Attrs.newKey("foo"); + + assertThrows(NullPointerException.class, () -> key.put(attrs, null)); + } + + @Test + public void attrsPutReplacesOld() { + Attrs attrs = Attrs.newInstance(); + Key key = Attrs.newKey("foo"); + key.put(attrs, "bar"); + key.put(attrs, "baz"); + + assertEquals("baz", key.get(attrs)); + Truth.assertThat(attrs.keySet()).containsExactly(key); + } + + @Test + public void getReturnsNull() { + Attrs attrs = Attrs.newInstance(); + Key key = Attrs.newKey("foo"); + + assertNull(key.get(attrs)); + } + + @Test + public void getOrDefault_picksDefault() { + Attrs attrs = Attrs.newInstance(); + Key key = Attrs.newKey("foo"); + + assertEquals("bar", key.getOrDefault(attrs, "bar")); + } + + @Test + public void getOrDefault_failsOnNullDefault() { + Attrs attrs = Attrs.newInstance(); + Key key = Attrs.newKey("foo"); + key.put(attrs, "bar"); + + assertThrows(NullPointerException.class, () -> key.getOrDefault(attrs, null)); + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/FilterLoaderTest.java b/zuul-core/src/test/java/com/netflix/zuul/DynamicFilterLoaderTest.java similarity index 74% rename from zuul-core/src/test/java/com/netflix/zuul/FilterLoaderTest.java rename to zuul-core/src/test/java/com/netflix/zuul/DynamicFilterLoaderTest.java index 719b67dc..a7299e6a 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/FilterLoaderTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/DynamicFilterLoaderTest.java @@ -15,23 +15,20 @@ */ package com.netflix.zuul; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.netflix.zuul.filters.BaseFilter; import com.netflix.zuul.filters.BaseSyncFilter; import com.netflix.zuul.filters.FilterRegistry; import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.MutableFilterRegistry; import com.netflix.zuul.filters.ZuulFilter; import com.netflix.zuul.message.ZuulMessage; import java.io.File; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.junit.Before; import org.junit.Test; @@ -41,29 +38,28 @@ import org.mockito.MockitoAnnotations; @RunWith(JUnit4.class) -public class FilterLoaderTest { +public class DynamicFilterLoaderTest { @Mock - File file; + private File file; @Mock - DynamicCodeCompiler compiler; + private DynamicCodeCompiler compiler; - @Mock - FilterRegistry registry; + private final FilterRegistry registry = new MutableFilterRegistry(); - FilterFactory filterFactory = new DefaultFilterFactory(); + private final FilterFactory filterFactory = new DefaultFilterFactory(); - FilterLoader loader; + private DynamicFilterLoader loader; - TestZuulFilter filter = new TestZuulFilter(); + private final TestZuulFilter filter = new TestZuulFilter(); @Before public void before() throws Exception { MockitoAnnotations.initMocks(this); - loader = spy(new FilterLoader(registry, compiler, filterFactory)); + loader = new DynamicFilterLoader(registry, compiler, filterFactory); doReturn(TestZuulFilter.class).when(compiler).compile(file); when(file.getAbsolutePath()).thenReturn("/filters/in/SomeFilter.groovy"); @@ -72,13 +68,17 @@ public void before() throws Exception @Test public void testGetFilterFromFile() throws Exception { assertTrue(loader.putFilter(file)); - verify(registry).put(any(String.class), any(BaseFilter.class)); + + Collection> filters = registry.getAllFilters(); + assertEquals(1, filters.size()); } @Test public void testPutFiltersForClasses() throws Exception { loader.putFiltersForClasses(new String[]{TestZuulFilter.class.getName()}); - verify(registry).put(any(String.class), any(BaseFilter.class)); + + Collection> filters = registry.getAllFilters(); + assertEquals(1, filters.size()); } @Test @@ -91,23 +91,21 @@ public void testPutFiltersForClassesException() throws Exception { caught = e; } assertTrue(caught != null); - verify(registry, times(0)).put(any(String.class), any(BaseFilter.class)); + Collection> filters = registry.getAllFilters(); + assertEquals(0, filters.size()); } @Test public void testGetFiltersByType() throws Exception { assertTrue(loader.putFilter(file)); - verify(registry).put(any(String.class), any(ZuulFilter.class)); - - final List filters = new ArrayList(); - filters.add(filter); - when(registry.getAllFilters()).thenReturn(filters); + Collection> filters = registry.getAllFilters(); + assertEquals(1, filters.size()); - List list = loader.getFiltersByType(FilterType.INBOUND); + Collection> list = loader.getFiltersByType(FilterType.INBOUND); assertTrue(list != null); assertTrue(list.size() == 1); - ZuulFilter filter = list.get(0); + ZuulFilter filter = list.iterator().next(); assertTrue(filter != null); assertTrue(filter.filterType().equals(FilterType.INBOUND)); } diff --git a/zuul-core/src/test/java/com/netflix/zuul/FilterFileManagerTest.java b/zuul-core/src/test/java/com/netflix/zuul/FilterFileManagerTest.java index 83bdb911..f25dab16 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/FilterFileManagerTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/FilterFileManagerTest.java @@ -23,12 +23,13 @@ import static org.mockito.Mockito.verify; import java.io.File; +import java.io.FilenameFilter; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; /** * Tests for {@link FilterFileManager}. @@ -51,7 +52,12 @@ public void before() { @Test public void testFileManagerInit() throws Exception { - FilterFileManager.FilterFileManagerConfig config = new FilterFileManager.FilterFileManagerConfig(new String[]{"test", "test1"}, new String[]{"com.netflix.blah.SomeFilter"}, 1); + FilterFileManager.FilterFileManagerConfig config = + new FilterFileManager.FilterFileManagerConfig( + new String[]{"test", "test1"}, + new String[]{"com.netflix.blah.SomeFilter"}, + 1, + (dir, name) -> false); FilterFileManager manager = new FilterFileManager(config, filterLoader); manager = spy(manager); diff --git a/zuul-core/src/test/java/com/netflix/zuul/StaticFilterLoaderTest.java b/zuul-core/src/test/java/com/netflix/zuul/StaticFilterLoaderTest.java new file mode 100644 index 00000000..d1113023 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/StaticFilterLoaderTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import com.google.common.collect.ImmutableSet; +import com.google.common.truth.Truth; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.ZuulFilter; +import com.netflix.zuul.filters.http.HttpInboundSyncFilter; +import com.netflix.zuul.message.http.HttpRequestMessage; +import java.util.ArrayList; +import java.util.List; +import java.util.SortedSet; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class StaticFilterLoaderTest { + + private static final FilterFactory factory = new DefaultFilterFactory(); + + @Test + public void getFiltersByType() { + StaticFilterLoader filterLoader = + new StaticFilterLoader(factory, + ImmutableSet.of(DummyFilter2.class, DummyFilter1.class, DummyFilter22.class)); + + SortedSet> filters = filterLoader.getFiltersByType(FilterType.INBOUND); + Truth.assertThat(filters).hasSize(3); + List> filterList = new ArrayList<>(filters); + + Truth.assertThat(filterList.get(0)).isInstanceOf(DummyFilter1.class); + Truth.assertThat(filterList.get(1)).isInstanceOf(DummyFilter2.class); + Truth.assertThat(filterList.get(2)).isInstanceOf(DummyFilter22.class); + } + + @Test + public void getFilterByNameAndType() { + StaticFilterLoader filterLoader = + new StaticFilterLoader(factory, ImmutableSet.of(DummyFilter2.class, DummyFilter1.class)); + + ZuulFilter filter = filterLoader.getFilterByNameAndType("Robin", FilterType.INBOUND); + + Truth.assertThat(filter).isInstanceOf(DummyFilter2.class); + } + + @Filter(order = 0, type = FilterType.INBOUND) + static class DummyFilter1 extends HttpInboundSyncFilter { + + @Override + public String filterName() { + return "Batman"; + } + + @Override + public int filterOrder() { + return 0; + } + + @Override + public boolean shouldFilter(HttpRequestMessage msg) { + return true; + } + + @Override + public HttpRequestMessage apply(HttpRequestMessage input) { + return input; + } + } + + @Filter(order = 1, type = FilterType.INBOUND) + static class DummyFilter2 extends HttpInboundSyncFilter { + + @Override + public String filterName() { + return "Robin"; + } + + @Override + public int filterOrder() { + return 1; + } + + @Override + public boolean shouldFilter(HttpRequestMessage msg) { + return true; + } + + @Override + public HttpRequestMessage apply(HttpRequestMessage input) { + return input; + } + } + + @Filter(order = 1, type = FilterType.INBOUND) + static class DummyFilter22 extends HttpInboundSyncFilter { + + @Override + public String filterName() { + return "Williams"; + } + + @Override + public int filterOrder() { + return 1; + } + + @Override + public boolean shouldFilter(HttpRequestMessage msg) { + return true; + } + + @Override + public HttpRequestMessage apply(HttpRequestMessage input) { + return input; + } + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/context/DebugTest.java b/zuul-core/src/test/java/com/netflix/zuul/context/DebugTest.java index 75c77967..3d2179cc 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/context/DebugTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/context/DebugTest.java @@ -26,20 +26,23 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; + +import com.google.common.truth.Truth; import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.http.HttpQueryParams; import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpRequestMessageImpl; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.message.http.HttpResponseMessageImpl; -import java.net.SocketAddress; +import com.netflix.zuul.message.util.HttpRequestBuilder; +import io.netty.handler.codec.http.HttpMethod; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.junit.runners.JUnit4; -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public class DebugTest { private SessionContext ctx; @@ -58,8 +61,10 @@ public void setup() { params = new HttpQueryParams(); params.add("k1", "v1"); - request = new HttpRequestMessageImpl(ctx, "HTTP/1.1", "post", "/some/where", - params, headers, "9.9.9.9", "https", 80, "localhost"); + request = new HttpRequestBuilder(ctx).withMethod(HttpMethod.POST) + .withUri("/some/where") + .withHeaders(headers) + .withQueryParams(params).build(); request.setBodyAsText("some text"); request.storeInboundRequest(); @@ -90,10 +95,10 @@ public void testWriteInboundRequestDebug() { Debug.writeDebugRequest(ctx, request, true).toBlocking().single(); List debugLines = getRequestDebug(ctx); - assertEquals(3, debugLines.size()); - assertEquals("REQUEST_INBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1", debugLines.get(0)); - assertEquals("REQUEST_INBOUND:: > HDR: Content-Length:13", debugLines.get(1)); - assertEquals("REQUEST_INBOUND:: > HDR: lah:deda", debugLines.get(2)); + Truth.assertThat(debugLines).containsExactly( + "REQUEST_INBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1", + "REQUEST_INBOUND:: > HDR: Content-Length:13", + "REQUEST_INBOUND:: > HDR: lah:deda"); } @Test @@ -103,10 +108,10 @@ public void testWriteOutboundRequestDebug() { Debug.writeDebugRequest(ctx, request, false).toBlocking().single(); List debugLines = getRequestDebug(ctx); - assertEquals(3, debugLines.size()); - assertEquals("REQUEST_OUTBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1", debugLines.get(0)); - assertEquals("REQUEST_OUTBOUND:: > HDR: Content-Length:13", debugLines.get(1)); - assertEquals("REQUEST_OUTBOUND:: > HDR: lah:deda", debugLines.get(2)); + Truth.assertThat(debugLines).containsExactly( + "REQUEST_OUTBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1", + "REQUEST_OUTBOUND:: > HDR: Content-Length:13", + "REQUEST_OUTBOUND:: > HDR: lah:deda"); } @Test @@ -116,11 +121,11 @@ public void testWriteRequestDebug_WithBody() { Debug.writeDebugRequest(ctx, request, true).toBlocking().single(); List debugLines = getRequestDebug(ctx); - assertEquals(4, debugLines.size()); - assertEquals("REQUEST_INBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1", debugLines.get(0)); - assertEquals("REQUEST_INBOUND:: > HDR: Content-Length:13", debugLines.get(1)); - assertEquals("REQUEST_INBOUND:: > HDR: lah:deda", debugLines.get(2)); - assertEquals("REQUEST_INBOUND:: > BODY: some text", debugLines.get(3)); + Truth.assertThat(debugLines).containsExactly( + "REQUEST_INBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1", + "REQUEST_INBOUND:: > HDR: Content-Length:13", + "REQUEST_INBOUND:: > HDR: lah:deda", + "REQUEST_INBOUND:: > BODY: some text"); } @Test @@ -130,10 +135,10 @@ public void testWriteInboundResponseDebug() { Debug.writeDebugResponse(ctx, response, true).toBlocking().single(); List debugLines = getRequestDebug(ctx); - assertEquals(3, debugLines.size()); - assertEquals("RESPONSE_INBOUND:: < STATUS: 200", debugLines.get(0)); - assertEquals("RESPONSE_INBOUND:: < HDR: Content-Length:13", debugLines.get(1)); - assertEquals("RESPONSE_INBOUND:: < HDR: lah:deda", debugLines.get(2)); + Truth.assertThat(debugLines).containsExactly( + "RESPONSE_INBOUND:: < STATUS: 200", + "RESPONSE_INBOUND:: < HDR: Content-Length:13", + "RESPONSE_INBOUND:: < HDR: lah:deda"); } @Test @@ -143,10 +148,10 @@ public void testWriteOutboundResponseDebug() { Debug.writeDebugResponse(ctx, response, false).toBlocking().single(); List debugLines = getRequestDebug(ctx); - assertEquals(3, debugLines.size()); - assertEquals("RESPONSE_OUTBOUND:: < STATUS: 200", debugLines.get(0)); - assertEquals("RESPONSE_OUTBOUND:: < HDR: Content-Length:13", debugLines.get(1)); - assertEquals("RESPONSE_OUTBOUND:: < HDR: lah:deda", debugLines.get(2)); + Truth.assertThat(debugLines).containsExactly( + "RESPONSE_OUTBOUND:: < STATUS: 200", + "RESPONSE_OUTBOUND:: < HDR: Content-Length:13", + "RESPONSE_OUTBOUND:: < HDR: lah:deda"); } @Test @@ -156,10 +161,20 @@ public void testWriteResponseDebug_WithBody() { Debug.writeDebugResponse(ctx, response, true).toBlocking().single(); List debugLines = getRequestDebug(ctx); - assertEquals(4, debugLines.size()); - assertEquals("RESPONSE_INBOUND:: < STATUS: 200", debugLines.get(0)); - assertEquals("RESPONSE_INBOUND:: < HDR: Content-Length:13", debugLines.get(1)); - assertEquals("RESPONSE_INBOUND:: < HDR: lah:deda", debugLines.get(2)); - assertEquals("RESPONSE_INBOUND:: < BODY: response text", debugLines.get(3)); + Truth.assertThat(debugLines).containsExactly( + "RESPONSE_INBOUND:: < STATUS: 200", + "RESPONSE_INBOUND:: < HDR: Content-Length:13", + "RESPONSE_INBOUND:: < HDR: lah:deda", + "RESPONSE_INBOUND:: < BODY: response text"); + } + + @Test + public void testNoCMEWhenComparingContexts() { + final SessionContext context = new SessionContext(); + final SessionContext copy = new SessionContext(); + + context.set("foo", "bar"); + + Debug.compareContextState("testfilter", context, copy); } } diff --git a/zuul-core/src/test/java/com/netflix/zuul/context/SessionContextTest.java b/zuul-core/src/test/java/com/netflix/zuul/context/SessionContextTest.java index c88235cd..1c6d6e0e 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/context/SessionContextTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/context/SessionContextTest.java @@ -19,7 +19,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class SessionContextTest { diff --git a/zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilter2Test.java b/zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilterTest.java similarity index 94% rename from zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilter2Test.java rename to zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilterTest.java index 602b6539..69934ef9 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilter2Test.java +++ b/zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilterTest.java @@ -29,11 +29,9 @@ /** * Tests for {@link BaseFilter}. Currently named BaseFilter2Test as there is an existing * class named BaseFilterTest. - * - * TODO(carl-mastrangelo): refactor {@link BaseFilterTest} to not conflict with this class. */ @RunWith(JUnit4.class) -public class BaseFilter2Test { +public class BaseFilterTest { @Mock private BaseFilter f1; diff --git a/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java b/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java index 009a4079..92d22463 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java @@ -19,9 +19,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; -import com.netflix.zuul.filters.BaseFilterTest; +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.message.Headers; import com.netflix.zuul.message.http.HttpHeaderNames; +import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpResponseMessage; import com.netflix.zuul.message.http.HttpResponseMessageImpl; import io.netty.buffer.Unpooled; @@ -29,26 +32,37 @@ import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.HttpContent; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.util.zip.GZIPInputStream; -import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) -public class GZipResponseFilterTest extends BaseFilterTest { +public class GZipResponseFilterTest { + private final SessionContext context = new SessionContext(); + private final Headers originalRequestHeaders = new Headers(); + + @Mock + private HttpRequestMessage request; + @Mock + private HttpRequestMessage originalRequest; GZipResponseFilter filter; HttpResponseMessage response; @Before public void setup() { - super.setup(); + //when(request.getContext()).thenReturn(context); + when(originalRequest.getHeaders()).thenReturn(originalRequestHeaders); + filter = Mockito.spy(new GZipResponseFilter()); response = new HttpResponseMessageImpl(context, request, 99); response.getHeaders().set(HttpHeaderNames.CONTENT_TYPE, "text/html"); + when(response.getInboundRequest()).thenReturn(originalRequest); } @Test @@ -70,14 +84,22 @@ public void prepareResponseBody_NeedsGZipping() throws Exception { hc1.content().readBytes(body, 0, hc1Len); hc2.content().readBytes(body, hc1Len, hc2Len); + String bodyStr; // Check body is a gzipped version of the origin body. - byte[] unzippedBytes = IOUtils.toByteArray(new GZIPInputStream(new ByteArrayInputStream(body))); - String bodyStr = new String(unzippedBytes, "UTF-8"); + try (ByteArrayInputStream bais = new ByteArrayInputStream(body); + GZIPInputStream gzis = new GZIPInputStream(bais); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + int b; + while ((b = gzis.read()) != -1) { + baos.write(b); + } + bodyStr = baos.toString("UTF-8"); + } assertEquals("blah", bodyStr); assertEquals("gzip", result.getHeaders().getFirst("Content-Encoding")); // Check Content-Length header has been removed.; - assertEquals(0, result.getHeaders().get("Content-Length").size()); + assertEquals(0, result.getHeaders().getAll("Content-Length").size()); } @Test diff --git a/zuul-core/src/test/java/com/netflix/zuul/message/HeadersTest.java b/zuul-core/src/test/java/com/netflix/zuul/message/HeadersTest.java index 64612dce..cd655a52 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/message/HeadersTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/message/HeadersTest.java @@ -17,19 +17,594 @@ package com.netflix.zuul.message; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.google.common.truth.Truth; +import com.netflix.zuul.exception.ZuulException; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.junit.runners.JUnit4; /** * Tests for {@link Headers}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public class HeadersTest { + @Test + public void copyOf() { + Headers headers = new Headers(); + headers.set("Content-Length", "5"); + Headers headers2 = Headers.copyOf(headers); + headers2.add("Via", "duct"); + + Truth.assertThat(headers.getAll("Via")).isEmpty(); + Truth.assertThat(headers2.size()).isEqualTo(2); + Truth.assertThat(headers2.getAll("Content-Length")).containsExactly("5"); + } + + @Test + public void getFirst_normalizesName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getFirst("cOOkIE")).isEqualTo("this=that"); + } + + @Test + public void getFirst_headerName_normalizesName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getFirst(new HeaderName("cOOkIE"))).isEqualTo("this=that"); + } + + @Test + public void getFirst_returnsNull() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getFirst("Date")).isNull(); + } + + @Test + public void getFirst_headerName_returnsNull() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getFirst(new HeaderName("Date"))).isNull(); + } + + @Test + public void getFirst_returnsDefault() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getFirst("Date", "tuesday")).isEqualTo("tuesday"); + } + + @Test + public void getFirst_headerName_returnsDefault() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getFirst(new HeaderName("Date"), "tuesday")).isEqualTo("tuesday"); + } + + @Test + public void forEachNormalised() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=Frazzle"); + Map> result = new LinkedHashMap<>(); + + headers.forEachNormalised((k, v) -> result.computeIfAbsent(k, discard -> new ArrayList<>()).add(v)); + + Truth.assertThat(result).containsExactly( + "via", Collections.singletonList("duct"), + "cookie", Arrays.asList("this=that", "frizzle=Frazzle")).inOrder(); + } + + @Test + public void getAll() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getAll("CookiE")).containsExactly("this=that", "frizzle=frazzle").inOrder(); + } + + @Test + public void getAll_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Truth.assertThat(headers.getAll(new HeaderName("CookiE"))) + .containsExactly("this=that", "frizzle=frazzle").inOrder(); + } + + @Test + public void setClearsExisting() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.set("cookIe", "dilly=dally"); + + Truth.assertThat(headers.getAll("CookiE")).containsExactly("dilly=dally"); + Truth.assertThat(headers.size()).isEqualTo(2); + } + + @Test + public void setClearsExisting_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.set(new HeaderName("cookIe"), "dilly=dally"); + + Truth.assertThat(headers.getAll("CookiE")).containsExactly("dilly=dally"); + Truth.assertThat(headers.size()).isEqualTo(2); + } + + @Test + public void setNullIsEmtpy() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.set("cookIe", null); + + Truth.assertThat(headers.getAll("CookiE")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void setNullIsEmtpy_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.set(new HeaderName("cookIe"), null); + + Truth.assertThat(headers.getAll("CookiE")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void setIfValidNullIsEmtpy() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfValid("cookIe", null); + + Truth.assertThat(headers.getAll("CookiE")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void setIfValidNullIsEmtpy_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfValid(new HeaderName("cookIe"), null); + + Truth.assertThat(headers.getAll("CookiE")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void setIfValidIgnoresInvalidValues() { + Headers headers = new Headers(); + headers.add("X-Valid-K1", "abc-xyz"); + headers.add("X-Valid-K2", "def-xyz"); + headers.add("X-Valid-K3", "xyz-xyz"); + + headers.setIfValid("X-Valid-K1", "abc\r\n-xy\r\nz"); + headers.setIfValid("X-Valid-K2", "abc\r-xy\rz"); + headers.setIfValid("X-Valid-K3", "abc\n-xy\nz"); + + Truth.assertThat(headers.getAll("X-Valid-K1")).containsExactly("abc-xyz"); + Truth.assertThat(headers.getAll("X-Valid-K2")).containsExactly("def-xyz"); + Truth.assertThat(headers.getAll("X-Valid-K3")).containsExactly("xyz-xyz"); + Truth.assertThat(headers.size()).isEqualTo(3); + } + + @Test + public void setIfValidIgnoresInvalidValues_headerName() { + Headers headers = new Headers(); + headers.add("X-Valid-K1", "abc-xyz"); + headers.add("X-Valid-K2", "def-xyz"); + headers.add("X-Valid-K3", "xyz-xyz"); + + headers.setIfValid(new HeaderName("X-Valid-K1"), "abc\r\n-xy\r\nz"); + headers.setIfValid(new HeaderName("X-Valid-K2"), "abc\r-xy\rz"); + headers.setIfValid(new HeaderName("X-Valid-K3"), "abc\n-xy\nz"); + + Truth.assertThat(headers.getAll("X-Valid-K1")).containsExactly("abc-xyz"); + Truth.assertThat(headers.getAll("X-Valid-K2")).containsExactly("def-xyz"); + Truth.assertThat(headers.getAll("X-Valid-K3")).containsExactly("xyz-xyz"); + Truth.assertThat(headers.size()).isEqualTo(3); + } + + @Test + public void setIfValidIgnoresInvalidKey() { + Headers headers = new Headers(); + headers.add("X-Valid-K1", "abc-xyz"); + + headers.setIfValid("X-K\r\ney-1", "abc-def"); + headers.setIfValid("X-K\ney-2", "def-xyz"); + headers.setIfValid("X-K\rey-3", "xyz-xyz"); + + Truth.assertThat(headers.getAll("X-Valid-K1")).containsExactly("abc-xyz"); + Truth.assertThat(headers.getAll("X-K\r\ney-1")).isEmpty(); + Truth.assertThat(headers.getAll("X-K\ney-2")).isEmpty(); + Truth.assertThat(headers.getAll("X-K\rey-3")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void setIfValidIgnoresInvalidKey_headerName() { + Headers headers = new Headers(); + headers.add("X-Valid-K1", "abc-xyz"); + + headers.setIfValid(new HeaderName("X-K\r\ney-1"), "abc-def"); + headers.setIfValid(new HeaderName("X-K\ney-2"), "def-xyz"); + headers.setIfValid(new HeaderName("X-K\rey-3"), "xyz-xyz"); + + Truth.assertThat(headers.getAll("X-Valid-K1")).containsExactly("abc-xyz"); + Truth.assertThat(headers.getAll("X-K\r\ney-1")).isEmpty(); + Truth.assertThat(headers.getAll("X-K\ney-2")).isEmpty(); + Truth.assertThat(headers.getAll("X-K\rey-3")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void setIfAbsentKeepsExisting() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfAbsent("cookIe", "dilly=dally"); + + Truth.assertThat(headers.getAll("CookiE")).containsExactly("this=that", "frizzle=frazzle").inOrder(); + } + + @Test + public void setIfAbsentKeepsExisting_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfAbsent(new HeaderName("cookIe"), "dilly=dally"); + + Truth.assertThat(headers.getAll("CookiE")).containsExactly("this=that", "frizzle=frazzle").inOrder(); + } + + @Test + public void setIfAbsentFailsOnNull() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + assertThrows(NullPointerException.class, () -> headers.setIfAbsent("cookIe", null)); + } + + @Test + public void setIfAbsentFailsOnNull_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + assertThrows(NullPointerException.class, () -> headers.setIfAbsent(new HeaderName("cookIe"), null)); + } + + @Test + public void setIfAbsent() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfAbsent("X-Netflix-Awesome", "true"); + + Truth.assertThat(headers.getAll("X-netflix-Awesome")).containsExactly("true"); + } + + @Test + public void setIfAbsent_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfAbsent(new HeaderName("X-Netflix-Awesome"), "true"); + + Truth.assertThat(headers.getAll("X-netflix-Awesome")).containsExactly("true"); + } + + @Test + public void setIfAbsentAndValid() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.setIfAbsentAndValid("X-Netflix-Awesome", "true"); + headers.setIfAbsentAndValid("X-Netflix-Awesome", "True"); + + Truth.assertThat(headers.getAll("X-netflix-Awesome")).containsExactly("true"); + Truth.assertThat(headers.size()).isEqualTo(4); + } + + @Test + public void setIfAbsentAndValidIgnoresInvalidValues() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + + headers.setIfAbsentAndValid("X-Invalid-K1", "abc\r\nxy\r\nz"); + headers.setIfAbsentAndValid("X-Invalid-K2", "abc\rxy\rz"); + headers.setIfAbsentAndValid("X-Invalid-K3", "abc\nxy\nz"); + + Truth.assertThat(headers.getAll("Via")).containsExactly("duct"); + Truth.assertThat(headers.getAll("X-Invalid-K1")).isEmpty(); + Truth.assertThat(headers.getAll("X-Invalid-K2")).isEmpty(); + Truth.assertThat(headers.getAll("X-Invalid-K3")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void add() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.add("via", "con Dios"); + + Truth.assertThat(headers.getAll("Via")).containsExactly("duct", "con Dios").inOrder(); + } + + @Test + public void add_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + headers.add(new HeaderName("via"), "con Dios"); + + Truth.assertThat(headers.getAll("Via")).containsExactly("duct", "con Dios").inOrder(); + } + + @Test + public void addIfValid() { + Headers headers = new Headers(); + headers.addIfValid("Via", "duct"); + headers.addIfValid("Cookie", "abc=def"); + headers.addIfValid("cookie", "uvw=xyz"); + + Truth.assertThat(headers.getAll("Via")).containsExactly("duct"); + Truth.assertThat(headers.getAll("Cookie")).containsExactly("abc=def", "uvw=xyz").inOrder(); + Truth.assertThat(headers.size()).isEqualTo(3); + } + + @Test + public void addIfValid_headerName() { + Headers headers = new Headers(); + headers.addIfValid("Via", "duct"); + headers.addIfValid("Cookie", "abc=def"); + headers.addIfValid(new HeaderName("cookie"), "uvw=xyz"); + + Truth.assertThat(headers.getAll("Via")).containsExactly("duct"); + Truth.assertThat(headers.getAll("Cookie")).containsExactly("abc=def", "uvw=xyz").inOrder(); + Truth.assertThat(headers.size()).isEqualTo(3); + } + + @Test + public void addIfValidIgnoresInvalidValues() { + Headers headers = new Headers(); + headers.addIfValid("Via", "duct"); + headers.addIfValid("Cookie", "abc=def"); + headers.addIfValid("X-Invalid-K1", "abc\r\nxy\r\nz"); + headers.addIfValid("X-Invalid-K2", "abc\rxy\rz"); + headers.addIfValid("X-Invalid-K3", "abc\nxy\nz"); + + Truth.assertThat(headers.getAll("Via")).containsExactly("duct"); + Truth.assertThat(headers.getAll("Cookie")).containsExactly("abc=def"); + Truth.assertThat(headers.getAll("X-Invalid-K1")).isEmpty(); + Truth.assertThat(headers.getAll("X-Invalid-K2")).isEmpty(); + Truth.assertThat(headers.getAll("X-Invalid-K3")).isEmpty(); + Truth.assertThat(headers.size()).isEqualTo(2); + } + + @Test + public void putAll() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + + Headers other = new Headers(); + other.add("cookie", "a=b"); + other.add("via", "com"); + + headers.putAll(other); + + // Only check the order per field, not for the entire set. + Truth.assertThat(headers.getAll("Via")).containsExactly("duct", "com").inOrder(); + Truth.assertThat(headers.getAll("coOkiE")).containsExactly("this=that", "frizzle=frazzle", "a=b").inOrder(); + Truth.assertThat(headers.size()).isEqualTo(5); + } + + + @Test + public void remove() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + List removed = headers.remove("Cookie"); + + Truth.assertThat(headers.getAll("Cookie")).isEmpty(); + Truth.assertThat(headers.getAll("Soup")).containsExactly("salad"); + Truth.assertThat(headers.size()).isEqualTo(2); + Truth.assertThat(removed).containsExactly("this=that", "frizzle=frazzle").inOrder(); + } + + @Test + public void remove_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + List removed = headers.remove(new HeaderName("Cookie")); + + Truth.assertThat(headers.getAll("Cookie")).isEmpty(); + Truth.assertThat(headers.getAll("Soup")).containsExactly("salad"); + Truth.assertThat(headers.size()).isEqualTo(2); + Truth.assertThat(removed).containsExactly("this=that", "frizzle=frazzle").inOrder(); + } + + @Test + public void removeEmpty() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + List removed = headers.remove("Monkey"); + + Truth.assertThat(headers.getAll("Cookie")).isNotEmpty(); + Truth.assertThat(headers.getAll("Soup")).containsExactly("salad"); + Truth.assertThat(headers.size()).isEqualTo(4); + Truth.assertThat(removed).isEmpty(); + } + + @Test + public void removeEmpty_headerName() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("Cookie", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + List removed = headers.remove(new HeaderName("Monkey")); + + Truth.assertThat(headers.getAll("Cookie")).isNotEmpty(); + Truth.assertThat(headers.getAll("Soup")).containsExactly("salad"); + Truth.assertThat(headers.size()).isEqualTo(4); + Truth.assertThat(removed).isEmpty(); + } + + @Test + public void removeIf() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("Cookie", "this=that"); + headers.add("COOkie", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + boolean removed = headers.removeIf(entry -> entry.getKey().getName().equals("Cookie")); + + assertTrue(removed); + Truth.assertThat(headers.getAll("cOoKie")).containsExactly("frizzle=frazzle"); + Truth.assertThat(headers.size()).isEqualTo(3); + } + + @Test + public void keySet() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("COOKie", "this=that"); + headers.add("cookIE", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + Set keySet = headers.keySet(); + + Truth.assertThat(keySet) + .containsExactly(new HeaderName("COOKie"), new HeaderName("Soup"), new HeaderName("Via")); + for (HeaderName headerName : keySet) { + if (headerName.getName().equals("COOKie")) { + return; + } + } + throw new AssertionError("didn't find right cookie in keys: " + keySet); + } + + @Test + public void contains() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("COOKie", "this=that"); + headers.add("cookIE", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + assertTrue(headers.contains("CoOkIe")); + assertTrue(headers.contains(new HeaderName("CoOkIe"))); + assertFalse(headers.contains("Monkey")); + assertFalse(headers.contains(new HeaderName("Monkey"))); + } + + @Test + public void containsValue() { + Headers headers = new Headers(); + headers.add("Via", "duct"); + headers.add("COOKie", "this=that"); + headers.add("cookIE", "frizzle=frazzle"); + headers.add("Soup", "salad"); + + // note the swapping of the two cookie casings. + assertTrue(headers.contains("CoOkIe", "frizzle=frazzle")); + assertTrue(headers.contains(new HeaderName("CoOkIe"), "frizzle=frazzle")); + assertFalse(headers.contains("Via", "lin")); + assertFalse(headers.contains(new HeaderName("Soup"), "of the day")); + } + @Test public void testCaseInsensitiveKeys_Set() { Headers headers = new Headers(); @@ -38,7 +613,7 @@ public void testCaseInsensitiveKeys_Set() { assertEquals("10", headers.getFirst("Content-Length")); assertEquals("10", headers.getFirst("content-length")); - assertEquals(1, headers.get("content-length").size()); + assertEquals(1, headers.getAll("content-length").size()); } @Test @@ -47,7 +622,7 @@ public void testCaseInsensitiveKeys_Add() { headers.add("Content-Length", "5"); headers.add("content-length", "10"); - List values = headers.get("content-length"); + List values = headers.getAll("content-length"); assertTrue(values.contains("10")); assertTrue(values.contains("5")); assertEquals(2, values.size()); @@ -59,7 +634,7 @@ public void testCaseInsensitiveKeys_SetIfAbsent() { headers.set("Content-Length", "5"); headers.setIfAbsent("content-length", "10"); - List values = headers.get("content-length"); + List values = headers.getAll("content-length"); assertEquals(1, values.size()); assertEquals("5", values.get(0)); } @@ -73,9 +648,74 @@ public void testCaseInsensitiveKeys_PutAll() { Headers headers2 = new Headers(); headers2.putAll(headers); - List values = headers2.get("content-length"); + List values = headers2.getAll("content-length"); assertTrue(values.contains("10")); assertTrue(values.contains("5")); assertEquals(2, values.size()); } + + @Test + public void testSanitizeValues_CRLF() { + Headers headers = new Headers(); + + assertThrows(ZuulException.class, () -> headers.addAndValidate("x-test-break1", "a\r\nb\r\nc")); + assertThrows(ZuulException.class, () -> headers.setAndValidate("x-test-break1", "a\r\nb\r\nc")); + } + + @Test + public void testSanitizeValues_LF() { + Headers headers = new Headers(); + + assertThrows(ZuulException.class, () -> headers.addAndValidate("x-test-break1", "a\nb\nc")); + assertThrows(ZuulException.class, () -> headers.setAndValidate("x-test-break1", "a\nb\nc")); + } + + @Test + public void testSanitizeValues_ISO88591Value() { + Headers headers = new Headers(); + + headers.addAndValidate("x-test-ISO-8859-1", "P Venkmän"); + Truth.assertThat(headers.getAll("x-test-ISO-8859-1")).containsExactly("P Venkmän"); + Truth.assertThat(headers.size()).isEqualTo(1); + + headers.setAndValidate("x-test-ISO-8859-1", "Venkmän"); + Truth.assertThat(headers.getAll("x-test-ISO-8859-1")).containsExactly("Venkmän"); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void testSanitizeValues_UTF8Value() { + // Ideally Unicode characters should not appear in the Header values. + Headers headers = new Headers(); + + String rawHeaderValue = "\u017d" + "\u0172" + "\u016e" + "\u013F"; //ŽŲŮĽ + byte[] bytes = rawHeaderValue.getBytes(StandardCharsets.UTF_8); + String utf8HeaderValue = new String(bytes, StandardCharsets.UTF_8); + headers.addAndValidate("x-test-UTF8", utf8HeaderValue); + Truth.assertThat(headers.getAll("x-test-UTF8")).containsExactly(utf8HeaderValue); + Truth.assertThat(headers.size()).isEqualTo(1); + + rawHeaderValue = "\u017d" + "\u0172" + "uuu" + "\u016e" + "\u013F"; //ŽŲuuuŮĽ + bytes = rawHeaderValue.getBytes(StandardCharsets.UTF_8); + utf8HeaderValue = new String(bytes, StandardCharsets.UTF_8); + headers.setAndValidate("x-test-UTF8", utf8HeaderValue); + Truth.assertThat(headers.getAll("x-test-UTF8")).containsExactly(utf8HeaderValue); + Truth.assertThat(headers.size()).isEqualTo(1); + } + + @Test + public void testSanitizeValues_addSetHeaderName() { + Headers headers = new Headers(); + + assertThrows(ZuulException.class, () -> headers.setAndValidate(new HeaderName("x-test-break1"), "a\nb\nc")); + assertThrows(ZuulException.class, () -> headers.addAndValidate(new HeaderName("x-test-break2"), "a\r\nb\r\nc")); + } + + @Test + public void testSanitizeValues_nameCRLF() { + Headers headers = new Headers(); + + assertThrows(ZuulException.class, () -> headers.addAndValidate("x-test-br\r\neak1", "a\r\nb\r\nc")); + assertThrows(ZuulException.class, () -> headers.setAndValidate("x-test-br\r\neak2", "a\r\nb\r\nc")); + } } diff --git a/zuul-core/src/test/java/com/netflix/zuul/message/ZuulMessageImplTest.java b/zuul-core/src/test/java/com/netflix/zuul/message/ZuulMessageImplTest.java index 8ffd0522..494490ae 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/message/ZuulMessageImplTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/message/ZuulMessageImplTest.java @@ -25,7 +25,7 @@ import io.netty.handler.codec.http.DefaultLastHttpContent; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ZuulMessageImplTest { diff --git a/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpQueryParamsTest.java b/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpQueryParamsTest.java index dea88f93..5766b942 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpQueryParamsTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpQueryParamsTest.java @@ -18,11 +18,11 @@ import static org.junit.Assert.assertEquals; - -import org.junit.Assert; +import static org.junit.Assert.assertTrue; +import java.util.Locale; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class HttpQueryParamsTest { @@ -72,7 +72,7 @@ public void testEquals() { } @Test - public void testParseKeysWithoutValues() { + public void parseKeysWithoutValues() { HttpQueryParams expected = new HttpQueryParams(); expected.add("k1", ""); expected.add("k2", "v2"); @@ -86,7 +86,7 @@ public void testParseKeysWithoutValues() { } @Test - public void testParseKeyWithoutValueEquals() { + public void parseKeyWithoutValueEquals() { HttpQueryParams expected = new HttpQueryParams(); expected.add("k1", ""); @@ -98,7 +98,7 @@ public void testParseKeyWithoutValueEquals() { } @Test - public void testParseKeyWithoutValue() { + public void parseKeyWithoutValue() { HttpQueryParams expected = new HttpQueryParams(); expected.add("k1", ""); @@ -110,7 +110,7 @@ public void testParseKeyWithoutValue() { } @Test - public void testParseKeyWithoutValueShort() { + public void parseKeyWithoutValueShort() { HttpQueryParams expected = new HttpQueryParams(); expected.add("=", ""); @@ -122,7 +122,7 @@ public void testParseKeyWithoutValueShort() { } @Test - public void testParseKeysWithoutValuesMixedTrailers() { + public void parseKeysWithoutValuesMixedTrailers() { HttpQueryParams expected = new HttpQueryParams(); expected.add("k1", ""); expected.add("k2", "v2"); @@ -135,4 +135,14 @@ public void testParseKeysWithoutValuesMixedTrailers() { assertEquals("k1=&k2=v2&k3&k4=v4", actual.toEncodedString()); } + + @Test + public void parseKeysIgnoreCase() { + String camelCaseKey = "keyName"; + HttpQueryParams queryParams = new HttpQueryParams(); + queryParams.add("foo", "bar"); + queryParams.add(camelCaseKey.toLowerCase(Locale.ROOT), "value"); + + assertTrue(queryParams.containsIgnoreCase(camelCaseKey)); + } } diff --git a/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpRequestMessageImplTest.java b/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpRequestMessageImplTest.java index 58bc787a..79ed9c64 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpRequestMessageImplTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpRequestMessageImplTest.java @@ -17,6 +17,7 @@ package com.netflix.zuul.message.http; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.spy; @@ -24,16 +25,21 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.common.net.InetAddresses; +import com.netflix.zuul.context.CommonContextKeys; import com.netflix.zuul.context.SessionContext; import com.netflix.zuul.message.Headers; import io.netty.channel.local.LocalAddress; + +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.net.URISyntaxException; import java.util.Optional; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class HttpRequestMessageImplTest { @@ -199,6 +205,38 @@ public void testGetOriginalHost() { "192.168.0.2", "https", 7002, "localhost"); Assert.assertEquals("blah.netflix.com", request.getOriginalHost()); + queryParams = new HttpQueryParams(); + headers = new Headers(); + headers.add("Host", "0.0.0.1"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals("0.0.0.1", request.getOriginalHost()); + + queryParams = new HttpQueryParams(); + headers = new Headers(); + headers.add("Host", "0.0.0.1:2"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals("0.0.0.1", request.getOriginalHost()); + + queryParams = new HttpQueryParams(); + headers = new Headers(); + headers.add("Host", "[::2]"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals("[::2]", request.getOriginalHost()); + + queryParams = new HttpQueryParams(); + headers = new Headers(); + headers.add("Host", "[::2]:3"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals("[::2]", request.getOriginalHost()); + headers = new Headers(); headers.add("Host", "blah.netflix.com"); headers.add("X-Forwarded-Host", "foo.netflix.com"); @@ -222,6 +260,18 @@ public void testGetOriginalHost() { Assert.assertEquals("blah.netflix.com", request.getOriginalHost()); } + @Test + public void getOriginalHost_failsOnUnbracketedIpv6Address() { + HttpQueryParams queryParams = new HttpQueryParams(); + Headers headers = new Headers(); + headers.add("Host", "ba::dd"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + + assertThrows(URISyntaxException.class, () -> HttpRequestMessageImpl.getOriginalHost(headers, "server")); + } + @Test public void testGetOriginalPort() { HttpQueryParams queryParams = new HttpQueryParams(); @@ -246,6 +296,34 @@ public void testGetOriginalPort() { "192.168.0.2", "https", 7002, "localhost"); Assert.assertEquals(443, request.getOriginalPort()); + headers = new Headers(); + headers.add("Host", "127.0.0.2:443"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals(443, request.getOriginalPort()); + + headers = new Headers(); + headers.add("Host", "127.0.0.2"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals(7002, request.getOriginalPort()); + + headers = new Headers(); + headers.add("Host", "[::2]:443"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals(443, request.getOriginalPort()); + + headers = new Headers(); + headers.add("Host", "[::2]"); + request = new HttpRequestMessageImpl(new SessionContext(), "HTTP/1.1", "POST", "/some/where", queryParams, + headers, + "192.168.0.2", "https", 7002, "localhost"); + Assert.assertEquals(7002, request.getOriginalPort()); + headers = new Headers(); headers.add("Host", "blah.netflix.com:443"); headers.add("X-Forwarded-Port", "7005"); @@ -255,6 +333,33 @@ public void testGetOriginalPort() { Assert.assertEquals(7005, request.getOriginalPort()); } + @Test + public void getOriginalPort_fallsBackOnUnbracketedIpv6Address() throws URISyntaxException { + Headers headers = new Headers(); + headers.add("Host", "ba::33"); + + assertEquals(9999, HttpRequestMessageImpl.getOriginalPort(new SessionContext(), headers, 9999)); + } + + @Test + public void getOriginalPort_EmptyXFFPort() throws URISyntaxException { + Headers headers = new Headers(); + headers.add(HttpHeaderNames.X_FORWARDED_PORT, ""); + + // Default to using server port + assertEquals(9999, HttpRequestMessageImpl.getOriginalPort(new SessionContext(), headers, 9999)); + } + + @Test + public void getOriginalPort_respectsProxyProtocol() throws URISyntaxException { + SessionContext context = new SessionContext(); + context.set(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS, + new InetSocketAddress(InetAddresses.forString("1.1.1.1"), 443)); + Headers headers = new Headers(); + headers.add("X-Forwarded-Port", "6000"); + assertEquals(443, HttpRequestMessageImpl.getOriginalPort(context, headers, 9999)); + } + @Test public void testCleanCookieHeaders() { assertEquals("BlahId=12345; something=67890;", diff --git a/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpResponseMessageImplTest.java b/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpResponseMessageImplTest.java index bd082af4..f0c050bd 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpResponseMessageImplTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/message/http/HttpResponseMessageImplTest.java @@ -26,7 +26,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link HttpResponseMessageImpl}. diff --git a/zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnCounterTest.java b/zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnCounterTest.java new file mode 100644 index 00000000..25b855f5 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnCounterTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.monitoring; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.Gauge; +import com.netflix.spectator.api.Registry; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ConnCounterTest { + @Test + public void record() { + EmbeddedChannel chan = new EmbeddedChannel(); + Attrs attrs = Attrs.newInstance(); + chan.attr(Server.CONN_DIMENSIONS).set(attrs); + Registry registry = new DefaultRegistry(); + ConnCounter counter = ConnCounter.install(chan, registry, registry.createId("foo")); + + counter.increment("start"); + counter.increment("middle"); + Attrs.newKey("bar").put(attrs, "baz"); + counter.increment("end"); + + Gauge meter1 = registry.gauge(registry.createId("foo.start", "from", "nascent")); + assertNotNull(meter1); + assertEquals(1, meter1.value(), 0); + + Gauge meter2 = registry.gauge(registry.createId("foo.middle", "from", "start")); + assertNotNull(meter2); + assertEquals(1, meter2.value(), 0); + + Gauge meter3 = registry.gauge(registry.createId("foo.end", "from", "middle", "bar", "baz")); + assertNotNull(meter3); + assertEquals(1, meter3.value(), 0); + } + + @Test + public void activeConnsCount() { + EmbeddedChannel channel = new EmbeddedChannel(); + Attrs attrs = Attrs.newInstance(); + channel.attr(Server.CONN_DIMENSIONS).set(attrs); + Registry registry = new DefaultRegistry(); + + ConnCounter.install(channel, registry, registry.createId("foo")); + + // Dedup increments + ConnCounter.from(channel).increment("active"); + ConnCounter.from(channel).increment("active"); + + + assertEquals(1, ConnCounter.from(channel).getCurrentActiveConns(), 0); + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnTimerTest.java b/zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnTimerTest.java new file mode 100644 index 00000000..cc07c028 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnTimerTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.monitoring; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.histogram.PercentileTimer; +import com.netflix.zuul.Attrs; +import com.netflix.zuul.netty.server.Server; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ConnTimerTest { + @Test + public void record() { + EmbeddedChannel chan = new EmbeddedChannel(); + Attrs attrs = Attrs.newInstance(); + chan.attr(Server.CONN_DIMENSIONS).set(attrs); + Registry registry = new DefaultRegistry(); + ConnTimer timer = ConnTimer.install(chan, registry, registry.createId("foo")); + + timer.record(1000L, "start"); + timer.record(2000L, "middle"); + Attrs.newKey("bar").put(attrs, "baz"); + timer.record(4000L, "end"); + + PercentileTimer meter1 = + PercentileTimer.get(registry, registry.createId("foo.start-middle")); + assertNotNull(meter1); + assertEquals(1000L, meter1.totalTime()); + + PercentileTimer meter2 = + PercentileTimer.get(registry, registry.createId("foo.middle-end", "bar", "baz")); + assertNotNull(meter2); + assertEquals(2000L, meter2.totalTime()); + + PercentileTimer meter3 = + PercentileTimer.get(registry, registry.createId("foo.start-end", "bar", "baz")); + assertNotNull(meter3); + assertEquals(3000L, meter3.totalTime()); + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java deleted file mode 100644 index 75426922..00000000 --- a/zuul-core/src/test/java/com/netflix/zuul/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2019 Netflix, Inc. - * - * 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 com.netflix.zuul.netty.common.proxyprotocol; - -import com.google.common.net.InetAddresses; -import com.netflix.netty.common.SourceAddressChannelHandler; -import com.netflix.netty.common.proxyprotocol.ElbProxyProtocolChannelHandler; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.handler.codec.haproxy.HAProxyProtocolVersion; -import java.net.InetSocketAddress; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.nio.charset.StandardCharsets; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -@RunWith(JUnit4.class) -public class ElbProxyProtocolChannelHandlerTest { - - @Test - public void noProxy() { - ElbProxyProtocolChannelHandler handler = new ElbProxyProtocolChannelHandler(/* withProxyProtocol= */ false); - EmbeddedChannel channel = new EmbeddedChannel(); - handler.addProxyProtocol(channel.pipeline()); - ByteBuf buf = Unpooled.wrappedBuffer( - "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf); - - Object dropped = channel.readInbound(); - assertEquals(dropped, buf); - - // TODO(carl-mastrangelo): the handler should remove itself, but it currently doesn't. - assertNotNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); - assertNull(channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_VERSION).get()); - assertNull(channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_MESSAGE).get()); - assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); - assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); - assertNull(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_PORT).get()); - assertNull(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); - assertNull(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_PORT).get()); - assertNull(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); - - } - - @Test - public void negotiateProxy_ppv1_ipv4() { - ElbProxyProtocolChannelHandler handler = new ElbProxyProtocolChannelHandler(/* withProxyProtocol= */ true); - EmbeddedChannel channel = new EmbeddedChannel(); - handler.addProxyProtocol(channel.pipeline()); - ByteBuf buf = Unpooled.wrappedBuffer( - "PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf); - - Object dropped = channel.readInbound(); - assertNull(dropped); - - // The handler should remove itself. - assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); - assertEquals( - HAProxyProtocolVersion.V1, channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_VERSION).get()); - // TODO(carl-mastrangelo): this check is in place, but it should be removed. The message is not properly GC'd - // in later versions of netty. - assertNotNull(channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_MESSAGE).get()); - assertEquals("124.123.111.111", channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); - assertEquals( - new InetSocketAddress(InetAddresses.forString("124.123.111.111"), 443), - channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); - assertEquals(Integer.valueOf(443), channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_PORT).get()); - assertEquals("192.168.0.1", channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); - assertEquals(Integer.valueOf(10008), channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_PORT).get()); - assertEquals( - new InetSocketAddress(InetAddresses.forString("192.168.0.1"), 10008), - channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); - } - - @Test - public void negotiateProxy_ppv1_ipv6() { - ElbProxyProtocolChannelHandler handler = new ElbProxyProtocolChannelHandler(/* withProxyProtocol= */ true); - EmbeddedChannel channel = new EmbeddedChannel(); - handler.addProxyProtocol(channel.pipeline()); - ByteBuf buf = Unpooled.wrappedBuffer( - "PROXY TCP6 ::1 ::2 10008 443\r\n".getBytes(StandardCharsets.US_ASCII)); - channel.writeInbound(buf); - - Object dropped = channel.readInbound(); - assertNull(dropped); - - // The handler should remove itself. - assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); - assertEquals( - HAProxyProtocolVersion.V1, channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_VERSION).get()); - // TODO(carl-mastrangelo): this check is in place, but it should be removed. The message is not properly GC'd - // in later versions of netty. - assertNotNull(channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_MESSAGE).get()); - assertEquals("::2", channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); - assertEquals( - new InetSocketAddress(InetAddresses.forString("::2"), 443), - channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); - assertEquals(Integer.valueOf(443), channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_PORT).get()); - assertEquals("::1", channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); - assertEquals(Integer.valueOf(10008), channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_PORT).get()); - assertEquals( - new InetSocketAddress(InetAddresses.forString("::1"), 10008), - channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); - } - - @Test - public void negotiateProxy_ppv2_ipv4() { - ElbProxyProtocolChannelHandler handler = new ElbProxyProtocolChannelHandler(/* withProxyProtocol= */ true); - EmbeddedChannel channel = new EmbeddedChannel(); - handler.addProxyProtocol(channel.pipeline()); - - ByteBuf buf = Unpooled.wrappedBuffer( - new byte[]{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A, 0x21, 0x11, 0x00, - 0x0C, (byte) 0xC0, (byte) 0xA8, 0x00, 0x01, 0x7C, 0x7B, 0x6F, 0x6F, 0x27, 0x18, 0x01, - (byte) 0xbb}); - channel.writeInbound(buf); - - Object dropped = channel.readInbound(); - assertNull(dropped); - - // The handler should remove itself. - assertNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); - assertEquals( - HAProxyProtocolVersion.V2, channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_VERSION).get()); - // TODO(carl-mastrangelo): this check is in place, but it should be removed. The message is not properly GC'd - // in later versions of netty. - assertNotNull(channel.attr(ElbProxyProtocolChannelHandler.ATTR_HAPROXY_MESSAGE).get()); - assertEquals("124.123.111.111", channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get()); - assertEquals( - new InetSocketAddress(InetAddresses.forString("124.123.111.111"), 443), - channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get()); - assertEquals(Integer.valueOf(443), channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_PORT).get()); - assertEquals("192.168.0.1", channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get()); - assertEquals(Integer.valueOf(10008), channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_PORT).get()); - assertEquals( - new InetSocketAddress(InetAddresses.forString("192.168.0.1"), 10008), - channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get()); - } -} diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManagerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManagerTest.java new file mode 100644 index 00000000..9c443f33 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManagerTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.connectionpool; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import com.google.common.net.InetAddresses; +import com.google.common.truth.Truth; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.Builder; +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.zuul.discovery.DiscoveryResult; +import com.netflix.zuul.discovery.DynamicServerResolver; +import com.netflix.zuul.discovery.NonDiscoveryServer; +import com.netflix.zuul.origins.OriginName; +import com.netflix.zuul.passport.CurrentPassport; +import io.netty.channel.DefaultEventLoop; +import io.netty.util.concurrent.Promise; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link DefaultClientChannelManager}. These tests don't use IPv6 addresses because {@link InstanceInfo} is + * not capable of expressing them. + */ +@RunWith(JUnit4.class) +public class DefaultClientChannelManagerTest { + + @Test + public void pickAddressInternal_discovery() { + InstanceInfo instanceInfo = + Builder.newBuilder().setAppName("app").setHostName("192.168.0.1").setPort(443).build(); + DiscoveryResult s = DiscoveryResult.from(instanceInfo, true); + + SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip("vip")); + + Truth.assertThat(addr).isInstanceOf(InetSocketAddress.class); + InetSocketAddress socketAddress = (InetSocketAddress) addr; + assertEquals(InetAddresses.forString("192.168.0.1"), socketAddress.getAddress()); + assertEquals(443, socketAddress.getPort()); + } + + @Test + public void pickAddressInternal_discovery_unresolved() { + InstanceInfo instanceInfo = + Builder.newBuilder().setAppName("app").setHostName("localhost").setPort(443).build(); + DiscoveryResult s = DiscoveryResult.from(instanceInfo, true); + + SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip("vip")); + + Truth.assertThat(addr).isInstanceOf(InetSocketAddress.class); + InetSocketAddress socketAddress = (InetSocketAddress) addr; + + assertTrue(socketAddress.toString(), socketAddress.getAddress().isLoopbackAddress()); + assertEquals(443, socketAddress.getPort()); + } + + @Test + public void pickAddressInternal_nonDiscovery() { + NonDiscoveryServer s = new NonDiscoveryServer("192.168.0.1", 443); + + SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip("vip")); + + Truth.assertThat(addr).isInstanceOf(InetSocketAddress.class); + InetSocketAddress socketAddress = (InetSocketAddress) addr; + assertEquals(InetAddresses.forString("192.168.0.1"), socketAddress.getAddress()); + assertEquals(443, socketAddress.getPort()); + } + + @Test + public void pickAddressInternal_nonDiscovery_unresolved() { + NonDiscoveryServer s = new NonDiscoveryServer("localhost", 443); + + SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip("vip")); + + Truth.assertThat(addr).isInstanceOf(InetSocketAddress.class); + InetSocketAddress socketAddress = (InetSocketAddress) addr; + + assertTrue(socketAddress.toString(), socketAddress.getAddress().isLoopbackAddress()); + assertEquals(443, socketAddress.getPort()); + } + + @Test + public void updateServerRefOnEmptyDiscoveryResult() { + OriginName originName = OriginName.fromVip("vip", "test"); + final DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); + final DynamicServerResolver resolver = mock(DynamicServerResolver.class); + + when(resolver.resolve(any())).thenReturn(DiscoveryResult.EMPTY); + + final DefaultClientChannelManager clientChannelManager = new DefaultClientChannelManager(originName, + clientConfig, resolver, new DefaultRegistry()); + + final AtomicReference serverRef = new AtomicReference<>(); + + final Promise promise = clientChannelManager + .acquire(new DefaultEventLoop(), null, CurrentPassport.create(), serverRef, new AtomicReference<>()); + + Truth.assertThat(promise.isSuccess()).isFalse(); + Truth.assertThat(serverRef.get()).isSameInstanceAs(DiscoveryResult.EMPTY); + } + + @Test + public void updateServerRefOnValidDiscoveryResult() { + OriginName originName = OriginName.fromVip("vip", "test"); + final DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl(); + + final DynamicServerResolver resolver = mock(DynamicServerResolver.class); + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("server-equality") + .setHostName("server-equality") + .setPort(7777).build(); + final DiscoveryResult discoveryResult = DiscoveryResult.from(instanceInfo, false); + + when(resolver.resolve(any())).thenReturn(discoveryResult); + + final DefaultClientChannelManager clientChannelManager = new DefaultClientChannelManager(originName, + clientConfig, resolver, new DefaultRegistry()); + + final AtomicReference serverRef = new AtomicReference<>(); + + //TODO(argha-c) capture and assert on the promise once we have a dummy with ServerStats initialized + clientChannelManager + .acquire(new DefaultEventLoop(), null, CurrentPassport.create(), serverRef, new AtomicReference<>()); + + Truth.assertThat(serverRef.get()).isSameInstanceAs(discoveryResult); + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/insights/ServerStateHandlerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/insights/ServerStateHandlerTest.java new file mode 100644 index 00000000..9bc4ed46 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/insights/ServerStateHandlerTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.insights; + +import static org.junit.Assert.assertEquals; +import com.netflix.spectator.api.Counter; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.Gauge; +import com.netflix.spectator.api.Id; +import com.netflix.spectator.api.Registry; +import com.netflix.zuul.netty.insights.ServerStateHandler.InboundHandler; +import com.netflix.zuul.netty.server.http2.DummyChannelHandler; +import com.netflix.zuul.passport.CurrentPassport; +import com.netflix.zuul.passport.PassportState; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ServerStateHandlerTest { + + private Registry registry; + private Id currentConnsId; + private Id connectsId; + private Id errorsId; + private Id closesId; + + final String listener = "test-conn-throttled"; + + @Before + public void init() { + registry = new DefaultRegistry(); + currentConnsId = registry.createId("server.connections.current").withTags("id", listener); + connectsId = registry.createId("server.connections.connect").withTags("id", listener); + closesId = registry.createId("server.connections.close").withTags("id", listener); + errorsId = registry.createId("server.connections.errors").withTags("id", listener); + } + + @Test + public void verifyConnMetrics() { + + final EmbeddedChannel channel = new EmbeddedChannel(); + channel.pipeline().addLast(new DummyChannelHandler()); + channel.pipeline().addLast(new InboundHandler(registry, listener)); + + final Counter connects = (Counter) registry.get(connectsId); + final Counter closes = (Counter) registry.get(closesId); + final Counter errors = (Counter) registry.get(errorsId); + + // Connects X 3 + channel.pipeline().context(DummyChannelHandler.class).fireChannelActive(); + channel.pipeline().context(DummyChannelHandler.class).fireChannelActive(); + channel.pipeline().context(DummyChannelHandler.class).fireChannelActive(); + + assertEquals(3, connects.count()); + + // Closes X 1 + channel.pipeline().context(DummyChannelHandler.class).fireChannelInactive(); + + assertEquals(3, connects.count()); + assertEquals(1, closes.count()); + assertEquals(0, errors.count()); + } + + @Test + public void setPassportStateOnConnect() { + + final EmbeddedChannel channel = new EmbeddedChannel(); + channel.pipeline().addLast(new DummyChannelHandler()); + channel.pipeline().addLast(new InboundHandler(registry, listener)); + + channel.pipeline().context(DummyChannelHandler.class).fireChannelActive(); + + assertEquals(PassportState.SERVER_CH_ACTIVE, CurrentPassport.fromChannel(channel).getState()); + } + + @Test + public void setPassportStateOnDisconnect() { + final EmbeddedChannel channel = new EmbeddedChannel(); + channel.pipeline().addLast(new DummyChannelHandler()); + channel.pipeline().addLast(new InboundHandler(registry, listener)); + + channel.pipeline().context(DummyChannelHandler.class).fireChannelInactive(); + + assertEquals(PassportState.SERVER_CH_INACTIVE, CurrentPassport.fromChannel(channel).getState()); + } + +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializerTest.java index 430f44f1..bc7e9137 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializerTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializerTest.java @@ -16,15 +16,15 @@ package com.netflix.zuul.netty.server; +import static org.junit.Assert.assertNotNull; import com.netflix.netty.common.SourceAddressChannelHandler; import com.netflix.netty.common.channel.config.ChannelConfig; import com.netflix.netty.common.channel.config.CommonChannelConfigKeys; import com.netflix.netty.common.metrics.PerEventLoopMetricsChannelHandler; -import com.netflix.netty.common.metrics.ServerChannelMetrics; import com.netflix.netty.common.proxyprotocol.ElbProxyProtocolChannelHandler; -import com.netflix.netty.common.proxyprotocol.OptionalHAProxyMessageDecoder; import com.netflix.netty.common.throttle.MaxInboundConnectionsHandler; import com.netflix.spectator.api.NoopRegistry; +import com.netflix.zuul.netty.insights.ServerStateHandler; import com.netflix.zuul.netty.ratelimiting.NullChannelHandlerProvider; import io.netty.channel.Channel; import io.netty.channel.embedded.EmbeddedChannel; @@ -35,9 +35,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - /** * Unit tests for {@link BaseZuulChannelInitializer}. */ @@ -65,10 +62,8 @@ protected void initChannel(Channel ch) {} init.addTcpRelatedHandlers(channel.pipeline()); assertNotNull(channel.pipeline().context(SourceAddressChannelHandler.class)); - assertNotNull(channel.pipeline().context(ServerChannelMetrics.class)); assertNotNull(channel.pipeline().context(PerEventLoopMetricsChannelHandler.Connections.class)); assertNotNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); - assertNull(channel.pipeline().context(OptionalHAProxyMessageDecoder.NAME)); assertNotNull(channel.pipeline().context(MaxInboundConnectionsHandler.class)); } @@ -94,10 +89,34 @@ protected void initChannel(Channel ch) {} init.addTcpRelatedHandlers(channel.pipeline()); assertNotNull(channel.pipeline().context(SourceAddressChannelHandler.class)); - assertNotNull(channel.pipeline().context(ServerChannelMetrics.class)); assertNotNull(channel.pipeline().context(PerEventLoopMetricsChannelHandler.Connections.class)); assertNotNull(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME)); - assertNotNull(channel.pipeline().context(OptionalHAProxyMessageDecoder.NAME)); assertNotNull(channel.pipeline().context(MaxInboundConnectionsHandler.class)); } + + @Test + public void serverStateHandlerAdded() { + ChannelConfig channelConfig = new ChannelConfig(); + ChannelConfig channelDependencies = new ChannelConfig(); + channelDependencies.set(ZuulDependencyKeys.registry, new NoopRegistry()); + channelDependencies.set( + ZuulDependencyKeys.rateLimitingChannelHandlerProvider, new NullChannelHandlerProvider()); + channelDependencies.set( + ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider, new NullChannelHandlerProvider()); + + ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + BaseZuulChannelInitializer init = + new BaseZuulChannelInitializer("1234", channelConfig, channelDependencies, channelGroup) { + + @Override + protected void initChannel(Channel ch) {} + }; + EmbeddedChannel channel = new EmbeddedChannel(); + + init.addPassportHandler(channel.pipeline()); + + assertNotNull(channel.pipeline().context(ServerStateHandler.InboundHandler.class)); + assertNotNull(channel.pipeline().context(ServerStateHandler.OutboundHandler.class)); + + } } diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientRequestReceiverTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientRequestReceiverTest.java index 74eba353..425729ec 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientRequestReceiverTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientRequestReceiverTest.java @@ -16,28 +16,88 @@ package com.netflix.zuul.netty.server; +import static org.junit.Assert.assertEquals; 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 com.google.common.net.InetAddresses; +import com.netflix.netty.common.HttpLifecycleChannelHandler; +import com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent; +import com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason; import com.netflix.netty.common.SourceAddressChannelHandler; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.zuul.context.CommonContextKeys; +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.message.http.HttpRequestMessage; import com.netflix.zuul.message.http.HttpRequestMessageImpl; +import com.netflix.zuul.netty.insights.PassportLoggingHandler; +import com.netflix.zuul.stats.status.StatusCategoryUtils; +import com.netflix.zuul.stats.status.ZuulStatusCategory; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpRequestEncoder; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpVersion; +import java.net.InetSocketAddress; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link ClientRequestReceiver}. */ -@RunWith(JUnit4.class) +@RunWith(MockitoJUnitRunner.class) public class ClientRequestReceiverTest { + + @Test + public void proxyProtocol_portSetInSessionContextAndInHttpRequestMessageImpl() { + EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null)); + channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234); + InetSocketAddress hapmDestinationAddress = new InetSocketAddress(InetAddresses.forString("2.2.2.2"), 444); + channel.attr(SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS).set(hapmDestinationAddress); + channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).set(hapmDestinationAddress); + HttpRequestMessageImpl result; + { + channel.writeInbound( + new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/post", Unpooled.buffer())); + result = channel.readInbound(); + result.disposeBufferedBody(); + } + assertEquals((int) result.getClientDestinationPort().get(), hapmDestinationAddress.getPort()); + int destinationPort = ((InetSocketAddress) result.getContext() + .get(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS)).getPort(); + assertEquals(destinationPort, 444); + assertEquals(result.getOriginalPort(), 444); + channel.close(); + } + + @Test + public void parseQueryParamsWithEncodedCharsInURI() { + + EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null)); + channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234); + HttpRequestMessageImpl result; + { + channel.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, + "foo/bar/somePath/%5E1.0.0?param1=foo¶m2=bar¶m3=baz", Unpooled.buffer())); + result = channel.readInbound(); + result.disposeBufferedBody(); + } + + assertEquals("foo", result.getQueryParams().getFirst("param1")); + assertEquals("bar", result.getQueryParams().getFirst("param2")); + assertEquals("baz", result.getQueryParams().getFirst("param3")); + + channel.close(); + } + @Test public void largeResponse_atLimit() { ClientRequestReceiver receiver = new ClientRequestReceiver(null); @@ -100,4 +160,69 @@ public void largeResponse_aboveLimit() { assertTrue(result.getContext().shouldSendErrorResponse()); channel.close(); } + + @Test + public void maxHeaderSizeExceeded_setBadRequestStatus() { + + int maxInitialLineLength = BaseZuulChannelInitializer.MAX_INITIAL_LINE_LENGTH.get(); + int maxHeaderSize = 10; + int maxChunkSize = BaseZuulChannelInitializer.MAX_CHUNK_SIZE.get(); + ClientRequestReceiver receiver = new ClientRequestReceiver(null); + EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestEncoder()); + PassportLoggingHandler loggingHandler = new PassportLoggingHandler(new DefaultRegistry()); + + // Required for messages + channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234); + channel.pipeline().addLast(new HttpServerCodec( + maxInitialLineLength, + maxHeaderSize, + maxChunkSize, + false + )); + channel.pipeline().addLast(receiver); + channel.pipeline().addLast(loggingHandler); + + String str = "test-header-value"; + ByteBuf buf = Unpooled.buffer(1); + HttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "/post", buf); + for (int i = 0; i < 100; i++) { + httpRequest.headers().add("test-header" + i, str); + } + + channel.writeOutbound(httpRequest); + ByteBuf byteBuf = channel.readOutbound(); + channel.writeInbound(byteBuf); + channel.readInbound(); + channel.close(); + + HttpRequestMessage request = ClientRequestReceiver.getRequestFromChannel(channel); + assertEquals(StatusCategoryUtils.getStatusCategory(request.getContext()), + ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST); + } + + @Test + public void setStatusCategoryForHttpPipelining() { + + EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null)); + channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234); + + final DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, + "?ELhAWDLM1hwm8bhU0UT4", Unpooled.buffer()); + + // Write the message and save a copy + channel.writeInbound(request); + final HttpRequestMessage inboundRequest = ClientRequestReceiver.getRequestFromChannel(channel); + + // Set the attr to emulate pipelining rejection + channel.attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT).set(Boolean.TRUE); + + // Fire completion event + channel.pipeline().fireUserEventTriggered(new CompleteEvent(CompleteReason.PIPELINE_REJECT, request, + new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST))); + channel.close(); + + assertEquals(ZuulStatusCategory.FAILURE_CLIENT_PIPELINE_REJECT, + StatusCategoryUtils.getStatusCategory(inboundRequest.getContext())); + } } + diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/server/ServerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/server/ServerTest.java index f43fb5eb..49f39487 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/netty/server/ServerTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/server/ServerTest.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.mock; import com.netflix.netty.common.metrics.EventLoopGroupMetrics; import com.netflix.netty.common.status.ServerStatusManager; +import com.netflix.spectator.api.NoopRegistry; import com.netflix.spectator.api.Spectator; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; @@ -47,15 +48,14 @@ public class ServerTest { @Test public void getListeningSockets() throws Exception { ServerStatusManager ssm = mock(ServerStatusManager.class); - Map> initializers = new HashMap<>(); + Map> initializers = new HashMap<>(); ChannelInitializer init = new ChannelInitializer() { @Override protected void initChannel(Channel ch) {} }; - initializers.put(new InetSocketAddress(0), init); - // Pick an InetAddress likely different than the above. The port to channel map has a unique Key; this - // prevents the key being a duplicate. - initializers.put(new InetSocketAddress(InetAddress.getLocalHost(), 0), init); + initializers.put(new NamedSocketAddress("test", new InetSocketAddress(0)), init); + // The port to channel map keys on the port, post bind. This should be unique even if InetAddress is same + initializers.put(new NamedSocketAddress("test2", new InetSocketAddress( 0)), init); ClientConnectionsShutdown ccs = new ClientConnectionsShutdown( new DefaultChannelGroup(GlobalEventExecutor.INSTANCE), @@ -73,15 +73,15 @@ public int acceptorCount() { return 1; } }; - Server s = new Server(ssm, initializers, ccs, elgm, elc); - s.start(/* sync= */ false); + Server s = new Server(new NoopRegistry(), ssm, initializers, ccs, elgm, elc); + s.start(); - List addrs = s.getListeningAddresses(); + List addrs = s.getListeningAddresses(); assertEquals(2, addrs.size()); - assertTrue(addrs.get(0) instanceof InetSocketAddress); - assertNotEquals(((InetSocketAddress) addrs.get(0)).getPort(), 0); - assertTrue(addrs.get(1) instanceof InetSocketAddress); - assertNotEquals(((InetSocketAddress) addrs.get(1)).getPort(), 0); + assertTrue(addrs.get(0).unwrap() instanceof InetSocketAddress); + assertNotEquals(((InetSocketAddress) addrs.get(0).unwrap()).getPort(), 0); + assertTrue(addrs.get(1).unwrap() instanceof InetSocketAddress); + assertNotEquals(((InetSocketAddress) addrs.get(1).unwrap()).getPort(), 0); s.stop(); } diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandlerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandlerTest.java new file mode 100644 index 00000000..fae34081 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandlerTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.server.http2; + +import static com.google.common.truth.Truth.assertThat; + +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.http.DefaultHttpContent; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.DefaultLastHttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http2.Http2ResetFrame; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class Http2ContentLengthEnforcingHandlerTest { + + @Test + public void failsOnMultipleContentLength() { + EmbeddedChannel chan = new EmbeddedChannel(); + chan.pipeline().addLast(new Http2ContentLengthEnforcingHandler()); + + HttpRequest req = new DefaultHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.GET, ""); + req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 1); + req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 2); + chan.writeInbound(req); + + Object out = chan.readOutbound(); + assertThat(out).isInstanceOf(Http2ResetFrame.class); + } + + @Test + public void failsOnMixedContentLengthAndChunked() { + EmbeddedChannel chan = new EmbeddedChannel(); + chan.pipeline().addLast(new Http2ContentLengthEnforcingHandler()); + + HttpRequest req = new DefaultHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.GET, ""); + req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 1); + req.headers().add(HttpHeaderNames.TRANSFER_ENCODING, "identity, chunked"); + req.headers().add(HttpHeaderNames.TRANSFER_ENCODING, "fzip"); + chan.writeInbound(req); + + Object out = chan.readOutbound(); + assertThat(out).isInstanceOf(Http2ResetFrame.class); + } + + @Test + public void failsOnShortContentLength() { + EmbeddedChannel chan = new EmbeddedChannel(); + chan.pipeline().addLast(new Http2ContentLengthEnforcingHandler()); + + DefaultHttpRequest req = new DefaultHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.GET, ""); + req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 1); + chan.writeInbound(req); + + Object out = chan.readOutbound(); + assertThat(out).isNull(); + + DefaultHttpContent content = + new DefaultHttpContent(ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, "a")); + chan.writeInbound(content); + + out = chan.readOutbound(); + assertThat(out).isNull(); + + DefaultHttpContent content2 = + new DefaultHttpContent(ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, "a")); + chan.writeInbound(content2); + + out = chan.readOutbound(); + assertThat(out).isInstanceOf(Http2ResetFrame.class); + } + + @Test + public void failsOnShortContent() { + EmbeddedChannel chan = new EmbeddedChannel(); + chan.pipeline().addLast(new Http2ContentLengthEnforcingHandler()); + + DefaultHttpRequest req = new DefaultHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.GET, ""); + req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 2); + chan.writeInbound(req); + + Object out = chan.readOutbound(); + assertThat(out).isNull(); + + DefaultHttpContent content = + new DefaultHttpContent(ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, "a")); + chan.writeInbound(content); + + out = chan.readOutbound(); + assertThat(out).isNull(); + + DefaultHttpContent content2 = new DefaultLastHttpContent(); + chan.writeInbound(content2); + + out = chan.readOutbound(); + assertThat(out).isInstanceOf(Http2ResetFrame.class); + } + +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandlerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandlerTest.java new file mode 100644 index 00000000..d57005a0 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandlerTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.server.http2; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import com.netflix.netty.common.Http2ConnectionCloseHandler; +import com.netflix.netty.common.Http2ConnectionExpiryHandler; +import com.netflix.netty.common.channel.config.ChannelConfig; +import com.netflix.netty.common.channel.config.ChannelConfigValue; +import com.netflix.netty.common.channel.config.CommonChannelConfigKeys; +import com.netflix.netty.common.metrics.Http2MetricsChannelHandlers; +import com.netflix.spectator.api.NoopRegistry; +import com.netflix.zuul.netty.server.BaseZuulChannelInitializer; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.http2.Http2FrameCodec; +import io.netty.handler.codec.http2.Http2MultiplexHandler; +import io.netty.handler.ssl.ApplicationProtocolNames; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * @author Argha C + * @since November 18, 2020 + */ + +@RunWith(JUnit4.class) +public class Http2OrHttpHandlerTest { + + @Test + public void swapInHttp2HandlerBasedOnALPN() throws Exception { + EmbeddedChannel channel = new EmbeddedChannel(); + final NoopRegistry registry = new NoopRegistry(); + final ChannelConfig channelConfig = new ChannelConfig(); + channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.maxHttp2HeaderListSize, 32768)); + + Http2ConnectionCloseHandler connectionCloseHandler = new Http2ConnectionCloseHandler(registry); + Http2ConnectionExpiryHandler connectionExpiryHandler = new Http2ConnectionExpiryHandler(100, 100, + 20 * 60 * 1000); + Http2MetricsChannelHandlers http2MetricsChannelHandlers = new Http2MetricsChannelHandlers(registry, "server", + "http2-443"); + final Http2OrHttpHandler http2OrHttpHandler = new Http2OrHttpHandler( + new Http2StreamInitializer(channel, (x) -> { + }, http2MetricsChannelHandlers, connectionCloseHandler, connectionExpiryHandler), + channelConfig, + cp -> { + }); + + channel.pipeline().addLast("codec_placeholder", new DummyChannelHandler()); + channel.pipeline().addLast(Http2OrHttpHandler.class.getSimpleName(), http2OrHttpHandler); + + http2OrHttpHandler.configurePipeline(channel.pipeline().lastContext(), ApplicationProtocolNames.HTTP_2); + + assertThat(channel.pipeline().get(Http2FrameCodec.class.getSimpleName() + "#0")) + .isInstanceOf(Http2FrameCodec.class); + assertThat(channel.pipeline().get(BaseZuulChannelInitializer.HTTP_CODEC_HANDLER_NAME)) + .isInstanceOf(Http2MultiplexHandler.class); + assertEquals("HTTP/2", channel.attr(Http2OrHttpHandler.PROTOCOL_NAME).get()); + } + +} \ No newline at end of file diff --git a/zuul-core/src/test/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManagerTest.java b/zuul-core/src/test/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManagerTest.java new file mode 100644 index 00000000..c79acc22 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManagerTest.java @@ -0,0 +1,137 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.netty.timeouts; + +import static com.netflix.zuul.netty.timeouts.OriginTimeoutManager.MAX_OUTBOUND_READ_TIMEOUT_MS; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import com.netflix.client.config.CommonClientConfigKey; +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.client.config.IClientConfig; +import com.netflix.zuul.context.CommonContextKeys; +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.message.http.HttpRequestMessage; +import com.netflix.zuul.origins.NettyOrigin; +import java.time.Duration; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * Origin Timeout Manager Test + * + * @author Arthur Gonigberg + * @since March 23, 2021 + */ +@RunWith(MockitoJUnitRunner.class) +public class OriginTimeoutManagerTest { + + @Mock + private NettyOrigin origin; + @Mock + private HttpRequestMessage request; + + private SessionContext context; + private IClientConfig requestConfig; + private IClientConfig originConfig; + + private OriginTimeoutManager originTimeoutManager; + + @Before + public void before() { + originTimeoutManager = new OriginTimeoutManager(origin); + + context = new SessionContext(); + when(request.getContext()).thenReturn(context); + + requestConfig = new DefaultClientConfigImpl(); + originConfig = new DefaultClientConfigImpl(); + + context.put(CommonContextKeys.REST_CLIENT_CONFIG, requestConfig); + when(origin.getClientConfig()).thenReturn(originConfig); + } + + @Test + public void computeReadTimeout_default() { + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(MAX_OUTBOUND_READ_TIMEOUT_MS.get(), timeout.toMillis()); + } + + @Test + public void computeReadTimeout_requestOnly() { + requestConfig.set(CommonClientConfigKey.ReadTimeout, 1000); + + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(1000, timeout.toMillis()); + } + + @Test + public void computeReadTimeout_originOnly() { + originConfig.set(CommonClientConfigKey.ReadTimeout, 1000); + + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(1000, timeout.toMillis()); + } + + @Test + public void computeReadTimeout_bolth_equal() { + requestConfig.set(CommonClientConfigKey.ReadTimeout, 1000); + originConfig.set(CommonClientConfigKey.ReadTimeout, 1000); + + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(1000, timeout.toMillis()); + } + + @Test + public void computeReadTimeout_bolth_originLower() { + requestConfig.set(CommonClientConfigKey.ReadTimeout, 1000); + originConfig.set(CommonClientConfigKey.ReadTimeout, 100); + + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(100, timeout.toMillis()); + } + + @Test + public void computeReadTimeout_bolth_requestLower() { + requestConfig.set(CommonClientConfigKey.ReadTimeout, 100); + originConfig.set(CommonClientConfigKey.ReadTimeout, 1000); + + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(100, timeout.toMillis()); + } + + @Test + public void computeReadTimeout_bolth_enforceMax() { + requestConfig.set(CommonClientConfigKey.ReadTimeout, + (int) MAX_OUTBOUND_READ_TIMEOUT_MS.get() + 1000); + originConfig.set(CommonClientConfigKey.ReadTimeout, + (int) MAX_OUTBOUND_READ_TIMEOUT_MS.get() + 10000); + + Duration timeout = originTimeoutManager.computeReadTimeout(request, 1); + + assertEquals(MAX_OUTBOUND_READ_TIMEOUT_MS.get(), timeout.toMillis()); + } +} \ No newline at end of file diff --git a/zuul-core/src/test/java/com/netflix/zuul/origins/OriginNameTest.java b/zuul-core/src/test/java/com/netflix/zuul/origins/OriginNameTest.java new file mode 100644 index 00000000..0916d872 --- /dev/null +++ b/zuul-core/src/test/java/com/netflix/zuul/origins/OriginNameTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.origins; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class OriginNameTest { + @Test + public void getAuthority() { + OriginName trusted = OriginName.fromVipAndApp("woodly-doodly", "westerndigital"); + + assertEquals("westerndigital", trusted.getAuthority()); + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsDataTest.java b/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsDataTest.java index 314a1f3c..5cdfe987 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsDataTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsDataTest.java @@ -22,7 +22,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link ErrorStatsData}. @@ -33,11 +33,10 @@ public class ErrorStatsDataTest { @Test public void testUpdateStats() { ErrorStatsData sd = new ErrorStatsData("route", "test"); - assertEquals(sd.error_cause, "test"); sd.update(); - assertEquals(sd.count.get(), 1); + assertEquals(sd.getCount(), 1); sd.update(); - assertEquals(sd.count.get(), 2); + assertEquals(sd.getCount(), 2); } diff --git a/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsManagerTest.java b/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsManagerTest.java index 24e4f9e0..423d1876 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsManagerTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsManagerTest.java @@ -22,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link ErrorStatsManager}. @@ -38,9 +38,9 @@ public void testPutStats() { assertNotNull(sm.routeMap.get("test")); ConcurrentHashMap map = sm.routeMap.get("test"); ErrorStatsData sd = map.get("cause"); - assertEquals(sd.count.get(), 1); + assertEquals(sd.getCount(), 1); sm.putStats("test", "cause"); - assertEquals(sd.count.get(), 2); + assertEquals(sd.getCount(), 2); } @Test diff --git a/zuul-core/src/test/java/com/netflix/zuul/stats/RouteStatusCodeMonitorTest.java b/zuul-core/src/test/java/com/netflix/zuul/stats/RouteStatusCodeMonitorTest.java index 2a1ae20b..48d28076 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/stats/RouteStatusCodeMonitorTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/stats/RouteStatusCodeMonitorTest.java @@ -22,21 +22,21 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.junit.runners.JUnit4; /** * Unit tests for {@link RouteStatusCodeMonitor}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public class RouteStatusCodeMonitorTest { @Test public void testUpdateStats() { RouteStatusCodeMonitor sd = new RouteStatusCodeMonitor("test", 200); assertEquals(sd.route, "test"); sd.update(); - assertEquals(sd.count.get(), 1); + assertEquals(sd.getCount(), 1); sd.update(); - assertEquals(sd.count.get(), 2); + assertEquals(sd.getCount(), 2); } @Test diff --git a/zuul-core/src/test/java/com/netflix/zuul/stats/StatsManagerTest.java b/zuul-core/src/test/java/com/netflix/zuul/stats/StatsManagerTest.java index 0e53ab88..400c7fa1 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/stats/StatsManagerTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/stats/StatsManagerTest.java @@ -28,7 +28,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link StatsManager}. diff --git a/zuul-core/src/test/java/com/netflix/zuul/util/ProxyUtilsTest.java b/zuul-core/src/test/java/com/netflix/zuul/util/ProxyUtilsTest.java deleted file mode 100644 index c1e16163..00000000 --- a/zuul-core/src/test/java/com/netflix/zuul/util/ProxyUtilsTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2019 Netflix, Inc. - * - * 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 com.netflix.zuul.util; - -import com.netflix.client.http.HttpResponse; -import com.netflix.zuul.message.http.HttpHeaderNames; -import com.netflix.zuul.message.http.HttpRequestMessage; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -/** - * Unit tests for {@link ProxyUtils}. - */ -@RunWith(MockitoJUnitRunner.class) -public class ProxyUtilsTest { - @Mock - HttpResponse proxyResp; - - @Mock - HttpRequestMessage request; - - @Test - public void testIsValidResponseHeader() { - Assert.assertTrue(ProxyUtils.isValidResponseHeader(HttpHeaderNames.get("test"))); - Assert.assertFalse(ProxyUtils.isValidResponseHeader(HttpHeaderNames.get("Keep-Alive"))); - Assert.assertFalse(ProxyUtils.isValidResponseHeader(HttpHeaderNames.get("keep-alive"))); - } -} diff --git a/zuul-core/src/test/java/com/netflix/zuul/util/VipUtilsTest.java b/zuul-core/src/test/java/com/netflix/zuul/util/VipUtilsTest.java index 823c1239..4eddbe7e 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/util/VipUtilsTest.java +++ b/zuul-core/src/test/java/com/netflix/zuul/util/VipUtilsTest.java @@ -39,10 +39,10 @@ public void testGetVIPPrefix() { @Test(expected = NullPointerException.class) public void testExtractAppNameFromVIP() { - assertEquals("api", VipUtils.extractAppNameFromVIP("api-test.netflix.net:7001")); - assertEquals("api", VipUtils.extractAppNameFromVIP("api-test-blah.netflix.net:7001")); - assertEquals("api", VipUtils.extractAppNameFromVIP("api")); - assertEquals("", VipUtils.extractAppNameFromVIP("")); - VipUtils.extractAppNameFromVIP(null); + assertEquals("api", VipUtils.extractUntrustedAppNameFromVIP("api-test.netflix.net:7001")); + assertEquals("api", VipUtils.extractUntrustedAppNameFromVIP("api-test-blah.netflix.net:7001")); + assertEquals("api", VipUtils.extractUntrustedAppNameFromVIP("api")); + assertEquals("", VipUtils.extractUntrustedAppNameFromVIP("")); + VipUtils.extractUntrustedAppNameFromVIP(null); } } diff --git a/zuul-discovery/build.gradle b/zuul-discovery/build.gradle new file mode 100644 index 00000000..49617190 --- /dev/null +++ b/zuul-discovery/build.gradle @@ -0,0 +1,26 @@ +apply plugin: "java-library" + +dependencies { + + implementation libraries.guava + implementation libraries.slf4j + + implementation "com.netflix.ribbon:ribbon-loadbalancer:${versions_ribbon}" + implementation "com.netflix.ribbon:ribbon-core:${versions_ribbon}" + implementation "com.netflix.ribbon:ribbon-eureka:${versions_ribbon}" + implementation "com.netflix.ribbon:ribbon-archaius:${versions_ribbon}" + + // Eureka + implementation "com.netflix.eureka:eureka-client:1.9.18" + + + testImplementation libraries.junit, + libraries.mockito, + libraries.truth +} + +test { + testLogging { + showStandardStreams = false + } +} diff --git a/zuul-discovery/dependencies.lock b/zuul-discovery/dependencies.lock new file mode 100644 index 00000000..2aaf0b05 --- /dev/null +++ b/zuul-discovery/dependencies.lock @@ -0,0 +1,187 @@ +{ + "compileClasspath": { + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.eureka:eureka-client": { + "locked": "1.9.18" + }, + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "locked": "2.4.4" + }, + "org.slf4j:slf4j-api": { + "locked": "1.7.25" + } + }, + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhCompileClasspath": { + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.eureka:eureka-client": { + "locked": "1.9.18" + }, + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "locked": "2.4.4" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "locked": "1.7.25" + } + }, + "jmhRuntimeClasspath": { + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.eureka:eureka-client": { + "locked": "1.9.18" + }, + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "locked": "2.4.4" + }, + "junit:junit": { + "locked": "4.13" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "locked": "1.7.25" + } + }, + "runtimeClasspath": { + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.eureka:eureka-client": { + "locked": "1.9.18" + }, + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "locked": "2.4.4" + }, + "org.slf4j:slf4j-api": { + "locked": "1.7.25" + } + }, + "testCompileClasspath": { + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.eureka:eureka-client": { + "locked": "1.9.18" + }, + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "locked": "2.4.4" + }, + "junit:junit": { + "locked": "4.13" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.slf4j:slf4j-api": { + "locked": "1.7.25" + } + }, + "testRuntimeClasspath": { + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.eureka:eureka-client": { + "locked": "1.9.18" + }, + "com.netflix.ribbon:ribbon-archaius": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "locked": "2.4.4" + }, + "junit:junit": { + "locked": "4.13" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.slf4j:slf4j-api": { + "locked": "1.7.25" + } + } +} \ No newline at end of file diff --git a/zuul-discovery/src/main/java/com/netflix/zuul/discovery/DiscoveryResult.java b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/DiscoveryResult.java new file mode 100644 index 00000000..f99e9d30 --- /dev/null +++ b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/DiscoveryResult.java @@ -0,0 +1,241 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.discovery; + +import com.google.common.annotations.VisibleForTesting; +import com.netflix.appinfo.AmazonInfo; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.PortType; +import com.netflix.loadbalancer.LoadBalancerStats; +import com.netflix.loadbalancer.ServerStats; +import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; + +/** + * @author Argha C + * @since 2/25/21 + *

+ * Wraps a single instance of discovery enabled server, and stats related to it. + */ +public final class DiscoveryResult implements ResolverResult { + + private final DiscoveryEnabledServer server; + private final ServerStats serverStats; + /** + * This exists to allow for a semblance of type safety, and encourages avoiding null checks on the underlying Server, + * thus representing a sentinel value for an empty resolution result. + */ + public static final DiscoveryResult EMPTY = + DiscoveryResult.from(InstanceInfo.Builder.newBuilder() + .setAppName("undefined") + .setHostName("undefined") + .setPort(-1).build(), false); + + public DiscoveryResult(DiscoveryEnabledServer server, LoadBalancerStats lbStats) { + this.server = server; + Objects.requireNonNull(lbStats, "Loadbalancer stats must be a valid instance"); + this.serverStats = lbStats.getSingleServerStat(server); + } + + /** + * + * This solely exists to create a result object from incomplete InstanceInfo. + * Usage of this for production code is strongly discouraged, since the underlying instances are prone to memory leaks + */ + public DiscoveryResult(DiscoveryEnabledServer server) { + this.server = server; + this.serverStats = new ServerStats(); + } + + /** + * + * This convenience method exists for usage in tests. For production usage, please use the constructor linked: + * @see DiscoveryResult#DiscoveryResult(DiscoveryEnabledServer, LoadBalancerStats) + */ + @VisibleForTesting + public static DiscoveryResult from(InstanceInfo instanceInfo, boolean useSecurePort) { + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, useSecurePort); + return new DiscoveryResult(server); + } + + public Optional getIPAddr() { + if (this == DiscoveryResult.EMPTY) { + return Optional.empty(); + } + if (server.getInstanceInfo() != null) { + String ip = server.getInstanceInfo().getIPAddr(); + if (ip != null && !ip.isEmpty()) { + return Optional.of(ip); + } + return Optional.empty(); + } + return Optional.empty(); + } + + @Override + public String getHost() { + return server == null ? "undefined" : server.getHost(); + } + + @Override + public boolean isDiscoveryEnabled() { + return server instanceof DiscoveryEnabledServer; + } + + @Override + public int getPort() { + return server == null ? -1 : server.getPort(); + } + + public int getSecurePort() { + return server.getInstanceInfo().getSecurePort(); + } + + public boolean isSecurePortEnabled() { + return server.getInstanceInfo().isPortEnabled(PortType.SECURE); + } + + public String getTarget() { + final InstanceInfo instanceInfo = server.getInstanceInfo(); + if (server.getPort() == instanceInfo.getSecurePort()) { + return instanceInfo.getSecureVipAddress(); + } else { + return instanceInfo.getVIPAddress(); + } + } + + + public SimpleMetaInfo getMetaInfo() { + return new SimpleMetaInfo(server.getMetaInfo()); + } + + @Nullable + public String getAvailabilityZone() { + final InstanceInfo instanceInfo = server.getInstanceInfo(); + if (instanceInfo.getDataCenterInfo() instanceof AmazonInfo) { + return ((AmazonInfo) instanceInfo.getDataCenterInfo()).getMetadata().get("availability-zone"); + } + return null; + } + + public String getZone() { + return server.getZone(); + } + + public String getServerId() { + return server.getInstanceInfo().getId(); + } + + public DiscoveryEnabledServer getServer() { + return server; + } + + @VisibleForTesting + ServerStats getServerStats(){ + return this.serverStats; + } + + public String getASGName() { + return server.getInstanceInfo().getASGName(); + } + + public String getAppName() { + return server.getInstanceInfo().getAppName().toLowerCase(Locale.ROOT); + } + + public void noteResponseTime(double msecs) { + serverStats.noteResponseTime(msecs); + } + + public boolean isCircuitBreakerTripped() { + return serverStats.isCircuitBreakerTripped(); + } + + public void incrementActiveRequestsCount() { + serverStats.incrementActiveRequestsCount(); + } + + public void incrementOpenConnectionsCount() { + serverStats.incrementOpenConnectionsCount(); + } + + public void incrementSuccessiveConnectionFailureCount() { + serverStats.incrementSuccessiveConnectionFailureCount(); + } + + public void incrementNumRequests() { + serverStats.incrementNumRequests(); + } + + public int getOpenConnectionsCount() { + return serverStats.getOpenConnectionsCount(); + } + + public long getTotalRequestsCount() { + return serverStats.getTotalRequestsCount(); + } + + public int getActiveRequestsCount() { + return serverStats.getActiveRequestsCount(); + } + + public void decrementOpenConnectionsCount() { + serverStats.decrementOpenConnectionsCount(); + } + + public void decrementActiveRequestsCount() { + serverStats.decrementActiveRequestsCount(); + } + + public void clearSuccessiveConnectionFailureCount() { + serverStats.clearSuccessiveConnectionFailureCount(); + } + + public void addToFailureCount() { + serverStats.addToFailureCount(); + } + + public void stopPublishingStats() { + serverStats.close(); + } + + @Override + public int hashCode() { + return Objects.hashCode(server); + } + + + /** + * Two instances are deemed identical if they wrap the same underlying discovery server instance. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof DiscoveryResult)) { + return false; + } + final DiscoveryResult other = (DiscoveryResult) obj; + return server.equals(other.server); + } + +} diff --git a/zuul-discovery/src/main/java/com/netflix/zuul/discovery/DynamicServerResolver.java b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/DynamicServerResolver.java new file mode 100644 index 00000000..35761209 --- /dev/null +++ b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/DynamicServerResolver.java @@ -0,0 +1,97 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.discovery; + +import static com.netflix.client.config.CommonClientConfigKey.NFLoadBalancerClassName; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.Sets; +import com.netflix.client.config.IClientConfig; +import com.netflix.loadbalancer.DynamicServerListLoadBalancer; +import com.netflix.loadbalancer.Server; +import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; +import com.netflix.zuul.resolver.Resolver; +import com.netflix.zuul.resolver.ResolverListener; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * @author Argha C + * @since 2/25/21 + * + * Implements a resolver, wrapping a ribbon load-balancer. + */ +public class DynamicServerResolver implements Resolver { + + private final DynamicServerListLoadBalancer loadBalancer; + ResolverListener listener; + + public DynamicServerResolver(IClientConfig clientConfig, ResolverListener listener) { + this.loadBalancer = createLoadBalancer(clientConfig); + this.loadBalancer.addServerListChangeListener(this::onUpdate); + this.listener = listener; + } + + @Override + public DiscoveryResult resolve(@Nullable Object key) { + final Server server = loadBalancer.chooseServer(key); + return server!= null ? new DiscoveryResult((DiscoveryEnabledServer) server, loadBalancer.getLoadBalancerStats()) : DiscoveryResult.EMPTY; + } + + @Override + public boolean hasServers() { + return !loadBalancer.getReachableServers().isEmpty(); + } + + @Override + public void shutdown() { + loadBalancer.shutdown(); + } + + private DynamicServerListLoadBalancer createLoadBalancer(IClientConfig clientConfig) { + //TODO(argha-c): Revisit this style of LB initialization post modularization. Ideally the LB should be pluggable. + + // Use a hard coded string for the LB default name to avoid a dependency on Ribbon classes. + String loadBalancerClassName = + clientConfig.get(NFLoadBalancerClassName, "com.netflix.loadbalancer.ZoneAwareLoadBalancer"); + + DynamicServerListLoadBalancer lb; + try { + Class clazz = Class.forName(loadBalancerClassName); + lb = clazz.asSubclass(DynamicServerListLoadBalancer.class).getConstructor().newInstance(); + lb.initWithNiwsConfig(clientConfig); + } catch (Exception e) { + Throwables.throwIfUnchecked(e); + throw new IllegalStateException("Could not instantiate LoadBalancer " + loadBalancerClassName, e); + } + + return lb; + } + + @VisibleForTesting + void onUpdate(List oldList, List newList) { + Set oldSet = new HashSet<>(oldList); + Set newSet = new HashSet<>(newList); + final List discoveryResults = Sets.difference(oldSet, newSet).stream() + .map(server -> new DiscoveryResult((DiscoveryEnabledServer) server, loadBalancer.getLoadBalancerStats())) + .collect(Collectors.toList()); + listener.onChange(discoveryResults); + } +} diff --git a/zuul-discovery/src/main/java/com/netflix/zuul/discovery/NonDiscoveryServer.java b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/NonDiscoveryServer.java new file mode 100644 index 00000000..e3a91ba3 --- /dev/null +++ b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/NonDiscoveryServer.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.discovery; + +import com.netflix.loadbalancer.Server; +import java.util.Objects; + +/** + * @author Argha C + * @since 3/1/21 + *

+ * This exists merely to wrap a resolver lookup result, that is not discovery enabled. + */ +public final class NonDiscoveryServer implements ResolverResult { + + private final Server server; + + public NonDiscoveryServer(String host, int port) { + Objects.requireNonNull(host, "host name"); + this.server = new Server(host, validatePort(port)); + } + + @Override + public String getHost() { + return server.getHost(); + } + + @Override + public int getPort() { + return server.getPort(); + } + + @Override + public boolean isDiscoveryEnabled() { + return false; + } + + private int validatePort(int port) { + if (port < 0 || port > 0xFFFF) + throw new IllegalArgumentException("port out of range:" + port); + return port; + } +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/plugins/ServoMonitor.java b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/ResolverResult.java similarity index 55% rename from zuul-core/src/main/java/com/netflix/zuul/plugins/ServoMonitor.java rename to zuul-discovery/src/main/java/com/netflix/zuul/discovery/ResolverResult.java index 1d79267e..b7bda4a2 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/plugins/ServoMonitor.java +++ b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/ResolverResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Netflix, Inc. + * Copyright 2021 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.zuul.plugins; - -import com.netflix.servo.monitor.Monitors; -import com.netflix.zuul.stats.monitoring.Monitor; -import com.netflix.zuul.stats.monitoring.NamedCount; +package com.netflix.zuul.discovery; /** - * implementation to hook up the Servo Monitors to register Named counters - * @author Mikey Cohen - * Date: 4/16/13 - * Time: 4:40 PM + * @author Argha C + * @since 2/25/21 + * + * Wraps the result of a resolution attempt. + * At this time, it doesn't encapsulate a collection of instances, but ideally should. */ -public class ServoMonitor implements Monitor { - @Override - public void register(NamedCount monitorObj) { - Monitors.registerObject(monitorObj); - } + +public interface ResolverResult { + + //TODO(argha-c): This should ideally model returning a collection of host/port pairs. + public String getHost(); + + public int getPort(); + + public boolean isDiscoveryEnabled(); } diff --git a/zuul-discovery/src/main/java/com/netflix/zuul/discovery/SimpleMetaInfo.java b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/SimpleMetaInfo.java new file mode 100644 index 00000000..29fed1bd --- /dev/null +++ b/zuul-discovery/src/main/java/com/netflix/zuul/discovery/SimpleMetaInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.discovery; + +import com.netflix.loadbalancer.Server.MetaInfo; + +/** + * @author Argha C + * @since 2/25/21 + * + * placeholder to mimic metainfo for a non-Eureka enabled server. + * This exists to preserve compatibility with some current logic, but should be revisited. + */ +public final class SimpleMetaInfo { + + private final MetaInfo metaInfo; + + public SimpleMetaInfo(MetaInfo metaInfo) { + this.metaInfo = metaInfo; + } + + public String getServerGroup() { + return metaInfo.getServerGroup(); + } + + public String getServiceIdForDiscovery() { + return metaInfo.getServiceIdForDiscovery(); + } + + public String getInstanceId() { + return metaInfo.getInstanceId(); + } +} diff --git a/zuul-discovery/src/main/java/com/netflix/zuul/resolver/Resolver.java b/zuul-discovery/src/main/java/com/netflix/zuul/resolver/Resolver.java new file mode 100644 index 00000000..65590085 --- /dev/null +++ b/zuul-discovery/src/main/java/com/netflix/zuul/resolver/Resolver.java @@ -0,0 +1,45 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.resolver; + +/** + * @author Argha C + * @since 2/25/21 + * + * Resolves a key to a discovery result type. + */ +public interface Resolver { + + /** + * + * @param key unique identifier that may be used by certain resolvers as part of lookup. Implementations + * can narrow this down to be nullable. + * @return the result of a resolver lookup + */ + //TODO(argha-c) Param needs to be typed, once the ribbon LB lookup API is figured out. + T resolve(Object key); + + /** + * @return true if the resolver has available servers, false otherwise + */ + boolean hasServers(); + + /** + * hook to perform activities on shutdown + */ + void shutdown(); +} diff --git a/zuul-core/src/main/java/com/netflix/zuul/event/ZuulEvent.java b/zuul-discovery/src/main/java/com/netflix/zuul/resolver/ResolverListener.java similarity index 54% rename from zuul-core/src/main/java/com/netflix/zuul/event/ZuulEvent.java rename to zuul-discovery/src/main/java/com/netflix/zuul/resolver/ResolverListener.java index 46f8d864..b72e54c5 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/event/ZuulEvent.java +++ b/zuul-discovery/src/main/java/com/netflix/zuul/resolver/ResolverListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Netflix, Inc. + * Copyright 2021 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,27 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.netflix.zuul.event; + +package com.netflix.zuul.resolver; + +import java.util.List; /** - * Simple event class - * @author Mikey Cohen - * Date: 3/22/13 - * Time: 10:09 AM + * @author Argha C + * @since 2/25/21 + * + * Listener for resolver updates. */ -public class ZuulEvent { - String eventType; - String eventMessage; - - public ZuulEvent( String eventType, String eventMessage) { - this.eventMessage = eventMessage; - this.eventType = eventType; - } +public interface ResolverListener { - public String getEventType() { - return this.eventType; - } - public String getEventMessage() { - return this.eventMessage; - } + /** + * Hook to respond to resolver updates + * @param removedSet the servers removed from the latest resolver update, but included in the previous update. + */ + void onChange(List removedSet); } diff --git a/zuul-discovery/src/test/java/com/netflix/zuul/discovery/DiscoveryResultTest.java b/zuul-discovery/src/test/java/com/netflix/zuul/discovery/DiscoveryResultTest.java new file mode 100644 index 00000000..301cbf4e --- /dev/null +++ b/zuul-discovery/src/test/java/com/netflix/zuul/discovery/DiscoveryResultTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.discovery; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import com.google.common.truth.Truth; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.Builder; +import com.netflix.appinfo.InstanceInfo.PortType; +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.loadbalancer.DynamicServerListLoadBalancer; +import com.netflix.loadbalancer.Server; +import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; +import java.util.Optional; +import org.junit.Test; + +public class DiscoveryResultTest { + + @Test + public void hashCodeForNull() { + final DiscoveryResult discoveryResult = new DiscoveryResult(null); + assertNotNull(discoveryResult.hashCode()); + assertEquals(0, discoveryResult.hashCode()); + } + + @Test + public void hostAndPortForNullServer() { + final DiscoveryResult discoveryResult = new DiscoveryResult(null); + + assertEquals("undefined", discoveryResult.getHost()); + assertEquals(-1, discoveryResult.getPort()); + } + + @Test + public void serverStatsCacheForSameServer() { + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("serverstats-cache") + .setHostName("serverstats-cache") + .setPort(7777).build(); + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false); + final DiscoveryEnabledServer serverSecure = new DiscoveryEnabledServer(instanceInfo, true); + + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + final DiscoveryResult result1 = new DiscoveryResult(serverSecure, lb.getLoadBalancerStats()); + + Truth.assertThat(result.getServerStats()).isSameInstanceAs(result1.getServerStats()); + } + + @Test + public void serverStatsDifferForDifferentServer() { + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("serverstats-cache") + .setHostName("serverstats-cache") + .setPort(7777).build(); + final InstanceInfo otherInstance = Builder.newBuilder() + .setAppName("serverstats-cache-2") + .setHostName("serverstats-cache-2") + .setPort(7777).build(); + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false); + final DiscoveryEnabledServer serverSecure = new DiscoveryEnabledServer(otherInstance, false); + + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + final DiscoveryResult result1 = new DiscoveryResult(serverSecure, lb.getLoadBalancerStats()); + + Truth.assertThat(result.getServerStats()).isNotSameInstanceAs(result1.getServerStats()); + } + + @Test + public void ipAddrV4FromInstanceInfo() { + final String ipAddr = "100.1.0.1"; + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("ipAddrv4") + .setHostName("ipAddrv4") + .setIPAddr(ipAddr) + .setPort(7777).build(); + + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false); + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + + Truth.assertThat(result.getIPAddr()).isEqualTo(Optional.of(ipAddr)); + } + + @Test + public void ipAddrEmptyForIncompleteInstanceInfo() { + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("ipAddrMissing") + .setHostName("ipAddrMissing") + .setPort(7777).build(); + + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false); + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + + Truth.assertThat(result.getIPAddr()).isEqualTo(Optional.empty()); + } + + @Test + public void sameUnderlyingInstanceInfoEqualsSameResult() { + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("server-equality") + .setHostName("server-equality") + .setPort(7777).build(); + + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false); + final DiscoveryEnabledServer otherServer = new DiscoveryEnabledServer(instanceInfo, false); + + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + final DiscoveryResult otherResult = new DiscoveryResult(otherServer, lb.getLoadBalancerStats()); + + Truth.assertThat(result).isEqualTo(otherResult); + } + + @Test + public void serverInstancesExposingDiffPortsAreNotEqual() { + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("server-equality") + .setHostName("server-equality") + .setPort(7777).build(); + final InstanceInfo otherPort = Builder.newBuilder() + .setAppName("server-equality") + .setHostName("server-equality") + .setPort(9999).build(); + + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false); + final DiscoveryEnabledServer otherServer = new DiscoveryEnabledServer(otherPort, false); + + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + final DiscoveryResult otherResult = new DiscoveryResult(otherServer, lb.getLoadBalancerStats()); + + Truth.assertThat(result).isNotEqualTo(otherResult); + } + + @Test + public void securePortMustCheckInstanceInfo() { + final InstanceInfo instanceInfo = Builder.newBuilder() + .setAppName("secure-port") + .setHostName("secure-port") + .setPort(7777) + .enablePort(PortType.SECURE, false) + .build(); + final InstanceInfo secureEnabled = Builder.newBuilder() + .setAppName("secure-port") + .setHostName("secure-port") + .setPort(7777) + .enablePort(PortType.SECURE, true) + .build(); + + final DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, true); + final DiscoveryEnabledServer secureServer = new DiscoveryEnabledServer(secureEnabled, true); + final DynamicServerListLoadBalancer lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl()); + + final DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats()); + final DiscoveryResult secure = new DiscoveryResult(secureServer, lb.getLoadBalancerStats()); + + Truth.assertThat(result.isSecurePortEnabled()).isFalse(); + Truth.assertThat(secure.isSecurePortEnabled()).isTrue(); + } +} diff --git a/zuul-discovery/src/test/java/com/netflix/zuul/discovery/DynamicServerResolverTest.java b/zuul-discovery/src/test/java/com/netflix/zuul/discovery/DynamicServerResolverTest.java new file mode 100644 index 00000000..412d56f4 --- /dev/null +++ b/zuul-discovery/src/test/java/com/netflix/zuul/discovery/DynamicServerResolverTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul.discovery; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.truth.Truth; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.Builder; +import com.netflix.client.config.DefaultClientConfigImpl; +import com.netflix.niws.loadbalancer.DiscoveryEnabledServer; +import com.netflix.zuul.resolver.ResolverListener; +import java.security.cert.TrustAnchor; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DynamicServerResolverTest { + + + @Test + public void verifyListenerUpdates() { + + class CustomListener implements ResolverListener { + + private List resultSet = Lists.newArrayList(); + + @Override + public void onChange(List changedSet) { + resultSet = changedSet; + } + + public List updatedList() { + return resultSet; + } + } + + final CustomListener listener = new CustomListener(); + final DynamicServerResolver resolver = new DynamicServerResolver(new DefaultClientConfigImpl(), listener); + + final InstanceInfo first = Builder.newBuilder() + .setAppName("zuul-discovery-1") + .setHostName("zuul-discovery-1") + .setIPAddr("100.10.10.1") + .setPort(443) + .build(); + final InstanceInfo second = Builder.newBuilder() + .setAppName("zuul-discovery-2") + .setHostName("zuul-discovery-2") + .setIPAddr("100.10.10.2") + .setPort(443) + .build(); + final DiscoveryEnabledServer server1 = new DiscoveryEnabledServer(first, true); + final DiscoveryEnabledServer server2 = new DiscoveryEnabledServer(second, true); + + resolver.onUpdate(ImmutableList.of(server1, server2), ImmutableList.of()); + + Truth.assertThat(listener.updatedList()).containsExactly(new DiscoveryResult(server1), new DiscoveryResult(server2)); + } + + @Test + public void properSentinelValueWhenServersUnavailable() { + final DynamicServerResolver resolver = new DynamicServerResolver(new DefaultClientConfigImpl(), new ResolverListener() { + @Override + public void onChange(List removedSet) { + } + }); + + final DiscoveryResult nonExistentServer = resolver.resolve(null); + + Truth.assertThat(nonExistentServer).isSameInstanceAs(DiscoveryResult.EMPTY); + Truth.assertThat(nonExistentServer.getHost()).isEqualTo("undefined"); + Truth.assertThat(nonExistentServer.getPort()).isEqualTo(-1); + } +} diff --git a/zuul-groovy/build.gradle b/zuul-groovy/build.gradle new file mode 100644 index 00000000..beb3203e --- /dev/null +++ b/zuul-groovy/build.gradle @@ -0,0 +1,18 @@ +apply plugin: "java-library" + +dependencies { + implementation libraries.guava + implementation project(":zuul-core") + api "org.codehaus.groovy:groovy-all:${versions_groovy}" + + testImplementation libraries.junit, + libraries.mockito, + libraries.truth +} + +// Silences log statements during tests. This still allows normal failures to be printed. +test { + testLogging { + showStandardStreams = false + } +} diff --git a/zuul-groovy/dependencies.lock b/zuul-groovy/dependencies.lock new file mode 100644 index 00000000..530d8b5a --- /dev/null +++ b/zuul-groovy/dependencies.lock @@ -0,0 +1,895 @@ +{ + "compileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "org.codehaus.groovy:groovy-all": { + "locked": "3.0.3" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "org.codehaus.groovy:groovy-all": { + "locked": "3.0.3" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "jmhRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.codehaus.groovy:groovy-all": { + "locked": "3.0.3" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "runtimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.codehaus.groovy:groovy-all": { + "locked": "3.0.3" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "testCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.codehaus.groovy:groovy-all": { + "locked": "3.0.3" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "testRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.codehaus.groovy:groovy-all": { + "locked": "3.0.3" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + } +} \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyCompiler.java b/zuul-groovy/src/main/java/com/netflix/zuul/groovy/GroovyCompiler.java similarity index 83% rename from zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyCompiler.java rename to zuul-groovy/src/main/java/com/netflix/zuul/groovy/GroovyCompiler.java index 5d17e050..5a0c46f2 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyCompiler.java +++ b/zuul-groovy/src/main/java/com/netflix/zuul/groovy/GroovyCompiler.java @@ -38,14 +38,11 @@ public class GroovyCompiler implements DynamicCodeCompiler { /** * Compiles Groovy code and returns the Class of the compiles code. * - * @param sCode - * @param sName - * @return */ - public Class compile(String sCode, String sName) { + public Class compile(String sCode, String sName) { GroovyClassLoader loader = getGroovyClassLoader(); LOG.warn("Compiling filter: " + sName); - Class groovyClass = loader.parseClass(sCode, sName); + Class groovyClass = loader.parseClass(sCode, sName); return groovyClass; } @@ -59,13 +56,10 @@ GroovyClassLoader getGroovyClassLoader() { /** * Compiles groovy class from a file * - * @param file - * @return - * @throws java.io.IOException */ - public Class compile(File file) throws IOException { + public Class compile(File file) throws IOException { GroovyClassLoader loader = getGroovyClassLoader(); - Class groovyClass = loader.parseClass(file); + Class groovyClass = loader.parseClass(file); return groovyClass; } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyFileFilter.java b/zuul-groovy/src/main/java/com/netflix/zuul/groovy/GroovyFileFilter.java similarity index 85% rename from zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyFileFilter.java rename to zuul-groovy/src/main/java/com/netflix/zuul/groovy/GroovyFileFilter.java index 373f30b9..21b7132c 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/groovy/GroovyFileFilter.java +++ b/zuul-groovy/src/main/java/com/netflix/zuul/groovy/GroovyFileFilter.java @@ -15,14 +15,6 @@ */ package com.netflix.zuul.groovy; -/** - * Created with IntelliJ IDEA. - * User: mcohen - * Date: 5/30/13 - * Time: 11:47 AM - * To change this template use File | Settings | File Templates. - */ - import java.io.File; import java.io.FilenameFilter; diff --git a/zuul-core/src/main/java/com/netflix/zuul/scriptManager/FilterInfo.java b/zuul-groovy/src/main/java/com/netflix/zuul/scriptManager/FilterInfo.java similarity index 85% rename from zuul-core/src/main/java/com/netflix/zuul/scriptManager/FilterInfo.java rename to zuul-groovy/src/main/java/com/netflix/zuul/scriptManager/FilterInfo.java index 99ab78d1..90009f31 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/scriptManager/FilterInfo.java +++ b/zuul-groovy/src/main/java/com/netflix/zuul/scriptManager/FilterInfo.java @@ -16,10 +16,10 @@ package com.netflix.zuul.scriptManager; import com.netflix.zuul.filters.FilterType; -import net.jcip.annotations.ThreadSafe; import java.util.Date; import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.concurrent.ThreadSafe; /** * Representation of a ZuulFilter for representing and storing in a database @@ -42,15 +42,15 @@ public class FilterInfo implements Comparable{ /** * Constructor - * @param filter_id - * @param filter_code - * @param filter_type - * @param filter_name - * @param disablePropertyName - * @param filter_order - * @param application_name */ - public FilterInfo(String filter_id, String filter_code, FilterType filter_type, String filter_name, String disablePropertyName, String filter_order, String application_name) { + public FilterInfo( + String filter_id, + String filter_code, + FilterType filter_type, + String filter_name, + String disablePropertyName, + String filter_order, + String application_name) { this.filter_id = filter_id; this.filter_code = filter_code; this.filter_type = filter_type; @@ -80,7 +80,6 @@ public String getFilterCode() { /** - * * @return the name of the property to disable the filter. */ public String getFilterDisablePropertyName() { @@ -88,7 +87,6 @@ public String getFilterDisablePropertyName() { } /** - * * @return the filter_type */ public FilterType getFilterType() { @@ -111,28 +109,25 @@ public String toString() { } /** - * the application name context of the filter. This is for if Zuul is applied to different applications in the same datastor - * @return + * the application name context of the filter. This is for if Zuul is applied to different applications in the same + * datastore. */ public String getApplication_name() { return application_name; } - /** - * - * @param filter_id - * @param revision - * @param creationDate - * @param isActive - * @param isCanary - * @param filter_code - * @param filter_type - * @param filter_name - * @param disablePropertyName - * @param filter_order - * @param application_name - */ - public FilterInfo(String filter_id, int revision, Date creationDate, boolean isActive, boolean isCanary, String filter_code, FilterType filter_type, String filter_name, String disablePropertyName, String filter_order, String application_name) { + public FilterInfo( + String filter_id, + int revision, + Date creationDate, + boolean isActive, + boolean isCanary, + String filter_code, + FilterType filter_type, + String filter_name, + String disablePropertyName, + String filter_order, + String application_name) { this.filter_id = filter_id; this.revision = revision; this.creationDate = creationDate; @@ -199,9 +194,6 @@ public String getFilterOrder() { /** * builds the unique filter_id key - * @param application_name - * @param filter_type - * @param filter_name * @return key is application_name:filter_name:filter_type */ public static String buildFilterID(String application_name, FilterType filter_type, String filter_name) { diff --git a/zuul-core/src/main/java/com/netflix/zuul/scriptManager/FilterVerifier.java b/zuul-groovy/src/main/java/com/netflix/zuul/scriptManager/FilterVerifier.java similarity index 73% rename from zuul-core/src/main/java/com/netflix/zuul/scriptManager/FilterVerifier.java rename to zuul-groovy/src/main/java/com/netflix/zuul/scriptManager/FilterVerifier.java index d77cfb9b..7d411849 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/scriptManager/FilterVerifier.java +++ b/zuul-groovy/src/main/java/com/netflix/zuul/scriptManager/FilterVerifier.java @@ -19,9 +19,10 @@ import com.netflix.zuul.ZuulApplicationInfo; import com.netflix.zuul.filters.BaseFilter; import groovy.lang.GroovyClassLoader; +import org.codehaus.groovy.control.CompilationFailedException; /** - * verifies that the given source code is compilable in Groovy, can be instanciated, and is a ZuulFilter type + * verifies that the given source code is compilable in Groovy, can be instantiated, and is a ZuulFilter type * * @author Mikey Cohen * Date: 6/12/12 @@ -41,16 +42,12 @@ public static FilterVerifier getInstance() { /** * verifies compilation, instanciation and that it is a ZuulFilter * - * @param sFilterCode * @return a FilterInfo object representing that code - * @throws org.codehaus.groovy.control.CompilationFailedException - * - * @throws IllegalAccessException - * @throws InstantiationException */ - public FilterInfo verifyFilter(String sFilterCode) throws org.codehaus.groovy.control.CompilationFailedException, IllegalAccessException, InstantiationException { - Class groovyClass = compileGroovy(sFilterCode); - Object instance = instanciateClass(groovyClass); + public FilterInfo verifyFilter(String sFilterCode) + throws CompilationFailedException, IllegalAccessException, InstantiationException { + Class groovyClass = compileGroovy(sFilterCode); + Object instance = instantiateClass(groovyClass); checkZuulFilterInstance(instance); BaseFilter filter = (BaseFilter) instance; @@ -60,7 +57,7 @@ public FilterInfo verifyFilter(String sFilterCode) throws org.codehaus.groovy.co return new FilterInfo(filter_id, sFilterCode, filter.filterType(), groovyClass.getSimpleName(), filter.disablePropertyName(), "" + filter.filterOrder(), ZuulApplicationInfo.getApplicationName()); } - Object instanciateClass(Class groovyClass) throws InstantiationException, IllegalAccessException { + Object instantiateClass(Class groovyClass) throws InstantiationException, IllegalAccessException { return groovyClass.newInstance(); } @@ -73,12 +70,9 @@ void checkZuulFilterInstance(Object zuulFilter) throws InstantiationException { /** * compiles the Groovy source code * - * @param sFilterCode - * @return - * @throws org.codehaus.groovy.control.CompilationFailedException - * */ - public Class compileGroovy(String sFilterCode) throws org.codehaus.groovy.control.CompilationFailedException { + public Class compileGroovy(String sFilterCode) + throws CompilationFailedException { GroovyClassLoader loader = new GroovyClassLoader(); return loader.parseClass(sFilterCode); } diff --git a/zuul-core/src/test/java/com/netflix/zuul/groovy/GroovyCompilerTest.java b/zuul-groovy/src/test/java/com/netflix/zuul/groovy/GroovyCompilerTest.java similarity index 92% rename from zuul-core/src/test/java/com/netflix/zuul/groovy/GroovyCompilerTest.java rename to zuul-groovy/src/test/java/com/netflix/zuul/groovy/GroovyCompilerTest.java index 7351aa59..c4b5ab57 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/groovy/GroovyCompilerTest.java +++ b/zuul-groovy/src/test/java/com/netflix/zuul/groovy/GroovyCompilerTest.java @@ -24,7 +24,8 @@ import groovy.lang.GroovyObject; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; /** * Unit tests for {@link GroovyCompiler}. @@ -35,7 +36,7 @@ public class GroovyCompilerTest { @Test public void testLoadGroovyFromString() { - GroovyCompiler compiler = spy(new GroovyCompiler()); + GroovyCompiler compiler = Mockito.spy(new GroovyCompiler()); try { diff --git a/zuul-core/src/test/java/com/netflix/zuul/groovy/GroovyFileFilterTest.java b/zuul-groovy/src/test/java/com/netflix/zuul/groovy/GroovyFileFilterTest.java similarity index 61% rename from zuul-core/src/test/java/com/netflix/zuul/groovy/GroovyFileFilterTest.java rename to zuul-groovy/src/test/java/com/netflix/zuul/groovy/GroovyFileFilterTest.java index f133032c..c9037a7a 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/groovy/GroovyFileFilterTest.java +++ b/zuul-groovy/src/test/java/com/netflix/zuul/groovy/GroovyFileFilterTest.java @@ -18,43 +18,24 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; import java.io.File; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; +import org.junit.runners.JUnit4; /** * Unit tests for {@link GroovyFileFilter}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(JUnit4.class) public class GroovyFileFilterTest { - @Mock - private File nonGroovyFile; - @Mock - private File groovyFile; - - @Mock - private File directory; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - @Test public void testGroovyFileFilter() { - when(nonGroovyFile.getName()).thenReturn("file.mikey"); - when(groovyFile.getName()).thenReturn("file.groovy"); GroovyFileFilter filter = new GroovyFileFilter(); - assertFalse(filter.accept(nonGroovyFile, "file.mikey")); - assertTrue(filter.accept(groovyFile, "file.groovy")); + assertFalse(filter.accept(new File("/"), "file.mikey")); + assertTrue(filter.accept(new File("/"), "file.groovy")); } } diff --git a/zuul-core/src/test/java/com/netflix/zuul/scriptManager/FilterVerifierTest.java b/zuul-groovy/src/test/java/com/netflix/zuul/scriptManager/FilterVerifierTest.java similarity index 97% rename from zuul-core/src/test/java/com/netflix/zuul/scriptManager/FilterVerifierTest.java rename to zuul-groovy/src/test/java/com/netflix/zuul/scriptManager/FilterVerifierTest.java index 1266ec13..16ff0533 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/scriptManager/FilterVerifierTest.java +++ b/zuul-groovy/src/test/java/com/netflix/zuul/scriptManager/FilterVerifierTest.java @@ -127,13 +127,13 @@ public void testZuulFilterInstance() throws Exception { Class filterClass = FilterVerifier.INSTANCE.compileGroovy(sGoodGroovyScriptFilter); assertNotNull(filterClass); - Object filter1 = FilterVerifier.INSTANCE.instanciateClass(filterClass); + Object filter1 = FilterVerifier.INSTANCE.instantiateClass(filterClass); FilterVerifier.INSTANCE.checkZuulFilterInstance(filter1); filterClass = FilterVerifier.INSTANCE.compileGroovy(sNotZuulFilterGroovy); assertNotNull(filterClass); - Object filter2 = FilterVerifier.INSTANCE.instanciateClass(filterClass); + Object filter2 = FilterVerifier.INSTANCE.instantiateClass(filterClass); assertThrows(InstantiationException.class, () -> FilterVerifier.INSTANCE.checkZuulFilterInstance(filter2)); } diff --git a/zuul-guice/build.gradle b/zuul-guice/build.gradle new file mode 100644 index 00000000..94a1a331 --- /dev/null +++ b/zuul-guice/build.gradle @@ -0,0 +1,18 @@ +apply plugin: "java-library" + +dependencies { + implementation project(":zuul-core") + api(group: 'com.google.inject', name: 'guice', version: "5.0.1") + implementation 'commons-configuration:commons-configuration:1.8' + + testImplementation libraries.junit, + libraries.mockito, + libraries.truth +} + +// Silences log statements during tests. This still allows normal failures to be printed. +test { + testLogging { + showStandardStreams = false + } +} diff --git a/zuul-guice/dependencies.lock b/zuul-guice/dependencies.lock new file mode 100644 index 00000000..ad4e6993 --- /dev/null +++ b/zuul-guice/dependencies.lock @@ -0,0 +1,904 @@ +{ + "compileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.inject:guice": { + "locked": "5.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.inject:guice": { + "locked": "5.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "jmhRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "30.1-jre" + }, + "com.google.inject:guice": { + "locked": "5.0.1" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "runtimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "30.1-jre" + }, + "com.google.inject:guice": { + "locked": "5.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "testCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.inject:guice": { + "locked": "5.0.1" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "testRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "30.1-jre" + }, + "com.google.inject:guice": { + "locked": "5.0.1" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.mockito:mockito-core": { + "locked": "3.9.0" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + } +} \ No newline at end of file diff --git a/zuul-core/src/main/java/com/netflix/zuul/guice/GuiceFilterFactory.java b/zuul-guice/src/main/java/com/netflix/zuul/guice/GuiceFilterFactory.java similarity index 88% rename from zuul-core/src/main/java/com/netflix/zuul/guice/GuiceFilterFactory.java rename to zuul-guice/src/main/java/com/netflix/zuul/guice/GuiceFilterFactory.java index de32cd72..2e731565 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/guice/GuiceFilterFactory.java +++ b/zuul-guice/src/main/java/com/netflix/zuul/guice/GuiceFilterFactory.java @@ -33,7 +33,7 @@ public GuiceFilterFactory(Injector injector) { } @Override - public ZuulFilter newInstance(Class clazz) throws Exception { - return (ZuulFilter) injector.getInstance(clazz); + public ZuulFilter newInstance(Class clazz) throws Exception { + return injector.getInstance(clazz.asSubclass(ZuulFilter.class)); } } diff --git a/zuul-core/src/main/java/com/netflix/zuul/init/ZuulFiltersModule.java b/zuul-guice/src/main/java/com/netflix/zuul/init/ZuulFiltersModule.java similarity index 92% rename from zuul-core/src/main/java/com/netflix/zuul/init/ZuulFiltersModule.java rename to zuul-guice/src/main/java/com/netflix/zuul/init/ZuulFiltersModule.java index efcf5efc..ffa7d497 100644 --- a/zuul-core/src/main/java/com/netflix/zuul/init/ZuulFiltersModule.java +++ b/zuul-guice/src/main/java/com/netflix/zuul/init/ZuulFiltersModule.java @@ -20,13 +20,12 @@ import com.google.inject.AbstractModule; import com.google.inject.Provides; import com.netflix.zuul.BasicFilterUsageNotifier; -import com.netflix.zuul.DynamicCodeCompiler; import com.netflix.zuul.FilterFactory; import com.netflix.zuul.FilterFileManager.FilterFileManagerConfig; import com.netflix.zuul.FilterUsageNotifier; import com.netflix.zuul.filters.ZuulFilter; -import com.netflix.zuul.groovy.GroovyCompiler; import com.netflix.zuul.guice.GuiceFilterFactory; +import java.io.FilenameFilter; import org.apache.commons.configuration.AbstractConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +49,6 @@ public class ZuulFiltersModule extends AbstractModule { protected void configure() { LOG.info("Starting Groovy Filter file manager"); - bind(DynamicCodeCompiler.class).to(GroovyCompiler.class); bind(FilterFactory.class).to(GuiceFilterFactory.class); bind(FilterUsageNotifier.class).to(BasicFilterUsageNotifier.class); @@ -59,13 +57,15 @@ protected void configure() { } @Provides - FilterFileManagerConfig provideFilterFileManagerConfig(AbstractConfiguration config) { + FilterFileManagerConfig provideFilterFileManagerConfig( + AbstractConfiguration config, FilenameFilter filenameFilter) { // Get filter directories. String[] filterLocations = findFilterLocations(config); String[] filterClassNames = findClassNames(config); // Init the FilterStore. - FilterFileManagerConfig filterConfig = new FilterFileManagerConfig(filterLocations, filterClassNames, 5); + FilterFileManagerConfig filterConfig = + new FilterFileManagerConfig(filterLocations, filterClassNames, 5, filenameFilter); return filterConfig; } diff --git a/zuul-guice/src/test/java/com/netflix/zuul/BaseInjectionIntegTest.java b/zuul-guice/src/test/java/com/netflix/zuul/BaseInjectionIntegTest.java new file mode 100644 index 00000000..aa07663b --- /dev/null +++ b/zuul-guice/src/test/java/com/netflix/zuul/BaseInjectionIntegTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021 Netflix, Inc. + * + * 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 com.netflix.zuul; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.netflix.zuul.init.InitTestModule; +import com.netflix.zuul.init.ZuulFiltersModule; +import org.junit.Before; + +/** + * Base Injection Integration Test + * + * This test should be extended for integration tests requiring Guice injection. + * + * @author Arthur Gonigberg + * @since February 22, 2021 + */ +public abstract class BaseInjectionIntegTest { + protected Injector injector = Guice.createInjector(new InitTestModule(), new ZuulFiltersModule()); + + @Before + public void setup () { + injector.injectMembers(this); + } +} diff --git a/zuul-core/src/test/java/com/netflix/zuul/guice/GuiceFilterFactoryIntegTest.java b/zuul-guice/src/test/java/com/netflix/zuul/guice/GuiceFilterFactoryIntegTest.java similarity index 77% rename from zuul-core/src/test/java/com/netflix/zuul/guice/GuiceFilterFactoryIntegTest.java rename to zuul-guice/src/test/java/com/netflix/zuul/guice/GuiceFilterFactoryIntegTest.java index 98da85f6..6882e696 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/guice/GuiceFilterFactoryIntegTest.java +++ b/zuul-guice/src/test/java/com/netflix/zuul/guice/GuiceFilterFactoryIntegTest.java @@ -16,20 +16,19 @@ package com.netflix.zuul.guice; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; -import com.netflix.governator.guice.test.ModulesForTesting; -import com.netflix.governator.guice.test.junit4.GovernatorJunit4ClassRunner; -import javax.inject.Inject; +import com.google.inject.Inject; +import com.netflix.zuul.BaseInjectionIntegTest; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; -@RunWith(GovernatorJunit4ClassRunner.class) -@ModulesForTesting() -public class GuiceFilterFactoryIntegTest { +@RunWith(BlockJUnit4ClassRunner.class) +public class GuiceFilterFactoryIntegTest extends BaseInjectionIntegTest { @Inject - GuiceFilterFactory filterFactory; + private GuiceFilterFactory filterFactory; @Test public void ctorInjection() throws Exception { diff --git a/zuul-core/src/test/java/com/netflix/zuul/guice/TestGuiceConstructorFilter.java b/zuul-guice/src/test/java/com/netflix/zuul/guice/TestGuiceConstructorFilter.java similarity index 100% rename from zuul-core/src/test/java/com/netflix/zuul/guice/TestGuiceConstructorFilter.java rename to zuul-guice/src/test/java/com/netflix/zuul/guice/TestGuiceConstructorFilter.java diff --git a/zuul-core/src/test/java/com/netflix/zuul/guice/TestGuiceFieldFilter.java b/zuul-guice/src/test/java/com/netflix/zuul/guice/TestGuiceFieldFilter.java similarity index 100% rename from zuul-core/src/test/java/com/netflix/zuul/guice/TestGuiceFieldFilter.java rename to zuul-guice/src/test/java/com/netflix/zuul/guice/TestGuiceFieldFilter.java diff --git a/zuul-core/src/test/java/com/netflix/zuul/init/InitTestModule.java b/zuul-guice/src/test/java/com/netflix/zuul/init/InitTestModule.java similarity index 80% rename from zuul-core/src/test/java/com/netflix/zuul/init/InitTestModule.java rename to zuul-guice/src/test/java/com/netflix/zuul/init/InitTestModule.java index 7adf5c17..967bf3ea 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/init/InitTestModule.java +++ b/zuul-guice/src/test/java/com/netflix/zuul/init/InitTestModule.java @@ -18,12 +18,17 @@ import com.google.inject.AbstractModule; import com.netflix.config.ConfigurationManager; +import com.netflix.spectator.api.NoopRegistry; +import com.netflix.spectator.api.Registry; +import java.io.FilenameFilter; import org.apache.commons.configuration.AbstractConfiguration; public class InitTestModule extends AbstractModule { @Override protected void configure() { bind(AbstractConfiguration.class).toInstance(ConfigurationManager.getConfigInstance()); + bind(FilenameFilter.class).toInstance((dir, name) -> false); + bind(Registry.class).to(NoopRegistry.class); } } diff --git a/zuul-core/src/test/java/com/netflix/zuul/init/TestZuulFilter.java b/zuul-guice/src/test/java/com/netflix/zuul/init/TestZuulFilter.java similarity index 100% rename from zuul-core/src/test/java/com/netflix/zuul/init/TestZuulFilter.java rename to zuul-guice/src/test/java/com/netflix/zuul/init/TestZuulFilter.java diff --git a/zuul-core/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleIntegTest.java b/zuul-guice/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleIntegTest.java similarity index 84% rename from zuul-core/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleIntegTest.java rename to zuul-guice/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleIntegTest.java index efcd8cbe..8a2e7508 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleIntegTest.java +++ b/zuul-guice/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleIntegTest.java @@ -16,24 +16,23 @@ package com.netflix.zuul.init; +import static org.junit.Assert.assertEquals; + import com.netflix.config.ConfigurationManager; -import com.netflix.governator.guice.test.ModulesForTesting; -import com.netflix.governator.guice.test.junit4.GovernatorJunit4ClassRunner; +import com.netflix.zuul.BaseInjectionIntegTest; import com.netflix.zuul.FilterFileManager.FilterFileManagerConfig; +import javax.inject.Inject; import org.apache.commons.configuration.AbstractConfiguration; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; -import javax.inject.Inject; - -import static org.junit.Assert.assertEquals; +@RunWith(BlockJUnit4ClassRunner.class) +public class ZuulFiltersModuleIntegTest extends BaseInjectionIntegTest { -@RunWith(GovernatorJunit4ClassRunner.class) -@ModulesForTesting({InitTestModule.class, ZuulFiltersModule.class}) -public class ZuulFiltersModuleIntegTest { @Inject - FilterFileManagerConfig filterFileManagerConfig; + private FilterFileManagerConfig filterFileManagerConfig; @BeforeClass public static void before() { diff --git a/zuul-core/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleTest.java b/zuul-guice/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleTest.java similarity index 58% rename from zuul-core/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleTest.java rename to zuul-guice/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleTest.java index af7dfe02..e8d5b86a 100644 --- a/zuul-core/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleTest.java +++ b/zuul-guice/src/test/java/com/netflix/zuul/init/ZuulFiltersModuleTest.java @@ -16,17 +16,16 @@ package com.netflix.zuul.init; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + import com.netflix.zuul.init2.TestZuulFilter2; import org.apache.commons.configuration.AbstractConfiguration; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.when; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class ZuulFiltersModuleTest { @@ -34,11 +33,12 @@ public class ZuulFiltersModuleTest { @Mock AbstractConfiguration configuration; - ZuulFiltersModule module = new ZuulFiltersModule(); + private final ZuulFiltersModule module = new ZuulFiltersModule(); @Test public void testDefaultFilterLocations() { - when(configuration.getStringArray(eq("zuul.filters.locations"))).thenReturn("inbound,outbound,endpoint".split(",")); + Mockito.when(configuration.getStringArray("zuul.filters.locations")) + .thenReturn("inbound,outbound,endpoint".split(",")); String[] filterLocations = module.findFilterLocations(configuration); @@ -48,7 +48,7 @@ public void testDefaultFilterLocations() { @Test public void testEmptyFilterLocations() { - when(configuration.getStringArray(eq("zuul.filters.locations"))).thenReturn(new String[0]); + Mockito.when(configuration.getStringArray("zuul.filters.locations")).thenReturn(new String[0]); String[] filterLocations = module.findFilterLocations(configuration); @@ -57,8 +57,8 @@ public void testEmptyFilterLocations() { @Test public void testEmptyClassNames() { - when(configuration.getStringArray(eq("zuul.filters.classes"))).thenReturn(new String[]{}); - when(configuration.getStringArray(eq("zuul.filters.packages"))).thenReturn(new String[]{}); + Mockito.when(configuration.getStringArray("zuul.filters.classes")).thenReturn(new String[]{}); + Mockito.when(configuration.getStringArray("zuul.filters.packages")).thenReturn(new String[]{}); String[] classNames = module.findClassNames(configuration); @@ -68,10 +68,11 @@ public void testEmptyClassNames() { @Test public void testClassNamesOnly() { - Class expectedClass = TestZuulFilter.class; + Class expectedClass = TestZuulFilter.class; - when(configuration.getStringArray(eq("zuul.filters.classes"))).thenReturn(new String[]{"com.netflix.zuul.init.TestZuulFilter"}); - when(configuration.getStringArray(eq("zuul.filters.packages"))).thenReturn(new String[]{}); + Mockito.when(configuration.getStringArray("zuul.filters.classes")) + .thenReturn(new String[]{"com.netflix.zuul.init.TestZuulFilter"}); + Mockito.when(configuration.getStringArray("zuul.filters.packages")).thenReturn(new String[]{}); String[] classNames = module.findClassNames(configuration); @@ -83,10 +84,11 @@ public void testClassNamesOnly() { @Test public void testClassNamesPackagesOnly() { - Class expectedClass = TestZuulFilter.class; + Class expectedClass = TestZuulFilter.class; - when(configuration.getStringArray(eq("zuul.filters.classes"))).thenReturn(new String[]{}); - when(configuration.getStringArray(eq("zuul.filters.packages"))).thenReturn(new String[]{"com.netflix.zuul.init"}); + Mockito.when(configuration.getStringArray("zuul.filters.classes")).thenReturn(new String[]{}); + Mockito.when(configuration.getStringArray("zuul.filters.packages")) + .thenReturn(new String[]{"com.netflix.zuul.init"}); String[] classNames = module.findClassNames(configuration); @@ -97,11 +99,13 @@ public void testClassNamesPackagesOnly() { @Test public void testMultiClasses() { - Class expectedClass1 = TestZuulFilter.class; - Class expectedClass2 = TestZuulFilter2.class; + Class expectedClass1 = TestZuulFilter.class; + Class expectedClass2 = TestZuulFilter2.class; - when(configuration.getStringArray(eq("zuul.filters.classes"))).thenReturn(new String[]{"com.netflix.zuul.init.TestZuulFilter", "com.netflix.zuul.init2.TestZuulFilter2"}); - when(configuration.getStringArray(eq("zuul.filters.packages"))).thenReturn(new String[0]); + Mockito.when(configuration.getStringArray("zuul.filters.classes")) + .thenReturn(new String[] { + "com.netflix.zuul.init.TestZuulFilter", "com.netflix.zuul.init2.TestZuulFilter2"}); + Mockito.when(configuration.getStringArray("zuul.filters.packages")).thenReturn(new String[0]); String[] classNames = module.findClassNames(configuration); @@ -112,11 +116,12 @@ public void testMultiClasses() { @Test public void testMultiPackages() { - Class expectedClass1 = TestZuulFilter.class; - Class expectedClass2 = TestZuulFilter2.class; + Class expectedClass1 = TestZuulFilter.class; + Class expectedClass2 = TestZuulFilter2.class; - when(configuration.getStringArray(eq("zuul.filters.classes"))).thenReturn(new String[0]); - when(configuration.getStringArray(eq("zuul.filters.packages"))).thenReturn(new String[]{"com.netflix.zuul.init", "com.netflix.zuul.init2"}); + Mockito.when(configuration.getStringArray("zuul.filters.classes")).thenReturn(new String[0]); + Mockito.when(configuration.getStringArray("zuul.filters.packages")) + .thenReturn(new String[]{"com.netflix.zuul.init", "com.netflix.zuul.init2"}); String[] classNames = module.findClassNames(configuration); diff --git a/zuul-core/src/test/java/com/netflix/zuul/init2/TestZuulFilter2.java b/zuul-guice/src/test/java/com/netflix/zuul/init2/TestZuulFilter2.java similarity index 100% rename from zuul-core/src/test/java/com/netflix/zuul/init2/TestZuulFilter2.java rename to zuul-guice/src/test/java/com/netflix/zuul/init2/TestZuulFilter2.java diff --git a/zuul-processor/build.gradle b/zuul-processor/build.gradle new file mode 100644 index 00000000..207a7f7e --- /dev/null +++ b/zuul-processor/build.gradle @@ -0,0 +1,17 @@ +apply plugin: "java" + +dependencies { + implementation libraries.guava + implementation project(":zuul-core") + + testImplementation libraries.junit, + libraries.truth + testAnnotationProcessor project(":zuul-processor") +} + +// Silences log statements during tests. This still allows normal failures to be printed. +test { + testLogging { + showStandardStreams = false + } +} diff --git a/zuul-processor/dependencies.lock b/zuul-processor/dependencies.lock new file mode 100644 index 00000000..0ab4d580 --- /dev/null +++ b/zuul-processor/dependencies.lock @@ -0,0 +1,1047 @@ +{ + "compileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "jmhRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" + }, + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "runtimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "testAnnotationProcessor": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery", + "com.netflix.zuul:zuul-processor" + ], + "locked": "29.0-jre" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-processor" + ], + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "com.netflix.zuul:zuul-processor": { + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "testCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.5" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.7.25" + } + }, + "testRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.fasterxml.jackson.core:jackson-databind": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.12.1" + }, + "com.google.guava:guava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "29.0-jre" + }, + "com.google.truth:truth": { + "locked": "1.0.1" + }, + "com.netflix.archaius:archaius-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.7.6" + }, + "com.netflix.eureka:eureka-client": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.9.18" + }, + "com.netflix.netflix-commons:netflix-commons-util": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.3.0" + }, + "com.netflix.ribbon:ribbon-archaius": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-eureka": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" + }, + "com.netflix.spectator:spectator-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "project": true + }, + "io.netty:netty-buffer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-haproxy": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-codec-http2": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-common": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-handler": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-tcnative-boringssl-static": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "2.0.38.Final" + }, + "io.netty:netty-transport": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-epoll": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.netty:netty-transport-native-kqueue": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "4.1.63.Final" + }, + "io.perfmark:perfmark-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "0.23.0" + }, + "io.reactivex:rxjava": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.2.1" + }, + "javax.inject:javax.inject": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1" + }, + "junit:junit": { + "locked": "4.13.1" + }, + "org.bouncycastle:bcprov-jdk15on": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core" + ], + "locked": "1.68" + }, + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + } +} \ No newline at end of file diff --git a/zuul-processor/src/main/java/com/netflix/zuul/filters/processor/FilterProcessor.java b/zuul-processor/src/main/java/com/netflix/zuul/filters/processor/FilterProcessor.java new file mode 100644 index 00000000..309e7247 --- /dev/null +++ b/zuul-processor/src/main/java/com/netflix/zuul/filters/processor/FilterProcessor.java @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.filters.processor; + +import com.google.common.annotations.VisibleForTesting; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.NoSuchFileException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + + +@SupportedAnnotationTypes(FilterProcessor.FILTER_TYPE) +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public final class FilterProcessor extends AbstractProcessor { + + static final String FILTER_TYPE = "com.netflix.zuul.Filter"; + + private final Set annotatedElements = new HashSet<>(); + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + Set annotated = + roundEnv.getElementsAnnotatedWith(processingEnv.getElementUtils().getTypeElement(FILTER_TYPE)); + for (Element el : annotated) { + if (el.getModifiers().contains(Modifier.ABSTRACT)) { + continue; + } + annotatedElements.add(processingEnv.getElementUtils().getBinaryName((TypeElement) el).toString()); + } + + if (roundEnv.processingOver()) { + try { + addNewClasses(processingEnv.getFiler(), annotatedElements); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + annotatedElements.clear(); + } + } + return true; + } + + static void addNewClasses(Filer filer, Collection elements) throws IOException { + String resourceName = "META-INF/zuul/allfilters"; + List existing = Collections.emptyList(); + try { + FileObject existingFilters = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceName); + try (InputStream is = existingFilters.openInputStream(); + InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + existing = readResourceFile(reader); + } + } catch (FileNotFoundException | NoSuchFileException e) { + // Perhaps log this. + } catch (IOException e) { + throw new RuntimeException(e); + } + + int sizeBefore = existing.size(); + Set existingSet = new LinkedHashSet<>(existing); + List newElements = new ArrayList<>(existingSet); + for (String element : elements) { + if (existingSet.add(element)) { + newElements.add(element); + } + } + if (newElements.size() == sizeBefore) { + // nothing to do. + return; + } + newElements.sort(String::compareTo); + + FileObject dest = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceName); + try (OutputStream os = dest.openOutputStream(); + OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { + writeResourceFile(osw, newElements); + } + } + + @VisibleForTesting + static List readResourceFile(Reader reader) throws IOException { + BufferedReader br = new BufferedReader(reader); + String line; + List lines = new ArrayList<>(); + while ((line = br.readLine()) != null) { + if (line.trim().isEmpty()) { + continue; + } + lines.add(line); + } + return Collections.unmodifiableList(lines); + } + + @VisibleForTesting + static void writeResourceFile(Writer writer, Collection elements) throws IOException { + BufferedWriter bw = new BufferedWriter(writer); + for (Object element : elements) { + bw.write(String.valueOf(element)); + bw.newLine(); + } + bw.flush(); + } +} diff --git a/zuul-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/zuul-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 00000000..ce8f0f56 --- /dev/null +++ b/zuul-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +com.netflix.zuul.filters.processor.FilterProcessor,aggregating \ No newline at end of file diff --git a/zuul-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/zuul-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 00000000..6479b872 --- /dev/null +++ b/zuul-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +com.netflix.zuul.filters.processor.FilterProcessor \ No newline at end of file diff --git a/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/FilterProcessorTest.java b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/FilterProcessorTest.java new file mode 100644 index 00000000..5ca74d16 --- /dev/null +++ b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/FilterProcessorTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.filters.processor; + +import com.google.common.truth.Truth; +import com.netflix.zuul.StaticFilterLoader; +import com.netflix.zuul.filters.ZuulFilter; +import com.netflix.zuul.filters.processor.override.SubpackageFilter; +import com.netflix.zuul.filters.processor.subpackage.OverrideFilter; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for {@link FilterProcessor}. + */ +@RunWith(JUnit4.class) +public class FilterProcessorTest { + + @Test + public void allFilterClassedRecorded() throws Exception { + Collection>> filters = + StaticFilterLoader.loadFilterTypesFromResources(getClass().getClassLoader()); + + Truth.assertThat(filters).containsExactly( + OuterClassFilter.class, + TopLevelFilter.class, + TopLevelFilter.StaticSubclassFilter.class, + TopLevelFilter.SubclassFilter.class, + OverrideFilter.class, + SubpackageFilter.class); + } +} diff --git a/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TestFilter.java b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TestFilter.java new file mode 100644 index 00000000..72cbe14b --- /dev/null +++ b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TestFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.filters.processor; + +import com.netflix.zuul.exception.ZuulFilterConcurrencyExceededException; +import com.netflix.zuul.filters.FilterSyncType; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.ZuulFilter; +import com.netflix.zuul.message.ZuulMessage; +import io.netty.handler.codec.http.HttpContent; +import rx.Observable; + +/** + * A dummy filter which is used for testing. + */ +public abstract class TestFilter implements ZuulFilter { + + @Override + public boolean isDisabled() { + throw new UnsupportedOperationException(); + } + + @Override + public String filterName() { + throw new UnsupportedOperationException(); + } + + @Override + public int filterOrder() { + throw new UnsupportedOperationException(); + } + + @Override + public FilterType filterType() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean overrideStopFilterProcessing() { + throw new UnsupportedOperationException(); + } + + @Override + public void incrementConcurrency() throws ZuulFilterConcurrencyExceededException { + throw new UnsupportedOperationException(); + } + + @Override + public Observable applyAsync(ZuulMessage input) { + throw new UnsupportedOperationException(); + } + + @Override + public void decrementConcurrency() { + throw new UnsupportedOperationException(); + } + + @Override + public FilterSyncType getSyncType() { + throw new UnsupportedOperationException(); + } + + @Override + public ZuulMessage getDefaultOutput(ZuulMessage input) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean needsBodyBuffered(ZuulMessage input) { + throw new UnsupportedOperationException(); + } + + @Override + public HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean shouldFilter(ZuulMessage msg) { + throw new UnsupportedOperationException(); + } +} diff --git a/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TopLevelFilter.java b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TopLevelFilter.java new file mode 100644 index 00000000..300a97cb --- /dev/null +++ b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TopLevelFilter.java @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.filters.processor; + +import com.netflix.zuul.Filter; +import com.netflix.zuul.filters.FilterType; + +/** + * Used to test generated code. + */ +@Filter(order = 20, type = FilterType.INBOUND) +final class TopLevelFilter extends TestFilter { + + @Filter(order = 21, type = FilterType.INBOUND) + static final class StaticSubclassFilter extends TestFilter {} + + @SuppressWarnings("unused") // This should be ignored by the processor, since it is abstract + @Filter(order = 22, type = FilterType.INBOUND) + static abstract class AbstractSubclassFilter extends TestFilter {} + + @SuppressWarnings("InnerClassMayBeStatic") // The purpose of this test + @Filter(order = 23, type = FilterType.INBOUND) + final class SubclassFilter extends TestFilter {} + + static { + // This should be ignored by the processor, since it is private. + // See https://bugs.openjdk.java.net/browse/JDK-6587158 + @SuppressWarnings("unused") + @Filter(order = 23, type = FilterType.INBOUND) + final class MethodClassFilter {} + } +} +@Filter(order = 24, type = FilterType.INBOUND) +final class OuterClassFilter extends TestFilter {} diff --git a/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/SubpackageFilter.java b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/SubpackageFilter.java new file mode 100644 index 00000000..3ec6fec3 --- /dev/null +++ b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/SubpackageFilter.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.filters.processor.override; + +import com.netflix.zuul.Filter; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.processor.TestFilter; + +@Filter(order = 30, type = FilterType.INBOUND) +public final class SubpackageFilter extends TestFilter { +} diff --git a/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/package-info.java b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/package-info.java new file mode 100644 index 00000000..2636cc1a --- /dev/null +++ b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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. + */ + +@com.netflix.zuul.Filter.FilterPackageName("MySubpackage") +package com.netflix.zuul.filters.processor.override; \ No newline at end of file diff --git a/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/subpackage/OverrideFilter.java b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/subpackage/OverrideFilter.java new file mode 100644 index 00000000..a3354077 --- /dev/null +++ b/zuul-processor/src/test/java/com/netflix/zuul/filters/processor/subpackage/OverrideFilter.java @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Netflix, Inc. + * + * 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 com.netflix.zuul.filters.processor.subpackage; + +import com.netflix.zuul.Filter; +import com.netflix.zuul.filters.FilterType; +import com.netflix.zuul.filters.processor.TestFilter; + +@Filter(order = 30, type = FilterType.INBOUND) +public final class OverrideFilter extends TestFilter { +} diff --git a/zuul-sample/build.gradle b/zuul-sample/build.gradle index d9abcae6..b3753cb8 100644 --- a/zuul-sample/build.gradle +++ b/zuul-sample/build.gradle @@ -1,10 +1,18 @@ apply plugin: 'groovy' +apply plugin: "java" apply plugin: 'application' dependencies { - compile project(":zuul-core") + implementation project(":zuul-core"), + project(":zuul-groovy"), + project(":zuul-guice") + implementation "com.netflix.eureka:eureka-client:1.9.18" + implementation 'commons-configuration:commons-configuration:1.8' + annotationProcessor project(":zuul-processor") + + runtimeOnly 'org.apache.logging.log4j:log4j-core:2.14.0' + runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.14.0' - compile 'com.netflix.blitz4j:blitz4j:1.37.2' } jar { diff --git a/zuul-sample/dependencies.lock b/zuul-sample/dependencies.lock index ac2f0741..2f75673e 100644 --- a/zuul-sample/dependencies.lock +++ b/zuul-sample/dependencies.lock @@ -1,1252 +1,24 @@ { - "compile": { + "annotationProcessor": { "com.fasterxml.jackson.core:jackson-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.netflix.archaius:archaius-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.7.6" - }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" - }, - "com.netflix.eureka:eureka-client": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.netflix-commons:netflix-commons-util": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.12.21" - }, - "com.netflix.spectator:spectator-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.59.0" - }, - "com.netflix.zuul:zuul-core": { - "project": true - }, - "commons-collections:commons-collections": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" - }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" - }, - "io.netty:netty-buffer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-common": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.0.28.Final" - }, - "io.netty:netty-transport": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.20.1" - }, - "io.reactivex:rxjava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.1" - }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.bouncycastle:bcprov-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.codehaus.groovy:groovy-all": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4.4" - }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" - }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.5" - }, - "org.slf4j:slf4j-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.7.25" - } - }, - "compileClasspath": { - "com.fasterxml.jackson.core:jackson-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.google.guava:guava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.netflix.archaius:archaius-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.7.5" - }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" - }, - "com.netflix.eureka:eureka-client": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.netflix-commons:netflix-commons-util": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.7.2" - }, - "com.netflix.spectator:spectator-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.59.0" - }, - "com.netflix.zuul:zuul-core": { - "project": true - }, - "commons-collections:commons-collections": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" - }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" - }, - "io.netty:netty-buffer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-common": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.0.28.Final" - }, - "io.netty:netty-transport": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.20.1" - }, - "io.reactivex:rxjava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.1" - }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.bouncycastle:bcprov-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.codehaus.groovy:groovy-all": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4.4" - }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" - }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.5" - }, - "org.slf4j:slf4j-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.7.25" - } - }, - "default": { - "com.fasterxml.jackson.core:jackson-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.google.guava:guava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.netflix.archaius:archaius-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.7.6" - }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" - }, - "com.netflix.eureka:eureka-client": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.netflix-commons:netflix-commons-util": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.12.21" - }, - "com.netflix.spectator:spectator-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.59.0" - }, - "com.netflix.zuul:zuul-core": { - "project": true - }, - "commons-collections:commons-collections": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" - }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" - }, - "io.netty:netty-buffer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-common": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.0.28.Final" - }, - "io.netty:netty-transport": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.20.1" - }, - "io.reactivex:rxjava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.1" - }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.bouncycastle:bcprov-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.codehaus.groovy:groovy-all": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4.4" - }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" - }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.5" - }, - "org.slf4j:slf4j-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.7.25" - } - }, - "runtime": { - "com.fasterxml.jackson.core:jackson-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.google.guava:guava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.netflix.archaius:archaius-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.7.6" - }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" - }, - "com.netflix.eureka:eureka-client": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.netflix-commons:netflix-commons-util": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.3.0" - }, - "com.netflix.ribbon:ribbon-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-httpclient": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-loadbalancer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.12.21" - }, - "com.netflix.spectator:spectator-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.59.0" - }, - "com.netflix.zuul:zuul-core": { - "project": true - }, - "commons-collections:commons-collections": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.2.2" - }, - "commons-configuration:commons-configuration": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.8" - }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" - }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" - }, - "io.netty:netty-buffer": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-http2": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-common": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-handler": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.0.28.Final" - }, - "io.netty:netty-transport": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.20.1" - }, - "io.reactivex:rxjava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.1" - }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.bouncycastle:bcprov-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.codehaus.groovy:groovy-all": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4.4" - }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" - }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.5" - }, - "org.slf4j:slf4j-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.7.25" - } - }, - "runtimeClasspath": { - "com.fasterxml.jackson.core:jackson-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.fasterxml.jackson.core:jackson-databind": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.9.8" - }, - "com.google.guava:guava": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject:guice": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery", + "com.netflix.zuul:zuul-processor" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "29.0-jre" }, "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ @@ -1254,33 +26,12 @@ ], "locked": "0.7.6" }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" - }, "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "1.17.10" + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ @@ -1288,140 +39,118 @@ ], "locked": "0.3.0" }, - "com.netflix.ribbon:ribbon-core": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-eureka": { + "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-httpclient": { + "com.netflix.ribbon:ribbon-eureka": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-loadbalancer": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.servo:servo-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "0.12.21" + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.59.0" + "locked": "0.123.1" }, "com.netflix.zuul:zuul-core": { - "project": true - }, - "commons-collections:commons-collections": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.2.2" - }, - "commons-configuration:commons-configuration": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-processor" ], - "locked": "1.8" + "project": true }, - "commons-fileupload:commons-fileupload": { + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.3" + "project": true }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" + "com.netflix.zuul:zuul-processor": { + "project": true }, "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-tcnative-boringssl-static": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.0.28.Final" + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { "firstLevelTransitive": [ @@ -1429,296 +158,258 @@ ], "locked": "1.2.1" }, - "junit:junit": { + "javax.inject:javax.inject": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.13" + "locked": "1" }, - "log4j:log4j": { + "org.bouncycastle:bcprov-jdk15on": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.2.17" + "locked": "1.68" }, - "org.apache.commons:commons-lang3": { + "org.slf4j:slf4j-api": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { + "locked": "1.7.30" + } + }, + "compileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.64" + "locked": "2.12.1" }, - "org.bouncycastle:bcprov-jdk15on": { + "com.google.inject:guice": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "1.64" + "locked": "5.0.1" }, - "org.codehaus.groovy:groovy-all": { + "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.4.4" + "locked": "0.7.5" }, - "org.json:json": { + "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "20090211" + "locked": "1.9.18" }, - "org.mockito:mockito-core": { + "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.9.5" + "locked": "0.3.0" }, - "org.slf4j:slf4j-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.7.25" - } - }, - "testCompile": { - "com.fasterxml.jackson.core:jackson-core": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.4.4" }, - "com.fasterxml.jackson.core:jackson-databind": { + "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.4.4" }, - "com.google.guava:guava": { + "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "28.1-jre" + "locked": "0.123.1" + }, + "com.netflix.zuul:zuul-core": { + "project": true }, - "com.google.inject.extensions:guice-assistedinject": { + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "project": true + }, + "com.netflix.zuul:zuul-groovy": { + "project": true + }, + "com.netflix.zuul:zuul-guice": { + "project": true + }, + "commons-configuration:commons-configuration": { + "locked": "1.8" }, - "com.google.inject.extensions:guice-grapher": { + "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-multibindings": { + "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-servlet": { + "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-throwingproviders": { + "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject:guice": { + "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.netflix.archaius:archaius-core": { + "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.7.6" - }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" + "locked": "4.1.63.Final" }, - "com.netflix.eureka:eureka-client": { + "io.reactivex:rxjava": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.9.4" + "locked": "1.2.1" }, - "com.netflix.governator:governator": { + "org.codehaus.groovy:groovy-all": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy" ], - "locked": "1.17.10" + "locked": "3.0.3" }, - "com.netflix.governator:governator-archaius": { + "org.slf4j:slf4j-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.17.10" + "locked": "1.7.25" + } + }, + "jmh": { + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" }, - "com.netflix.governator:governator-core": { + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" + } + }, + "jmhCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.17.10" + "locked": "2.12.1" }, - "com.netflix.netflix-commons:netflix-commons-util": { + "com.google.inject:guice": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "0.3.0" + "locked": "5.0.1" }, - "com.netflix.ribbon:ribbon-core": { + "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "0.7.5" }, - "com.netflix.ribbon:ribbon-eureka": { + "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "1.9.18" }, - "com.netflix.ribbon:ribbon-httpclient": { + "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "0.3.0" }, - "com.netflix.ribbon:ribbon-loadbalancer": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.servo:servo-core": { + "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.12.21" + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.59.0" + "locked": "0.123.1" }, "com.netflix.zuul:zuul-core": { "project": true }, - "commons-collections:commons-collections": { + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "3.2.2" + "project": true }, - "commons-configuration:commons-configuration": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.8" + "com.netflix.zuul:zuul-groovy": { + "project": true }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" + "com.netflix.zuul:zuul-guice": { + "project": true }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" + "commons-configuration:commons-configuration": { + "locked": "1.8" }, "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-codec-haproxy": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-tcnative-boringssl-static": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.0.28.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-epoll": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-transport-native-kqueue": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" - }, - "io.perfmark:perfmark-api": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "0.20.1" + "locked": "4.1.63.Final" }, "io.reactivex:rxjava": { "firstLevelTransitive": [ @@ -1726,53 +417,17 @@ ], "locked": "1.2.1" }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.2.17" - }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" - }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, - "org.bouncycastle:bcprov-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" - }, "org.codehaus.groovy:groovy-all": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy" ], - "locked": "2.4.4" + "locked": "3.0.3" }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.5" + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" }, "org.slf4j:slf4j-api": { "firstLevelTransitive": [ @@ -1781,100 +436,45 @@ "locked": "1.7.25" } }, - "testCompileClasspath": { + "jmhRuntimeClasspath": { "com.fasterxml.jackson.core:jackson-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery", + "com.netflix.zuul:zuul-groovy" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "30.1-jre" }, "com.google.inject:guice": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "5.0.1" }, "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.7.5" - }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" + "locked": "0.7.6" }, "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "1.17.10" + "locked": "1.9.18" }, "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ @@ -1882,140 +482,128 @@ ], "locked": "0.3.0" }, - "com.netflix.ribbon:ribbon-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.2.4" - }, - "com.netflix.ribbon:ribbon-eureka": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-httpclient": { + "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-loadbalancer": { + "com.netflix.ribbon:ribbon-eureka": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.servo:servo-core": { + "com.netflix.ribbon:ribbon-loadbalancer": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "0.7.2" + "locked": "2.4.4" }, "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.59.0" + "locked": "0.123.1" }, "com.netflix.zuul:zuul-core": { - "project": true - }, - "commons-collections:commons-collections": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy", + "com.netflix.zuul:zuul-guice" ], - "locked": "3.2.2" + "project": true }, - "commons-configuration:commons-configuration": { + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.8" + "project": true }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" + "com.netflix.zuul:zuul-groovy": { + "project": true + }, + "com.netflix.zuul:zuul-guice": { + "project": true }, - "commons-io:commons-io": { + "commons-configuration:commons-configuration": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "2.4" + "locked": "1.8" }, "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-tcnative-boringssl-static": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.0.28.Final" + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { "firstLevelTransitive": [ @@ -2023,121 +611,70 @@ ], "locked": "1.2.1" }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { + "javax.inject:javax.inject": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.2.17" + "locked": "1" }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" + "org.apache.logging.log4j:log4j-core": { + "locked": "2.14.0" }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" + "org.apache.logging.log4j:log4j-slf4j-impl": { + "locked": "2.14.0" }, "org.bouncycastle:bcprov-jdk15on": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.64" + "locked": "1.68" }, "org.codehaus.groovy:groovy-all": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy" ], - "locked": "2.4.4" + "locked": "3.0.3" }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" + "org.openjdk.jmh:jmh-core": { + "locked": "1.21" }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.5" + "org.openjdk.jmh:jmh-generator-bytecode": { + "locked": "1.21" }, "org.slf4j:slf4j-api": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "1.7.25" + "locked": "1.7.30" } }, - "testRuntime": { + "runtimeClasspath": { "com.fasterxml.jackson.core:jackson-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.12.1" }, "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.12.1" }, "com.google.guava:guava": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "28.1-jre" - }, - "com.google.inject.extensions:guice-assistedinject": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-grapher": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-multibindings": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-servlet": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.2.2", - "requested": "4.2.2" - }, - "com.google.inject.extensions:guice-throwingproviders": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery", + "com.netflix.zuul:zuul-groovy" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "30.1-jre" }, "com.google.inject:guice": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "5.0.1" }, "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ @@ -2145,174 +682,141 @@ ], "locked": "0.7.6" }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" - }, "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.17.10" - }, - "com.netflix.governator:governator-archaius": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "1.17.10" + "locked": "1.9.18" }, - "com.netflix.governator:governator-core": { + "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.17.10" + "locked": "0.3.0" }, - "com.netflix.netflix-commons:netflix-commons-util": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "0.3.0" + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, "com.netflix.ribbon:ribbon-eureka": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-httpclient": { + "com.netflix.ribbon:ribbon-loadbalancer": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.ribbon:ribbon-loadbalancer": { + "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "0.123.1" }, - "com.netflix.servo:servo-core": { + "com.netflix.zuul:zuul-core": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy", + "com.netflix.zuul:zuul-guice" ], - "locked": "0.12.21" + "project": true }, - "com.netflix.spectator:spectator-api": { + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.59.0" + "project": true }, - "com.netflix.zuul:zuul-core": { + "com.netflix.zuul:zuul-groovy": { "project": true }, - "commons-collections:commons-collections": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.2.2" + "com.netflix.zuul:zuul-guice": { + "project": true }, "commons-configuration:commons-configuration": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], "locked": "1.8" }, - "commons-fileupload:commons-fileupload": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.3" - }, - "commons-io:commons-io": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4" - }, "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-tcnative-boringssl-static": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.0.28.Final" + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { "firstLevelTransitive": [ @@ -2320,296 +824,328 @@ ], "locked": "1.2.1" }, - "junit:junit": { + "javax.inject:javax.inject": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.13" + "locked": "1" + }, + "org.apache.logging.log4j:log4j-core": { + "locked": "2.14.0" }, - "log4j:log4j": { + "org.apache.logging.log4j:log4j-slf4j-impl": { + "locked": "2.14.0" + }, + "org.bouncycastle:bcprov-jdk15on": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.2.17" + "locked": "1.68" }, - "org.apache.commons:commons-lang3": { + "org.codehaus.groovy:groovy-all": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy" ], - "locked": "3.4" + "locked": "3.0.3" }, - "org.bouncycastle:bcpg-jdk15on": { + "org.slf4j:slf4j-api": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" + ], + "locked": "1.7.30" + } + }, + "testCompileClasspath": { + "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.64" + "locked": "2.12.1" }, - "org.bouncycastle:bcprov-jdk15on": { + "com.google.inject:guice": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "1.64" + "locked": "5.0.1" }, - "org.codehaus.groovy:groovy-all": { + "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.4.4" + "locked": "0.7.5" }, - "org.json:json": { + "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "20090211" + "locked": "1.9.18" }, - "org.mockito:mockito-core": { + "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.9.5" + "locked": "0.3.0" }, - "org.slf4j:slf4j-api": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.7.25" - } - }, - "testRuntimeClasspath": { - "com.fasterxml.jackson.core:jackson-core": { + "locked": "2.4.4" + }, + "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "2.4.4" }, - "com.fasterxml.jackson.core:jackson-databind": { + "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.9.8" + "locked": "0.123.1" }, - "com.google.guava:guava": { + "com.netflix.zuul:zuul-core": { + "project": true + }, + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "28.1-jre" + "project": true + }, + "com.netflix.zuul:zuul-groovy": { + "project": true + }, + "com.netflix.zuul:zuul-guice": { + "project": true }, - "com.google.inject.extensions:guice-assistedinject": { + "commons-configuration:commons-configuration": { + "locked": "1.8" + }, + "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-grapher": { + "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-multibindings": { + "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-servlet": { + "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject.extensions:guice-throwingproviders": { + "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.google.inject:guice": { + "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.2.2", - "requested": "4.2.2" + "locked": "4.1.63.Final" }, - "com.netflix.archaius:archaius-core": { + "io.reactivex:rxjava": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.7.6" + "locked": "1.2.1" }, - "com.netflix.blitz4j:blitz4j": { - "locked": "1.37.2", - "requested": "1.37.2" + "org.codehaus.groovy:groovy-all": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-groovy" + ], + "locked": "3.0.3" }, - "com.netflix.eureka:eureka-client": { + "org.slf4j:slf4j-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.9.4" - }, - "com.netflix.governator:governator": { + "locked": "1.7.25" + } + }, + "testRuntimeClasspath": { + "com.fasterxml.jackson.core:jackson-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.17.10" + "locked": "2.12.1" }, - "com.netflix.governator:governator-archaius": { + "com.fasterxml.jackson.core:jackson-databind": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.17.10" + "locked": "2.12.1" }, - "com.netflix.governator:governator-core": { + "com.google.guava:guava": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery", + "com.netflix.zuul:zuul-groovy" ], - "locked": "1.17.10" + "locked": "30.1-jre" }, - "com.netflix.netflix-commons:netflix-commons-util": { + "com.google.inject:guice": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "0.3.0" + "locked": "5.0.1" }, - "com.netflix.ribbon:ribbon-core": { + "com.netflix.archaius:archaius-core": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "0.7.6" }, - "com.netflix.ribbon:ribbon-eureka": { + "com.netflix.eureka:eureka-client": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "1.9.18" }, - "com.netflix.ribbon:ribbon-httpclient": { + "com.netflix.netflix-commons:netflix-commons-util": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.2.4" + "locked": "0.3.0" }, - "com.netflix.ribbon:ribbon-loadbalancer": { + "com.netflix.ribbon:ribbon-archaius": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "2.2.4" + "locked": "2.4.4" }, - "com.netflix.servo:servo-core": { + "com.netflix.ribbon:ribbon-core": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "0.12.21" + "locked": "2.4.4" }, - "com.netflix.spectator:spectator-api": { + "com.netflix.ribbon:ribbon-eureka": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-discovery" ], - "locked": "0.59.0" + "locked": "2.4.4" }, - "com.netflix.zuul:zuul-core": { - "project": true + "com.netflix.ribbon:ribbon-loadbalancer": { + "firstLevelTransitive": [ + "com.netflix.zuul:zuul-discovery" + ], + "locked": "2.4.4" }, - "commons-collections:commons-collections": { + "com.netflix.spectator:spectator-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "3.2.2" + "locked": "0.123.1" }, - "commons-configuration:commons-configuration": { + "com.netflix.zuul:zuul-core": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy", + "com.netflix.zuul:zuul-guice" ], - "locked": "1.8" + "project": true }, - "commons-fileupload:commons-fileupload": { + "com.netflix.zuul:zuul-discovery": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.3" + "project": true + }, + "com.netflix.zuul:zuul-groovy": { + "project": true + }, + "com.netflix.zuul:zuul-guice": { + "project": true }, - "commons-io:commons-io": { + "commons-configuration:commons-configuration": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-guice" ], - "locked": "2.4" + "locked": "1.8" }, "io.netty:netty-buffer": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-haproxy": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-codec-http2": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-common": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-handler": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" - }, - "io.netty:netty-resolver": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-tcnative-boringssl-static": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "2.0.28.Final" + "locked": "2.0.38.Final" }, "io.netty:netty-transport": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-epoll": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.netty:netty-transport-native-kqueue": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "4.1.45.Final" + "locked": "4.1.63.Final" }, "io.perfmark:perfmark-api": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "0.20.1" + "locked": "0.23.0" }, "io.reactivex:rxjava": { "firstLevelTransitive": [ @@ -2617,59 +1153,36 @@ ], "locked": "1.2.1" }, - "junit:junit": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "4.13" - }, - "log4j:log4j": { + "javax.inject:javax.inject": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.2.17" + "locked": "1" }, - "org.apache.commons:commons-lang3": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "3.4" + "org.apache.logging.log4j:log4j-core": { + "locked": "2.14.0" }, - "org.bouncycastle:bcpg-jdk15on": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "1.64" + "org.apache.logging.log4j:log4j-slf4j-impl": { + "locked": "2.14.0" }, "org.bouncycastle:bcprov-jdk15on": { "firstLevelTransitive": [ "com.netflix.zuul:zuul-core" ], - "locked": "1.64" + "locked": "1.68" }, "org.codehaus.groovy:groovy-all": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "2.4.4" - }, - "org.json:json": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" - ], - "locked": "20090211" - }, - "org.mockito:mockito-core": { - "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-groovy" ], - "locked": "1.9.5" + "locked": "3.0.3" }, "org.slf4j:slf4j-api": { "firstLevelTransitive": [ - "com.netflix.zuul:zuul-core" + "com.netflix.zuul:zuul-core", + "com.netflix.zuul:zuul-discovery" ], - "locked": "1.7.25" + "locked": "1.7.30" } } } \ No newline at end of file diff --git a/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Routes.groovy b/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Routes.groovy index 7b58eb9e..03db3cf2 100644 --- a/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Routes.groovy +++ b/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Routes.groovy @@ -17,9 +17,9 @@ package com.netflix.zuul.sample.filters.inbound import com.netflix.zuul.context.SessionContext +import com.netflix.zuul.filters.endpoint.ProxyEndpoint import com.netflix.zuul.filters.http.HttpInboundSyncFilter import com.netflix.zuul.message.http.HttpRequestMessage -import com.netflix.zuul.netty.filter.ZuulEndPointRunner import com.netflix.zuul.sample.filters.endpoint.Healthcheck /** @@ -51,7 +51,7 @@ class Routes extends HttpInboundSyncFilter { context.setEndpoint(Healthcheck.class.getCanonicalName()) } else { - context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME); + context.setEndpoint(ProxyEndpoint.class.getCanonicalName()); context.setRouteVIP("api") } diff --git a/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/outbound/ZuulResponseFilter.groovy b/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/outbound/ZuulResponseFilter.groovy index 3971dcee..92313b93 100644 --- a/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/outbound/ZuulResponseFilter.groovy +++ b/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/outbound/ZuulResponseFilter.groovy @@ -39,7 +39,7 @@ import static com.netflix.zuul.constants.ZuulHeaders.* * Date: December 21, 2017 */ class ZuulResponseFilter extends HttpOutboundSyncFilter { - private static final Logger log = LoggerFactory.getLogger(ZuulResponseFilter.class) + private static final Logger logger = LoggerFactory.getLogger(ZuulResponseFilter.class) private static final DynamicBooleanProperty SEND_RESPONSE_HEADERS = new DynamicBooleanProperty("zuul.responseFilter.send.headers", true) @@ -76,7 +76,6 @@ class ZuulResponseFilter extends HttpOutboundSyncFilter { headers.set(X_ZUUL, "zuul") headers.set(X_ZUUL_INSTANCE, System.getenv("EC2_INSTANCE_ID") ?: "unknown") headers.set(CONNECTION, KEEP_ALIVE) - headers.set(X_ZUUL_FILTER_EXECUTION_STATUS, context.getFilterExecutionSummary().toString()) headers.set(X_ORIGINATING_URL, response.getInboundRequest().reconstructURI()) if (response.getStatus() >= 400 && context.getError() != null) { @@ -86,13 +85,17 @@ class ZuulResponseFilter extends HttpOutboundSyncFilter { } if (response.getStatus() >= 500) { - log.info("Passport: {}", CurrentPassport.fromSessionContext(context)) + logger.info("Passport: {}", CurrentPassport.fromSessionContext(context)) + } + + if (logger.isDebugEnabled()) { + logger.debug("Filter execution summary :: {}", context.getFilterExecutionSummary()) } } if (context.debugRequest()) { - Debug.getRequestDebug(context).forEach({ s -> log.info("REQ_DEBUG: " + s) }) - Debug.getRoutingDebug(context).forEach({ s -> log.info("ZUUL_DEBUG: " + s) }) + Debug.getRequestDebug(context).forEach({ s -> logger.info("REQ_DEBUG: " + s) }) + Debug.getRoutingDebug(context).forEach({ s -> logger.info("ZUUL_DEBUG: " + s) }) } return response diff --git a/zuul-sample/src/main/java/com/netflix/zuul/sample/Bootstrap.java b/zuul-sample/src/main/java/com/netflix/zuul/sample/Bootstrap.java index e4085064..eaec2ec9 100644 --- a/zuul-sample/src/main/java/com/netflix/zuul/sample/Bootstrap.java +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/Bootstrap.java @@ -16,8 +16,8 @@ package com.netflix.zuul.sample; +import com.google.inject.Guice; import com.google.inject.Injector; -import com.netflix.governator.InjectorBuilder; import com.netflix.zuul.netty.server.BaseServerStartup; import com.netflix.zuul.netty.server.Server; @@ -41,14 +41,15 @@ public void start() { Server server = null; try { - Injector injector = InjectorBuilder.fromModule(new ZuulSampleModule()).createInjector(); + Injector injector = Guice.createInjector(new ZuulSampleModule()); BaseServerStartup serverStartup = injector.getInstance(BaseServerStartup.class); server = serverStartup.server(); long startupDuration = System.currentTimeMillis() - startTime; System.out.println("Zuul Sample: finished startup. Duration = " + startupDuration + " ms"); - server.start(true); + server.start(); + server.awaitTermination(); } catch (Throwable t) { t.printStackTrace(); diff --git a/zuul-sample/src/main/java/com/netflix/zuul/sample/SampleServerStartup.java b/zuul-sample/src/main/java/com/netflix/zuul/sample/SampleServerStartup.java index 7a13284a..51fd4783 100644 --- a/zuul-sample/src/main/java/com/netflix/zuul/sample/SampleServerStartup.java +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/SampleServerStartup.java @@ -89,8 +89,8 @@ public SampleServerStartup(ServerStatusManager serverStatusManager, FilterLoader } @Override - protected Map> chooseAddrsAndChannels(ChannelGroup clientChannels) { - Map> addrsToChannels = new HashMap<>(); + protected Map> chooseAddrsAndChannels(ChannelGroup clientChannels) { + Map> addrsToChannels = new HashMap<>(); SocketAddress sockAddr; String metricId; { @@ -131,7 +131,7 @@ protected Map> chooseAddrsAndChannels(Chann channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, false); addrsToChannels.put( - sockAddr, + new NamedSocketAddress("http", sockAddr), new ZuulServerChannelInitializer( metricId, channelConfig, channelDependencies, clientChannels)); logAddrConfigured(sockAddr); @@ -155,7 +155,7 @@ protected Map> chooseAddrsAndChannels(Chann addHttp2DefaultConfig(channelConfig, mainListenAddressName); addrsToChannels.put( - sockAddr, + new NamedSocketAddress("http2", sockAddr), new Http2SslChannelInitializer( metricId, channelConfig, channelDependencies, clientChannels)); logAddrConfigured(sockAddr, sslConfig); @@ -173,7 +173,6 @@ protected Map> chooseAddrsAndChannels(Chann ServerSslConfig.getDefaultCiphers(), loadFromResources("server.cert"), loadFromResources("server.key"), - null, ClientAuth.REQUIRE, loadFromResources("truststore.jks"), loadFromResources("truststore.key"), @@ -187,7 +186,7 @@ protected Map> chooseAddrsAndChannels(Chann channelConfig.set(CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig)); addrsToChannels.put( - sockAddr, + new NamedSocketAddress("http_mtls", sockAddr), new Http1MutualSslChannelInitializer( metricId, channelConfig, channelDependencies, clientChannels)); logAddrConfigured(sockAddr, sslConfig); @@ -204,13 +203,13 @@ protected Map> chooseAddrsAndChannels(Chann channelDependencies.set(ZuulDependencyKeys.pushConnectionRegistry, pushConnectionRegistry); addrsToChannels.put( - sockAddr, + new NamedSocketAddress("websocket", sockAddr), new SampleWebSocketPushChannelInitializer( metricId, channelConfig, channelDependencies, clientChannels)); logAddrConfigured(sockAddr); // port to accept push message from the backend, should be accessible on internal network only. - addrsToChannels.put(pushSockAddr, pushSenderInitializer); + addrsToChannels.put(new NamedSocketAddress("http.push", pushSockAddr), pushSenderInitializer); logAddrConfigured(pushSockAddr); break; @@ -225,12 +224,12 @@ protected Map> chooseAddrsAndChannels(Chann channelDependencies.set(ZuulDependencyKeys.pushConnectionRegistry, pushConnectionRegistry); addrsToChannels.put( - sockAddr, + new NamedSocketAddress("sse", sockAddr), new SampleSSEPushChannelInitializer( metricId, channelConfig, channelDependencies, clientChannels)); logAddrConfigured(sockAddr); // port to accept push message from the backend, should be accessible on internal network only. - addrsToChannels.put(pushSockAddr, pushSenderInitializer); + addrsToChannels.put(new NamedSocketAddress("http.push", pushSockAddr), pushSenderInitializer); logAddrConfigured(pushSockAddr); break; } diff --git a/zuul-sample/src/main/java/com/netflix/zuul/sample/ZuulSampleModule.java b/zuul-sample/src/main/java/com/netflix/zuul/sample/ZuulSampleModule.java index b1791051..ef43a426 100644 --- a/zuul-sample/src/main/java/com/netflix/zuul/sample/ZuulSampleModule.java +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/ZuulSampleModule.java @@ -18,17 +18,23 @@ import com.google.inject.AbstractModule; import com.netflix.config.ConfigurationManager; -import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; -import com.netflix.discovery.DiscoveryClient; +import com.netflix.discovery.guice.EurekaModule; import com.netflix.netty.common.accesslog.AccessLogPublisher; import com.netflix.netty.common.status.ServerStatusManager; import com.netflix.spectator.api.DefaultRegistry; import com.netflix.spectator.api.Registry; import com.netflix.zuul.BasicRequestCompleteHandler; +import com.netflix.zuul.DynamicCodeCompiler; +import com.netflix.zuul.DynamicFilterLoader; import com.netflix.zuul.FilterFileManager; +import com.netflix.zuul.FilterLoader; import com.netflix.zuul.RequestCompleteHandler; import com.netflix.zuul.context.SessionContextDecorator; import com.netflix.zuul.context.ZuulSessionContextDecorator; +import com.netflix.zuul.filters.FilterRegistry; +import com.netflix.zuul.filters.MutableFilterRegistry; +import com.netflix.zuul.groovy.GroovyCompiler; +import com.netflix.zuul.groovy.GroovyFileFilter; import com.netflix.zuul.init.ZuulFiltersModule; import com.netflix.zuul.netty.server.BaseServerStartup; import com.netflix.zuul.netty.server.ClientRequestReceiver; @@ -36,6 +42,7 @@ import com.netflix.zuul.origins.OriginManager; import com.netflix.zuul.stats.BasicRequestMetricsPublisher; import com.netflix.zuul.stats.RequestMetricsPublisher; +import java.io.FilenameFilter; import org.apache.commons.configuration.AbstractConfiguration; /** @@ -54,6 +61,10 @@ protected void configure() { } bind(AbstractConfiguration.class).toInstance(ConfigurationManager.getConfigInstance()); + bind(DynamicCodeCompiler.class).to(GroovyCompiler.class); + bind(FilenameFilter.class).to(GroovyFileFilter.class); + + install(new EurekaModule()); // sample specific bindings bind(BaseServerStartup.class).to(SampleServerStartup.class); @@ -63,14 +74,16 @@ protected void configure() { // zuul filter loading install(new ZuulFiltersModule()); + bind(FilterLoader.class).to(DynamicFilterLoader.class); + bind(FilterRegistry.class).to(MutableFilterRegistry.class); bind(FilterFileManager.class).asEagerSingleton(); + // general server bindings bind(ServerStatusManager.class); // health/discovery status bind(SessionContextDecorator.class).to(ZuulSessionContextDecorator.class); // decorate new sessions when requests come in bind(Registry.class).to(DefaultRegistry.class); // atlas metrics registry bind(RequestCompleteHandler.class).to(BasicRequestCompleteHandler.class); // metrics post-request completion - bind(AbstractDiscoveryClientOptionalArgs.class).to(DiscoveryClient.DiscoveryClientOptionalArgs.class); // discovery client bind(RequestMetricsPublisher.class).to(BasicRequestMetricsPublisher.class); // timings publisher // access logger, including request ID generator diff --git a/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Debug.groovy b/zuul-sample/src/main/java/com/netflix/zuul/sample/filters/Debug.java similarity index 61% rename from zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Debug.groovy rename to zuul-sample/src/main/java/com/netflix/zuul/sample/filters/Debug.java index de254de4..0e9f4067 100644 --- a/zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters/inbound/Debug.groovy +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/filters/Debug.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.netflix.zuul.sample.filters.inbound +package com.netflix.zuul.sample.filters; -import com.netflix.zuul.filters.http.HttpInboundSyncFilter -import com.netflix.zuul.message.http.HttpRequestMessage +import com.netflix.zuul.Filter; +import com.netflix.zuul.filters.http.HttpInboundSyncFilter; +import com.netflix.zuul.message.http.HttpRequestMessage; /** * Determine if requests need to be debugged. @@ -27,23 +28,23 @@ * Author: Arthur Gonigberg * Date: December 22, 2017 */ -class Debug extends HttpInboundSyncFilter { +@Filter(order = 20) +public class Debug extends HttpInboundSyncFilter { @Override - int filterOrder() { - return 20 + public int filterOrder() { + return 20; } @Override - boolean shouldFilter(HttpRequestMessage request) { - return "true".equalsIgnoreCase(request.getQueryParams().getFirst("debugRequest")) + public boolean shouldFilter(HttpRequestMessage request) { + return "true".equalsIgnoreCase(request.getQueryParams().getFirst("debugRequest")); } @Override - HttpRequestMessage apply(HttpRequestMessage request) { - request.getContext().setDebugRequest(true) - request.getContext().setDebugRouting(true) + public HttpRequestMessage apply(HttpRequestMessage request) { + request.getContext().setDebugRequest(true); + request.getContext().setDebugRouting(true); - return request + return request; } - } diff --git a/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushAuthHandler.java b/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushAuthHandler.java index e0b1c1e7..03061e9f 100644 --- a/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushAuthHandler.java +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushAuthHandler.java @@ -42,9 +42,6 @@ public SamplePushAuthHandler(String path) { /** * We support only cookie based auth in this sample - * @param req - * @param ctx - * @return */ @Override protected boolean isDelayedAuth(FullHttpRequest req, ChannelHandlerContext ctx) { diff --git a/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSender.java b/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSender.java index 80ffecfc..d206675a 100644 --- a/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSender.java +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSender.java @@ -16,13 +16,13 @@ package com.netflix.zuul.sample.push; import com.google.common.base.Strings; -import com.google.inject.Singleton; import com.netflix.zuul.netty.server.push.PushConnectionRegistry; import com.netflix.zuul.netty.server.push.PushMessageSender; import com.netflix.zuul.netty.server.push.PushUserAuth; import io.netty.channel.ChannelHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; +import javax.inject.Singleton; /** * Author: Susheel Aroskar diff --git a/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSenderInitializer.java b/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSenderInitializer.java index 52dd2317..85945091 100644 --- a/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSenderInitializer.java +++ b/zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSenderInitializer.java @@ -15,11 +15,11 @@ */ package com.netflix.zuul.sample.push; -import com.google.inject.Inject; -import com.google.inject.Singleton; import com.netflix.zuul.netty.server.push.PushConnectionRegistry; import com.netflix.zuul.netty.server.push.PushMessageSender; import com.netflix.zuul.netty.server.push.PushMessageSenderInitializer; +import javax.inject.Inject; +import javax.inject.Singleton; /** * Author: Susheel Aroskar diff --git a/zuul-sample/src/main/resources/log4j.properties b/zuul-sample/src/main/resources/log4j.properties index af17cd19..e24cf1af 100644 --- a/zuul-sample/src/main/resources/log4j.properties +++ b/zuul-sample/src/main/resources/log4j.properties @@ -30,10 +30,5 @@ log4j.logger.com.netflix.config=WARN log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout.ConversionPattern=%d %-5p %c [%t] %m%n -# filter out repeating lines for Rx -log4j.appender.stdout.layout=com.netflix.zuul.logging.FilteredPatternLayout -log4j.appender.stdout.layout.Filters=rx.Observable,rx.internal,rx.Subscriber - # async appender -batcher.com.netflix.logging.AsyncAppender.stdout.waitTimeinMillis=120000 -log4j.logger.asyncAppenders=INFO,stdout +log4j.logger.asyncAppenders=INFO