[
  {
    "path": ".github/CODEOWNERS",
    "content": "# Default reviewers for Zuul OSS\n* @argha-c @jguerra @gavinbunney @lalernehl @lindseyreynolds @AlexanderEllis @fool1280 @tappenzeller @ilanachalom\n\n# Note: exclusions aren't well supported atm.\n# If needed, use workflows to exclude specific files from review.\n\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"gradle\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/benchmark.yml",
    "content": "name: benchmark\n\non:\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\nenv:\n  JDK: '21'\n  DISTRIBUTION: 'zulu'\n  GRADLE_COMMAND: './gradlew --no-daemon'\n\njobs:\n  benchmark:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: Set up JDK ${{ env.JDK }}\n        uses: actions/setup-java@v5\n        with:\n          java-version: ${{ env.JDK }}\n          distribution: ${{ env.DISTRIBUTION }}\n      - name: JMH\n        run: ${{ env.GRADLE_COMMAND }} clean :zuul-core:jmh\n"
  },
  {
    "path": ".github/workflows/branch_snapshot.yml",
    "content": "name: Branch Snapshot\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: 'Branch to publish snapshot of'\n        required: true\n        default: 'master'\n      repository:\n        description: 'Repository name (override for forks)'\n        required: false\n        default: Netflix/zuul\n      version:\n        description: 'The version number to use'\n        required: true\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    environment: Publish\n    steps:\n      - name: Setup Git\n        run: |\n          git config --global user.name 'Zuul Build'\n          git config --global user.email 'zuul-build@netflix.com'\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n          ref: ${{ github.event.inputs.branch }}\n          repository: ${{ github.event.inputs.repository }}\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: 21\n          cache: 'gradle'\n      - name: Build snapshot\n        run: ./gradlew build snapshot -Prelease.version=\"$BUILD_VERSION\"\n        env:\n          BUILD_VERSION: ${{ github.event.inputs.version }}\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n"
  },
  {
    "path": ".github/workflows/gradle-wrapper-validation.yml",
    "content": "name: \"Validate Gradle Wrapper\"\non: [push, pull_request]\n\njobs:\n  validation:\n    name: \"Gradle wrapper validation\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: gradle/actions/wrapper-validation@v5\n"
  },
  {
    "path": ".github/workflows/pr.yml",
    "content": "name: PR Build\n\non: [pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        java: [21]\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Set up JDK ${{ matrix.java }}\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'zulu'\n          java-version: ${{ matrix.java }}\n          cache: 'gradle'\n      - name: Build\n        run: |\n          sudo env \"PATH=$PATH\" bash -c \"ulimit -l 65536 && ulimit -a && ./gradlew --no-daemon build\"\n          echo \"Status of build: $?\"\n  validation:\n    name: \"Gradle Validation\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: gradle/actions/wrapper-validation@v5\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - v[0-9]+.[0-9]+.[0-9]+\n      - v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    environment: Publish\n    steps:\n      - name: Setup Git\n        run: |\n          git config --global user.name 'Zuul Build'\n          git config --global user.email 'zuul-build@netflix.com'\n      - uses: actions/checkout@v6\n      - uses: gradle/actions/wrapper-validation@v5\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: 21\n          cache: 'gradle'\n      - name: Build candidate\n        if: contains(github.ref, '-rc.')\n        run: ./gradlew --info --stacktrace -Prelease.useLastTag=true candidate\n        env:\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n      - name: Build release\n        if: (!contains(github.ref, '-rc.'))\n        run: ./gradlew --info -Prelease.useLastTag=true final\n        env:\n          NETFLIX_OSS_SONATYPE_USERNAME: ${{ secrets.ORG_SONATYPE_USERNAME }}\n          NETFLIX_OSS_SONATYPE_PASSWORD: ${{ secrets.ORG_SONATYPE_PASSWORD }}\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n"
  },
  {
    "path": ".github/workflows/snapshot.yml",
    "content": "name: Snapshot\n\non:\n  push:\n    branches:\n      - master\n      - zuul-v4\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    environment: Publish\n    steps:\n      - name: Setup Git\n        run: |\n          git config --global user.name 'Zuul Build'\n          git config --global user.email 'zuul-build@netflix.com'\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Set up JDK\n        uses: actions/setup-java@v5\n        with:\n          distribution: 'temurin'\n          java-version: 21\n          cache: 'gradle'\n      - name: Build snapshot\n        run: ./gradlew build snapshot\n        env:\n          NETFLIX_OSS_SIGNING_KEY: ${{ secrets.ORG_SIGNING_KEY }}\n          NETFLIX_OSS_SIGNING_PASSWORD: ${{ secrets.ORG_SIGNING_PASSWORD }}\n          NETFLIX_OSS_REPO_USERNAME: ${{ secrets.ORG_NETFLIXOSS_USERNAME }}\n          NETFLIX_OSS_REPO_PASSWORD: ${{ secrets.ORG_NETFLIXOSS_PASSWORD }}\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 'Close stale issues and PRs'\non:\n  schedule:\n    - cron: \"*/10 5 * * *\"\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v10\n        with:\n          stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.'\n          stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.'\n          close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'\n          close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.so\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n\n# Logs and databases #\n######################\n*.log\n\n# OS generated files #\n######################\n.DS_Store*\nehthumbs.db\nIcon?\nThumbs.db\n\n# Editor Files #\n################\n*~\n*.swp\n\n# Gradle Files #\n################\n.gradle\n\n# Build output directies\n/target\n*/target\n/build\n*/build\n.m2\n/classes\n# IntelliJ specific files/directories\nout\n.idea\n*.ipr\n*.iws\n*.iml\natlassian-ide-plugin.xml\n\n# Visual Studio Code\n.vscode\n\n# Java heap profile\n*.hprof\n\n# Eclipse specific files/directories\n.classpath\n.project\n.settings\n.metadata\n\nzuul-sample/src/main/generated/\n\n# NetBeans specific files/directories\n.nbattrs\n\n# publishing secrets\nsecrets/signing-key\n"
  },
  {
    "path": ".netflixoss",
    "content": "jdk=8\n\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": ""
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2012-2015 Netflix, Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "OSSMETADATA",
    "content": "osslifecycle=active\n"
  },
  {
    "path": "README.md",
    "content": "[![Snapshot](https://github.com/Netflix/zuul/actions/workflows/snapshot.yml/badge.svg)](https://github.com/Netflix/zuul/actions/workflows/snapshot.yml)\n\n# Zuul\n\n<img src=\"https://i.imgur.com/mRSosEp.png\" width=500/>\n\n\nZuul is an L7 application gateway that provides capabilities for dynamic routing, monitoring, resiliency, security, and more.\nPlease view the wiki for usage, information, HOWTO, etc https://github.com/Netflix/zuul/wiki\n\nHere are some links to help you learn more about the Zuul Project. Feel free to PR to add any other info, presentations, etc.\n\n---\n\nArticles from Netflix:\n\nZuul 1: http://techblog.netflix.com/2013/06/announcing-zuul-edge-service-in-cloud.html\n\nZuul 2:\n\nhttps://netflixtechblog.com/open-sourcing-zuul-2-82ea476cb2b3\n\nhttps://netflixtechblog.com/zuul-2-the-netflix-journey-to-asynchronous-non-blocking-systems-45947377fb5c\n\nhttps://netflixtechblog.com/the-show-must-go-on-securing-netflix-studios-at-scale-19b801c86479\n\n---\n\nNetflix presentations about Zuul:\n\nStrange Loop 2017 - Zuul 2: https://youtu.be/2oXqbLhMS_A\n\nAWS re:Invent 2018 - Scaling push messaging for millions of Netflix devices: https://youtu.be/IdR6N9B-S1E\n \n---\n\nSlides from Netflix presentations about Zuul:\n\nhttp://www.slideshare.net/MikeyCohen1/zuul-netflix-springone-platform\n\nhttp://www.slideshare.net/MikeyCohen1/rethinking-cloud-proxies-54923218\n\nhttps://github.com/strangeloop/StrangeLoop2017/blob/master/slides/ArthurGonigberg-ZuulsJourneyToNonBlocking.pdf\n\nhttps://www.slideshare.net/SusheelAroskar/scaling-push-messaging-for-millions-of-netflix-devices\n\n---\n\nProjects Using Zuul:\n\nhttps://cloud.spring.io/\n\nhttps://jhipster.github.io/\n\n---\n\nInfo and examples from various projects:\n\nhttps://cloud.spring.io/spring-cloud-netflix/multi/multi__router_and_filter_zuul\n\nhttp://www.baeldung.com/spring-rest-with-zuul-proxy\n\nhttps://blog.heroku.com/using_netflix_zuul_to_proxy_your_microservices\n\nhttp://blog.ippon.tech/jhipster-3-0-introducing-microservices/\n\n---\n\nOther blog posts about Zuul:\n\nhttps://engineering.riotgames.com/news/riot-games-api-fulfilling-zuuls-destiny\n\nhttps://engineering.riotgames.com/news/riot-games-api-deep-dive\n\nhttp://instea.sk/2015/04/netflix-zuul-vs-nginx-performance/\n\n---\n\n\n# How to release Zuul\n\nThis project uses a GitHub Action workflow for publishing a new release.\nThe workflow is triggered by a Git tag.\n\n```\ngit checkout master\ngit tag vX.Y.Z\ngit push --tags\n```\n\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    dependencies {\n        classpath 'com.palantir.javaformat:gradle-palantir-java-format:2.83.0'\n    }\n}\n\nplugins {\n    id 'com.netflix.nebula.netflixoss' version '13.0.0'\n    id \"com.google.osdetector\" version '1.7.3'\n    id 'me.champeau.jmh' version '0.7.2'\n    id 'org.openrewrite.rewrite' version '7.12.1'\n    id 'net.ltgt.errorprone' version '4.1.0'\n    id 'com.diffplug.spotless' version \"8.1.0\"\n    id 'idea'\n}\n\next.githubProjectName = rootProject.name\n\n\nidea {\n    project {\n        languageLevel = '21'\n    }\n}\n\nconfigurations.all {\n    exclude group: 'asm', module: 'asm'\n    exclude group: 'asm', module: 'asm-all'\n}\n\nallprojects {\n    repositories {\n        mavenCentral()\n    }\n\n    apply plugin: 'com.diffplug.spotless'\n\n    spotless {\n        enforceCheck false\n        java {\n            rootProject.hasProperty('spotlessJavaTarget') ? target(rootProject.getProperty('spotlessJavaTarget').split(\",\")) : target('src/*/java/**/*.java')\n            removeUnusedImports('cleanthat-javaparser-unnecessaryimport')\n            palantirJavaFormat()\n        }\n    }\n}\n\nsubprojects {\n    apply plugin: 'com.netflix.nebula.netflixoss'\n    apply plugin: 'java'\n    apply plugin: 'com.netflix.nebula.javadoc-jar'\n    apply plugin: 'com.netflix.nebula.dependency-lock'\n    apply plugin: 'me.champeau.jmh'\n    apply plugin: 'org.openrewrite.rewrite'\n    apply plugin: 'net.ltgt.errorprone'\n\n    license {\n        ignoreFailures = false\n        excludes([\n                \"**/META-INF/services/javax.annotation.processing.Processor\",\n                \"**/META-INF/gradle/incremental.annotation.processors\",\n                \"**/*.cert\",\n                \"**/*.jks\",\n                \"**/*.key\",\n        ])\n    }\n\n    group = \"com.netflix.${githubProjectName}\"\n\n    java {\n        toolchain {\n            languageVersion = JavaLanguageVersion.of(21)\n        }\n    }\n\n    tasks.withType(JavaCompile).configureEach {\n        dependencies {\n            errorprone \"com.uber.nullaway:nullaway:0.12.4\"\n            errorprone \"com.google.errorprone:error_prone_core:2.45.0\"\n        }\n\n        options.compilerArgs << \"-Werror\"\n\n        options.errorprone {\n            check(\"NullAway\", net.ltgt.gradle.errorprone.CheckSeverity.OFF)\n            option(\"NullAway:AnnotatedPackages\", \"com.netflix.zuul\")\n            errorproneArgs.addAll(\n                // Uncomment and remove -Werror javac flag to automatically apply fixes for a check.\n                // N.B: disables all other checks while enabled.\n                // \"-XepPatchChecks:UnnecessaryParentheses\",\n                // \"-XepPatchLocation:IN_PLACE\",\n                \"-Xep:ClassCanBeStatic:OFF\",\n                \"-Xep:EmptyBlockTag:OFF\",\n                \"-Xep:FutureReturnValueIgnored:OFF\",\n                \"-Xep:InlineMeSuggester:OFF\",\n                \"-Xep:MissingSummary:OFF\",\n            )\n        }\n    }\n\n    eclipse {\n        classpath {\n            downloadSources = true\n            downloadJavadoc = true\n        }\n    }\n\n    tasks.withType(Javadoc).each {\n        it.classpath = sourceSets.main.compileClasspath\n        // Ignore Javadoc warnings for now, re-enable after Zuul 3.\n        it.options.addStringOption('Xdoclint:none', '-quiet')\n    }\n\n    ext {\n        libraries = [\n                guava: \"com.google.guava:guava:33.3.0-jre\",\n                okhttp: 'com.squareup.okhttp3:okhttp:4.12.0',\n                jupiterApi: 'org.junit.jupiter:junit-jupiter-api:5.13.+',\n                jupiterParams: 'org.junit.jupiter:junit-jupiter-params:5.13.+',\n                jupiterEngine: 'org.junit.jupiter:junit-jupiter-engine:5.13.+',\n                junitPlatformLauncher: 'org.junit.platform:junit-platform-launcher:1.13.+',\n                jupiterMockito: 'org.mockito:mockito-junit-jupiter:5.13.+',\n                mockito: 'org.mockito:mockito-core:5.+',\n\n                slf4j: \"org.slf4j:slf4j-api:2.0.16\",\n                assertj: 'org.assertj:assertj-core:3.26.3',\n                awaitility: 'org.awaitility:awaitility:4.2.2',\n                lombok: 'org.projectlombok:lombok:1.18.42'\n        ]\n    }\n\n    test {\n        useJUnitPlatform()\n        testLogging {\n            showStandardStreams = true\n        }\n        maxParallelForks = Runtime.runtime.availableProcessors();\n    }\n}\n\ndependencies {\n    rewrite(platform(\"org.openrewrite.recipe:rewrite-recipe-bom:3.12.1\"))\n    rewrite(\"org.openrewrite.recipe:rewrite-logging-frameworks\")\n    rewrite(\"org.openrewrite.recipe:rewrite-testing-frameworks\")\n    rewrite(\"org.openrewrite.recipe:rewrite-static-analysis\")\n}\n\nrewrite {\n    failOnDryRunResults = true\n    activeRecipe(\"org.openrewrite.java.testing.junit5.JUnit5BestPractices\")\n    activeRecipe(\"org.openrewrite.java.logging.slf4j.Slf4jBestPractices\")\n}\n"
  },
  {
    "path": "codequality/checkstyle.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE module PUBLIC\n    \"-//Puppy Crawl//DTD Check Configuration 1.2//EN\"\n    \"http://www.puppycrawl.com/dtds/configuration_1_2.dtd\">\n\n<module name=\"Checker\">\n\n    <!-- Checks that a package-info.java file exists for each package.     -->\n    <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage -->\n    <!--\n    <module name=\"JavadocPackage\">\n      <property name=\"allowLegacy\" value=\"true\"/>\n    </module>\n    -->\n\n    <!-- Checks whether files end with a new line.                        -->\n    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->\n    <module name=\"NewlineAtEndOfFile\"/>\n\n    <!-- Checks that property files contain the same keys.         -->\n    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->\n    <module name=\"Translation\"/>\n\n    <!-- Checks for Size Violations.                    -->\n    <!-- See http://checkstyle.sf.net/config_sizes.html -->\n    <module name=\"FileLength\"/>\n\n    <!-- Checks for whitespace                               -->\n    <!-- See http://checkstyle.sf.net/config_whitespace.html -->\n    <module name=\"FileTabCharacter\"/>\n\n    <!-- Miscellaneous other checks.                   -->\n    <!-- See http://checkstyle.sf.net/config_misc.html -->\n    <module name=\"RegexpSingleline\">\n       <property name=\"format\" value=\"\\s+$\"/>\n       <property name=\"minimum\" value=\"0\"/>\n       <property name=\"maximum\" value=\"0\"/>\n       <property name=\"message\" value=\"Line has trailing spaces.\"/>\n       <property name=\"severity\" value=\"info\"/>\n    </module>\n\n    <module name=\"TreeWalker\">\n\n        <!-- Checks for Javadoc comments.                     -->\n        <!-- See http://checkstyle.sf.net/config_javadoc.html -->\n        <module name=\"JavadocMethod\">\n          <property name=\"scope\" value=\"package\"/>\n          <property name=\"allowMissingParamTags\" value=\"true\"/>\n          <property name=\"allowMissingThrowsTags\" value=\"true\"/>\n          <property name=\"allowMissingReturnTag\" value=\"true\"/>\n          <property name=\"allowThrowsTagsForSubclasses\" value=\"true\"/>\n          <property name=\"allowUndeclaredRTE\" value=\"true\"/>\n          <property name=\"allowMissingPropertyJavadoc\" value=\"true\"/>\n        </module>\n        <module name=\"JavadocType\">\n          <property name=\"scope\" value=\"package\"/>\n        </module>\n        <module name=\"JavadocVariable\">\n          <property name=\"scope\" value=\"package\"/>\n        </module>\n        <module name=\"JavadocStyle\">\n          <property name=\"checkEmptyJavadoc\" value=\"true\"/>\n        </module>\n\n        <!-- Checks for Naming Conventions.                  -->\n        <!-- See http://checkstyle.sf.net/config_naming.html -->\n        <module name=\"ConstantName\"/>\n        <module name=\"LocalFinalVariableName\"/>\n        <module name=\"LocalVariableName\"/>\n        <module name=\"MemberName\"/>\n        <module name=\"MethodName\"/>\n        <module name=\"PackageName\"/>\n        <module name=\"ParameterName\"/>\n        <module name=\"StaticVariableName\"/>\n        <module name=\"TypeName\"/>\n\n        <!-- Checks for imports                              -->\n        <!-- See http://checkstyle.sf.net/config_import.html -->\n        <module name=\"AvoidStarImport\"/>\n        <module name=\"IllegalImport\"/> <!-- defaults to sun.* packages -->\n        <module name=\"RedundantImport\"/>\n        <module name=\"UnusedImports\"/>\n\n\n        <!-- Checks for Size Violations.                    -->\n        <!-- See http://checkstyle.sf.net/config_sizes.html -->\n        <module name=\"LineLength\">\n          <!-- what is a good max value? -->\n          <property name=\"max\" value=\"120\"/>\n          <!-- ignore lines like \"$File: //depot/... $\" -->\n          <property name=\"ignorePattern\" value=\"\\$File.*\\$\"/>\n          <property name=\"severity\" value=\"info\"/>\n        </module>\n        <module name=\"MethodLength\"/>\n        <module name=\"ParameterNumber\"/>\n\n\n        <!-- Checks for whitespace                               -->\n        <!-- See http://checkstyle.sf.net/config_whitespace.html -->\n        <module name=\"EmptyForIteratorPad\"/>\n        <module name=\"GenericWhitespace\"/>\n        <module name=\"MethodParamPad\"/>\n        <module name=\"NoWhitespaceAfter\"/>\n        <module name=\"NoWhitespaceBefore\"/>\n        <module name=\"OperatorWrap\"/>\n        <module name=\"ParenPad\"/>\n        <module name=\"TypecastParenPad\"/>\n        <module name=\"WhitespaceAfter\"/>\n        <module name=\"WhitespaceAround\"/>\n\n        <!-- Modifier Checks                                    -->\n        <!-- See http://checkstyle.sf.net/config_modifiers.html -->\n        <module name=\"ModifierOrder\"/>\n        <module name=\"RedundantModifier\"/>\n\n\n        <!-- Checks for blocks. You know, those {}'s         -->\n        <!-- See http://checkstyle.sf.net/config_blocks.html -->\n        <module name=\"AvoidNestedBlocks\"/>\n        <module name=\"EmptyBlock\">\n          <property name=\"option\" value=\"text\"/>\n        </module>\n        <module name=\"LeftCurly\"/>\n        <module name=\"NeedBraces\"/>\n        <module name=\"RightCurly\"/>\n\n\n        <!-- Checks for common coding problems               -->\n        <!-- See http://checkstyle.sf.net/config_coding.html -->\n        <!-- <module name=\"AvoidInlineConditionals\"/> -->\n        <module name=\"DoubleCheckedLocking\"/>    <!-- MY FAVOURITE -->\n        <module name=\"EmptyStatement\"/>\n        <module name=\"EqualsHashCode\"/>\n        <module name=\"HiddenField\">\n          <property name=\"ignoreConstructorParameter\" value=\"true\"/>\n          <property name=\"ignoreSetter\" value=\"true\"/>\n          <property name=\"severity\" value=\"warning\"/>\n        </module>\n        <module name=\"IllegalInstantiation\"/>\n        <module name=\"InnerAssignment\"/>\n        <module name=\"MagicNumber\">\n          <property name=\"severity\" value=\"warning\"/>\n        </module>\n        <module name=\"MissingSwitchDefault\"/>\n        <!-- Problem with finding exception types... -->\n        <module name=\"RedundantThrows\">\n          <property name=\"allowUnchecked\" value=\"true\"/>\n          <property name=\"suppressLoadErrors\" value=\"true\"/>\n          <property name=\"severity\" value=\"info\"/>\n        </module>\n        <module name=\"SimplifyBooleanExpression\"/>\n        <module name=\"SimplifyBooleanReturn\"/>\n\n        <!-- Checks for class design                         -->\n        <!-- See http://checkstyle.sf.net/config_design.html -->\n        <!-- <module name=\"DesignForExtension\"/> -->\n        <module name=\"FinalClass\"/>\n        <module name=\"HideUtilityClassConstructor\"/>\n        <module name=\"InterfaceIsType\"/>\n        <module name=\"VisibilityModifier\"/>\n\n\n        <!-- Miscellaneous other checks.                   -->\n        <!-- See http://checkstyle.sf.net/config_misc.html -->\n        <module name=\"ArrayTypeStyle\"/>\n        <!-- <module name=\"FinalParameters\"/> -->\n        <module name=\"TodoComment\">\n          <property name=\"format\" value=\"TODO\"/>\n          <property name=\"severity\" value=\"info\"/>\n        </module>\n        <module name=\"UpperEll\"/>\n\n        <module name=\"FileContentsHolder\"/> <!-- Required by comment suppression filters -->\n\n    </module>\n\n    <!-- Enable suppression comments -->\n    <module name=\"SuppressionCommentFilter\">\n      <property name=\"offCommentFormat\" value=\"CHECKSTYLE IGNORE\\s+(\\S+)\"/>\n      <property name=\"onCommentFormat\" value=\"CHECKSTYLE END IGNORE\\s+(\\S+)\"/>\n      <property name=\"checkFormat\" value=\"$1\"/>\n    </module>\n    <module name=\"SuppressWithNearbyCommentFilter\">\n      <!-- Syntax is \"SUPPRESS CHECKSTYLE name\" -->\n      <property name=\"commentFormat\" value=\"SUPPRESS CHECKSTYLE (\\w+)\"/>\n      <property name=\"checkFormat\" value=\"$1\"/>\n      <property name=\"influenceFormat\" value=\"1\"/>\n    </module>\n</module>\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.2.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "versions_ribbon=2.4.4\nversions_netty=4.2.10.Final\nversions_brotli4j=1.16.0\nrelease.scope=patch\nrelease.version=3.3.0-SNAPSHOT\norg.gradle.jvmargs=-Xms1g -Xmx2g\n\ncom.netflix.testcontainers-cloud.enabled=false\n\npalantir.native.formatter=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name='zuul'\n\ninclude 'zuul-core'\ninclude 'zuul-processor'\ninclude 'zuul-sample'\ninclude 'zuul-discovery'\ninclude 'zuul-integration-test'\n"
  },
  {
    "path": "zuul-core/build.gradle",
    "content": "apply plugin: \"com.google.osdetector\"\napply plugin: \"java-library\"\n\ndependencies {\n\n    compileOnly libraries.lombok\n    testCompileOnly(libraries.lombok)\n    annotationProcessor(libraries.lombok)\n\n    implementation libraries.guava\n    // TODO(carl-mastrangelo): this can be implementation; remove Logger from public api points.\n    api libraries.slf4j\n\n    implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'\n    implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'\n    implementation 'org.bouncycastle:bctls-jdk18on:1.78.1'\n\n    implementation 'com.fasterxml.jackson.core:jackson-core:2.19.2'\n    api 'com.fasterxml.jackson.core:jackson-databind:2.19.2'\n\n    api \"com.netflix.archaius:archaius-core:0.7.12\"\n    api \"com.netflix.spectator:spectator-api:latest.release\"\n    api \"com.netflix.netflix-commons:netflix-commons-util:0.3.0\"\n\n    api project(\":zuul-discovery\")\n\n    api \"com.netflix.ribbon:ribbon-core:${versions_ribbon}\"\n    api \"com.netflix.ribbon:ribbon-archaius:${versions_ribbon}\"\n\n    api \"com.netflix.eureka:eureka-client:2.0.4\"\n    api \"io.reactivex:rxjava:1.3.8\"\n    api platform(\"io.netty:netty-bom:${versions_netty}\")\n\n    // TODO(carl-mastrangelo): some of these could probably be implementation.   Do a deeper check.\n    api \"io.netty:netty-common\"\n    api \"io.netty:netty-buffer\"\n    api \"io.netty:netty-codec-http\"\n    api \"io.netty:netty-codec-http2\"\n    api \"io.netty:netty-handler\"\n    api \"io.netty:netty-transport\"\n\n    implementation \"io.netty:netty-codec-haproxy\"\n    implementation (group: \"io.netty\", \"name\": \"netty-transport-native-epoll\", \"classifier\": \"linux-x86_64\")\n    implementation (group: \"io.netty\", \"name\": \"netty-transport-native-io_uring\", \"classifier\": \"linux-x86_64\")\n    implementation (group: \"io.netty\", \"name\": \"netty-transport-native-kqueue\", \"classifier\": \"osx-x86_64\")\n\n    // We are using the long-form dependency syntax here because we want to\n    // explicitly set the classifier. We do not have the version number so we can't use\n    // Gradle's short-form dependency notation.\n    runtimeOnly( group: \"io.netty\", name: \"netty-tcnative-boringssl-static\", classifier: \"linux-x86_64\" )\n    runtimeOnly( group: \"io.netty\", name: \"netty-tcnative-boringssl-static\", classifier: \"linux-aarch_64\" )\n    runtimeOnly( group: \"io.netty\", name: \"netty-tcnative-boringssl-static\", classifier: \"osx-x86_64\" )\n    runtimeOnly( group: \"io.netty\", name: \"netty-tcnative-boringssl-static\", classifier: \"osx-aarch_64\" )\n\n    implementation 'io.perfmark:perfmark-api:0.27.0'\n    api 'jakarta.inject:jakarta.inject-api:2.0.1'\n    api 'org.jspecify:jspecify:1.0.0'\n\n    testImplementation libraries.jupiterApi, libraries.jupiterParams, libraries.jupiterEngine, libraries.junitPlatformLauncher, libraries.jupiterMockito,\n            libraries.mockito,\n            libraries.assertj,\n            libraries.awaitility\n\n\n    testImplementation 'commons-configuration:commons-configuration:1.10'\n\n    testRuntimeOnly 'org.slf4j:slf4j-simple:2.0.17'\n\n    jmh 'org.openjdk.jmh:jmh-core:1.+'\n    jmh 'org.openjdk.jmh:jmh-generator-annprocess:1.+'\n    jmh 'org.openjdk.jmh:jmh-generator-bytecode:1.+'\n}\n\n// Silences log statements during tests.   This still allows normal failures to be printed.\ntest {\n    testLogging {\n        showStandardStreams = false\n    }\n}\n\n// ./gradlew --no-daemon clean :zuul-core:jmh\njmh {\n    profilers = [\"gc\"]\n    timeOnIteration = \"1s\"\n    warmup = \"1s\"\n    fork = 1\n    warmupIterations = 10\n    iterations = 5\n    // Not sure why duplicate classes are on the path.  Something Nebula related I think.\n    duplicateClassesStrategy = DuplicatesStrategy.EXCLUDE\n}\n"
  },
  {
    "path": "zuul-core/src/jmh/java/com/netflix/zuul/message/HeadersBenchmark.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.message;\n\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.annotations.BenchmarkMode;\nimport org.openjdk.jmh.annotations.Mode;\nimport org.openjdk.jmh.annotations.OutputTimeUnit;\nimport org.openjdk.jmh.annotations.Param;\nimport org.openjdk.jmh.annotations.Scope;\nimport org.openjdk.jmh.annotations.Setup;\nimport org.openjdk.jmh.annotations.State;\nimport org.openjdk.jmh.infra.Blackhole;\n\n@State(Scope.Thread)\npublic class HeadersBenchmark {\n\n    @State(Scope.Thread)\n    public static class AddHeaders {\n        @Param({\"0\", \"1\", \"5\", \"10\", \"30\"})\n        public int count;\n\n        @Param({\"10\"})\n        public int nameLength;\n\n        private String[] stringNames;\n        private HeaderName[] names;\n        private String[] values;\n\n        @Setup\n        public void setUp() {\n            stringNames = new String[count];\n            names = new HeaderName[stringNames.length];\n            values = new String[stringNames.length];\n            for (int i = 0; i < stringNames.length; i++) {\n                UUID uuid = new UUID(\n                        ThreadLocalRandom.current().nextLong(),\n                        ThreadLocalRandom.current().nextLong());\n                String name = uuid.toString();\n                assert name.length() >= nameLength;\n                name = name.substring(0, nameLength);\n                names[i] = new HeaderName(name);\n                stringNames[i] = name;\n                values[i] = name;\n            }\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public Headers addHeaders_string() {\n            Headers headers = new Headers();\n            for (int i = 0; i < count; i++) {\n                headers.add(stringNames[i], values[i]);\n            }\n            return headers;\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public Headers addHeaders_headerName() {\n            Headers headers = new Headers();\n            for (int i = 0; i < count; i++) {\n                headers.add(names[i], values[i]);\n            }\n            return headers;\n        }\n    }\n\n    @State(Scope.Thread)\n    public static class GetSetHeaders {\n        @Param({\"1\", \"5\", \"10\", \"30\"})\n        public int count;\n\n        @Param({\"10\"})\n        public int nameLength;\n\n        private String[] stringNames;\n        private HeaderName[] names;\n        private String[] values;\n        Headers headers;\n\n        @Setup\n        public void setUp() {\n            headers = new Headers();\n            stringNames = new String[count];\n            names = new HeaderName[stringNames.length];\n            values = new String[stringNames.length];\n            for (int i = 0; i < stringNames.length; i++) {\n                UUID uuid = new UUID(\n                        ThreadLocalRandom.current().nextLong(),\n                        ThreadLocalRandom.current().nextLong());\n                String name = uuid.toString();\n                assert name.length() >= nameLength;\n                name = name.substring(0, nameLength);\n                names[i] = new HeaderName(name);\n                stringNames[i] = name;\n                values[i] = name;\n                headers.add(names[i], values[i]);\n            }\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public void setHeader_first() {\n            headers.set(names[0], \"blah\");\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public void setHeader_last() {\n            headers.set(names[count - 1], \"blah\");\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public List<String> getHeader_first() {\n            return headers.getAll(names[0]);\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public List<String> getHeader_last() {\n            return headers.getAll(names[count - 1]);\n        }\n\n        @Benchmark\n        @BenchmarkMode(Mode.AverageTime)\n        @OutputTimeUnit(TimeUnit.NANOSECONDS)\n        public void entries(Blackhole blackhole) {\n            for (Header header : headers.entries()) {\n                blackhole.consume(header);\n            }\n        }\n    }\n\n    @Benchmark\n    @BenchmarkMode(Mode.AverageTime)\n    @OutputTimeUnit(TimeUnit.NANOSECONDS)\n    public Headers newHeaders() {\n        return new Headers();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/config/DynamicIntegerSetProperty.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.config;\n\nimport java.util.Set;\n\npublic class DynamicIntegerSetProperty extends DynamicSetProperty<Integer> {\n    public DynamicIntegerSetProperty(String propName, String defaultValue) {\n        super(propName, defaultValue);\n    }\n\n    public DynamicIntegerSetProperty(String propName, String defaultValue, String delimiterRegex) {\n        super(propName, defaultValue, delimiterRegex);\n    }\n\n    public DynamicIntegerSetProperty(String propName, Set<Integer> defaultValue) {\n        super(propName, defaultValue);\n    }\n\n    public DynamicIntegerSetProperty(String propName, Set<Integer> defaultValue, String delimiterRegex) {\n        super(propName, defaultValue, delimiterRegex);\n    }\n\n    @Override\n    protected Integer from(String value) {\n        return Integer.valueOf(value);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/config/PatternListStringProperty.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.config;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.regex.Pattern;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/15/17\n * Time: 4:38 PM\n */\npublic class PatternListStringProperty extends DerivedStringProperty<List<Pattern>> {\n    private static final Logger LOG = LoggerFactory.getLogger(PatternListStringProperty.class);\n\n    public PatternListStringProperty(String name, String defaultValue) {\n        super(name, defaultValue);\n    }\n\n    @Override\n    protected List<Pattern> derive(String value) {\n        ArrayList<Pattern> ptns = new ArrayList<>();\n        if (value != null) {\n            for (String ptnTxt : value.split(\",\", -1)) {\n                try {\n                    ptns.add(Pattern.compile(ptnTxt.trim()));\n                } catch (Exception e) {\n                    LOG.error(\n                            \"Error parsing regex pattern list from property! name = {}, value = {}, pattern = {}\",\n                            String.valueOf(this.getName()),\n                            String.valueOf(this.getValue()),\n                            String.valueOf(value));\n                }\n            }\n        }\n        return ptns;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/AbstrHttpConnectionExpiryHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.config.CachedDynamicLongProperty;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.util.HttpUtils;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport java.util.concurrent.ThreadLocalRandom;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/17/17\n * Time: 10:54 AM\n */\npublic abstract class AbstrHttpConnectionExpiryHandler extends ChannelOutboundHandlerAdapter {\n    protected static final Logger LOG = LoggerFactory.getLogger(AbstrHttpConnectionExpiryHandler.class);\n    protected static final CachedDynamicLongProperty MAX_EXPIRY_DELTA =\n            new CachedDynamicLongProperty(\"server.connection.expiry.delta\", 20 * 1000);\n\n    protected final ConnectionCloseType connectionCloseType;\n    protected final int maxRequests;\n    protected final int maxExpiry;\n    protected final long connectionStartTime;\n    protected final long connectionExpiryTime;\n\n    protected int requestCount = 0;\n    protected int maxRequestsUnderBrownout = 0;\n\n    public AbstrHttpConnectionExpiryHandler(\n            ConnectionCloseType connectionCloseType, int maxRequestsUnderBrownout, int maxRequests, int maxExpiry) {\n        this.connectionCloseType = connectionCloseType;\n        this.maxRequestsUnderBrownout = maxRequestsUnderBrownout;\n        this.maxRequests = maxRequests;\n\n        this.maxExpiry = maxExpiry;\n        this.connectionStartTime = System.currentTimeMillis();\n        long randomDelta = ThreadLocalRandom.current().nextLong(MAX_EXPIRY_DELTA.get());\n        this.connectionExpiryTime = connectionStartTime + maxExpiry + randomDelta;\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        if (isResponseHeaders(msg)) {\n            // Update the request count attribute for this channel.\n            requestCount++;\n\n            if (isConnectionExpired(ctx.channel())) {\n                // Flag this channel to be closed after response is written.\n                Channel channel = HttpUtils.getMainChannel(ctx);\n                ctx.channel()\n                        .attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE)\n                        .set(ctx.newPromise());\n                ConnectionCloseType.setForChannel(channel, connectionCloseType);\n            }\n        }\n\n        super.write(ctx, msg, promise);\n    }\n\n    protected boolean isConnectionExpired(Channel channel) {\n        boolean expired = requestCount >= maxRequests(channel) || System.currentTimeMillis() > connectionExpiryTime;\n        if (expired) {\n            long lifetime = System.currentTimeMillis() - connectionStartTime;\n            LOG.info(\n                    \"Connection is expired. requestCount={}, lifetime={}, {}\",\n                    requestCount,\n                    lifetime,\n                    ChannelUtils.channelInfoForLogging(channel));\n        }\n        return expired;\n    }\n\n    protected abstract boolean isResponseHeaders(Object msg);\n\n    protected int maxRequests(Channel ch) {\n        if (HttpChannelFlags.IN_BROWNOUT.get(ch)) {\n            return this.maxRequestsUnderBrownout;\n        } else {\n            return this.maxRequests;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/ByteBufUtil.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.zuul.message.ZuulMessage;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.util.ReferenceCounted;\nimport io.netty.util.ResourceLeakDetector;\n\n/**\n * ByteBufUtil\n *\n * @author Arthur Gonigberg\n * @since October 20, 2022\n */\npublic class ByteBufUtil {\n\n    @SuppressWarnings(\"EnumOrdinal\")\n    private static final boolean isAdvancedLeakDetection =\n            ResourceLeakDetector.getLevel().ordinal() >= ResourceLeakDetector.Level.ADVANCED.ordinal();\n\n    public static void touch(ReferenceCounted byteBuf, String hint, ZuulMessage msg) {\n        if (isAdvancedLeakDetection) {\n            byteBuf.touch(hint + msg);\n        }\n    }\n\n    public static void touch(ReferenceCounted byteBuf, String hint) {\n        if (isAdvancedLeakDetection) {\n            byteBuf.touch(hint);\n        }\n    }\n\n    public static void touch(ReferenceCounted byteBuf, String hint, String filterName) {\n        if (isAdvancedLeakDetection) {\n            byteBuf.touch(hint + filterName);\n        }\n    }\n\n    public static void touch(HttpResponse originResponse, String hint, ZuulMessage msg) {\n        if (isAdvancedLeakDetection && originResponse instanceof ReferenceCounted) {\n            ((ReferenceCounted) originResponse).touch(hint + msg);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/CategorizedThreadFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.util.concurrent.FastThreadLocalThread;\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * User: Mike Smith\n * Date: 6/8/16\n * Time: 11:49 AM\n */\npublic class CategorizedThreadFactory implements ThreadFactory {\n    private final String category;\n    private int num = 0;\n\n    public CategorizedThreadFactory(String category) {\n        super();\n        this.category = category;\n    }\n\n    @Override\n    public Thread newThread(Runnable r) {\n        FastThreadLocalThread t = new FastThreadLocalThread(r, category + \"-\" + num++);\n        return t;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/CloseOnIdleStateHandler.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on\n * an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the\n * specific language governing permissions and limitations under the License.\n */\npackage com.netflix.netty.common;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.timeout.IdleStateEvent;\n\n/**\n * Just listens for the IdleStateEvent and closes the channel if received.\n */\npublic class CloseOnIdleStateHandler extends ChannelInboundHandlerAdapter {\n\n    private final Counter counter;\n\n    public CloseOnIdleStateHandler(Registry registry, String metricId) {\n        this.counter = registry.counter(\"server.connections.idle.timeout\", \"id\", metricId);\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        super.userEventTriggered(ctx, evt);\n\n        if (evt instanceof IdleStateEvent) {\n            counter.increment();\n            ctx.close();\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/ConnectionCloseChannelAttributes.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.util.AttributeKey;\n\npublic class ConnectionCloseChannelAttributes {\n    public static final AttributeKey<ChannelPromise> CLOSE_AFTER_RESPONSE =\n            AttributeKey.newInstance(\"CLOSE_AFTER_RESPONSE\");\n    public static final AttributeKey<ConnectionCloseType> CLOSE_TYPE = AttributeKey.newInstance(\"CLOSE_TYPE\");\n\n    public static int gracefulCloseDelay(Channel channel) {\n        ChannelConfig channelConfig =\n                channel.attr(BaseZuulChannelInitializer.ATTR_CHANNEL_CONFIG).get();\n        Integer gracefulCloseDelay = channelConfig.get(CommonChannelConfigKeys.connCloseDelay);\n        return gracefulCloseDelay == null ? 0 : gracefulCloseDelay;\n    }\n\n    public static boolean allowGracefulDelayed(Channel channel) {\n        ChannelConfig channelConfig =\n                channel.attr(BaseZuulChannelInitializer.ATTR_CHANNEL_CONFIG).get();\n        Boolean value = channelConfig.get(CommonChannelConfigKeys.http2AllowGracefulDelayed);\n        return value == null ? false : value;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/ConnectionCloseType.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.channel.Channel;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 2:04 PM\n */\npublic enum ConnectionCloseType {\n    IMMEDIATE,\n    GRACEFUL,\n    DELAYED_GRACEFUL;\n\n    public static ConnectionCloseType fromChannel(Channel ch) {\n        ConnectionCloseType type =\n                ch.attr(ConnectionCloseChannelAttributes.CLOSE_TYPE).get();\n        if (type == null) {\n            // Default to immediate.\n            type = ConnectionCloseType.IMMEDIATE;\n        }\n        return type;\n    }\n\n    public static void setForChannel(Channel ch, ConnectionCloseType type) {\n        ch.attr(ConnectionCloseChannelAttributes.CLOSE_TYPE).set(type);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/Http1ConnectionCloseHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 2:03 PM\n */\npublic class Http1ConnectionCloseHandler extends ChannelDuplexHandler {\n    private static final Logger LOG = LoggerFactory.getLogger(Http1ConnectionCloseHandler.class);\n\n    private final AtomicBoolean requestInflight = new AtomicBoolean(Boolean.FALSE);\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        ChannelPromise closePromise = ctx.channel()\n                .attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE)\n                .get();\n\n        if (msg instanceof HttpResponse response && closePromise != null) {\n            // Add header to tell client that they should close this connection.\n            response.headers().set(HttpHeaderNames.CONNECTION, \"close\");\n        }\n\n        super.write(ctx, msg, promise);\n\n        // Close the connection immediately after LastContent is written, rather than\n        // waiting until the graceful-delay is up if this flag is set.\n        if (msg instanceof LastHttpContent) {\n            if (closePromise != null) {\n                promise.addListener(future -> {\n                    ConnectionCloseType type = ConnectionCloseType.fromChannel(ctx.channel());\n                    closeChannel(ctx, type, closePromise);\n                });\n            }\n        }\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        // Track when there's an inflight request.\n        if (evt instanceof HttpLifecycleChannelHandler.StartEvent) {\n            requestInflight.set(Boolean.TRUE);\n        } else if (evt instanceof HttpLifecycleChannelHandler.CompleteEvent) {\n            requestInflight.set(Boolean.FALSE);\n        }\n\n        super.userEventTriggered(ctx, evt);\n    }\n\n    protected void closeChannel(ChannelHandlerContext ctx, ConnectionCloseType evt, ChannelPromise promise) {\n        switch (evt) {\n            case DELAYED_GRACEFUL:\n                gracefully(ctx, promise);\n                break;\n            case GRACEFUL:\n                gracefully(ctx, promise);\n                break;\n            case IMMEDIATE:\n                immediately(ctx, promise);\n                break;\n            default:\n                throw new IllegalArgumentException(\"Unknown ConnectionCloseEvent type! - \" + String.valueOf(evt));\n        }\n    }\n\n    protected void gracefully(ChannelHandlerContext ctx, ChannelPromise promise) {\n        Channel channel = ctx.channel();\n        if (channel.isActive()) {\n            String channelId = channel.id().asShortText();\n\n            // In gracefulCloseDelay secs time, go ahead and close the connection if it hasn't already been.\n            int gracefulCloseDelay = ConnectionCloseChannelAttributes.gracefulCloseDelay(channel);\n            ctx.executor()\n                    .schedule(\n                            () -> {\n\n                                // Check that the client hasn't already closed the connection.\n                                if (channel.isActive()) {\n\n                                    // If there is still an inflight request, then don't close the conn now. Instead\n                                    // assume that it will be closed\n                                    // either after the response finally gets written (due to us having set the\n                                    // CLOSE_AFTER_RESPONSE flag), or when the IdleTimeout\n                                    // for this conn fires.\n                                    if (requestInflight.get()) {\n                                        LOG.debug(\n                                                \"gracefully: firing graceful_shutdown event to close connection, but\"\n                                                        + \" request still inflight, so leaving. channel={}\",\n                                                channelId);\n                                    } else {\n                                        LOG.debug(\n                                                \"gracefully: firing graceful_shutdown event to close connection.\"\n                                                        + \" channel={}\",\n                                                channelId);\n                                        ctx.close(promise);\n                                    }\n                                } else {\n                                    LOG.debug(\"gracefully: connection already closed. channel={}\", channelId);\n                                    promise.setSuccess();\n                                }\n                            },\n                            gracefulCloseDelay,\n                            TimeUnit.SECONDS);\n        } else {\n            promise.setSuccess();\n        }\n    }\n\n    protected void immediately(ChannelHandlerContext ctx, ChannelPromise promise) {\n        if (ctx.channel().isActive()) {\n            ctx.close(promise);\n        } else {\n            promise.setSuccess();\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/Http1ConnectionExpiryHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.handler.codec.http.HttpResponse;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 9:58 AM\n */\npublic class Http1ConnectionExpiryHandler extends AbstrHttpConnectionExpiryHandler {\n    public Http1ConnectionExpiryHandler(int maxRequests, int maxRequestsUnderBrownout, int maxExpiry) {\n        super(ConnectionCloseType.GRACEFUL, maxRequestsUnderBrownout, maxRequests, maxExpiry);\n    }\n\n    @Override\n    protected boolean isResponseHeaders(Object msg) {\n        return msg instanceof HttpResponse;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/Http2ConnectionCloseHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.util.HttpUtils;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.DelegatingChannelPromiseNotifier;\nimport io.netty.handler.codec.http2.DefaultHttp2GoAwayFrame;\nimport io.netty.handler.codec.http2.Http2DataFrame;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport io.netty.handler.codec.http2.Http2HeadersFrame;\nimport io.netty.util.concurrent.EventExecutor;\nimport jakarta.inject.Inject;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 2:03 PM\n */\n@ChannelHandler.Sharable\npublic class Http2ConnectionCloseHandler extends ChannelDuplexHandler {\n    private static final Logger LOG = LoggerFactory.getLogger(Http2ConnectionCloseHandler.class);\n\n    private final Registry registry;\n    private final Id counterBaseId;\n\n    @Inject\n    public Http2ConnectionCloseHandler(Registry registry) {\n        super();\n        this.registry = registry;\n        this.counterBaseId = registry.createId(\"server.connection.close.handled\");\n    }\n\n    private void incrementCounter(ConnectionCloseType closeType, int port) {\n        registry.counter(counterBaseId\n                        .withTag(\"close_type\", closeType.name())\n                        .withTag(\"port\", Integer.toString(port))\n                        .withTag(\"protocol\", \"http2\"))\n                .increment();\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        // Close the connection immediately after LastContent is written, rather than\n        // waiting until the graceful-delay is up if this flag is set.\n        if (isEndOfRequestResponse(msg)) {\n            Channel parent = HttpUtils.getMainChannel(ctx);\n            ChannelPromise closeAfterPromise = shouldCloseAfter(ctx, parent);\n            if (closeAfterPromise != null) {\n\n                // Add listener to close the channel AFTER response has been sent.\n                promise.addListener(future -> {\n                    // Close the parent (tcp connection) channel.\n                    closeChannel(ctx, closeAfterPromise);\n                });\n            }\n        }\n\n        super.write(ctx, msg, promise);\n    }\n\n    /**\n     * Look on both the stream channel, and the parent channel to see if the CLOSE_AFTER_RESPONSE flag has been set.\n     * If so, return that promise.\n     *\n     * @param ctx\n     * @param parent\n     * @return\n     */\n    private ChannelPromise shouldCloseAfter(ChannelHandlerContext ctx, Channel parent) {\n        ChannelPromise closeAfterPromise = ctx.channel()\n                .attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE)\n                .get();\n        if (closeAfterPromise == null) {\n            closeAfterPromise = parent.attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE)\n                    .get();\n        }\n        return closeAfterPromise;\n    }\n\n    private boolean isEndOfRequestResponse(Object msg) {\n        if (msg instanceof Http2HeadersFrame) {\n            return ((Http2HeadersFrame) msg).isEndStream();\n        }\n        if (msg instanceof Http2DataFrame) {\n            return ((Http2DataFrame) msg).isEndStream();\n        }\n        return false;\n    }\n\n    private void closeChannel(ChannelHandlerContext ctx, ChannelPromise promise) {\n        Channel child = ctx.channel();\n        Channel parent = HttpUtils.getMainChannel(ctx);\n\n        // 1. Check if already_closing flag on this stream channel. If there is, then success this promise and return.\n        //    If not, then add already_closing flag to this stream channel.\n        // 2. Check if already_closing flag on the parent channel.\n        //    If so, then just return.\n        //    If not, then set already_closing on parent channel, and then allow through.\n\n        if (isAlreadyClosing(child)) {\n            promise.setSuccess();\n            return;\n        }\n\n        if (isAlreadyClosing(parent)) {\n            return;\n        }\n\n        // Close according to the specified close type.\n        ConnectionCloseType closeType = ConnectionCloseType.fromChannel(parent);\n        Integer port =\n                parent.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).get();\n        port = port == null ? -1 : port;\n        incrementCounter(closeType, port);\n        switch (closeType) {\n            case DELAYED_GRACEFUL:\n                gracefullyWithDelay(ctx.executor(), parent, promise);\n                break;\n            case GRACEFUL:\n            case IMMEDIATE:\n                immediate(parent, promise);\n                break;\n            default:\n                throw new IllegalArgumentException(\"Unknown ConnectionCloseEvent type! - \" + closeType);\n        }\n    }\n\n    /**\n     * WARNING: Found the OkHttp client gets confused by this behaviour (it ends up putting itself in a bad shutdown state\n     * after receiving the first goaway frame, and then dropping any inflight responses but also timing out waiting for them).\n     *\n     * And worried that other http/2 stacks may be similar, so for now we should NOT use this.\n     *\n     * This is unfortunate, as FTL wanted this, and it is correct according to the spec.\n     *\n     * See this code in okhttp where it drops response header frame if state is already shutdown:\n     * https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java#L609\n     */\n    private void gracefullyWithDelay(EventExecutor executor, Channel parent, ChannelPromise promise) {\n        // See javadoc for explanation of why this may be disabled.\n        boolean allowGracefulDelayed = ConnectionCloseChannelAttributes.allowGracefulDelayed(parent);\n        if (!allowGracefulDelayed) {\n            immediate(parent, promise);\n            return;\n        }\n\n        if (!parent.isActive()) {\n            promise.setSuccess();\n            return;\n        }\n\n        // First send a 'graceful shutdown' GOAWAY frame.\n        /*\n        \"A server that is attempting to gracefully shut down a connection SHOULD send an initial GOAWAY frame with\n        the last stream identifier set to 231-1 and a NO_ERROR code. This signals to the client that a shutdown is\n        imminent and that initiating further requests is prohibited.\"\n          -- https://http2.github.io/http2-spec/#GOAWAY\n         */\n        DefaultHttp2GoAwayFrame goaway = new DefaultHttp2GoAwayFrame(Http2Error.NO_ERROR);\n        goaway.setExtraStreamIds(Integer.MAX_VALUE);\n        parent.writeAndFlush(goaway);\n        LOG.debug(\n                \"gracefullyWithDelay: flushed initial go_away frame. channel={}\",\n                parent.id().asShortText());\n\n        // In N secs time, throw an error that causes the http2 codec to send another GOAWAY frame\n        // (this time with accurate lastStreamId) and then close the connection.\n        int gracefulCloseDelay = ConnectionCloseChannelAttributes.gracefulCloseDelay(parent);\n        executor.schedule(\n                () -> {\n\n                    // Check that the client hasn't already closed the connection (due to the earlier goaway we sent).\n                    if (parent.isActive()) {\n                        // NOTE - the netty Http2ConnectionHandler specifically does not send another goaway when we\n                        // call\n                        // channel.close() if one has already been sent .... so when we want more than one sent, we need\n                        // to do it\n                        // explicitly ourselves like this.\n                        LOG.debug(\n                                \"gracefullyWithDelay: firing graceful_shutdown event to make netty send a final\"\n                                        + \" go_away frame and then close connection. channel={}\",\n                                parent.id().asShortText());\n                        Http2Exception h2e =\n                                new Http2Exception(Http2Error.NO_ERROR, Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN);\n                        parent.pipeline().fireExceptionCaught(h2e);\n\n                        parent.close().addListener(future -> {\n                            promise.setSuccess();\n                        });\n                    } else {\n                        promise.setSuccess();\n                    }\n                },\n                gracefulCloseDelay,\n                TimeUnit.SECONDS);\n    }\n\n    private void immediate(Channel parent, ChannelPromise promise) {\n        if (parent.isActive()) {\n            parent.close().addListener(new DelegatingChannelPromiseNotifier(promise));\n        } else {\n            promise.setSuccess();\n        }\n    }\n\n    protected boolean isAlreadyClosing(Channel parentChannel) {\n        // If already closing, then just return.\n        // This will happen because close() is called a 2nd time after sending the goaway frame.\n        if (HttpChannelFlags.CLOSING.get(parentChannel)) {\n            return true;\n        } else {\n            HttpChannelFlags.CLOSING.set(parentChannel);\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/Http2ConnectionExpiryHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.handler.codec.http2.Http2HeadersFrame;\n\n/**\n * This needs to be inserted in the pipeline after the Http2 Codex, but before any h2-&gt;h1 conversion.\n *\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 9:58 AM\n */\n@ChannelHandler.Sharable\npublic class Http2ConnectionExpiryHandler extends AbstrHttpConnectionExpiryHandler {\n    public Http2ConnectionExpiryHandler(int maxRequests, int maxRequestsUnderBrownout, int maxExpiry) {\n        super(ConnectionCloseType.DELAYED_GRACEFUL, maxRequestsUnderBrownout, maxRequests, maxExpiry);\n    }\n\n    @Override\n    protected boolean isResponseHeaders(Object msg) {\n        return msg instanceof Http2HeadersFrame;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/HttpChannelFlags.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.util.Attribute;\nimport io.netty.util.AttributeKey;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/10/17\n * Time: 4:29 PM\n */\npublic class HttpChannelFlags {\n    public static final Flag IN_BROWNOUT = new Flag(\"_brownout\");\n\n    public static final Flag CLOSING = new Flag(\"_connection_closing\");\n\n    public static class Flag {\n        private final AttributeKey<Boolean> attributeKey;\n\n        public Flag(String name) {\n            attributeKey = AttributeKey.newInstance(name);\n        }\n\n        public void set(Channel ch) {\n            ch.attr(attributeKey).set(Boolean.TRUE);\n        }\n\n        public void set(ChannelHandlerContext ctx) {\n            set(ctx.channel());\n        }\n\n        public void remove(Channel ch) {\n            ch.attr(attributeKey).set(null);\n        }\n\n        public boolean get(Channel ch) {\n            Attribute<Boolean> attr = ch.attr(attributeKey);\n            Boolean value = attr.get();\n            return (value == null) ? false : value;\n        }\n\n        public boolean get(ChannelHandlerContext ctx) {\n            return get(ctx.channel());\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/HttpClientLifecycleChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.LastHttpContent;\n\n/**\n * @author michaels\n */\npublic class HttpClientLifecycleChannelHandler extends HttpLifecycleChannelHandler {\n    public static final ChannelHandler INBOUND_CHANNEL_HANDLER = new HttpClientLifecycleInboundChannelHandler();\n    public static final ChannelHandler OUTBOUND_CHANNEL_HANDLER = new HttpClientLifecycleOutboundChannelHandler();\n\n    @ChannelHandler.Sharable\n    private static class HttpClientLifecycleInboundChannelHandler extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            if (msg instanceof HttpResponse) {\n                ctx.channel().attr(ATTR_HTTP_RESP).set((HttpResponse) msg);\n            }\n\n            try {\n                super.channelRead(ctx, msg);\n            } finally {\n                if (msg instanceof LastHttpContent) {\n                    fireCompleteEventIfNotAlready(ctx, CompleteReason.SESSION_COMPLETE);\n                }\n            }\n        }\n\n        @Override\n        public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n            try {\n                super.channelInactive(ctx);\n            } finally {\n                fireCompleteEventIfNotAlready(ctx, CompleteReason.INACTIVE);\n            }\n        }\n    }\n\n    @ChannelHandler.Sharable\n    private static class HttpClientLifecycleOutboundChannelHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            if (msg instanceof HttpRequest) {\n                fireStartEvent(ctx, (HttpRequest) msg);\n            }\n\n            super.write(ctx, msg, promise);\n        }\n\n        @Override\n        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            fireCompleteEventIfNotAlready(ctx, CompleteReason.DISCONNECT);\n\n            super.disconnect(ctx, promise);\n        }\n\n        @Override\n        public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            fireCompleteEventIfNotAlready(ctx, CompleteReason.DEREGISTER);\n\n            super.deregister(ctx, promise);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            super.exceptionCaught(ctx, cause);\n\n            fireCompleteEventIfNotAlready(ctx, CompleteReason.EXCEPTION);\n        }\n\n        @Override\n        public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            fireCompleteEventIfNotAlready(ctx, CompleteReason.CLOSE);\n\n            super.close(ctx, promise);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/HttpLifecycleChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.util.Attribute;\nimport io.netty.util.AttributeKey;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/24/16\n * Time: 4:09 PM\n */\npublic abstract class HttpLifecycleChannelHandler {\n    private static final Logger logger = LoggerFactory.getLogger(HttpLifecycleChannelHandler.class);\n\n    public static final AttributeKey<HttpRequest> ATTR_HTTP_REQ = AttributeKey.newInstance(\"_http_request\");\n    public static final AttributeKey<HttpResponse> ATTR_HTTP_RESP = AttributeKey.newInstance(\"_http_response\");\n    public static final AttributeKey<Boolean> ATTR_HTTP_PIPELINE_REJECT =\n            AttributeKey.newInstance(\"_http_pipeline_reject\");\n\n    protected enum State {\n        STARTED,\n        COMPLETED\n    }\n\n    @VisibleForTesting\n    protected static final AttributeKey<State> ATTR_STATE = AttributeKey.newInstance(\"_httplifecycle_state\");\n\n    protected static boolean fireStartEvent(ChannelHandlerContext ctx, HttpRequest request) {\n        // Only allow this method to run once per request.\n        Channel channel = ctx.channel();\n        Attribute<State> attr = channel.attr(ATTR_STATE);\n        State state = attr.get();\n\n        if (state == State.STARTED) {\n            // This could potentially happen if a bad client sends a 2nd request on the same connection\n            // without waiting for the response from the first. And we don't support HTTP Pipelining.\n            logger.debug(\n                    \"Received a http request on connection where we already have a request being processed. Closing\"\n                            + \" the connection now. channel = {}\",\n                    channel.id().asLongText());\n            channel.attr(ATTR_HTTP_PIPELINE_REJECT).set(Boolean.TRUE);\n            channel.close();\n            return false;\n        }\n\n        channel.attr(ATTR_STATE).set(State.STARTED);\n        channel.attr(ATTR_HTTP_REQ).set(request);\n        ctx.pipeline().fireUserEventTriggered(new StartEvent(request));\n\n        return true;\n    }\n\n    protected static boolean fireCompleteEventIfNotAlready(ChannelHandlerContext ctx, CompleteReason reason) {\n        // Only allow this method to run once per request.\n        Attribute<State> attr = ctx.channel().attr(ATTR_STATE);\n        State state = attr.get();\n\n        if (state == null || state != State.STARTED) {\n            return false;\n        }\n\n        attr.set(State.COMPLETED);\n\n        HttpRequest request = ctx.channel().attr(ATTR_HTTP_REQ).get();\n        HttpResponse response = ctx.channel().attr(ATTR_HTTP_RESP).get();\n\n        // Cleanup channel attributes.\n        ctx.channel().attr(ATTR_HTTP_REQ).set(null);\n        ctx.channel().attr(ATTR_HTTP_RESP).set(null);\n\n        // Fire the event to whole pipeline.\n        ctx.pipeline().fireUserEventTriggered(new CompleteEvent(reason, request, response));\n\n        return true;\n    }\n\n    protected static void addPassportState(ChannelHandlerContext ctx, PassportState state) {\n        CurrentPassport passport = CurrentPassport.fromChannel(ctx.channel());\n        passport.add(state);\n    }\n\n    public enum CompleteReason {\n        SESSION_COMPLETE,\n        INACTIVE,\n        //        IDLE,\n        DISCONNECT,\n        DEREGISTER,\n        PIPELINE_REJECT,\n        EXCEPTION,\n        CLOSE\n        //        FAILURE_CLIENT_CANCELLED,\n        //        FAILURE_CLIENT_TIMEOUT;\n\n        //        private final NfStatus nfStatus;\n        //        private final int responseStatus;\n        //\n        //        CompleteReason(NfStatus nfStatus, int responseStatus) {\n        //            this.nfStatus = nfStatus;\n        //            this.responseStatus = responseStatus;\n        //        }\n        //\n        //        CompleteReason() {\n        //            //For status that never gets returned back to client, like channel inactive\n        //            nfStatus = null;\n        //            responseStatus = 501;\n        //        }\n        //\n        //        public NfStatus getNfStatus() {\n        //            return nfStatus;\n        //        }\n        //\n        //        public int getResponseStatus() {\n        //            return responseStatus;\n        //        }\n    }\n\n    public static class StartEvent {\n        private final HttpRequest request;\n\n        public StartEvent(HttpRequest request) {\n            this.request = request;\n        }\n\n        public HttpRequest getRequest() {\n            return request;\n        }\n    }\n\n    public static class CompleteEvent {\n        private final CompleteReason reason;\n        private final HttpRequest request;\n        private final HttpResponse response;\n\n        public CompleteEvent(CompleteReason reason, HttpRequest request, HttpResponse response) {\n            this.reason = reason;\n            this.request = request;\n            this.response = response;\n        }\n\n        public CompleteReason getReason() {\n            return reason;\n        }\n\n        public HttpRequest getRequest() {\n            return request;\n        }\n\n        public HttpResponse getResponse() {\n            return response;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/HttpRequestReadTimeoutEvent.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\n/**\n * Indicates a timeout in reading the full http request.\n *\n * ie. time between receiving request headers and LastHttpContent of request body.\n */\npublic class HttpRequestReadTimeoutEvent {\n    public static final HttpRequestReadTimeoutEvent INSTANCE = new HttpRequestReadTimeoutEvent();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/HttpRequestReadTimeoutHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.handler.timeout.ReadTimeoutHandler;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * This handler times from the point a HttpRequest is read until the LastHttpContent is read,\n * and fires a HttpRequestTimeoutEvent if that time has exceed the configured timeout.\n *\n * Unlike ReadTimeoutHandler, this impl does NOT close the channel on a timeout. Only fires the\n * event.\n *\n * @author michaels\n */\npublic class HttpRequestReadTimeoutHandler extends ChannelInboundHandlerAdapter {\n    private static final String HANDLER_NAME = \"http_request_read_timeout_handler\";\n    private static final String INTERNAL_HANDLER_NAME = \"http_request_read_timeout_internal\";\n\n    private final long timeout;\n    private final TimeUnit unit;\n    private final Counter httpRequestReadTimeoutCounter;\n\n    protected HttpRequestReadTimeoutHandler(long timeout, TimeUnit unit, Counter httpRequestReadTimeoutCounter) {\n        this.timeout = timeout;\n        this.unit = unit;\n        this.httpRequestReadTimeoutCounter = httpRequestReadTimeoutCounter;\n    }\n\n    /**\n     * Factory which ensures that this handler is added to the pipeline using the\n     * correct name.\n     */\n    public static void addLast(\n            ChannelPipeline pipeline, long timeout, TimeUnit unit, Counter httpRequestReadTimeoutCounter) {\n        HttpRequestReadTimeoutHandler handler =\n                new HttpRequestReadTimeoutHandler(timeout, unit, httpRequestReadTimeoutCounter);\n        pipeline.addLast(HANDLER_NAME, handler);\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof LastHttpContent) {\n            removeInternalHandler(ctx);\n        } else if (msg instanceof HttpRequest) {\n            // Start timeout handler.\n            InternalReadTimeoutHandler handler = new InternalReadTimeoutHandler(timeout, unit);\n            ctx.pipeline().addBefore(HANDLER_NAME, INTERNAL_HANDLER_NAME, handler);\n        }\n\n        super.channelRead(ctx, msg);\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof HttpRequestReadTimeoutEvent) {\n            CurrentPassport.fromChannel(ctx.channel()).add(PassportState.IN_REQ_READ_TIMEOUT);\n            removeInternalHandler(ctx);\n            httpRequestReadTimeoutCounter.increment();\n        }\n\n        super.userEventTriggered(ctx, evt);\n    }\n\n    @Override\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\n        removeInternalHandler(ctx);\n        super.handlerRemoved(ctx);\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        removeInternalHandler(ctx);\n        super.channelInactive(ctx);\n    }\n\n    protected void removeInternalHandler(ChannelHandlerContext ctx) {\n        // Remove timeout handler if not already removed.\n        ChannelHandlerContext handlerContext = ctx.pipeline().context(INTERNAL_HANDLER_NAME);\n        if (handlerContext != null && !handlerContext.isRemoved()) {\n            ctx.pipeline().remove(INTERNAL_HANDLER_NAME);\n        }\n    }\n\n    static class InternalReadTimeoutHandler extends ReadTimeoutHandler {\n        public InternalReadTimeoutHandler(long timeout, TimeUnit unit) {\n            super(timeout, unit);\n        }\n\n        @Override\n        protected void readTimedOut(ChannelHandlerContext ctx) throws Exception {\n            ctx.fireUserEventTriggered(HttpRequestReadTimeoutEvent.INSTANCE);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/HttpServerLifecycleChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport java.util.Objects;\n\n/**\n * @author michaels\n */\npublic final class HttpServerLifecycleChannelHandler extends HttpLifecycleChannelHandler {\n    public static final class HttpServerLifecycleInboundChannelHandler extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            if (msg instanceof HttpRequest) {\n                // Fire start event, and if that succeeded, then allow processing to\n                // continue to next handler in pipeline.\n                if (fireStartEvent(ctx, (HttpRequest) msg)) {\n                    super.channelRead(ctx, msg);\n                } else {\n                    ReferenceCountUtil.release(msg);\n                }\n            } else {\n                super.channelRead(ctx, msg);\n            }\n        }\n\n        @Override\n        public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n            fireCompleteEventIfNotAlready(ctx, CompleteReason.INACTIVE);\n\n            super.channelInactive(ctx);\n        }\n    }\n\n    public static final class HttpServerLifecycleOutboundChannelHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            if (msg instanceof HttpResponse) {\n                ctx.channel().attr(ATTR_HTTP_RESP).set((HttpResponse) msg);\n            }\n\n            try {\n                super.write(ctx, msg, promise);\n            } finally {\n                if (msg instanceof LastHttpContent) {\n\n                    boolean dontFireCompleteYet = false;\n                    if (msg instanceof HttpResponse) {\n                        // Handle case of 100 CONTINUE, where server sends an initial 100 status response to indicate to\n                        // client\n                        // that it can continue sending the initial request body.\n                        // ie. in this case we don't want to consider the state to be COMPLETE until after the 2nd\n                        // response.\n                        if (Objects.equals(((HttpResponse) msg).status(), HttpResponseStatus.CONTINUE)) {\n                            dontFireCompleteYet = true;\n                        }\n                    }\n\n                    if (!dontFireCompleteYet) {\n                        if (promise.isDone()) {\n                            fireCompleteEventIfNotAlready(ctx, CompleteReason.SESSION_COMPLETE);\n                        } else {\n                            promise.addListener(future -> {\n                                fireCompleteEventIfNotAlready(ctx, CompleteReason.SESSION_COMPLETE);\n                            });\n                        }\n                    }\n                }\n            }\n        }\n\n        @Override\n        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            fireCompleteEventIfNotAlready(ctx, CompleteReason.DISCONNECT);\n\n            super.disconnect(ctx, promise);\n        }\n\n        @Override\n        public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            addPassportState(ctx, PassportState.SERVER_CH_CLOSE);\n            // This will likely expand based on more specific reasons for completion\n            if (ctx.channel()\n                            .attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT)\n                            .get()\n                    == null) {\n                fireCompleteEventIfNotAlready(ctx, CompleteReason.CLOSE);\n            } else {\n                fireCompleteEventIfNotAlready(ctx, CompleteReason.PIPELINE_REJECT);\n            }\n            super.close(ctx, promise);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/RequestResponseCompleteEvent.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/24/16\n * Time: 1:04 PM\n */\npublic class RequestResponseCompleteEvent {}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/SourceAddressChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.util.AttributeKey;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.net.UnknownHostException;\nimport javax.annotation.Nullable;\n\n/**\n * Stores the source IP address as an attribute of the channel. This has the advantage of allowing us to overwrite it if\n * we have more info (eg. ELB sends a HAProxyMessage with info of REAL source host + port).\n * <p>\n * User: michaels@netflix.com Date: 4/14/16 Time: 4:29 PM\n */\n@ChannelHandler.Sharable\npublic final class SourceAddressChannelHandler extends ChannelInboundHandlerAdapter {\n\n    /**\n     * Indicates the actual source (remote) address of the channel.  This can be different than the one {@link Channel}\n     * returns if the connection is being proxied.  (e.g. over HAProxy)\n     */\n    public static final AttributeKey<SocketAddress> ATTR_REMOTE_ADDR = AttributeKey.newInstance(\"_remote_addr\");\n\n    /**\n     * Indicates the destination address received from Proxy Protocol. Not set otherwise\n     */\n    public static final AttributeKey<InetSocketAddress> ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS =\n            AttributeKey.newInstance(\"_proxy_protocol_destination_address\");\n\n    /**\n     * Use {@link #ATTR_REMOTE_ADDR} instead.\n     */\n    @Deprecated\n    public static final AttributeKey<InetSocketAddress> ATTR_SOURCE_INET_ADDR =\n            AttributeKey.newInstance(\"_source_inet_addr\");\n\n    /**\n     * The host address of the source.   This is derived from {@link #ATTR_REMOTE_ADDR}.   If the address is an IPv6\n     * address, the scope identifier is absent.\n     */\n    public static final AttributeKey<String> ATTR_SOURCE_ADDRESS = AttributeKey.newInstance(\"_source_address\");\n\n    /**\n     * Indicates the local address of the channel.  This can be different than the one {@link Channel} returns if the\n     * connection is being proxied.  (e.g. over HAProxy)\n     */\n    public static final AttributeKey<SocketAddress> ATTR_LOCAL_ADDR = AttributeKey.newInstance(\"_local_addr\");\n\n    /**\n     * Use {@link #ATTR_LOCAL_ADDR} instead.\n     */\n    @Deprecated\n    public static final AttributeKey<InetSocketAddress> ATTR_LOCAL_INET_ADDR =\n            AttributeKey.newInstance(\"_local_inet_addr\");\n\n    /**\n     * The local address of this channel.  This is derived from {@code channel.localAddress()}, or from the Proxy\n     * Protocol preface if provided.  If the address is an IPv6 address, the scope identifier is absent. Unlike {@link\n     * #ATTR_SERVER_LOCAL_ADDRESS}, this value is overwritten with the Proxy Protocol local address (e.g. the LB's local\n     * address), if enabled.\n     */\n    public static final AttributeKey<String> ATTR_LOCAL_ADDRESS = AttributeKey.newInstance(\"_local_address\");\n\n    /**\n     * The actual local address of the channel, in string form.  If the address is an IPv6 address, the scope identifier\n     * is absent.  Unlike {@link #ATTR_LOCAL_ADDRESS}, this is not overwritten by the Proxy Protocol message if\n     * present.\n     *\n     * @deprecated Use {@code channel.localAddress()}  instead.\n     */\n    @Deprecated\n    public static final AttributeKey<String> ATTR_SERVER_LOCAL_ADDRESS =\n            AttributeKey.newInstance(\"_server_local_address\");\n\n    /**\n     * The port number of the local socket, or {@code -1} if not appropriate.  This is not overwritten by the Proxy\n     * Protocol message if present.\n     */\n    public static final AttributeKey<Integer> ATTR_SERVER_LOCAL_PORT = AttributeKey.newInstance(\"_server_local_port\");\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        ctx.channel().attr(ATTR_REMOTE_ADDR).set(ctx.channel().remoteAddress());\n        InetSocketAddress sourceAddress = sourceAddress(ctx.channel());\n        ctx.channel().attr(ATTR_SOURCE_INET_ADDR).setIfAbsent(sourceAddress);\n        ctx.channel().attr(ATTR_SOURCE_ADDRESS).setIfAbsent(getHostAddress(sourceAddress));\n        ctx.channel().attr(ATTR_LOCAL_ADDR).set(ctx.channel().localAddress());\n        InetSocketAddress localAddress = localAddress(ctx.channel());\n        ctx.channel().attr(ATTR_LOCAL_INET_ADDR).setIfAbsent(localAddress);\n        ctx.channel().attr(ATTR_LOCAL_ADDRESS).setIfAbsent(getHostAddress(localAddress));\n        // ATTR_LOCAL_ADDRESS and ATTR_LOCAL_PORT get overwritten with what is received in\n        // Proxy Protocol (via the LB), so set local server's address, port explicitly\n        ctx.channel()\n                .attr(ATTR_SERVER_LOCAL_ADDRESS)\n                .setIfAbsent(localAddress.getAddress().getHostAddress());\n        ctx.channel().attr(ATTR_SERVER_LOCAL_PORT).setIfAbsent(localAddress.getPort());\n\n        super.channelActive(ctx);\n    }\n\n    /**\n     * Returns the String form of a socket address, or {@code null} if there isn't one.\n     */\n    @VisibleForTesting\n    @Nullable\n    static String getHostAddress(InetSocketAddress socketAddress) {\n        InetAddress address = socketAddress.getAddress();\n        if (address instanceof Inet6Address) {\n            // Strip the scope from the address since some other classes choke on it.\n            // TODO(carl-mastrangelo): Consider adding this back in once issues like\n            // https://github.com/google/guava/issues/2587 are fixed.\n            try {\n                return InetAddress.getByAddress(address.getAddress()).getHostAddress();\n            } catch (UnknownHostException e) {\n                throw new RuntimeException(e);\n            }\n        } else if (address instanceof Inet4Address) {\n            return address.getHostAddress();\n        } else {\n            assert address == null;\n            return null;\n        }\n    }\n\n    private InetSocketAddress sourceAddress(Channel channel) {\n        SocketAddress remoteSocketAddr = channel.remoteAddress();\n        if (remoteSocketAddr != null && InetSocketAddress.class.isAssignableFrom(remoteSocketAddr.getClass())) {\n            InetSocketAddress inetSocketAddress = (InetSocketAddress) remoteSocketAddr;\n            if (inetSocketAddress.getAddress() != null) {\n                return inetSocketAddress;\n            }\n        }\n        return null;\n    }\n\n    private InetSocketAddress localAddress(Channel channel) {\n        SocketAddress localSocketAddress = channel.localAddress();\n        if (localSocketAddress != null && InetSocketAddress.class.isAssignableFrom(localSocketAddress.getClass())) {\n            InetSocketAddress inetSocketAddress = (InetSocketAddress) localSocketAddress;\n            if (inetSocketAddress.getAddress() != null) {\n                return inetSocketAddress;\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/SslExceptionsHandler.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.spectator.api.Registry;\nimport io.netty.channel.ChannelHandler.Sharable;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport javax.net.ssl.SSLHandshakeException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Swallow specific SSL related exceptions to avoid propagating deep stack traces up the pipeline.\n *\n * @author Argha C\n * @since 4/17/23\n */\n@Sharable\npublic class SslExceptionsHandler extends ChannelInboundHandlerAdapter {\n\n    private static final Logger logger = LoggerFactory.getLogger(SslExceptionsHandler.class);\n    private final Registry registry;\n\n    public SslExceptionsHandler(Registry registry) {\n        this.registry = registry;\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        // In certain cases, depending on the client, these stack traces can get very deep.\n        // We intentionally avoid propagating this up the pipeline, to avoid verbose disk logging.\n        if (cause.getCause() instanceof SSLHandshakeException) {\n            logger.debug(\"SSL handshake failed on channel {}\", ctx.channel(), cause);\n            registry.counter(\"server.ssl.exception.swallowed\", \"cause\", \"SSLHandshakeException\")\n                    .increment();\n        } else {\n            super.exceptionCaught(ctx, cause);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/SwallowSomeHttp2ExceptionsHandler.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport com.netflix.spectator.api.Registry;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.unix.Errors;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n@ChannelHandler.Sharable\npublic class SwallowSomeHttp2ExceptionsHandler extends ChannelOutboundHandlerAdapter {\n    private static final Logger LOG = LoggerFactory.getLogger(SwallowSomeHttp2ExceptionsHandler.class);\n\n    private final Registry registry;\n\n    public SwallowSomeHttp2ExceptionsHandler(Registry registry) {\n        this.registry = registry;\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        incrementExceptionCounter(cause);\n\n        if (cause instanceof Http2Exception h2e) {\n            if (h2e.error() == Http2Error.NO_ERROR\n                    && h2e.shutdownHint().equals(Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN)) {\n                // This is the exception we threw ourselves to make the http2 codec gracefully close the connection. So\n                // just\n                // swallow it so that it doesn't propagate and get logged.\n                LOG.debug(\"Swallowed Http2Exception.ShutdownHint.GRACEFUL_SHUTDOWN \", cause);\n            } else {\n                super.exceptionCaught(ctx, cause);\n            }\n        } else if (cause instanceof Errors.NativeIoException) {\n            LOG.debug(\"Swallowed NativeIoException\", cause);\n        } else {\n            super.exceptionCaught(ctx, cause);\n        }\n    }\n\n    private void incrementExceptionCounter(Throwable throwable) {\n        registry.counter(\n                        \"server.connection.pipeline.exception\",\n                        \"id\",\n                        throwable.getClass().getSimpleName())\n                .increment();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/accesslog/AccessLogChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.accesslog;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.util.AttributeKey;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 4/14/16\n * Time: 3:51 PM\n */\npublic final class AccessLogChannelHandler {\n    private static final AttributeKey<RequestState> ATTR_REQ_STATE =\n            AttributeKey.newInstance(\"_accesslog_requeststate\");\n\n    private static final Logger LOG = LoggerFactory.getLogger(AccessLogChannelHandler.class);\n\n    public static class AccessLogInboundChannelHandler extends ChannelInboundHandlerAdapter {\n        private final AccessLogPublisher publisher;\n\n        public AccessLogInboundChannelHandler(AccessLogPublisher publisher) {\n            this.publisher = publisher;\n        }\n\n        protected Integer getLocalPort(ChannelHandlerContext ctx) {\n            return ctx.channel()\n                    .attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT)\n                    .get();\n        }\n\n        protected String getRemoteIp(ChannelHandlerContext ctx) {\n            return ctx.channel()\n                    .attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS)\n                    .get();\n        }\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            if (msg instanceof HttpRequest) {\n                RequestState state = new RequestState();\n                state.request = (HttpRequest) msg;\n                state.startTimeNs = System.nanoTime();\n                state.requestBodySize = 0;\n                ctx.channel().attr(ATTR_REQ_STATE).set(state);\n            }\n\n            if (msg instanceof HttpContent) {\n                RequestState state = ctx.channel().attr(ATTR_REQ_STATE).get();\n                if (state != null) {\n                    state.requestBodySize += ((HttpContent) msg).content().readableBytes();\n                }\n            }\n\n            super.channelRead(ctx, msg);\n        }\n\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n            if (evt instanceof HttpLifecycleChannelHandler.CompleteEvent) {\n                // Get the stored request, and remove the attr from channel to cleanup.\n                RequestState state = ctx.channel().attr(ATTR_REQ_STATE).get();\n                ctx.channel().attr(ATTR_REQ_STATE).set(null);\n\n                // Response complete, so now write to access log.\n                long durationNs = System.nanoTime() - state.startTimeNs;\n\n                Integer localPort = getLocalPort(ctx);\n                String remoteIp = getRemoteIp(ctx);\n\n                if (state.response == null) {\n                    LOG.debug(\n                            \"Response null in AccessLog, Complete reason={}, duration={}, url={}, method={}\",\n                            ((HttpLifecycleChannelHandler.CompleteEvent) evt).getReason(),\n                            durationNs / (1000 * 1000),\n                            state.request != null ? state.request.uri() : \"-\",\n                            state.request != null ? state.request.method() : \"-\");\n                }\n\n                publisher.log(\n                        ctx.channel(),\n                        state.request,\n                        state.response,\n                        state.dateTime,\n                        localPort,\n                        remoteIp,\n                        durationNs,\n                        state.requestBodySize,\n                        state.responseBodySize);\n            }\n\n            super.userEventTriggered(ctx, evt);\n        }\n    }\n\n    public static final class AccessLogOutboundChannelHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            RequestState state = ctx.channel().attr(ATTR_REQ_STATE).get();\n\n            if (msg instanceof HttpResponse) {\n                state.response = (HttpResponse) msg;\n                state.responseBodySize = 0;\n            }\n\n            if (msg instanceof HttpContent) {\n                state.responseBodySize += ((HttpContent) msg).content().readableBytes();\n            }\n\n            super.write(ctx, msg, promise);\n        }\n    }\n\n    private static class RequestState {\n        final LocalDateTime dateTime = LocalDateTime.now(ZoneId.systemDefault());\n        HttpRequest request;\n        HttpResponse response;\n        long startTimeNs;\n        long requestBodySize = 0;\n        long responseBodySize = 0;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/accesslog/AccessLogPublisher.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.accesslog;\n\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.config.DynamicStringListProperty;\nimport io.netty.channel.Channel;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.function.BiFunction;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class AccessLogPublisher {\n    private static final char DELIM = '\\t';\n    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;\n\n    private static final List<String> LOG_REQ_HEADERS = new DynamicStringListProperty(\n                    \"zuul.access.log.requestheaders\",\n                    \"host,x-forwarded-for,x-forwarded-proto,x-forwarded-host,x-forwarded-port,user-agent\")\n            .get();\n    private static final List<String> LOG_RESP_HEADERS =\n            new DynamicStringListProperty(\"zuul.access.log.responseheaders\", \"server,via,content-type\").get();\n    private static final DynamicIntProperty URI_LENGTH_LIMIT =\n            new DynamicIntProperty(\"zuul.access.log.uri.length.limit\", Integer.MAX_VALUE);\n\n    private final Logger logger;\n    private final BiFunction<Channel, HttpRequest, String> requestIdProvider;\n\n    private static final Logger LOG = LoggerFactory.getLogger(AccessLogPublisher.class);\n\n    public AccessLogPublisher(String loggerName, BiFunction<Channel, HttpRequest, String> requestIdProvider) {\n        this.logger = LoggerFactory.getLogger(loggerName);\n        this.requestIdProvider = requestIdProvider;\n    }\n\n    public void log(\n            Channel channel,\n            HttpRequest request,\n            HttpResponse response,\n            LocalDateTime dateTime,\n            Integer localPort,\n            String remoteIp,\n            Long durationNs,\n            Long requestBodySize,\n            Long responseBodySize) {\n        StringBuilder sb = new StringBuilder(512);\n\n        String dateTimeStr = dateTime != null ? dateTime.format(DATE_TIME_FORMATTER) : \"-----T-:-:-\";\n        String remoteIpStr = (remoteIp != null && !remoteIp.isEmpty()) ? remoteIp : \"-\";\n        String port = localPort != null ? localPort.toString() : \"-\";\n        String method = request != null ? request.method().toString().toUpperCase(Locale.ROOT) : \"-\";\n        String uri = request != null ? request.uri() : \"-\";\n        if (uri.length() > URI_LENGTH_LIMIT.get()) {\n            uri = uri.substring(0, URI_LENGTH_LIMIT.get());\n        }\n        String status = response != null ? String.valueOf(response.status().code()) : \"-\";\n\n        String requestId = null;\n        try {\n            requestId = requestIdProvider.apply(channel, request);\n        } catch (Exception ex) {\n            LOG.error(\n                    \"requestIdProvider failed in AccessLogPublisher method={}, uri={}, status={}\", method, uri, status);\n        }\n        requestId = requestId != null ? requestId : \"-\";\n\n        // Convert duration to microseconds.\n        String durationStr = (durationNs != null && durationNs > 0) ? String.valueOf(durationNs / 1000) : \"-\";\n\n        String requestBodySizeStr = (requestBodySize != null && requestBodySize > 0) ? requestBodySize.toString() : \"-\";\n        String responseBodySizeStr =\n                (responseBodySize != null && responseBodySize > 0) ? responseBodySize.toString() : \"-\";\n\n        // Build the line.\n        sb.append(dateTimeStr)\n                .append(DELIM)\n                .append(remoteIpStr)\n                .append(DELIM)\n                .append(port)\n                .append(DELIM)\n                .append(method)\n                .append(DELIM)\n                .append(uri)\n                .append(DELIM)\n                .append(status)\n                .append(DELIM)\n                .append(durationStr)\n                .append(DELIM)\n                .append(responseBodySizeStr)\n                .append(DELIM)\n                .append(requestId)\n                .append(DELIM)\n                .append(requestBodySizeStr);\n\n        if (request != null && request.headers() != null) {\n            includeMatchingHeaders(sb, LOG_REQ_HEADERS, request.headers());\n        }\n\n        if (response != null && response.headers() != null) {\n            includeMatchingHeaders(sb, LOG_RESP_HEADERS, response.headers());\n        }\n\n        // Write to logger.\n        String access = sb.toString();\n        logger.info(access);\n        LOG.debug(access);\n    }\n\n    void includeMatchingHeaders(StringBuilder builder, List<String> requiredHeaders, HttpHeaders headers) {\n        for (String headerName : requiredHeaders) {\n            String value = headerAsString(headers, headerName);\n            builder.append(DELIM).append('\\\"').append(value).append('\\\"');\n        }\n    }\n\n    String headerAsString(HttpHeaders headers, String headerName) {\n        List<String> values = headers.getAll(headerName);\n        return values.isEmpty() ? \"-\" : String.join(\",\", values);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/channel/config/ChannelConfig.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.channel.config;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 6:43 PM\n */\npublic class ChannelConfig implements Cloneable {\n    private final HashMap<ChannelConfigKey, ChannelConfigValue> parameters;\n\n    public ChannelConfig() {\n        parameters = new HashMap<>();\n    }\n\n    public ChannelConfig(Map<ChannelConfigKey, ChannelConfigValue> parameters) {\n        this.parameters = new HashMap<ChannelConfigKey, ChannelConfigValue>(parameters);\n    }\n\n    public void add(ChannelConfigValue param) {\n        this.parameters.put(param.key(), param);\n    }\n\n    public <T> void set(ChannelConfigKey<T> key, T value) {\n        this.parameters.put(key, new ChannelConfigValue<>(key, value));\n    }\n\n    public <T> T get(ChannelConfigKey<T> key) {\n        ChannelConfigValue<T> ccv = parameters.get(key);\n        T value = ccv == null ? null : (T) ccv.value();\n\n        if (value == null) {\n            value = key.defaultValue();\n        }\n\n        return value;\n    }\n\n    public <T> ChannelConfigValue<T> getConfig(ChannelConfigKey<T> key) {\n        return (ChannelConfigValue<T>) parameters.get(key);\n    }\n\n    public <T> boolean contains(ChannelConfigKey<T> key) {\n        return parameters.containsKey(key);\n    }\n\n    @Override\n    public ChannelConfig clone() {\n        return new ChannelConfig(parameters);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/channel/config/ChannelConfigKey.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.channel.config;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 6:17 PM\n */\npublic class ChannelConfigKey<T> {\n    private final String key;\n    private final T defaultValue;\n\n    public ChannelConfigKey(String key, T defaultValue) {\n        this.key = key;\n        this.defaultValue = defaultValue;\n    }\n\n    public ChannelConfigKey(String key) {\n        this.key = key;\n        this.defaultValue = null;\n    }\n\n    public String key() {\n        return key;\n    }\n\n    public T defaultValue() {\n        return defaultValue;\n    }\n\n    public boolean hasDefaultValue() {\n        return defaultValue != null;\n    }\n\n    @Override\n    public String toString() {\n        return \"ChannelConfigKey{\" + \"key='\" + key + '\\'' + \", defaultValue=\" + defaultValue + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/channel/config/ChannelConfigValue.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.channel.config;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 6:41 PM\n */\npublic class ChannelConfigValue<T> {\n    private final ChannelConfigKey<T> key;\n    private final T value;\n\n    public ChannelConfigValue(ChannelConfigKey<T> key, T value) {\n        this.key = key;\n        this.value = value;\n    }\n\n    public ChannelConfigKey<T> key() {\n        return key;\n    }\n\n    public T value() {\n        return value;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/channel/config/CommonChannelConfigKeys.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.channel.config;\n\nimport com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.zuul.netty.server.ServerTimeout;\nimport com.netflix.zuul.netty.ssl.SslContextFactory;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.util.AsyncMapping;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/8/17\n * Time: 6:21 PM\n */\npublic class CommonChannelConfigKeys {\n    public static final ChannelConfigKey<Boolean> withProxyProtocol =\n            new ChannelConfigKey<>(\"withProxyProtocol\", false);\n    public static final ChannelConfigKey<StripUntrustedProxyHeadersHandler.AllowWhen> allowProxyHeadersWhen =\n            new ChannelConfigKey<>(\"allowProxyHeadersWhen\", StripUntrustedProxyHeadersHandler.AllowWhen.ALWAYS);\n    public static final ChannelConfigKey<Boolean> preferProxyProtocolForClientIp =\n            new ChannelConfigKey<>(\"preferProxyProtocolForClientIp\", true);\n\n    /** The Idle timeout of a connection, in milliseconds */\n    public static final ChannelConfigKey<Integer> idleTimeout = new ChannelConfigKey<>(\"idleTimeout\", 65000);\n\n    public static final ChannelConfigKey<ServerTimeout> serverTimeout = new ChannelConfigKey<>(\"serverTimeout\");\n    /** The HTTP request read timeout, in milliseconds */\n    public static final ChannelConfigKey<Integer> httpRequestReadTimeout =\n            new ChannelConfigKey<>(\"httpRequestReadTimeout\", 5000);\n    /** The maximum number of inbound connections to proxy. */\n    public static final ChannelConfigKey<Integer> maxConnections = new ChannelConfigKey<>(\"maxConnections\", 20000);\n\n    public static final ChannelConfigKey<Integer> maxRequestsPerConnection =\n            new ChannelConfigKey<>(\"maxRequestsPerConnection\", 4000);\n    public static final ChannelConfigKey<Integer> maxRequestsPerConnectionInBrownout =\n            new ChannelConfigKey<>(\"maxRequestsPerConnectionInBrownout\", 100);\n    public static final ChannelConfigKey<Integer> connectionExpiry =\n            new ChannelConfigKey<>(\"connectionExpiry\", 20 * 60 * 1000);\n\n    // SSL:\n    public static final ChannelConfigKey<Boolean> isSSlFromIntermediary =\n            new ChannelConfigKey<>(\"isSSlFromIntermediary\", false);\n    public static final ChannelConfigKey<ServerSslConfig> serverSslConfig = new ChannelConfigKey<>(\"serverSslConfig\");\n    public static final ChannelConfigKey<SslContextFactory> sslContextFactory =\n            new ChannelConfigKey<>(\"sslContextFactory\");\n    public static final ChannelConfigKey<AsyncMapping<String, SslContext>> sniMapping =\n            new ChannelConfigKey<>(\"sniMapping\");\n\n    // HTTP/2 specific:\n    public static final ChannelConfigKey<Integer> maxConcurrentStreams =\n            new ChannelConfigKey<>(\"maxConcurrentStreams\", 100);\n    public static final ChannelConfigKey<Integer> initialWindowSize =\n            new ChannelConfigKey<>(\"initialWindowSize\", 5242880); // 5MB\n    /* The amount of time to wait before closing a connection that has the `Connection: Close` header, in seconds */\n    public static final ChannelConfigKey<Integer> connCloseDelay = new ChannelConfigKey<>(\"connCloseDelay\", 10);\n    public static final ChannelConfigKey<Integer> maxHttp2HeaderTableSize =\n            new ChannelConfigKey<>(\"maxHttp2HeaderTableSize\", 4096);\n    public static final ChannelConfigKey<Integer> maxHttp2HeaderListSize =\n            new ChannelConfigKey<>(\"maxHttp2HeaderListSize\");\n    public static final ChannelConfigKey<Boolean> http2AllowGracefulDelayed =\n            new ChannelConfigKey<>(\"http2AllowGracefulDelayed\", true);\n    public static final ChannelConfigKey<Boolean> http2SwallowUnknownExceptionsOnConnClose =\n            new ChannelConfigKey<>(\"http2SwallowUnknownExceptionsOnConnClose\", false);\n    public static final ChannelConfigKey<Boolean> http2CatchConnectionErrors =\n            new ChannelConfigKey<>(\"http2CatchConnectionErrors\", true);\n    public static final ChannelConfigKey<Integer> http2EncoderMaxResetFrames =\n            new ChannelConfigKey<>(\"http2EncoderMaxResetFrames\", 200);\n    public static final ChannelConfigKey<Integer> http2EncoderMaxResetFramesWindow =\n            new ChannelConfigKey<>(\"http2EncoderMaxResetFramesWindow\", 30);\n    public static final ChannelConfigKey<Boolean> http2ConnectProtocolEnabled =\n            new ChannelConfigKey<>(\"http2ConnectProtocolEnabled\", false);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/http2/DynamicHttp2FrameLogger.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.http2;\n\nimport com.google.errorprone.annotations.FormatMethod;\nimport com.google.errorprone.annotations.FormatString;\nimport com.netflix.config.DynamicStringSetProperty;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http2.Http2Flags;\nimport io.netty.handler.codec.http2.Http2FrameLogger;\nimport io.netty.handler.codec.http2.Http2Headers;\nimport io.netty.handler.codec.http2.Http2Settings;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.internal.ObjectUtil;\nimport io.netty.util.internal.logging.InternalLogLevel;\nimport io.netty.util.internal.logging.InternalLogger;\nimport io.netty.util.internal.logging.InternalLoggerFactory;\n\npublic class DynamicHttp2FrameLogger extends Http2FrameLogger {\n    public static final AttributeKey<Object> ATTR_ENABLE = AttributeKey.valueOf(\"http2.frame.logger.enabled\");\n    private static final int BUFFER_LENGTH_THRESHOLD = 64;\n    private static final DynamicStringSetProperty FRAMES_TO_LOG = new DynamicStringSetProperty(\n            \"server.http2.logger.framestolog\",\n            \"SETTINGS,WINDOW_UPDATE,HEADERS,GO_AWAY,RST_STREAM,PRIORITY,PING,PUSH_PROMISE\");\n\n    private final InternalLogger logger;\n    private final InternalLogLevel level;\n\n    public DynamicHttp2FrameLogger(LogLevel level, Class<?> clazz) {\n        super(level, clazz);\n        this.level = ObjectUtil.checkNotNull(level.toInternalLevel(), \"level\");\n        this.logger = ObjectUtil.checkNotNull(InternalLoggerFactory.getInstance(clazz), \"logger\");\n    }\n\n    protected boolean enabled(ChannelHandlerContext ctx) {\n        return ctx.channel().hasAttr(ATTR_ENABLE);\n    }\n\n    protected boolean enabled() {\n        return logger.isEnabled(level);\n    }\n\n    @Override\n    public void logData(\n            Direction direction,\n            ChannelHandlerContext ctx,\n            int streamId,\n            ByteBuf data,\n            int padding,\n            boolean endStream) {\n        if (enabled()) {\n            log(\n                    direction,\n                    \"DATA\",\n                    ctx,\n                    \"streamId=%d, endStream=%b, length=%d\",\n                    streamId,\n                    endStream,\n                    data.readableBytes());\n        }\n    }\n\n    @Override\n    public void logHeaders(\n            Direction direction,\n            ChannelHandlerContext ctx,\n            int streamId,\n            Http2Headers headers,\n            int padding,\n            boolean endStream) {\n        if (enabled()) {\n            log(direction, \"HEADERS\", ctx, \"streamId=%d, headers=%s, endStream=%b\", streamId, headers, endStream);\n        }\n    }\n\n    @Override\n    public void logHeaders(\n            Direction direction,\n            ChannelHandlerContext ctx,\n            int streamId,\n            Http2Headers headers,\n            int streamDependency,\n            short weight,\n            boolean exclusive,\n            int padding,\n            boolean endStream) {\n        if (enabled()) {\n            log(\n                    direction,\n                    \"HEADERS\",\n                    ctx,\n                    \"streamId=%d, headers=%s, streamDependency=%d, weight=%d, \" + \"exclusive=%b, endStream=%b\",\n                    streamId,\n                    headers,\n                    streamDependency,\n                    weight,\n                    exclusive,\n                    endStream);\n        }\n    }\n\n    @Override\n    public void logPriority(\n            Direction direction,\n            ChannelHandlerContext ctx,\n            int streamId,\n            int streamDependency,\n            short weight,\n            boolean exclusive) {\n        if (enabled()) {\n            log(\n                    direction,\n                    \"PRIORITY\",\n                    ctx,\n                    \"streamId=%d, streamDependency=%d, weight=%d, exclusive=%b\",\n                    streamId,\n                    streamDependency,\n                    weight,\n                    exclusive);\n        }\n    }\n\n    @Override\n    public void logRstStream(Direction direction, ChannelHandlerContext ctx, int streamId, long errorCode) {\n        if (enabled()) {\n            log(direction, \"RST_STREAM\", ctx, \"streamId=%d, errorCode=%d\", streamId, errorCode);\n        }\n    }\n\n    @Override\n    public void logSettingsAck(Direction direction, ChannelHandlerContext ctx) {\n        if (enabled()) {\n            log(direction, \"SETTINGS\", ctx, \"ack=true\");\n        }\n    }\n\n    @Override\n    public void logSettings(Direction direction, ChannelHandlerContext ctx, Http2Settings settings) {\n        if (enabled()) {\n            log(direction, \"SETTINGS\", ctx, \"ack=false, settings=%s\", settings);\n        }\n    }\n\n    @Override\n    public void logPing(Direction direction, ChannelHandlerContext ctx, long data) {\n        if (enabled()) {\n            log(direction, \"PING\", ctx, \"ack=false, length=%d\", data);\n        }\n    }\n\n    @Override\n    public void logPingAck(Direction direction, ChannelHandlerContext ctx, long data) {\n        if (enabled()) {\n            log(direction, \"PING\", ctx, \"ack=true, length=%d\", data);\n        }\n    }\n\n    @Override\n    public void logPushPromise(\n            Direction direction,\n            ChannelHandlerContext ctx,\n            int streamId,\n            int promisedStreamId,\n            Http2Headers headers,\n            int padding) {\n        if (enabled()) {\n            log(\n                    direction,\n                    \"PUSH_PROMISE\",\n                    ctx,\n                    \"streamId=%d, promisedStreamId=%d, headers=%s, padding=%d\",\n                    streamId,\n                    promisedStreamId,\n                    headers,\n                    padding);\n        }\n    }\n\n    @Override\n    public void logGoAway(\n            Direction direction, ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData) {\n        if (enabled()) {\n            log(\n                    direction,\n                    \"GO_AWAY\",\n                    ctx,\n                    \"lastStreamId=%d, errorCode=%d, length=%d, bytes=%s\",\n                    lastStreamId,\n                    errorCode,\n                    debugData.readableBytes(),\n                    toString(debugData));\n        }\n    }\n\n    @Override\n    public void logWindowsUpdate(\n            Direction direction, ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) {\n        if (enabled()) {\n            log(direction, \"WINDOW_UPDATE\", ctx, \"streamId=%d, windowSizeIncrement=%d\", streamId, windowSizeIncrement);\n        }\n    }\n\n    @Override\n    public void logUnknownFrame(\n            Direction direction,\n            ChannelHandlerContext ctx,\n            byte frameType,\n            int streamId,\n            Http2Flags flags,\n            ByteBuf data) {\n        if (enabled()) {\n            log(\n                    direction,\n                    \"UNKNOWN\",\n                    ctx,\n                    \"frameType=%d, streamId=%d, flags=%d, length=%d, bytes=%s\",\n                    frameType & 0xFF,\n                    streamId,\n                    flags.value(),\n                    data.readableBytes(),\n                    toString(data));\n        }\n    }\n\n    private String toString(ByteBuf buf) {\n        if (level == InternalLogLevel.TRACE || buf.readableBytes() <= BUFFER_LENGTH_THRESHOLD) {\n            // Log the entire buffer.\n            return ByteBufUtil.hexDump(buf);\n        }\n\n        // Otherwise just log the first 64 bytes.\n        int length = Math.min(buf.readableBytes(), BUFFER_LENGTH_THRESHOLD);\n        return ByteBufUtil.hexDump(buf, buf.readerIndex(), length) + \"...\";\n    }\n\n    @FormatMethod\n    private void log(\n            Direction direction, String frame, ChannelHandlerContext ctx, @FormatString String format, Object... args) {\n        if (shouldLogFrame(frame)) {\n            StringBuilder b = new StringBuilder(200)\n                    .append(direction.name())\n                    .append(\": \")\n                    .append(frame)\n                    .append(\": \")\n                    .append(String.format(format, args))\n                    .append(\" -- \")\n                    .append(String.valueOf(ctx.channel()));\n            logger.log(level, b.toString());\n        }\n    }\n\n    protected boolean shouldLogFrame(String frame) {\n        return FRAMES_TO_LOG.get().contains(frame);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/EventLoopGroupMetrics.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.netflix.spectator.api.Registry;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/7/17\n * Time: 3:17 PM\n */\n@Singleton\npublic class EventLoopGroupMetrics {\n    private final ThreadLocal<EventLoopMetrics> metricsForCurrentThread;\n    private final Map<Thread, EventLoopMetrics> byEventLoop = new HashMap<>();\n\n    @Inject\n    public EventLoopGroupMetrics(Registry registry) {\n\n        this.metricsForCurrentThread = ThreadLocal.withInitial(() -> {\n            String name = nameForCurrentEventLoop();\n            EventLoopMetrics metrics = new EventLoopMetrics(registry, name);\n            byEventLoop.put(Thread.currentThread(), metrics);\n            return metrics;\n        });\n    }\n\n    public Map<Thread, Integer> connectionsPerEventLoop() {\n        Map<Thread, Integer> map = new HashMap<>(byEventLoop.size());\n        for (Map.Entry<Thread, EventLoopMetrics> entry : byEventLoop.entrySet()) {\n            map.put(entry.getKey(), entry.getValue().currentConnectionsCount());\n        }\n        return map;\n    }\n\n    public Map<Thread, Integer> httpRequestsPerEventLoop() {\n        Map<Thread, Integer> map = new HashMap<>(byEventLoop.size());\n        for (Map.Entry<Thread, EventLoopMetrics> entry : byEventLoop.entrySet()) {\n            map.put(entry.getKey(), entry.getValue().currentHttpRequestsCount());\n        }\n        return map;\n    }\n\n    public EventLoopMetrics getForCurrentEventLoop() {\n        return metricsForCurrentThread.get();\n    }\n\n    private static String nameForCurrentEventLoop() {\n        // We're relying on the knowledge that we name the eventloop threads consistently.\n        String threadName = Thread.currentThread().getName();\n        String parts[] = threadName.split(\"-ClientToZuulWorker-\", -1);\n        if (parts.length == 2) {\n            return parts[1];\n        }\n        return threadName;\n    }\n\n    interface EventLoopInfo {\n        int currentConnectionsCount();\n\n        int currentHttpRequestsCount();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/EventLoopMetrics.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/7/17\n * Time: 3:18 PM\n */\npublic class EventLoopMetrics implements EventLoopGroupMetrics.EventLoopInfo {\n    private final String name;\n    public final AtomicInteger currentRequests = new AtomicInteger(0);\n    public final AtomicInteger currentConnections = new AtomicInteger(0);\n\n    private final Registry registry;\n    private final Id currentRequestsId;\n    private final Id currentConnectionsId;\n\n    public EventLoopMetrics(Registry registry, String eventLoopName) {\n        this.name = eventLoopName;\n\n        this.registry = registry;\n        this.currentRequestsId = this.registry.createId(\"server.eventloop.http.requests.current\");\n        this.currentConnectionsId = this.registry.createId(\"server.eventloop.connections.current\");\n    }\n\n    @Override\n    public int currentConnectionsCount() {\n        return currentConnections.get();\n    }\n\n    @Override\n    public int currentHttpRequestsCount() {\n        return currentRequests.get();\n    }\n\n    public void incrementCurrentRequests() {\n        int value = this.currentRequests.incrementAndGet();\n        updateGauge(currentRequestsId, value);\n    }\n\n    public void decrementCurrentRequests() {\n        int value = this.currentRequests.decrementAndGet();\n        updateGauge(currentRequestsId, value);\n    }\n\n    public void incrementCurrentConnections() {\n        int value = this.currentConnections.incrementAndGet();\n        updateGauge(currentConnectionsId, value);\n    }\n\n    public void decrementCurrentConnections() {\n        int value = this.currentConnections.decrementAndGet();\n        updateGauge(currentConnectionsId, value);\n    }\n\n    private void updateGauge(Id gaugeId, int value) {\n        registry.gauge(gaugeId.withTag(\"eventloop\", name)).set(value);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/Http2MetricsChannelHandlers.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.netflix.spectator.api.Registry;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport io.netty.handler.codec.http2.Http2Frame;\nimport io.netty.handler.codec.http2.Http2GoAwayFrame;\nimport io.netty.handler.codec.http2.Http2ResetFrame;\n\npublic class Http2MetricsChannelHandlers {\n    private final Inbound inbound;\n    private final Outbound outbound;\n\n    public Http2MetricsChannelHandlers(Registry registry, String metricPrefix, String metricId) {\n        super();\n        this.inbound = new Inbound(registry, metricId, metricPrefix);\n        this.outbound = new Outbound(registry, metricId, metricPrefix);\n    }\n\n    public Inbound inbound() {\n        return inbound;\n    }\n\n    public Outbound outbound() {\n        return outbound;\n    }\n\n    protected void incrementErrorCounter(Registry registry, String counterName, String metricId, Http2Exception h2e) {\n        String h2Error = h2e.error() != null ? h2e.error().name() : \"NA\";\n        String exceptionName = h2e.getClass().getSimpleName();\n\n        registry.counter(counterName, \"id\", metricId, \"error\", h2Error, \"exception\", exceptionName)\n                .increment();\n    }\n\n    protected void incrementCounter(Registry registry, String counterName, String metricId, Http2Frame frame) {\n        long errorCode;\n        if (frame instanceof Http2ResetFrame) {\n            errorCode = ((Http2ResetFrame) frame).errorCode();\n        } else if (frame instanceof Http2GoAwayFrame) {\n            errorCode = ((Http2GoAwayFrame) frame).errorCode();\n        } else {\n            errorCode = -1;\n        }\n\n        registry.counter(counterName, \"id\", metricId, \"frame\", frame.name(), \"error_code\", Long.toString(errorCode))\n                .increment();\n    }\n\n    @ChannelHandler.Sharable\n    private class Inbound extends ChannelInboundHandlerAdapter {\n        private final Registry registry;\n        private final String metricId;\n        private final String frameCounterName;\n        private final String errorCounterName;\n\n        public Inbound(Registry registry, String metricId, String metricPrefix) {\n            this.registry = registry;\n            this.metricId = metricId;\n            this.frameCounterName = metricPrefix + \".http2.frame.inbound\";\n            this.errorCounterName = metricPrefix + \".http2.error.inbound\";\n        }\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            try {\n                if (msg instanceof Http2Frame) {\n                    incrementCounter(registry, frameCounterName, metricId, (Http2Frame) msg);\n                }\n            } finally {\n                super.channelRead(ctx, msg);\n            }\n        }\n\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n            try {\n                if (evt instanceof Http2Frame) {\n                    incrementCounter(registry, frameCounterName, metricId, (Http2Frame) evt);\n                }\n            } finally {\n                super.userEventTriggered(ctx, evt);\n            }\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            try {\n                if (cause instanceof Http2Exception) {\n                    incrementErrorCounter(registry, errorCounterName, metricId, (Http2Exception) cause);\n                }\n            } finally {\n                super.exceptionCaught(ctx, cause);\n            }\n        }\n    }\n\n    @ChannelHandler.Sharable\n    private class Outbound extends ChannelOutboundHandlerAdapter {\n        private final Registry registry;\n        private final String metricId;\n        private final String frameCounterName;\n        private final String errorCounterName;\n\n        public Outbound(Registry registry, String metricId, String metricPrefix) {\n            this.registry = registry;\n            this.metricId = metricId;\n            this.frameCounterName = metricPrefix + \".http2.frame.outbound\";\n            this.errorCounterName = metricPrefix + \".http2.error.outbound\";\n        }\n\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            super.write(ctx, msg, promise);\n\n            if (msg instanceof Http2Frame) {\n                incrementCounter(registry, frameCounterName, metricId, (Http2Frame) msg);\n            }\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            try {\n                if (cause instanceof Http2Exception) {\n                    incrementErrorCounter(registry, errorCounterName, metricId, (Http2Exception) cause);\n                }\n            } finally {\n                super.exceptionCaught(ctx, cause);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpBodySizeRecordingChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.util.AttributeKey;\nimport jakarta.inject.Provider;\n\n/**\n * User: michaels@netflix.com\n * Date: 4/14/16\n * Time: 3:51 PM\n */\npublic final class HttpBodySizeRecordingChannelHandler {\n    private static final AttributeKey<State> ATTR_STATE = AttributeKey.newInstance(\"_http_body_size_state\");\n\n    public static Provider<Long> getCurrentInboundBodySize(Channel ch) {\n        return new InboundBodySizeProvider(ch);\n    }\n\n    public static Provider<Long> getCurrentOutboundBodySize(Channel ch) {\n        return new OutboundBodySizeProvider(ch);\n    }\n\n    private static State getOrCreateCurrentState(Channel ch) {\n        State state = ch.attr(ATTR_STATE).get();\n        if (state == null) {\n            state = createNewState(ch);\n        }\n        return state;\n    }\n\n    private static State createNewState(Channel ch) {\n        State state = new State();\n        ch.attr(ATTR_STATE).set(state);\n        return state;\n    }\n\n    public static final class InboundChannelHandler extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            State state = null;\n\n            // Reset the state as each new inbound request comes in.\n            if (msg instanceof HttpRequest) {\n                state = createNewState(ctx.channel());\n            }\n\n            // Update the inbound body size with this chunk.\n            if (msg instanceof HttpContent) {\n                if (state == null) {\n                    state = getOrCreateCurrentState(ctx.channel());\n                }\n                state.inboundBodySize += ((HttpContent) msg).content().readableBytes();\n            }\n\n            super.channelRead(ctx, msg);\n        }\n\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n            try {\n                super.userEventTriggered(ctx, evt);\n            } finally {\n                if (evt instanceof HttpLifecycleChannelHandler.CompleteEvent) {\n                    ctx.channel().attr(ATTR_STATE).set(null);\n                }\n            }\n        }\n    }\n\n    public static final class OutboundChannelHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            State state = null;\n\n            // Reset the state as each new outbound request goes out.\n            if (msg instanceof HttpRequest) {\n                state = createNewState(ctx.channel());\n            }\n\n            // Update the outbound body size with this chunk.\n            if (msg instanceof HttpContent) {\n                if (state == null) {\n                    state = getOrCreateCurrentState(ctx.channel());\n                }\n                state.outboundBodySize += ((HttpContent) msg).content().readableBytes();\n            }\n\n            super.write(ctx, msg, promise);\n        }\n    }\n\n    private static class State {\n        long inboundBodySize = 0;\n        long outboundBodySize = 0;\n    }\n\n    static class InboundBodySizeProvider implements Provider<Long> {\n        private final Channel channel;\n\n        public InboundBodySizeProvider(Channel channel) {\n            this.channel = channel;\n        }\n\n        @Override\n        public Long get() {\n            State state = getOrCreateCurrentState(channel);\n            return state == null ? 0 : state.inboundBodySize;\n        }\n    }\n\n    static class OutboundBodySizeProvider implements Provider<Long> {\n        private final Channel channel;\n\n        public OutboundBodySizeProvider(Channel channel) {\n            this.channel = channel;\n        }\n\n        @Override\n        public Long get() {\n            State state = getOrCreateCurrentState(channel);\n            return state == null ? 0 : state.outboundBodySize;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/HttpMetricsChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Gauge;\nimport com.netflix.spectator.api.Registry;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.util.AttributeKey;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * User: michaels@netflix.com\n * Date: 4/14/16\n * Time: 3:51 PM\n */\n@ChannelHandler.Sharable\npublic class HttpMetricsChannelHandler extends ChannelInboundHandlerAdapter {\n    private static final AttributeKey<Object> ATTR_REQ_INFLIGHT = AttributeKey.newInstance(\"_httpmetrics_inflight\");\n    private static final Object INFLIGHT = \"is_inflight\";\n    private static final AttributeKey<AtomicInteger> ATTR_CURRENT_REQS =\n            AttributeKey.newInstance(\"_server_http_current_count\");\n\n    private final AtomicInteger currentRequests = new AtomicInteger(0);\n\n    private final Registry registry;\n    private final Gauge currentRequestsGauge;\n    private final Counter unSupportedPipeliningCounter;\n\n    public HttpMetricsChannelHandler(Registry registry, String name, String id) {\n        super();\n\n        this.registry = registry;\n\n        this.currentRequestsGauge =\n                this.registry.gauge(this.registry.createId(name + \".http.requests.current\", \"id\", id));\n        this.unSupportedPipeliningCounter = this.registry.counter(name + \".http.requests.pipelining.dropped\", \"id\", id);\n    }\n\n    public static int getInflightRequestCountFromChannel(Channel ch) {\n        AtomicInteger current = ch.attr(ATTR_CURRENT_REQS).get();\n        return current == null ? 0 : current.get();\n    }\n\n    public int getInflightRequestsCount() {\n        return currentRequests.get();\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        // Store a ref to the count of current inflight requests onto this channel. So that\n        // other code can query it using getInflightRequestCountFromChannel().\n        ctx.channel().attr(ATTR_CURRENT_REQS).set(currentRequests);\n\n        super.channelActive(ctx);\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof HttpLifecycleChannelHandler.StartEvent) {\n            incrementCurrentRequestsInFlight(ctx);\n        } else if (evt instanceof CompleteEvent\n                && ((CompleteEvent) evt).getReason() == CompleteReason.PIPELINE_REJECT) {\n            unSupportedPipeliningCounter.increment();\n        } else if (evt instanceof CompleteEvent) {\n            decrementCurrentRequestsIfOneInflight(ctx);\n        }\n        super.userEventTriggered(ctx, evt);\n    }\n\n    private void incrementCurrentRequestsInFlight(ChannelHandlerContext ctx) {\n        currentRequestsGauge.set(currentRequests.incrementAndGet());\n        ctx.channel().attr(ATTR_REQ_INFLIGHT).set(INFLIGHT);\n    }\n\n    private void decrementCurrentRequestsIfOneInflight(ChannelHandlerContext ctx) {\n        if (ctx.channel().attr(ATTR_REQ_INFLIGHT).getAndSet(null) != null) {\n            currentRequestsGauge.set(currentRequests.decrementAndGet());\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetector.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport io.netty.util.ResourceLeakDetector;\nimport java.lang.reflect.Field;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Pluggable ResourceLeakDetector to track metrics for leaks\n *\n * Author: Arthur Gonigberg\n * Date: September 20, 2016\n */\npublic class InstrumentedResourceLeakDetector<T> extends ResourceLeakDetector<T> {\n\n    @VisibleForTesting\n    final AtomicInteger leakCounter;\n\n    public InstrumentedResourceLeakDetector(Class<?> resourceType, int samplingInterval) {\n        super(resourceType, samplingInterval);\n\n        this.leakCounter =\n                SpectatorUtils.newGauge(\"NettyLeakDetector\", resourceType.getSimpleName(), new AtomicInteger());\n    }\n\n    public InstrumentedResourceLeakDetector(Class<?> resourceType, int samplingInterval, long maxActive) {\n        this(resourceType, samplingInterval);\n    }\n\n    @Override\n    protected void reportTracedLeak(String resourceType, String records) {\n        super.reportTracedLeak(resourceType, records);\n        leakCounter.incrementAndGet();\n        resetReportedLeaks();\n    }\n\n    @Override\n    protected void reportUntracedLeak(String resourceType) {\n        super.reportUntracedLeak(resourceType);\n        leakCounter.incrementAndGet();\n        resetReportedLeaks();\n    }\n\n    /**\n     * This private field in the superclass needs to be reset so that we can continue reporting leaks even\n     * if they're duplicates. This is ugly but ideally should not be called frequently (or at all).\n     */\n    private void resetReportedLeaks() {\n        try {\n            Field reportedLeaks = ResourceLeakDetector.class.getDeclaredField(\"reportedLeaks\");\n            reportedLeaks.setAccessible(true);\n            Object f = reportedLeaks.get(this);\n            if (f instanceof Map) {\n                ((Map) f).clear();\n            }\n        } catch (Throwable t) {\n            // do nothing\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/metrics/PerEventLoopMetricsChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.util.AttributeKey;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/6/17\n * Time: 2:21 PM\n */\npublic class PerEventLoopMetricsChannelHandler {\n    private static final AttributeKey<Object> ATTR_REQ_INFLIGHT =\n            AttributeKey.newInstance(\"_eventloop_metrics_inflight\");\n    private static final Object INFLIGHT = \"eventloop_is_inflight\";\n\n    private final EventLoopGroupMetrics groupMetrics;\n\n    public PerEventLoopMetricsChannelHandler(EventLoopGroupMetrics groupMetrics) {\n        this.groupMetrics = groupMetrics;\n    }\n\n    @ChannelHandler.Sharable\n    public class Connections extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelActive(ChannelHandlerContext ctx) throws Exception {\n            groupMetrics.getForCurrentEventLoop().incrementCurrentConnections();\n            super.channelActive(ctx);\n        }\n\n        @Override\n        public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n            try {\n                super.channelInactive(ctx);\n            } finally {\n                groupMetrics.getForCurrentEventLoop().decrementCurrentConnections();\n            }\n        }\n    }\n\n    @ChannelHandler.Sharable\n    public class HttpRequests extends ChannelInboundHandlerAdapter {\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n            if (evt instanceof HttpLifecycleChannelHandler.StartEvent) {\n                incrementCurrentRequestsInFlight(ctx);\n            } else if (evt instanceof HttpLifecycleChannelHandler.CompleteEvent) {\n                decrementCurrentRequestsIfOneInflight(ctx);\n            }\n\n            super.userEventTriggered(ctx, evt);\n        }\n\n        private void incrementCurrentRequestsInFlight(ChannelHandlerContext ctx) {\n            groupMetrics.getForCurrentEventLoop().incrementCurrentRequests();\n            ctx.channel().attr(ATTR_REQ_INFLIGHT).set(INFLIGHT);\n        }\n\n        private void decrementCurrentRequestsIfOneInflight(ChannelHandlerContext ctx) {\n            if (ctx.channel().attr(ATTR_REQ_INFLIGHT).getAndSet(null) != null) {\n                groupMetrics.getForCurrentEventLoop().decrementCurrentRequests();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.proxyprotocol;\n\nimport com.google.common.base.Preconditions;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.spectator.api.Registry;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.ProtocolDetectionState;\nimport io.netty.handler.codec.haproxy.HAProxyMessageDecoder;\n\n/**\n * Decides if we need to decode a HAProxyMessage. If so, adds the decoder followed by the handler.\n * Else, removes itself from the pipeline.\n */\npublic final class ElbProxyProtocolChannelHandler extends ChannelInboundHandlerAdapter {\n\n    public static final String NAME = ElbProxyProtocolChannelHandler.class.getSimpleName();\n    private final boolean withProxyProtocol;\n    private final Registry registry;\n\n    public ElbProxyProtocolChannelHandler(Registry registry, boolean withProxyProtocol) {\n        this.withProxyProtocol = withProxyProtocol;\n        this.registry = Preconditions.checkNotNull(registry);\n    }\n\n    public void addProxyProtocol(ChannelPipeline pipeline) {\n        pipeline.addLast(NAME, this);\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (!withProxyProtocol) {\n            ctx.pipeline().remove(this);\n            super.channelRead(ctx, msg);\n            return;\n        }\n\n        ProtocolDetectionState haProxyState = getDetectionState(msg);\n        if (haProxyState == ProtocolDetectionState.DETECTED) {\n            ctx.pipeline()\n                    .addAfter(NAME, null, new HAProxyMessageChannelHandler())\n                    .replace(this, null, new HAProxyMessageDecoder());\n        } else {\n            int port = ctx.channel()\n                    .attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT)\n                    .get();\n\n            // This likely means initialization was requested with proxy protocol, but we encountered a non-ppv2\n            // message\n            registry.counter(\n                            \"zuul.hapm.decode\",\n                            \"success\",\n                            \"false\",\n                            \"port\",\n                            String.valueOf(port),\n                            \"needs_more_data\",\n                            String.valueOf(haProxyState == ProtocolDetectionState.NEEDS_MORE_DATA))\n                    .increment();\n            ctx.pipeline().remove(this);\n        }\n        super.channelRead(ctx, msg);\n    }\n\n    private ProtocolDetectionState getDetectionState(Object msg) {\n        return HAProxyMessageDecoder.detectProtocol((ByteBuf) msg).state();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.proxyprotocol;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.net.InetAddresses;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.haproxy.HAProxyMessage;\nimport io.netty.handler.codec.haproxy.HAProxyProtocolVersion;\nimport io.netty.handler.codec.haproxy.HAProxyTLV;\nimport io.netty.util.AttributeKey;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Copies any decoded HAProxyMessage into the channel attributes, and doesn't pass it any further along the pipeline.\n * Use in conjunction with HAProxyMessageDecoder if proxy protocol is enabled on the ELB.\n */\npublic final class HAProxyMessageChannelHandler extends ChannelInboundHandlerAdapter {\n\n    public static final AttributeKey<HAProxyMessage> ATTR_HAPROXY_MESSAGE =\n            AttributeKey.newInstance(\"_haproxy_message\");\n    public static final AttributeKey<HAProxyProtocolVersion> ATTR_HAPROXY_VERSION =\n            AttributeKey.newInstance(\"_haproxy_version\");\n    public static final AttributeKey<List<HAProxyTLV>> ATTR_HAPROXY_CUSTOM_TLVS =\n            AttributeKey.newInstance(\"_haproxy_tlvs\");\n\n    @VisibleForTesting\n    static final Attrs.Key<Integer> HAPM_DEST_PORT = Attrs.newKey(\"hapm_port\");\n\n    @VisibleForTesting\n    static final Attrs.Key<String> HAPM_DEST_IP_VERSION = Attrs.newKey(\"hapm_dst_ipproto\");\n\n    @VisibleForTesting\n    static final Attrs.Key<String> HAPM_SRC_IP_VERSION = Attrs.newKey(\"hapm_src_ipproto\");\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) {\n        if (msg instanceof HAProxyMessage hapm) {\n            Channel channel = ctx.channel();\n            channel.attr(ATTR_HAPROXY_MESSAGE).set(hapm);\n            ctx.channel().closeFuture().addListener((ChannelFutureListener) future -> hapm.release());\n            channel.attr(ATTR_HAPROXY_VERSION).set(hapm.protocolVersion());\n            // Parse and persist any custom TLVs that might be part of the connection\n            List<HAProxyTLV> tlvList = hapm.tlvs().stream()\n                    .filter(tlv -> tlv.type() == HAProxyTLV.Type.OTHER)\n                    .collect(Collectors.toList());\n            channel.attr(ATTR_HAPROXY_CUSTOM_TLVS).set(tlvList);\n            // Get the real host and port that the client connected with.\n            parseDstAddr(hapm, channel);\n            parseSrcAddr(hapm, channel);\n            // Remove ourselves (this handler) from the channel now, as this is conn. level info\n            ctx.pipeline().remove(this);\n        }\n    }\n\n    private void parseSrcAddr(HAProxyMessage hapm, Channel channel) {\n        String sourceAddress = hapm.sourceAddress();\n        if (sourceAddress != null) {\n            channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(sourceAddress);\n\n            SocketAddress srcAddr;\n            switch (hapm.proxiedProtocol()) {\n                case UNKNOWN:\n                    throw new IllegalArgumentException(\"unknown proxy protocol\" + sourceAddress);\n                case TCP4:\n                case TCP6:\n                    InetSocketAddress inetAddr;\n                    srcAddr =\n                            inetAddr = new InetSocketAddress(InetAddresses.forString(sourceAddress), hapm.sourcePort());\n                    Attrs attrs = channel.attr(Server.CONN_DIMENSIONS).get();\n                    if (inetAddr.getAddress() instanceof Inet4Address) {\n                        HAPM_SRC_IP_VERSION.put(attrs, \"v4\");\n                    } else if (inetAddr.getAddress() instanceof Inet6Address) {\n                        HAPM_SRC_IP_VERSION.put(attrs, \"v6\");\n                    } else {\n                        HAPM_SRC_IP_VERSION.put(attrs, \"unknown\");\n                    }\n                    break;\n                case UNIX_STREAM: // TODO: implement\n                case UDP4:\n                case UDP6:\n                case UNIX_DGRAM:\n                    throw new IllegalArgumentException(\"unknown proxy protocol\" + sourceAddress);\n                default:\n                    throw new AssertionError(hapm.proxiedProtocol());\n            }\n            channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).set(srcAddr);\n        }\n    }\n\n    private void parseDstAddr(HAProxyMessage hapm, Channel channel) {\n        String destinationAddress = hapm.destinationAddress();\n        if (destinationAddress != null) {\n            channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).set(destinationAddress);\n\n            SocketAddress dstAddr;\n            switch (hapm.proxiedProtocol()) {\n                case UNKNOWN:\n                    throw new IllegalArgumentException(\"unknown proxy protocol\" + destinationAddress);\n                case TCP4:\n                case TCP6:\n                    InetSocketAddress inetAddr =\n                            new InetSocketAddress(InetAddresses.forString(destinationAddress), hapm.destinationPort());\n                    dstAddr = inetAddr;\n                    // set ppv2 attr explicitly because ATTR_LOCAL_ADDR could be non ppv2\n                    channel.attr(SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS)\n                            .set(inetAddr);\n                    Attrs attrs = channel.attr(Server.CONN_DIMENSIONS).get();\n                    if (inetAddr.getAddress() instanceof Inet4Address) {\n                        HAPM_DEST_IP_VERSION.put(attrs, \"v4\");\n                    } else if (inetAddr.getAddress() instanceof Inet6Address) {\n                        HAPM_DEST_IP_VERSION.put(attrs, \"v6\");\n                    } else {\n                        HAPM_DEST_IP_VERSION.put(attrs, \"unknown\");\n                    }\n                    HAPM_DEST_PORT.put(attrs, hapm.destinationPort());\n                    break;\n                case UNIX_STREAM: // TODO: implement\n                case UDP4:\n                case UDP6:\n                case UNIX_DGRAM:\n                    throw new IllegalArgumentException(\"unknown proxy protocol\" + destinationAddress);\n                default:\n                    throw new AssertionError(hapm.proxiedProtocol());\n            }\n            channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).set(dstAddr);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandler.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.proxyprotocol;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.Sets;\nimport com.netflix.config.DynamicStringListProperty;\nimport com.netflix.netty.common.ssl.SslHandshakeInfo;\nimport com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.ssl.ClientAuth;\nimport io.netty.util.AsciiString;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * Strip out any X-Forwarded-* headers from inbound http requests if connection is not trusted.\n */\n@ChannelHandler.Sharable\npublic class StripUntrustedProxyHeadersHandler extends ChannelInboundHandlerAdapter {\n    private static final DynamicStringListProperty XFF_BLACKLIST =\n            new DynamicStringListProperty(\"zuul.proxy.headers.host.blacklist\", \"\");\n\n    public enum AllowWhen {\n        ALWAYS,\n        MUTUAL_SSL_AUTH,\n        NEVER\n    }\n\n    private static final Collection<AsciiString> HEADERS_TO_STRIP = Sets.newHashSet(\n            new AsciiString(\"x-forwarded-for\"),\n            new AsciiString(\"x-forwarded-port\"),\n            new AsciiString(\"x-forwarded-proto\"),\n            new AsciiString(\"x-forwarded-proto-version\"),\n            new AsciiString(\"x-real-ip\"));\n\n    private final AllowWhen allowWhen;\n\n    public StripUntrustedProxyHeadersHandler(AllowWhen allowWhen) {\n        this.allowWhen = allowWhen;\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof HttpRequest req) {\n            switch (allowWhen) {\n                case NEVER:\n                    stripXFFHeaders(req);\n                    break;\n                case MUTUAL_SSL_AUTH:\n                    if (!connectionIsUsingMutualSSLWithAuthEnforced(ctx.channel())) {\n                        stripXFFHeaders(req);\n                    } else {\n                        checkBlacklist(req, XFF_BLACKLIST.get());\n                    }\n                    break;\n                case ALWAYS:\n                    checkBlacklist(req, XFF_BLACKLIST.get());\n                    break;\n                default:\n                    // default to not allow.\n                    stripXFFHeaders(req);\n            }\n        }\n\n        super.channelRead(ctx, msg);\n    }\n\n    @VisibleForTesting\n    boolean connectionIsUsingMutualSSLWithAuthEnforced(Channel ch) {\n        boolean is = false;\n        SslHandshakeInfo sslHandshakeInfo =\n                ch.attr(SslHandshakeInfoHandler.ATTR_SSL_INFO).get();\n        if (sslHandshakeInfo != null) {\n            if (sslHandshakeInfo.getClientAuthRequirement() == ClientAuth.REQUIRE) {\n                is = true;\n            }\n        }\n        return is;\n    }\n\n    @VisibleForTesting\n    void stripXFFHeaders(HttpRequest req) {\n        HttpHeaders headers = req.headers();\n        for (AsciiString headerName : HEADERS_TO_STRIP) {\n            headers.remove(headerName);\n        }\n    }\n\n    @VisibleForTesting\n    void checkBlacklist(HttpRequest req, List<String> blacklist) {\n        // blacklist headers from\n        if (blacklist.stream().anyMatch(h -> h.equalsIgnoreCase(req.headers().get(HttpHeaderNames.HOST)))) {\n            stripXFFHeaders(req);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/ssl/ServerSslConfig.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.ssl;\n\nimport com.netflix.config.DynamicLongProperty;\nimport io.netty.handler.ssl.ClientAuth;\nimport java.io.File;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Arrays;\nimport java.util.List;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport lombok.AccessLevel;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Getter;\n\n/**\n * Server-side SSL/TLS configuration including protocols, ciphers, certificate\n * material, client authentication, and session settings.\n */\n@Getter\n@Builder\n@AllArgsConstructor(access = AccessLevel.PACKAGE)\npublic class ServerSslConfig {\n    private static final DynamicLongProperty DEFAULT_SESSION_TIMEOUT =\n            new DynamicLongProperty(\"server.ssl.session.timeout\", (18 * 60)); // 18 hours\n\n    private static final List<String> DEFAULT_CIPHERS;\n\n    static {\n        try {\n            SSLContext context = SSLContext.getDefault();\n            SSLSocketFactory sf = context.getSocketFactory();\n            DEFAULT_CIPHERS = List.of(sf.getSupportedCipherSuites());\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    private final String[] protocols;\n    private final List<String> ciphers;\n    private final File certChainFile;\n    private final File keyFile;\n\n    @Builder.Default\n    private final ClientAuth clientAuth = ClientAuth.NONE;\n\n    private final File clientAuthTrustStoreFile;\n    private final String clientAuthTrustStorePassword;\n    private final File clientAuthTrustStorePasswordFile;\n\n    @Builder.Default\n    private final long sessionTimeout = DEFAULT_SESSION_TIMEOUT.get();\n\n    @Builder.Default\n    private final boolean sessionTicketsEnabled = false;\n\n    /**\n     * @deprecated Use {@link ServerSslConfig#builder()} instead.\n     */\n    @Deprecated\n    public ServerSslConfig(String[] protocols, String[] ciphers, File certChainFile, File keyFile) {\n        this(protocols, ciphers, certChainFile, keyFile, ClientAuth.NONE, null, (File) null, false);\n    }\n\n    /**\n     * @deprecated Use {@link ServerSslConfig#builder()} instead.\n     */\n    @Deprecated\n    public ServerSslConfig(\n            String[] protocols, String[] ciphers, File certChainFile, File keyFile, ClientAuth clientAuth) {\n        this(protocols, ciphers, certChainFile, keyFile, clientAuth, null, (File) null, true);\n    }\n\n    /**\n     * @deprecated Use {@link ServerSslConfig#builder()} instead.\n     */\n    @Deprecated\n    public ServerSslConfig(\n            String[] protocols,\n            String[] ciphers,\n            File certChainFile,\n            File keyFile,\n            ClientAuth clientAuth,\n            File clientAuthTrustStoreFile,\n            File clientAuthTrustStorePasswordFile,\n            boolean sessionTicketsEnabled) {\n        this.protocols = protocols;\n        this.ciphers = ciphers != null ? Arrays.asList(ciphers) : null;\n        this.certChainFile = certChainFile;\n        this.keyFile = keyFile;\n        this.clientAuth = clientAuth;\n        this.clientAuthTrustStoreFile = clientAuthTrustStoreFile;\n        this.clientAuthTrustStorePassword = null;\n        this.clientAuthTrustStorePasswordFile = clientAuthTrustStorePasswordFile;\n        this.sessionTimeout = DEFAULT_SESSION_TIMEOUT.get();\n        this.sessionTicketsEnabled = sessionTicketsEnabled;\n    }\n\n    /**\n     * @deprecated Use {@link ServerSslConfig#builder()} instead.\n     */\n    @Deprecated\n    public ServerSslConfig(\n            String[] protocols,\n            String[] ciphers,\n            File certChainFile,\n            File keyFile,\n            ClientAuth clientAuth,\n            File clientAuthTrustStoreFile,\n            String clientAuthTrustStorePassword,\n            boolean sessionTicketsEnabled) {\n        this.protocols = protocols;\n        this.ciphers = Arrays.asList(ciphers);\n        this.certChainFile = certChainFile;\n        this.keyFile = keyFile;\n        this.clientAuth = clientAuth;\n        this.clientAuthTrustStoreFile = clientAuthTrustStoreFile;\n        this.clientAuthTrustStorePassword = clientAuthTrustStorePassword;\n        this.clientAuthTrustStorePasswordFile = null;\n        this.sessionTimeout = DEFAULT_SESSION_TIMEOUT.get();\n        this.sessionTicketsEnabled = sessionTicketsEnabled;\n    }\n\n    public static List<String> getDefaultCiphers() {\n        return DEFAULT_CIPHERS;\n    }\n\n    /**\n     * @deprecated Use {@link ServerSslConfig#builder()} instead.\n     */\n    @Deprecated\n    public static ServerSslConfig withDefaultCiphers(File certChainFile, File keyFile, String... protocols) {\n        return ServerSslConfig.builder()\n                .protocols(protocols)\n                .ciphers(getDefaultCiphers())\n                .certChainFile(certChainFile)\n                .keyFile(keyFile)\n                .build();\n    }\n\n    @Override\n    public String toString() {\n        return \"ServerSslConfig{\" + \"protocols=\"\n                + Arrays.toString(protocols) + \", ciphers=\"\n                + ciphers + \", certChainFile=\"\n                + certChainFile + \", keyFile=\"\n                + keyFile + \", clientAuth=\"\n                + clientAuth + \", clientAuthTrustStoreFile=\"\n                + clientAuthTrustStoreFile + \", sessionTimeout=\"\n                + sessionTimeout + \", sessionTicketsEnabled=\"\n                + sessionTicketsEnabled + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/ssl/SslHandshakeInfo.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.ssl;\n\nimport com.netflix.zuul.netty.server.psk.ClientPSKIdentityInfo;\nimport io.netty.handler.ssl.ClientAuth;\nimport java.security.cert.Certificate;\nimport java.security.cert.X509Certificate;\nimport lombok.AccessLevel;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\n\n/**\n * Captures TLS handshake details for a connection, including the negotiated protocol,\n * cipher suite, named group, and client authentication state.\n */\n@Builder\n@AllArgsConstructor(access = AccessLevel.PACKAGE)\npublic class SslHandshakeInfo {\n\n    private final String requestedSni;\n    private final String protocol;\n    private final String cipherSuite;\n    private final String namedGroup;\n    private final ClientAuth clientAuthRequirement;\n    private final Certificate serverCertificate;\n    private final X509Certificate clientCertificate;\n    private final boolean isOfIntermediary;\n    private final boolean usingExternalPSK;\n    private final ClientPSKIdentityInfo clientPSKIdentityInfo;\n\n    /**\n     * Use {@link SslHandshakeInfo#builder()} instead.\n     */\n    @Deprecated\n    public SslHandshakeInfo(\n            boolean isOfIntermediary,\n            String protocol,\n            String cipherSuite,\n            ClientAuth clientAuthRequirement,\n            Certificate serverCertificate,\n            X509Certificate clientCertificate) {\n        this(\"\", isOfIntermediary, protocol, cipherSuite, clientAuthRequirement, serverCertificate, clientCertificate);\n    }\n\n    /**\n     * Use {@link SslHandshakeInfo#builder()} instead.\n     */\n    @Deprecated\n    public SslHandshakeInfo(\n            String requestedSni,\n            boolean isOfIntermediary,\n            String protocol,\n            String cipherSuite,\n            ClientAuth clientAuthRequirement,\n            Certificate serverCertificate,\n            X509Certificate clientCertificate) {\n        this(\n                requestedSni,\n                isOfIntermediary,\n                protocol,\n                cipherSuite,\n                clientAuthRequirement,\n                serverCertificate,\n                clientCertificate,\n                false,\n                null);\n    }\n\n    /**\n     * Use {@link SslHandshakeInfo#builder()} instead.\n     */\n    @Deprecated\n    public SslHandshakeInfo(\n            boolean isOfIntermediary,\n            String protocol,\n            String cipherSuite,\n            ClientAuth clientAuthRequirement,\n            Certificate serverCertificate,\n            X509Certificate clientCertificate,\n            boolean usingExternalPSK,\n            ClientPSKIdentityInfo clientPSKIdentityInfo) {\n        this(\n                \"\",\n                isOfIntermediary,\n                protocol,\n                cipherSuite,\n                clientAuthRequirement,\n                serverCertificate,\n                clientCertificate,\n                usingExternalPSK,\n                clientPSKIdentityInfo);\n    }\n\n    /**\n     * Use {@link SslHandshakeInfo#builder()} instead.\n     */\n    @Deprecated\n    public SslHandshakeInfo(\n            String requestedSni,\n            boolean isOfIntermediary,\n            String protocol,\n            String cipherSuite,\n            ClientAuth clientAuthRequirement,\n            Certificate serverCertificate,\n            X509Certificate clientCertificate,\n            boolean usingExternalPSK,\n            ClientPSKIdentityInfo clientPSKIdentityInfo) {\n        this.requestedSni = requestedSni;\n        this.protocol = protocol;\n        this.cipherSuite = cipherSuite;\n        this.namedGroup = null;\n        this.clientAuthRequirement = clientAuthRequirement;\n        this.serverCertificate = serverCertificate;\n        this.clientCertificate = clientCertificate;\n        this.isOfIntermediary = isOfIntermediary;\n        this.usingExternalPSK = usingExternalPSK;\n        this.clientPSKIdentityInfo = clientPSKIdentityInfo;\n    }\n\n    public String getRequestedSni() {\n        return requestedSni;\n    }\n\n    public boolean isOfIntermediary() {\n        return isOfIntermediary;\n    }\n\n    public String getProtocol() {\n        return protocol;\n    }\n\n    public String getCipherSuite() {\n        return cipherSuite;\n    }\n\n    public String getNamedGroup() {\n        return namedGroup;\n    }\n\n    public ClientAuth getClientAuthRequirement() {\n        return clientAuthRequirement;\n    }\n\n    public Certificate getServerCertificate() {\n        return serverCertificate;\n    }\n\n    public X509Certificate getClientCertificate() {\n        return clientCertificate;\n    }\n\n    public boolean usingExternalPSK() {\n        return usingExternalPSK;\n    }\n\n    public ClientPSKIdentityInfo geClientPSKIdentityInfo() {\n        return clientPSKIdentityInfo;\n    }\n\n    @Override\n    public String toString() {\n        return \"SslHandshakeInfo{\" + \"protocol='\"\n                + protocol + '\\'' + \", cipherSuite='\"\n                + cipherSuite + '\\'' + \", namedGroup='\"\n                + namedGroup + '\\'' + \", clientAuthRequirement=\"\n                + clientAuthRequirement + \", serverCertificate=\"\n                + serverCertificate + \", clientCertificate=\"\n                + clientCertificate + \", isOfIntermediary=\"\n                + isOfIntermediary + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/status/ServerStatusManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.status;\n\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.appinfo.InstanceInfo;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/6/17\n * Time: 3:37 PM\n */\n@Singleton\npublic class ServerStatusManager {\n    private final ApplicationInfoManager applicationInfoManager;\n\n    @Inject\n    public ServerStatusManager(ApplicationInfoManager applicationInfoManager) {\n        this.applicationInfoManager = applicationInfoManager;\n    }\n\n    public void localStatus(InstanceInfo.InstanceStatus status) {\n        applicationInfoManager.setInstanceStatus(status);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.throttle;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.ReferenceCountUtil;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Closes any incoming new connections if current count is above a configured threshold.\n *\n * When a connection is throttled, the channel is closed, and then a CONNECTION_THROTTLED_EVENT event is fired\n * not notify any other interested handlers.\n *\n */\n@ChannelHandler.Sharable\npublic class MaxInboundConnectionsHandler extends ChannelInboundHandlerAdapter {\n    public static final AttributeKey<Boolean> ATTR_CH_THROTTLED = AttributeKey.newInstance(\"_channel_throttled\");\n\n    private static final Logger LOG = LoggerFactory.getLogger(MaxInboundConnectionsHandler.class);\n\n    private static final AtomicInteger connections = new AtomicInteger(0);\n    private final Counter connectionThrottled;\n    private final int maxConnections;\n\n    public MaxInboundConnectionsHandler(Registry registry, String metricId, int maxConnections) {\n        this.maxConnections = maxConnections;\n        this.connectionThrottled = registry.counter(\"server.connections.throttled\", \"id\", metricId);\n    }\n\n    @Override\n    public void channelActive(ChannelHandlerContext ctx) throws Exception {\n        if (maxConnections > 0) {\n            int currentCount = connections.getAndIncrement();\n\n            if (currentCount + 1 > maxConnections) {\n                LOG.warn(\n                        \"Throttling incoming connection as above configured max connections threshold of {}\",\n                        maxConnections);\n                Channel channel = ctx.channel();\n                channel.attr(ATTR_CH_THROTTLED).set(Boolean.TRUE);\n                CurrentPassport.fromChannel(channel).add(PassportState.SERVER_CH_THROTTLING);\n                channel.close();\n                connectionThrottled.increment();\n            }\n        }\n\n        super.channelActive(ctx);\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (ctx.channel().attr(ATTR_CH_THROTTLED).get() != null) {\n            // Discard this msg as channel is in process of being closed.\n            ReferenceCountUtil.safeRelease(msg);\n        } else {\n            super.channelRead(ctx, msg);\n        }\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        if (maxConnections > 0) {\n            connections.decrementAndGet();\n        }\n\n        super.channelInactive(ctx);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionType.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.throttle;\n\n/**\n * Indicates a rejection type for DoS protection.  While similar, rejection is distinct from throttling in that\n * throttling is intended for non-malicious traffic.\n */\npublic enum RejectionType {\n    // \"It's not you, it's me.\"\n\n    /**\n     * Indicates that the request should not be allowed.  An HTTP response will be generated as a result.\n     */\n    REJECT,\n\n    /**\n     * Indicates that the connection should be closed, not allowing the request to proceed.  No HTTP response will be\n     * returned.\n     */\n    CLOSE,\n\n    /**\n     * Allows the request to proceed, followed by closing the connection.  This is typically used in conjunction with\n     * throttling handling, where the response may need to be handled by the filter chain.  It is not expected that the\n     * request will be proxied.\n     */\n    ALLOW_THEN_CLOSE;\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/throttle/RejectionUtils.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.throttle;\n\nimport com.netflix.netty.common.ConnectionCloseChannelAttributes;\nimport com.netflix.netty.common.proxyprotocol.HAProxyMessageChannelHandler;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.haproxy.HAProxyProtocolVersion;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\nimport io.netty.handler.codec.http.EmptyHttpHeaders;\nimport io.netty.handler.codec.http.FullHttpResponse;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport javax.annotation.Nullable;\n\n/**\n * A collection of rejection related utilities useful for failing requests. These are tightly coupled with the channel\n * pipeline, but can be called from different handlers.\n */\npublic final class RejectionUtils {\n\n    // TODO(carl-mastrangelo): add tests for this.\n\n    public static final HttpResponseStatus REJECT_CLOSING_STATUS = new HttpResponseStatus(999, \"Closing(Rejection)\");\n\n    /**\n     * Closes the connection without sending a response, and fires a {@link RequestRejectedEvent} back up the pipeline.\n     *\n     * @param nfStatus              the status to use for metric reporting\n     * @param reason                the reason for rejecting the request.  This is not sent back to the client.\n     * @param request               the request that is being rejected.\n     * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still\n     *                              sent up the pipeline.\n     */\n    public static void rejectByClosingConnection(\n            ChannelHandlerContext ctx,\n            StatusCategory nfStatus,\n            String reason,\n            HttpRequest request,\n            @Nullable Integer injectedLatencyMillis) {\n\n        // Notify other handlers before closing the conn.\n        notifyHandlers(ctx, nfStatus, REJECT_CLOSING_STATUS, reason, request);\n\n        if (injectedLatencyMillis != null && injectedLatencyMillis > 0) {\n            // Delay closing the connection for configured time.\n            ctx.executor()\n                    .schedule(\n                            () -> {\n                                CurrentPassport.fromChannel(ctx.channel()).add(PassportState.SERVER_CH_REJECTING);\n                                ctx.close();\n                            },\n                            injectedLatencyMillis,\n                            TimeUnit.MILLISECONDS);\n        } else {\n            // Close the connection immediately.\n            CurrentPassport.fromChannel(ctx.channel()).add(PassportState.SERVER_CH_REJECTING);\n            ctx.close();\n        }\n    }\n\n    /**\n     * Sends a rejection response back to the client, and fires a {@link RequestRejectedEvent} back up the pipeline.\n     *\n     * @param ctx                   the channel handler processing the request\n     * @param nfStatus              the status to use for metric reporting\n     * @param reason                the reason for rejecting the request.  This is not sent back to the client.\n     * @param request               the request that is being rejected.\n     * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still\n     *                              sent up the pipeline.\n     * @param rejectedCode          the HTTP code to send back to the client.\n     * @param rejectedBody          the HTTP body to be sent back.  It is assumed to be of type text/plain.\n     * @param rejectionHeaders      additional HTTP headers to add to the rejection response\n     */\n    public static void sendRejectionResponse(\n            ChannelHandlerContext ctx,\n            StatusCategory nfStatus,\n            String reason,\n            HttpRequest request,\n            @Nullable Integer injectedLatencyMillis,\n            HttpResponseStatus rejectedCode,\n            String rejectedBody,\n            Map<String, String> rejectionHeaders) {\n        boolean shouldClose = closeConnectionAfterReject(ctx.channel());\n        // Write out a rejection response message.\n        FullHttpResponse response = createRejectionResponse(rejectedCode, rejectedBody, shouldClose, rejectionHeaders);\n\n        if (injectedLatencyMillis != null && injectedLatencyMillis > 0) {\n            // Delay writing the response for configured time.\n            ctx.executor()\n                    .schedule(\n                            () -> {\n                                CurrentPassport.fromChannel(ctx.channel()).add(PassportState.IN_REQ_REJECTED);\n                                ctx.writeAndFlush(response);\n                            },\n                            injectedLatencyMillis,\n                            TimeUnit.MILLISECONDS);\n        } else {\n            // Write the response immediately.\n            CurrentPassport.fromChannel(ctx.channel()).add(PassportState.IN_REQ_REJECTED);\n            ctx.writeAndFlush(response);\n        }\n\n        // Notify other handlers that we've rejected this request.\n        notifyHandlers(ctx, nfStatus, rejectedCode, reason, request);\n    }\n\n    /**\n     * Marks the given channel for being closed after the next response.\n     *\n     * @param ctx the channel handler processing the request\n     */\n    public static void allowThenClose(ChannelHandlerContext ctx) {\n        // Just flag this channel to be closed after response complete.\n        ctx.channel()\n                .attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE)\n                .set(ctx.newPromise());\n\n        // And allow this request through without rejecting.\n    }\n\n    /**\n     * Throttle either by sending rejection response message, or by closing the connection now, or just drop the\n     * message. Only call this if ThrottleResult.shouldThrottle() returned {@code true}.\n     *\n     * @param ctx                   the channel handler processing the request\n     * @param msg                   the request that is being rejected.\n     * @param rejectionType         the type of rejection\n     * @param nfStatus              the status to use for metric reporting\n     * @param reason                the reason for rejecting the request.  This is not sent back to the client.\n     * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still\n     *                              sent up the pipeline.\n     * @param rejectedCode          the HTTP code to send back to the client.\n     * @param rejectedBody          the HTTP body to be sent back.  It is assumed to be of type text/plain.\n     * @param rejectionHeaders      additional HTTP headers to add to the rejection response\n     */\n    public static void handleRejection(\n            ChannelHandlerContext ctx,\n            Object msg,\n            RejectionType rejectionType,\n            StatusCategory nfStatus,\n            String reason,\n            @Nullable Integer injectedLatencyMillis,\n            HttpResponseStatus rejectedCode,\n            String rejectedBody,\n            Map<String, String> rejectionHeaders) {\n\n        boolean shouldDropMessage = false;\n        if (rejectionType == RejectionType.REJECT || rejectionType == RejectionType.CLOSE) {\n            shouldDropMessage = true;\n        }\n\n        boolean shouldRejectNow = false;\n        if (rejectionType == RejectionType.REJECT && msg instanceof LastHttpContent) {\n            shouldRejectNow = true;\n        } else if (rejectionType == RejectionType.CLOSE && msg instanceof HttpRequest) {\n            shouldRejectNow = true;\n        } else if (rejectionType == RejectionType.ALLOW_THEN_CLOSE && msg instanceof HttpRequest) {\n            shouldRejectNow = true;\n        }\n\n        if (shouldRejectNow) {\n            // Send a rejection response.\n            HttpRequest request = msg instanceof HttpRequest ? (HttpRequest) msg : null;\n            reject(\n                    ctx,\n                    rejectionType,\n                    nfStatus,\n                    reason,\n                    request,\n                    injectedLatencyMillis,\n                    rejectedCode,\n                    rejectedBody,\n                    rejectionHeaders);\n        }\n\n        if (shouldDropMessage) {\n            ReferenceCountUtil.safeRelease(msg);\n        } else {\n            ctx.fireChannelRead(msg);\n        }\n    }\n\n    /**\n     * Switches on the rejection type to decide how to reject the request and or close the conn.\n     *\n     * @param ctx                   the channel handler processing the request\n     * @param rejectionType         the type of rejection\n     * @param nfStatus              the status to use for metric reporting\n     * @param reason                the reason for rejecting the request.  This is not sent back to the client.\n     * @param request               the request that is being rejected.\n     * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still\n     *                              sent up the pipeline.\n     * @param rejectedCode          the HTTP code to send back to the client.\n     * @param rejectedBody          the HTTP body to be sent back.  It is assumed to be of type text/plain.\n     */\n    public static void reject(\n            ChannelHandlerContext ctx,\n            RejectionType rejectionType,\n            StatusCategory nfStatus,\n            String reason,\n            HttpRequest request,\n            @Nullable Integer injectedLatencyMillis,\n            HttpResponseStatus rejectedCode,\n            String rejectedBody) {\n        reject(\n                ctx,\n                rejectionType,\n                nfStatus,\n                reason,\n                request,\n                injectedLatencyMillis,\n                rejectedCode,\n                rejectedBody,\n                Collections.emptyMap());\n    }\n\n    /**\n     * Switches on the rejection type to decide how to reject the request and or close the conn.\n     *\n     * @param ctx                   the channel handler processing the request\n     * @param rejectionType         the type of rejection\n     * @param nfStatus              the status to use for metric reporting\n     * @param reason                the reason for rejecting the request.  This is not sent back to the client.\n     * @param request               the request that is being rejected.\n     * @param injectedLatencyMillis optional parameter to delay sending a response. The reject notification is still\n     *                              sent up the pipeline.\n     * @param rejectedCode          the HTTP code to send back to the client.\n     * @param rejectedBody          the HTTP body to be sent back.  It is assumed to be of type text/plain.\n     * @param rejectionHeaders      additional HTTP headers to add to the rejection response\n     */\n    public static void reject(\n            ChannelHandlerContext ctx,\n            RejectionType rejectionType,\n            StatusCategory nfStatus,\n            String reason,\n            HttpRequest request,\n            @Nullable Integer injectedLatencyMillis,\n            HttpResponseStatus rejectedCode,\n            String rejectedBody,\n            Map<String, String> rejectionHeaders) {\n        switch (rejectionType) {\n            case REJECT:\n                sendRejectionResponse(\n                        ctx,\n                        nfStatus,\n                        reason,\n                        request,\n                        injectedLatencyMillis,\n                        rejectedCode,\n                        rejectedBody,\n                        rejectionHeaders);\n                return;\n            case CLOSE:\n                rejectByClosingConnection(ctx, nfStatus, reason, request, injectedLatencyMillis);\n                return;\n            case ALLOW_THEN_CLOSE:\n                allowThenClose(ctx);\n                return;\n        }\n        throw new AssertionError(\"Bad rejection type: \" + rejectionType);\n    }\n\n    private static void notifyHandlers(\n            ChannelHandlerContext ctx,\n            StatusCategory nfStatus,\n            HttpResponseStatus status,\n            String reason,\n            HttpRequest request) {\n        RequestRejectedEvent event = new RequestRejectedEvent(request, nfStatus, status, reason);\n        // Send this from the beginning of the pipeline, as it may be sent from the ClientRequestReceiver.\n        ctx.pipeline().fireUserEventTriggered(event);\n    }\n\n    private static boolean closeConnectionAfterReject(Channel channel) {\n        if (channel.hasAttr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)) {\n            return channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)\n                            .get()\n                    == HAProxyProtocolVersion.V2;\n        } else {\n            return false;\n        }\n    }\n\n    private static FullHttpResponse createRejectionResponse(\n            HttpResponseStatus status,\n            String plaintextMessage,\n            boolean closeConnection,\n            Map<String, String> rejectionHeaders) {\n        ByteBuf body = Unpooled.wrappedBuffer(plaintextMessage.getBytes(StandardCharsets.UTF_8));\n        int length = body.readableBytes();\n        DefaultHttpHeaders headers = new DefaultHttpHeaders();\n        headers.set(HttpHeaderNames.CONTENT_TYPE, \"text/plain; charset=utf-8\");\n        headers.set(HttpHeaderNames.CONTENT_LENGTH, length);\n        if (closeConnection) {\n            headers.set(HttpHeaderNames.CONNECTION, \"close\");\n        }\n        rejectionHeaders.forEach(headers::add);\n\n        return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, body, headers, EmptyHttpHeaders.INSTANCE);\n    }\n\n    private RejectionUtils() {}\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/netty/common/throttle/RequestRejectedEvent.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.throttle;\n\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport jakarta.annotation.Nullable;\n\npublic record RequestRejectedEvent(\n        HttpRequest request,\n        StatusCategory nfStatus,\n        HttpResponseStatus httpStatus,\n        String reason,\n        @Nullable String reasonMessage) {\n\n    public RequestRejectedEvent(\n            HttpRequest request, StatusCategory nfStatus, HttpResponseStatus httpStatus, String reason) {\n        this(request, nfStatus, httpStatus, reason, null);\n    }\n\n    // leaving behind old getters for backwards compatibility\n\n    public StatusCategory getNfStatus() {\n        return nfStatus;\n    }\n\n    public HttpResponseStatus getHttpStatus() {\n        return httpStatus;\n    }\n\n    public String getReason() {\n        return reason;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/Attrs.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.BiConsumer;\nimport javax.annotation.Nullable;\n\n/**\n * A heterogeneous map of attributes.\n *\n * <p>Implementation Note: this class is not a proper Map or Collection, in order to encourage callers\n * to refer to the keys by their literal name.   In the past, finding where a Key was used was difficult,\n * so this class is somewhat of an experiment to try to make tracking down usage easier.  If it becomes\n * too onerous to use this, consider making this class extend AbstractMap.\n */\npublic final class Attrs {\n\n    final IdentityHashMap<Key<?>, Object> storage = new IdentityHashMap<>();\n\n    public static <T> Key<T> newKey(String keyName) {\n        return new Key<>(keyName);\n    }\n\n    public static final class Key<T> {\n\n        private final String name;\n\n        /**\n         * Returns the value in the attributes, or {@code null} if absent.\n         */\n        @Nullable\n        @SuppressWarnings(\"unchecked\")\n        public T get(Attrs attrs) {\n            Objects.requireNonNull(attrs, \"attrs\");\n            return (T) attrs.storage.get(this);\n        }\n\n        /**\n         * Returns the value in the attributes or {@code defaultValue} if absent.\n         * @throws NullPointerException if defaultValue is null.\n         */\n        @SuppressWarnings(\"unchecked\")\n        public T getOrDefault(Attrs attrs, T defaultValue) {\n            Objects.requireNonNull(attrs, \"attrs\");\n            Objects.requireNonNull(defaultValue, \"defaultValue\");\n            T result = (T) attrs.storage.get(this);\n            if (result != null) {\n                return result;\n            }\n            return defaultValue;\n        }\n\n        public void put(Attrs attrs, T value) {\n            Objects.requireNonNull(attrs, \"attrs\");\n            Objects.requireNonNull(value);\n            attrs.storage.put(this, value);\n        }\n\n        public String name() {\n            return name;\n        }\n\n        private Key(String name) {\n            this.name = Objects.requireNonNull(name);\n        }\n\n        @Override\n        public String toString() {\n            return \"Key{\" + name + '}';\n        }\n    }\n\n    private Attrs() {}\n\n    public static Attrs newInstance() {\n        return new Attrs();\n    }\n\n    public Set<Key<?>> keySet() {\n        return Collections.unmodifiableSet(new LinkedHashSet<>(storage.keySet()));\n    }\n\n    public void forEach(BiConsumer<? super Key<?>, Object> consumer) {\n        storage.forEach(consumer);\n    }\n\n    public int size() {\n        return storage.size();\n    }\n\n    @Override\n    public String toString() {\n        return \"Attrs{\" + storage + '}';\n    }\n\n    @Override\n    @VisibleForTesting\n    public boolean equals(Object other) {\n        if (!(other instanceof Attrs)) {\n            return false;\n        }\n        Attrs that = (Attrs) other;\n        return Objects.equals(this.storage, that.storage);\n    }\n\n    @Override\n    @VisibleForTesting\n    public int hashCode() {\n        return Objects.hash(storage);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/BasicFilterUsageNotifier.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport jakarta.inject.Inject;\n\n/**\n * Publishes a counter metric for each filter on each use.\n */\npublic class BasicFilterUsageNotifier implements FilterUsageNotifier {\n\n    private final Registry registry;\n\n    @Inject\n    public BasicFilterUsageNotifier(Registry registry) {\n        this.registry = registry;\n    }\n\n    @Override\n    public void notify(ZuulFilter<?, ?> filter, ExecutionStatus status) {\n        registry.counter(\n                        \"zuul.filter-\" + filter.getClass().getSimpleName(),\n                        \"status\",\n                        status.name(),\n                        \"filtertype\",\n                        filter.filterType().toString())\n                .increment();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/BasicRequestCompleteHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.stats.RequestMetricsPublisher;\nimport jakarta.inject.Inject;\nimport javax.annotation.Nullable;\n\n/**\n * User: michaels@netflix.com\n * Date: 6/4/15\n * Time: 4:26 PM\n */\npublic class BasicRequestCompleteHandler implements RequestCompleteHandler {\n    @Inject\n    @Nullable\n    private RequestMetricsPublisher requestMetricsPublisher;\n\n    @Override\n    public void handle(HttpRequestInfo inboundRequest, HttpResponseMessage response) {\n        SessionContext context = inboundRequest.getContext();\n\n        // Publish request-level metrics.\n        if (requestMetricsPublisher != null) {\n            requestMetricsPublisher.collectAndPublish(context);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/DefaultFilterFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.filters.ZuulFilter;\nimport java.lang.reflect.InvocationTargetException;\n\n/**\n * Default factory for creating instances of ZuulFilter.\n */\npublic class DefaultFilterFactory implements FilterFactory {\n\n    /**\n     * Returns a new implementation of ZuulFilter as specified by the provided\n     * Class. The Class is instantiated using its nullary constructor.\n     *\n     * @param clazz the Class to instantiate\n     * @return A new instance of ZuulFilter\n     */\n    @Override\n    public ZuulFilter<?, ?> newInstance(Class<?> clazz)\n            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,\n                    NoSuchMethodException {\n        return (ZuulFilter<?, ?>) clazz.getDeclaredConstructor().newInstance();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/DynamicFilterLoader.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.filters.FilterRegistry;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n@Singleton\npublic final class DynamicFilterLoader implements FilterLoader {\n    private static final Logger LOG = LoggerFactory.getLogger(DynamicFilterLoader.class);\n\n    private final ConcurrentMap<String, Long> filterClassLastModified = new ConcurrentHashMap<>();\n    private final ConcurrentMap<FilterType, SortedSet<ZuulFilter<?, ?>>> hashFiltersByType = new ConcurrentHashMap<>();\n    private final ConcurrentMap<String, ZuulFilter<?, ?>> filtersByNameAndType = new ConcurrentHashMap<>();\n\n    private final FilterRegistry filterRegistry;\n\n    private final FilterFactory filterFactory;\n\n    @Inject\n    public DynamicFilterLoader(FilterRegistry filterRegistry, FilterFactory filterFactory) {\n        this.filterRegistry = filterRegistry;\n        this.filterFactory = filterFactory;\n    }\n\n    /**\n     * @return the total number of Zuul filters\n     */\n    public int filterInstanceMapSize() {\n        return filterRegistry.size();\n    }\n\n    private void putFilter(String filterName, ZuulFilter<?, ?> filter, long lastModified) {\n        if (!filterRegistry.isMutable()) {\n            LOG.warn(\"Filter registry is not mutable, discarding {}\", filterName);\n            return;\n        }\n        SortedSet<ZuulFilter<?, ?>> set = hashFiltersByType.get(filter.filterType());\n        if (set != null) {\n            hashFiltersByType.remove(filter.filterType()); // rebuild this list\n        }\n\n        String nameAndType = filter.filterType() + \":\" + filter.filterName();\n        filtersByNameAndType.put(nameAndType, filter);\n\n        filterRegistry.put(filterName, filter);\n        filterClassLastModified.put(filterName, lastModified);\n    }\n\n    /**\n     * Load and cache filters by className\n     *\n     * @param classNames The class names to load\n     * @return List of the loaded filters\n     * @throws Exception If any specified filter fails to load, this will abort. This is a safety mechanism so we can\n     * prevent running in a partially loaded state.\n     */\n    @Override\n    public List<ZuulFilter<?, ?>> putFiltersForClasses(String[] classNames) throws Exception {\n        List<ZuulFilter<?, ?>> newFilters = new ArrayList<>();\n        for (String className : classNames) {\n            newFilters.add(putFilterForClassName(className));\n        }\n        return Collections.unmodifiableList(newFilters);\n    }\n\n    @Override\n    public ZuulFilter<?, ?> putFilterForClassName(String className) throws Exception {\n        Class<?> clazz = Class.forName(className);\n        if (!ZuulFilter.class.isAssignableFrom(clazz)) {\n            throw new IllegalArgumentException(\"Specified filter class does not implement ZuulFilter interface!\");\n        } else {\n            ZuulFilter<?, ?> filter = filterFactory.newInstance(clazz);\n            putFilter(className, filter, System.currentTimeMillis());\n            return filter;\n        }\n    }\n\n    /**\n     * Returns a list of filters by the filterType specified\n     */\n    @Override\n    public SortedSet<ZuulFilter<?, ?>> getFiltersByType(FilterType filterType) {\n        SortedSet<ZuulFilter<?, ?>> set = hashFiltersByType.get(filterType);\n        if (set != null) {\n            return set;\n        }\n\n        set = new TreeSet<>(FILTER_COMPARATOR);\n\n        for (ZuulFilter<?, ?> filter : filterRegistry.getAllFilters()) {\n            if (filter.filterType().equals(filterType)) {\n                set.add(filter);\n            }\n        }\n\n        hashFiltersByType.putIfAbsent(filterType, set);\n        return Collections.unmodifiableSortedSet(set);\n    }\n\n    @Override\n    public ZuulFilter<?, ?> getFilterByNameAndType(String name, FilterType type) {\n        if (name == null || type == null) {\n            return null;\n        }\n\n        String nameAndType = type + \":\" + name;\n        return filtersByNameAndType.get(nameAndType);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/ExecutionStatus.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\npublic enum ExecutionStatus {\n    SUCCESS,\n    SKIPPED,\n    DISABLED,\n    FAILED,\n    BODY_AWAIT,\n    ASYNC_AWAIT,\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/Filter.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.filters.FilterSyncType;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Identifies a {@link ZuulFilter}.\n */\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface Filter {\n\n    /**\n     * The order in which to run.   See {@link ZuulFilter#filterOrder()}.\n     */\n    int order();\n\n    /**\n     * Indicates the type of this filter.\n     */\n    FilterType type() default FilterType.INBOUND;\n\n    /**\n     * Category of the filter.\n     */\n    FilterCategory category() default FilterCategory.HTTP;\n\n    /**\n     * Indicates if this is a synchronous filter.\n     */\n    FilterSyncType sync() default FilterSyncType.SYNC;\n\n    /**\n     * Indicates if this filter has any constraints that should prevent it from executing\n     */\n    Class<? extends FilterConstraint>[] constraints() default {};\n\n    @Target({ElementType.PACKAGE})\n    @Retention(RetentionPolicy.CLASS)\n    @Documented\n    @interface FilterPackageName {\n        String value();\n    }\n\n    /**\n     * Indicates that the annotated filter should run after another filter in the chain, if the other filter is present.\n     * In the case of inbound filters, this implies that the annotated filter should have an order greater than the\n     * filters listed.  For outbound filters, the order of this filter should be less than the ones listed.  Usage of\n     * this annotation should be used on homogeneous filter types.  Additionally, this should not be applied to endpoint\n     * filters.\n     */\n    @Target({ElementType.TYPE})\n    @Retention(RetentionPolicy.RUNTIME)\n    @Documented\n    @interface ApplyAfter {\n        Class<? extends ZuulFilter<?, ?>>[] value();\n    }\n\n    /**\n     * Indicates that the annotated filter should run before another filter in the chain, if the other filter is present.\n     * In the case of inbound filters, this implies that the annotated filter should have an order less than the\n     * filters listed.  For outbound filters, the order of this filter should be greater than the ones listed.  Usage of\n     * this annotation should be used on homogeneous filter types.  Additionally, this should not be applied to endpoint\n     * filters.\n     *\n     * <p>Prefer to use this {@link ApplyAfter} instead.  This annotation is meant in case where it may be infeasible\n     * to use {@linkplain ApplyAfter}.  (such as due to dependency cycles)\n     *\n     * @see ApplyAfter\n     */\n    @Target({ElementType.TYPE})\n    @Retention(RetentionPolicy.RUNTIME)\n    @Documented\n    @interface ApplyBefore {\n        Class<? extends ZuulFilter<?, ?>>[] value();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/FilterCategory.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\n/**\n * Categorization of filters.\n */\npublic enum FilterCategory {\n    ABUSE(\n            \"abuse\",\n            \"Abuse detection and protection filters, such as rate-limiting, malicious request detection, geo-blocking\"),\n    ACCESS(\"access\", \"Authentication and authorization filters\"),\n    ADMIN(\"admin\", \"Admin only filters providing operational support\"),\n    CHAOS(\"chaos\", \"Failure injection testing and resilience support\"),\n    CONTEXT_DECORATOR(\"context-decorator\", \"Decorate context based on request and detected client\"),\n    HEALTHCHECK(\"healthcheck\", \"Support for healthcheck endpoints\"),\n    HTTP(\"http\", \"Filter operating on HTTP request/response protocol features\"),\n    ORIGIN(\"origin\", \"Origin connectivity filters\"),\n    OBSERVABILITY(\"observability\", \"Filters providing observability features\"),\n    OVERLOAD(\"overload\", \"Filters to respond on the server being in an overloaded state such as brownout\"),\n    ROUTING(\"routing\", \"Filters which make routing decisions\"),\n    UNSPECIFIED(\"unspecified\", \"Default category when no category is specified\"),\n    ;\n\n    private final String code;\n    private final String description;\n\n    FilterCategory(String code, String description) {\n        this.code = code;\n        this.description = description;\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n    public String getDescription() {\n        return description;\n    }\n\n    @Override\n    public String toString() {\n        return code;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/FilterConstraint.java",
    "content": "/*\n * Copyright 2026 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.message.ZuulMessage;\nimport lombok.NonNull;\n\n/**\n * A filter constraint can be registered on {@link Filter#constraints()} to indicate that a given filter should\n * not be run against a ZuulMessage. FilterConstraint's act as a centralized way to implement logic that would otherwise\n * need to be duplicated across multiple {@link com.netflix.zuul.filters.ShouldFilter#shouldFilter(ZuulMessage)}\n *\n * @author Justin Guerra\n * @since 1/9/26\n */\npublic interface FilterConstraint {\n    boolean isConstrained(@NonNull ZuulMessage msg);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/FilterFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.filters.ZuulFilter;\n\n/**\n * Interface to provide instances of ZuulFilter from a given class.\n */\npublic interface FilterFactory {\n\n    /**\n     * Returns an instance of the specified class.\n     *\n     * @param clazz the Class to instantiate\n     * @return an instance of ZuulFilter\n     * @throws Exception if an error occurs\n     */\n    ZuulFilter<?, ?> newInstance(Class<?> clazz) throws Exception;\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/FilterFileManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class manages the loading of filters from a set of known packages.\n *\n * @author Mikey Cohen\n *         Date: 12/7/11\n *         Time: 12:09 PM\n */\n@Singleton\npublic class FilterFileManager {\n\n    private static final Logger LOG = LoggerFactory.getLogger(FilterFileManager.class);\n\n    private final FilterFileManagerConfig config;\n    private final FilterLoader filterLoader;\n\n    @Inject\n    public FilterFileManager(FilterFileManagerConfig config, FilterLoader filterLoader) {\n        this.config = config;\n        this.filterLoader = filterLoader;\n    }\n\n    @Inject\n    public void init() throws Exception {\n        if (!config.enabled) {\n            return;\n        }\n\n        long startTime = System.currentTimeMillis();\n        filterLoader.putFiltersForClasses(config.getClassNames());\n        LOG.warn(\"Finished loading all zuul filters. Duration = {} ms.\", (System.currentTimeMillis() - startTime));\n    }\n\n    public static class FilterFileManagerConfig {\n        private final String[] classNames;\n        boolean enabled;\n\n        public FilterFileManagerConfig(String[] classNames) {\n            this(classNames, true);\n        }\n\n        public FilterFileManagerConfig(String[] classNames, boolean enabled) {\n            this.classNames = classNames;\n            this.enabled = enabled;\n        }\n\n        public String[] getClassNames() {\n            return classNames;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/FilterLoader.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.SortedSet;\n\n/**\n * This class is one of the core classes in Zuul. It compiles, loads from a File, and checks if source code changed.\n * It also holds ZuulFilters by filterType.\n */\npublic interface FilterLoader {\n\n    /**\n     * Load and cache filters by className.\n     *\n     * @param classNames The class names to load\n     * @return List of the loaded filters\n     * @throws Exception If any specified filter fails to load, this will abort. This is a safety mechanism so we can\n     * prevent running in a partially loaded state.\n     */\n    List<ZuulFilter<?, ?>> putFiltersForClasses(String[] classNames) throws Exception;\n\n    ZuulFilter<?, ?> putFilterForClassName(String className) throws Exception;\n\n    /**\n     * Returns a sorted set of filters by the filterType specified.\n     */\n    SortedSet<ZuulFilter<?, ?>> getFiltersByType(FilterType filterType);\n\n    ZuulFilter<?, ?> getFilterByNameAndType(String name, FilterType type);\n\n    Comparator<ZuulFilter<?, ?>> FILTER_COMPARATOR =\n            Comparator.<ZuulFilter<?, ?>>comparingInt(ZuulFilter::filterOrder).thenComparing(ZuulFilter::filterName);\n\n    Comparator<Class<? extends ZuulFilter<?, ?>>> FILTER_CLASS_COMPARATOR =\n            Comparator.<Class<? extends ZuulFilter<?, ?>>>comparingInt(\n                            c -> Objects.requireNonNull(c.getAnnotation(Filter.class), () -> \"missing annotation: \" + c)\n                                    .order())\n                    .thenComparing(Class::getName);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/FilterUsageNotifier.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.filters.ZuulFilter;\n\n/**\n * Interface to implement for registering a callback for each time a filter\n * is used.\n *\n * User: michaels\n * Date: 5/13/14\n * Time: 9:55 PM\n */\npublic interface FilterUsageNotifier {\n    void notify(ZuulFilter<?, ?> filter, ExecutionStatus status);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/RequestCompleteHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\n\npublic interface RequestCompleteHandler {\n    void handle(HttpRequestInfo inboundRequest, HttpResponseMessage response);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/StaticFilterLoader.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport com.google.errorprone.annotations.DoNotCall;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport jakarta.inject.Inject;\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collections;\nimport java.util.EnumMap;\nimport java.util.HashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport java.util.SortedSet;\nimport java.util.TreeSet;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * An immutable static collection of filters.\n */\npublic final class StaticFilterLoader implements FilterLoader {\n\n    private static final Logger logger = LoggerFactory.getLogger(StaticFilterLoader.class);\n\n    public static final String RESOURCE_NAME = \"META-INF/zuul/allfilters\";\n\n    private final Map<FilterType, ? extends SortedSet<ZuulFilter<?, ?>>> filtersByType;\n    private final Map<FilterType, ? extends Map<String, ZuulFilter<?, ?>>> filtersByTypeAndName;\n\n    @Inject\n    public StaticFilterLoader(\n            FilterFactory filterFactory, Set<? extends Class<? extends ZuulFilter<?, ?>>> filterTypes) {\n        Map<FilterType, SortedSet<ZuulFilter<?, ?>>> filtersByType = new EnumMap<>(FilterType.class);\n        Map<FilterType, Map<String, ZuulFilter<?, ?>>> filtersByName = new EnumMap<>(FilterType.class);\n        for (Class<? extends ZuulFilter<?, ?>> clz : filterTypes) {\n            try {\n                ZuulFilter<?, ?> f = filterFactory.newInstance(clz);\n                filtersByType\n                        .computeIfAbsent(f.filterType(), k -> new TreeSet<>(FILTER_COMPARATOR))\n                        .add(f);\n                filtersByName\n                        .computeIfAbsent(f.filterType(), k -> new HashMap<>())\n                        .put(f.filterName(), f);\n            } catch (RuntimeException | Error e) {\n                throw e;\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        }\n        for (Entry<FilterType, SortedSet<ZuulFilter<?, ?>>> entry : filtersByType.entrySet()) {\n            entry.setValue(Collections.unmodifiableSortedSet(entry.getValue()));\n        }\n        Map<FilterType, Map<String, ZuulFilter<?, ?>>> immutableFiltersByName = new EnumMap<>(FilterType.class);\n        for (Entry<FilterType, Map<String, ZuulFilter<?, ?>>> entry : filtersByName.entrySet()) {\n            immutableFiltersByName.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue()));\n        }\n        this.filtersByTypeAndName = Collections.unmodifiableMap(immutableFiltersByName);\n        this.filtersByType = Collections.unmodifiableMap(filtersByType);\n    }\n\n    public static Set<Class<ZuulFilter<?, ?>>> loadFilterTypesFromResources(ClassLoader loader) throws IOException {\n        Set<Class<ZuulFilter<?, ?>>> filterTypes = new LinkedHashSet<>();\n        for (URL url : Collections.list(loader.getResources(RESOURCE_NAME))) {\n            try (InputStream is = url.openStream();\n                    InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);\n                    BufferedReader br = new BufferedReader(isr)) {\n                String line;\n                while ((line = br.readLine()) != null) {\n                    String trimmed = line.trim();\n                    if (!trimmed.isEmpty()) {\n                        Class<?> clz;\n                        try {\n                            clz = Class.forName(trimmed, false, loader);\n                        } catch (ClassNotFoundException e) {\n                            // This can happen if a filter is deleted, but the annotation processor doesn't\n                            // remove it from the list.   This is mainly a problem with IntelliJ, which\n                            // forces append only annotation processors.  Incremental recompilation drops\n                            // most of the classpath, making the processor unable to reconstruct the filter\n                            // list.  To work around this problem, use the stale, cached filter list from\n                            // the initial full compilation and add to it.  This makes incremental\n                            // compilation work later, at the cost of polluting the filter list.  It's a\n                            // better experience to log a warning (and do a clean build), than to\n                            // mysteriously classes.\n\n                            logger.warn(\"Missing Filter\", e);\n                            continue;\n                        }\n                        @SuppressWarnings(\"unchecked\")\n                        Class<ZuulFilter<?, ?>> filterClz = (Class<ZuulFilter<?, ?>>) clz.asSubclass(ZuulFilter.class);\n                        filterTypes.add(filterClz);\n                    }\n                }\n            }\n        }\n        return Collections.unmodifiableSet(filterTypes);\n    }\n\n    @Override\n    @DoNotCall\n    public List<ZuulFilter<?, ?>> putFiltersForClasses(String[] classNames) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    @DoNotCall\n    public ZuulFilter<?, ?> putFilterForClassName(String className) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public SortedSet<ZuulFilter<?, ?>> getFiltersByType(FilterType filterType) {\n        return filtersByType.get(filterType);\n    }\n\n    @Override\n    @Nullable\n    public ZuulFilter<?, ?> getFilterByNameAndType(String name, FilterType type) {\n        Map<String, ZuulFilter<?, ?>> filtersByName = filtersByTypeAndName.get(type);\n        if (filtersByName == null) {\n            return null;\n        }\n        return filtersByName.get(name);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/ZuulApplicationInfo.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\n/**\n * Metadata about the Zuul instance/ application name and \"stack\"\n * @author Mikey Cohen\n * Date: 2/15/13\n * Time: 1:56 PM\n */\npublic class ZuulApplicationInfo {\n    public static String applicationName;\n    public static String stack;\n\n    public static String getApplicationName() {\n        return applicationName;\n    }\n\n    public static void setApplicationName(String applicationName) {\n        ZuulApplicationInfo.applicationName = applicationName;\n    }\n\n    public static String getStack() {\n        return stack;\n    }\n\n    public static void setStack(String stack) {\n        ZuulApplicationInfo.stack = stack;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/constants/ZuulConstants.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.constants;\n\n/**\n * property constants\n * Date: 5/15/13\n * Time: 2:22 PM\n */\npublic class ZuulConstants {\n    public static final String ZUUL_NIWS_CLIENTLIST = \"zuul.niws.clientlist\";\n    public static final String DEFAULT_NFASTYANAX_READCONSISTENCY = \"default.nfastyanax.readConsistency\";\n    public static final String DEFAULT_NFASTYANAX_WRITECONSISTENCY = \"default.nfastyanax.writeConsistency\";\n    public static final String DEFAULT_NFASTYANAX_SOCKETTIMEOUT = \"default.nfastyanax.socketTimeout\";\n    public static final String DEFAULT_NFASTYANAX_MAXCONNSPERHOST = \"default.nfastyanax.maxConnsPerHost\";\n    public static final String DEFAULT_NFASTYANAX_MAXTIMEOUTWHENEXHAUSTED =\n            \"default.nfastyanax.maxTimeoutWhenExhausted\";\n    public static final String DEFAULT_NFASTYANAX_MAXFAILOVERCOUNT = \"default.nfastyanax.maxFailoverCount\";\n    public static final String DEFAULT_NFASTYANAX_FAILOVERWAITTIME = \"default.nfastyanax.failoverWaitTime\";\n    public static final String ZUUL_CASSANDRA_KEYSPACE = \"zuul.cassandra.keyspace\";\n    public static final String ZUUL_CASSANDRA_MAXCONNECTIONSPERHOST = \"zuul.cassandra.maxConnectionsPerHost\";\n    public static final String ZUUL_CASSANDRA_HOST = \"zuul.cassandra.host\";\n    public static final String ZUUL_CASSANDRA_PORT = \"zuul.cassandra.port\";\n    public static final String ZUUL_EUREKA = \"zuul.eureka.\";\n    public static final String ZUUL_AUTODETECT_BACKEND_VIPS = \"zuul.autodetect-backend-vips\";\n    public static final String ZUUL_RIBBON_NAMESPACE = \"zuul.ribbon.namespace\";\n    public static final String ZUUL_RIBBON_VIPADDRESS_TEMPLATE = \"zuul.ribbon.vipAddress.template\";\n    public static final String ZUUL_CASSANDRA_CACHE_MAX_SIZE = \"zuul.cassandra.cache.max-size\";\n    public static final String ZUUL_HTTPCLIENT = \"zuul.httpClient.\";\n    public static final String ZUUL_USE_ACTIVE_FILTERS = \"zuul.use.active.filters\";\n    public static final String ZUUL_USE_CANARY_FILTERS = \"zuul.use.canary.filters\";\n    public static final String ZUUL_FILTER_PRE_PATH = \"zuul.filter.pre.path\";\n    public static final String ZUUL_FILTER_POST_PATH = \"zuul.filter.post.path\";\n    public static final String ZUUL_FILTER_ROUTING_PATH = \"zuul.filter.routing.path\";\n    public static final String ZUUL_FILTER_CUSTOM_PATH = \"zuul.filter.custom.path\";\n    public static final String ZUUL_FILTER_ADMIN_ENABLED = \"zuul.filter.admin.enabled\";\n    public static final String ZUUL_FILTER_ADMIN_REDIRECT = \"zuul.filter.admin.redirect.path\";\n\n    public static final String ZUUL_DEBUG_REQUEST = \"zuul.debug.request\";\n    public static final String ZUUL_DEBUG_PARAMETER = \"zuul.debug.parameter\";\n    public static final String ZUUL_ROUTER_ALT_ROUTE_VIP = \"zuul.router.alt.route.vip\";\n    public static final String ZUUL_ROUTER_ALT_ROUTE_HOST = \"zuul.router.alt.route.host\";\n    public static final String ZUUL_ROUTER_ALT_ROUTE_PERMYRIAD = \"zuul.router.alt.route.permyriad\";\n    public static final String ZUUL_ROUTER_ALT_ROUTE_MAXLIMIT = \"zuul.router.alt.route.maxlimit\";\n    public static final String ZUUL_NIWS_DEFAULTCLIENT = \"zuul.niws.defaultClient\";\n    public static final String ZUUL_DEFAULT_HOST = \"zuul.default.host\";\n    public static final String ZUUL_HOST_SOCKET_TIMEOUT_MILLIS = \"zuul.host.socket-timeout-millis\";\n    public static final String ZUUL_HOST_CONNECT_TIMEOUT_MILLIS = \"zuul.host.connect-timeout-millis\";\n    public static final String ZUUL_INCLUDE_DEBUG_HEADER = \"zuul.include-debug-header\";\n    public static final String ZUUL_INITIAL_STREAM_BUFFER_SIZE = \"zuul.initial-stream-buffer-size\";\n    public static final String ZUUL_SET_CONTENT_LENGTH = \"zuul.set-content-length\";\n    public static final String ZUUL_DEBUGFILTERS_DISABLED = \"zuul.debugFilters.disabled\";\n    public static final String ZUUL_DEBUG_VIP = \"zuul.debug.vip\";\n    public static final String ZUUL_DEBUG_HOST = \"zuul.debug.host\";\n    public static final String ZUUL_REQUEST_BODY_MAX_SIZE = \"zuul.body.request.max.size\";\n    public static final String ZUUL_RESPONSE_BODY_MAX_SIZE = \"zuul.body.response.max.size\";\n\n    // Prevent instantiation\n    private ZuulConstants() {\n        throw new AssertionError(\"Must not instantiate constant utility class\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/constants/ZuulHeaders.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.constants;\n\n/**\n * HTTP Headers that are accessed or added by Zuul\n * User: mcohen\n * Date: 5/15/13\n * Time: 4:38 PM\n */\npublic class ZuulHeaders {\n\n    /* Standard headers */\n\n    public static final String TRANSFER_ENCODING = \"transfer-encoding\";\n    public static final String CHUNKED = \"chunked\";\n    public static final String ORIGIN = \"Origin\";\n    public static final String CONTENT_ENCODING = \"Content-Encoding\";\n    public static final String ACCEPT_ENCODING = \"accept-encoding\";\n    public static final String CONNECTION = \"Connection\";\n    public static final String KEEP_ALIVE = \"keep-alive\";\n    public static final String X_FORWARDED_PROTO = \"X-Forwarded-Proto\";\n    public static final String X_FORWARDED_FOR = \"X-Forwarded-For\";\n    public static final String HOST = \"Host\";\n    public static final String X_ORIGINATING_URL = \"X-Originating-URL\";\n\n    /* X-Zuul headers */\n\n    public static final String X_ZUUL = \"X-Zuul\";\n    public static final String X_ZUUL_STATUS = X_ZUUL + \"-Status\";\n    public static final String X_ZUUL_PROXY_ATTEMPTS = X_ZUUL + \"-Proxy-Attempts\";\n    public static final String X_ZUUL_INSTANCE = X_ZUUL + \"-Instance\";\n    public static final String X_ZUUL_ERROR_CAUSE = X_ZUUL + \"-Error-Cause\";\n    public static final String X_ZUUL_SURGICAL_FILTER = X_ZUUL + \"-Surgical-Filter\";\n    public static final String X_ZUUL_FILTER_EXECUTION_STATUS = X_ZUUL + \"-Filter-Executions\";\n\n    // Prevent instantiation\n    private ZuulHeaders() {\n        throw new AssertionError(\"Must not instantiate constant utility class\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/CommonContextKeys.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.context;\n\nimport com.google.common.collect.ImmutableList;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport io.netty.channel.Channel;\nimport jakarta.inject.Provider;\nimport java.net.InetAddress;\nimport java.util.Map;\n\n/**\n * Common Context Keys\n *\n * Author: Arthur Gonigberg\n * Date: November 21, 2017\n */\npublic class CommonContextKeys {\n\n    public static final SessionContext.Key<StatusCategory> STATUS_CATEGORY = SessionContext.newKey(\"status_category\");\n    public static final SessionContext.Key<String> STATUS_CATEGORY_REASON =\n            SessionContext.newKey(\"status_category_reason\");\n    public static final SessionContext.Key<StatusCategory> ORIGIN_STATUS_CATEGORY =\n            SessionContext.newKey(\"origin_status_category\");\n    public static final SessionContext.Key<String> ORIGIN_STATUS_CATEGORY_REASON =\n            SessionContext.newKey(\"origin_status_category_reason\");\n    public static final SessionContext.Key<Integer> ORIGIN_STATUS = SessionContext.newKey(\"origin_status\");\n    public static final SessionContext.Key<RequestAttempts> REQUEST_ATTEMPTS =\n            SessionContext.newKey(\"request_attempts\");\n\n    public static final SessionContext.Key<String> BROWNOUT_REASON = SessionContext.newKey(\"brownout_reason\");\n\n    public static final SessionContext.Key<IClientConfig> REST_CLIENT_CONFIG =\n            SessionContext.newKey(\"rest_client_config\");\n\n    public static final SessionContext.Key<ZuulFilter<HttpRequestMessage, HttpResponseMessage>> ZUUL_ENDPOINT =\n            SessionContext.newKey(\"_zuul_endpoint\");\n    public static final SessionContext.Key<Map<Integer, InetAddress>> ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY =\n            SessionContext.newKey(\"_zuul_origin_chosen_host_addr_map\");\n    public static final SessionContext.Key<Channel> ORIGIN_CHANNEL = SessionContext.newKey(\"_origin_channel\");\n    public static final String ORIGIN_MANAGER = \"origin_manager\";\n    public static final SessionContext.Key<ImmutableList.Builder<String>> ROUTING_LOG =\n            SessionContext.newKey(\"routing_log\");\n    public static final String USE_FULL_VIP_NAME = \"use_full_vip_name\";\n    public static final String ACTUAL_VIP = \"origin_vip_actual\";\n    public static final String ORIGIN_VIP_SECURE = \"origin_vip_secure\";\n\n    /**\n     * The original client destination address Zuul by a proxy running Proxy Protocol.\n     * Will only be set if both Zuul and the connected proxy are both using set to use Proxy Protocol.\n     */\n    public static final String PROXY_PROTOCOL_DESTINATION_ADDRESS = \"proxy_protocol_destination_address\";\n\n    public static final String SSL_HANDSHAKE_INFO = \"ssl_handshake_info\";\n\n    public static final String GZIPPER = \"gzipper\";\n    public static final String OVERRIDE_GZIP_REQUESTED = \"overrideGzipRequested\";\n\n    /* Netty-specific keys */\n    public static final String NETTY_HTTP_REQUEST = \"_netty_http_request\";\n    public static final String NETTY_SERVER_CHANNEL_HANDLER_CONTEXT = \"_netty_server_channel_handler_context\";\n    public static final String REQ_BODY_DCS = \"_request_body_dcs\";\n    public static final String RESP_BODY_DCS = \"_response_body_dcs\";\n\n    public static final SessionContext.Key<Provider<Long>> REQ_BODY_SIZE_PROVIDER =\n            SessionContext.newKey(\"request_body_size\");\n    public static final SessionContext.Key<Provider<Long>> RESP_BODY_SIZE_PROVIDER =\n            SessionContext.newKey(\"response_body_size\");\n\n    public static final SessionContext.Key<CurrentPassport> PASSPORT = SessionContext.newKey(\"_passport\");\n    public static final SessionContext.Key<Boolean> ZUUL_USE_DECODED_URI =\n            SessionContext.newKey(\"zuul_use_decoded_uri\");\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/Debug.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.context;\n\nimport com.netflix.zuul.message.Header;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpResponseInfo;\nimport io.netty.util.ReferenceCounted;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Locale;\nimport rx.Observable;\n\n/**\n * Simple wrapper class around the RequestContext for setting and managing Request level Debug data.\n * @author Mikey Cohen\n * Date: 1/25/12\n * Time: 2:26 PM\n */\npublic class Debug {\n\n    public static void setDebugRequest(SessionContext ctx, boolean bDebug) {\n        ctx.setDebugRequest(bDebug);\n    }\n\n    public static void setDebugRequestHeadersOnly(SessionContext ctx, boolean bHeadersOnly) {\n        ctx.setDebugRequestHeadersOnly(bHeadersOnly);\n    }\n\n    public static boolean debugRequestHeadersOnly(SessionContext ctx) {\n        return ctx.debugRequestHeadersOnly();\n    }\n\n    public static void setDebugRouting(SessionContext ctx, boolean bDebug) {\n        ctx.setDebugRouting(bDebug);\n    }\n\n    public static boolean debugRequest(SessionContext ctx) {\n        return ctx.debugRequest();\n    }\n\n    public static boolean debugRouting(SessionContext ctx) {\n        return ctx.debugRouting();\n    }\n\n    public static void addRoutingDebug(SessionContext ctx, String line) {\n        List<String> rd = getRoutingDebug(ctx);\n        rd.add(line);\n    }\n\n    public static void addRequestDebugForMessage(SessionContext ctx, ZuulMessage message, String prefix) {\n        for (Header header : message.getHeaders().entries()) {\n            Debug.addRequestDebug(ctx, prefix + \" \" + header.getKey() + \" \" + header.getValue());\n        }\n\n        if (message.hasBody()) {\n            String bodyStr = message.getBodyAsText();\n            Debug.addRequestDebug(ctx, prefix + \" \" + bodyStr);\n        }\n    }\n\n    /**\n     *\n     * @return Returns the list of routiong debug messages\n     */\n    public static List<String> getRoutingDebug(SessionContext ctx) {\n        List<String> rd = (List<String>) ctx.get(\"routingDebug\");\n        if (rd == null) {\n            rd = new ArrayList<String>();\n            ctx.set(\"routingDebug\", rd);\n        }\n        return rd;\n    }\n\n    /**\n     * Adds a line to the  Request debug messages\n     * @param line\n     */\n    public static void addRequestDebug(SessionContext ctx, String line) {\n        List<String> rd = getRequestDebug(ctx);\n        rd.add(line);\n    }\n\n    /**\n     *\n     * @return returns the list of request debug messages\n     */\n    public static List<String> getRequestDebug(SessionContext ctx) {\n        List<String> rd = (List<String>) ctx.get(\"requestDebug\");\n        if (rd == null) {\n            rd = new ArrayList<String>();\n            ctx.set(\"requestDebug\", rd);\n        }\n        return rd;\n    }\n\n    /**\n     * Adds debug details about changes that a given filter made to the request context.\n     * @param filterName\n     * @param copy\n     */\n    public static void compareContextState(String filterName, SessionContext context, SessionContext copy) {\n        // TODO - only comparing Attributes. Need to compare the messages too.\n\n        // Ensure that the routingDebug property already exists, otherwise we'll have a ConcurrentModificationException\n        // below\n        getRoutingDebug(context);\n\n        Iterator<String> it = context.keySet().iterator();\n        String key = it.next();\n        while (key != null) {\n            if ((!key.equals(\"routingDebug\") && !key.equals(\"requestDebug\"))) {\n                Object newValue = context.get(key);\n                Object oldValue = copy.get(key);\n                if (!(newValue instanceof ReferenceCounted) && !(oldValue instanceof ReferenceCounted)) {\n                    if (oldValue == null && newValue != null) {\n                        addRoutingDebug(context, \"{\" + filterName + \"} added \" + key + \"=\" + newValue.toString());\n                    } else if (oldValue != null && newValue != null) {\n                        if (!oldValue.equals(newValue)) {\n                            addRoutingDebug(context, \"{\" + filterName + \"} changed \" + key + \"=\" + newValue.toString());\n                        }\n                    }\n                }\n            }\n            if (it.hasNext()) {\n                key = it.next();\n            } else {\n                key = null;\n            }\n        }\n    }\n\n    public static Observable<Boolean> writeDebugRequest(\n            SessionContext context, HttpRequestInfo request, boolean isInbound) {\n        Observable<Boolean> obs = null;\n        if (Debug.debugRequest(context)) {\n            String prefix = isInbound ? \"REQUEST_INBOUND\" : \"REQUEST_OUTBOUND\";\n            String arrow = \">\";\n\n            Debug.addRequestDebug(\n                    context,\n                    String.format(\n                            \"%s:: %s LINE: %s %s %s\",\n                            prefix,\n                            arrow,\n                            request.getMethod().toUpperCase(Locale.ROOT),\n                            request.getPathAndQuery(),\n                            request.getProtocol()));\n            obs = Debug.writeDebugMessage(context, request, prefix, arrow);\n        }\n\n        if (obs == null) {\n            obs = Observable.just(Boolean.FALSE);\n        }\n\n        return obs;\n    }\n\n    public static Observable<Boolean> writeDebugResponse(\n            SessionContext context, HttpResponseInfo response, boolean isInbound) {\n        Observable<Boolean> obs = null;\n        if (Debug.debugRequest(context)) {\n            String prefix = isInbound ? \"RESPONSE_INBOUND\" : \"RESPONSE_OUTBOUND\";\n            String arrow = \"<\";\n\n            Debug.addRequestDebug(context, String.format(\"%s:: %s STATUS: %s\", prefix, arrow, response.getStatus()));\n            obs = Debug.writeDebugMessage(context, response, prefix, arrow);\n        }\n\n        if (obs == null) {\n            obs = Observable.just(Boolean.FALSE);\n        }\n\n        return obs;\n    }\n\n    public static Observable<Boolean> writeDebugMessage(\n            SessionContext context, ZuulMessage msg, String prefix, String arrow) {\n        Observable<Boolean> obs = null;\n\n        for (Header header : msg.getHeaders().entries()) {\n            Debug.addRequestDebug(\n                    context, String.format(\"%s:: %s HDR: %s:%s\", prefix, arrow, header.getKey(), header.getValue()));\n        }\n\n        // Capture the response body into a Byte array for later usage.\n        if (msg.hasBody()) {\n            if (!Debug.debugRequestHeadersOnly(context)) {\n                // Convert body to a String and add to debug log.\n                String body = msg.getBodyAsText();\n                Debug.addRequestDebug(context, String.format(\"%s:: %s BODY: %s\", prefix, arrow, body));\n            }\n        }\n\n        if (obs == null) {\n            obs = Observable.just(Boolean.FALSE);\n        }\n\n        return obs;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/SessionCleaner.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.context;\n\nimport rx.Observable;\n\n/**\n * User: michaels@netflix.com\n * Date: 8/3/15\n * Time: 12:30 PM\n */\npublic interface SessionCleaner {\n    Observable<Void> cleanup(SessionContext context);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/SessionContext.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.context;\n\nimport com.google.errorprone.annotations.CanIgnoreReturnValue;\nimport com.netflix.config.DynamicPropertyFactory;\nimport com.netflix.zuul.filters.FilterError;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport jakarta.annotation.Nullable;\nimport java.net.URL;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport lombok.NonNull;\n\n/**\n * Represents the context between client and origin server for the duration of the dedicated connection/session\n * between them. But we're currently still only modelling single request/response pair per session.\n *\n * NOTE: Not threadsafe, and not intended to be used concurrently.\n *\n * User: Mike Smith\n * Date: 4/28/15\n * Time: 6:45 PM\n */\npublic final class SessionContext extends HashMap<String, Object> implements Cloneable {\n    private static final int INITIAL_SIZE = DynamicPropertyFactory.getInstance()\n            .getIntProperty(\"com.netflix.zuul.context.SessionContext.initialSize\", 60)\n            .get();\n\n    private static final int EVENT_PROPERTIES_INITIAL_SIZE = DynamicPropertyFactory.getInstance()\n            .getIntProperty(\"com.netflix.zuul.context.SessionContext.eventProperties.initialSize\", 128)\n            .get();\n\n    private boolean brownoutMode = false;\n    private boolean shouldStopFilterProcessing = false;\n    private boolean shouldSendErrorResponse = false;\n    private boolean errorResponseSent = false;\n    private boolean debugRouting = false;\n    private boolean debugRequest = false;\n    private boolean debugRequestHeadersOnly = false;\n    private boolean cancelled = false;\n\n    private static final String KEY_UUID = \"_uuid\";\n    private static final String KEY_VIP = \"routeVIP\";\n    private static final String KEY_ENDPOINT = \"_endpoint\";\n    private static final String KEY_STATIC_RESPONSE = \"_static_response\";\n\n    private static final String KEY_EVENT_PROPS = \"eventProperties\";\n    private static final String KEY_FILTER_ERRORS = \"_filter_errors\";\n    private static final String KEY_FILTER_EXECS = \"_filter_executions\";\n\n    private final IdentityHashMap<Key<?>, ?> typedMap = new IdentityHashMap<>();\n\n    /**\n     * A Key is type-safe, identity-based key into the Session Context.\n     * @param <T>\n     */\n    public static final class Key<T> {\n\n        private final String name;\n        private final Supplier<T> defaultValueSupplier;\n\n        private Key(String name, Supplier<T> defaultValueSupplier) {\n            this.name = Objects.requireNonNull(name, \"name\");\n            this.defaultValueSupplier = defaultValueSupplier;\n        }\n\n        @Override\n        public String toString() {\n            return \"Key{\" + name + '}';\n        }\n\n        public String name() {\n            return name;\n        }\n\n        /**\n         * This method exists solely to indicate that Keys are based on identity and not name.\n         */\n        @Override\n        public boolean equals(Object o) {\n            return super.equals(o);\n        }\n\n        /**\n         * This method exists solely to indicate that Keys are based on identity and not name.\n         */\n        @Override\n        public int hashCode() {\n            return super.hashCode();\n        }\n\n        public T defaultValue() {\n            return defaultValueSupplier != null ? defaultValueSupplier.get() : null;\n        }\n    }\n\n    @SuppressWarnings(\"UnnecessaryStringBuilder\")\n    public SessionContext() {\n        // Use a higher than default initial capacity for the hashmap as we generally have more than the default\n        // 16 entries.\n        super(INITIAL_SIZE);\n\n        put(KEY_FILTER_EXECS, new StringBuilder());\n        put(KEY_EVENT_PROPS, new HashMap<String, Object>(EVENT_PROPERTIES_INITIAL_SIZE));\n        put(KEY_FILTER_ERRORS, new ArrayList<FilterError>());\n    }\n\n    public static <T> Key<T> newKey(String name) {\n        return newKey(name, null);\n    }\n\n    public static <T> Key<T> newKey(String name, Supplier<T> defaultValueSupplier) {\n        return new Key<>(name, defaultValueSupplier);\n    }\n\n    /**\n     * {@inheritDoc}\n     *\n     * <p>This method exists for static analysis.\n     */\n    @Override\n    public Object get(Object key) {\n        return super.get(key);\n    }\n\n    /**\n     * Returns the value in the context, or {@code null} if absent.\n     */\n    @SuppressWarnings(\"unchecked\")\n    @Nullable\n    public <T> T get(@NonNull Key<T> key) {\n        T value = (T) typedMap.get(key);\n        if (value == null) {\n            value = key.defaultValue();\n        }\n\n        return value;\n    }\n\n    /**\n     * Returns the value in the context, or default value from the\n     * typed key default value supplier if absent.\n     */\n    @NonNull\n    public <T> T getOrDefault(@NonNull Key<T> key) {\n        return Objects.requireNonNull(this.get(key), \"expected non-null value or defaultValue supplier\");\n    }\n\n    /**\n     * Returns the value in the context, or {@code defaultValue} if absent.\n     */\n    @SuppressWarnings(\"unchecked\")\n    public <T> T getOrDefault(Key<T> key, T defaultValue) {\n        Objects.requireNonNull(key, \"key\");\n        Objects.requireNonNull(defaultValue, \"defaultValue\");\n        T value = (T) typedMap.get(Objects.requireNonNull(key));\n        if (value != null) {\n            return value;\n        }\n        return defaultValue;\n    }\n\n    /**\n     * {@inheritDoc}\n     *\n     * <p>This method exists for static analysis.\n     */\n    @Override\n    public boolean containsKey(Object key) {\n        return super.containsKey(key);\n    }\n\n    /**\n     * Checks for the existence of the key in the context.\n     */\n    public <T> boolean containsKey(Key<T> key) {\n        return typedMap.containsKey(Objects.requireNonNull(key, \"key\"));\n    }\n\n    /**\n     * {@inheritDoc}\n     *\n     * <p>This method exists for static analysis.\n     */\n    @Override\n    public Object put(String key, Object value) {\n        return super.put(key, value);\n    }\n\n    /**\n     * Returns the previous value associated with key, or {@code null} if there was no mapping for key.  Unlike\n     * {@link #put(String, Object)}, this will never return a null value if the key is present in the map.\n     */\n    @Nullable\n    @CanIgnoreReturnValue\n    public <T> T put(Key<T> key, T value) {\n        Objects.requireNonNull(key, \"key\");\n        Objects.requireNonNull(value, \"value\");\n\n        @SuppressWarnings(\"unchecked\") // Sorry.\n        T res = ((Map<Key<T>, T>) (Map) typedMap).put(key, value);\n        return res;\n    }\n\n    /**\n     * {@inheritDoc}\n     *\n     * <p>This method exists for static analysis.\n     */\n    @Override\n    public boolean remove(Object key, Object value) {\n        return super.remove(key, value);\n    }\n\n    public <T> boolean remove(Key<T> key, T value) {\n        Objects.requireNonNull(key, \"key\");\n        Objects.requireNonNull(value, \"value\");\n        @SuppressWarnings(\"unchecked\") // sorry\n        boolean res = ((Map<Key<T>, T>) (Map) typedMap).remove(key, value);\n        return res;\n    }\n\n    /**\n     * {@inheritDoc}\n     *\n     * <p>This method exists for static analysis.\n     */\n    @Override\n    public Object remove(Object key) {\n        return super.remove(key);\n    }\n\n    public <T> T remove(Key<T> key) {\n        Objects.requireNonNull(key, \"key\");\n        @SuppressWarnings(\"unchecked\") // sorry\n        T res = ((Map<Key<T>, T>) (Map) typedMap).remove(key);\n        return res;\n    }\n\n    public Set<Key<?>> keys() {\n        return Collections.unmodifiableSet(new HashSet<>(typedMap.keySet()));\n    }\n\n    /**\n     * Makes a copy of the RequestContext. This is used for debugging.\n     */\n    @Override\n    public SessionContext clone() {\n        // TODO(carl-mastrangelo): copy over the type safe keys\n        return (SessionContext) super.clone();\n    }\n\n    public String getString(String key) {\n        return (String) get(key);\n    }\n\n    /**\n     * Convenience method to return a boolean value for a given key\n     *\n     * @return true or false depending what was set. default is false\n     */\n    public boolean getBoolean(String key) {\n        return getBoolean(key, false);\n    }\n\n    /**\n     * Convenience method to return a boolean value for a given key\n     *\n     * @return true or false depending what was set. default defaultResponse\n     */\n    public boolean getBoolean(String key, boolean defaultResponse) {\n        Boolean b = (Boolean) get(key);\n        if (b != null) {\n            return b;\n        }\n        return defaultResponse;\n    }\n\n    /**\n     * sets a key value to Boolean.TRUE\n     */\n    public void set(String key) {\n        put(key, Boolean.TRUE);\n    }\n\n    /**\n     * puts the key, value into the map. a null value will remove the key from the map\n     *\n     */\n    public void set(String key, Object value) {\n        if (value != null) {\n            put(key, value);\n        } else {\n            remove(key);\n        }\n    }\n\n    public String getUUID() {\n        return getString(KEY_UUID);\n    }\n\n    public void setUUID(String uuid) {\n        set(KEY_UUID, uuid);\n    }\n\n    public void setStaticResponse(HttpResponseMessage response) {\n        set(KEY_STATIC_RESPONSE, response);\n    }\n\n    public HttpResponseMessage getStaticResponse() {\n        return (HttpResponseMessage) get(KEY_STATIC_RESPONSE);\n    }\n\n    /**\n     * Gets the throwable that will be use in the Error endpoint.\n     *\n     */\n    public Throwable getError() {\n        return (Throwable) get(\"_error\");\n    }\n\n    /**\n     * Sets throwable to use for generating a response in the Error endpoint.\n     */\n    public void setError(Throwable th) {\n        put(\"_error\", th);\n    }\n\n    public String getErrorEndpoint() {\n        return (String) get(\"_error-endpoint\");\n    }\n\n    public void setErrorEndpoint(String name) {\n        put(\"_error-endpoint\", name);\n    }\n\n    /**\n     * sets  debugRouting\n     */\n    public void setDebugRouting(boolean bDebug) {\n        this.debugRouting = bDebug;\n    }\n\n    /**\n     * @return \"debugRouting\"\n     */\n    public boolean debugRouting() {\n        return debugRouting;\n    }\n\n    /**\n     * sets \"debugRequestHeadersOnly\" to bHeadersOnly\n     *\n     */\n    public void setDebugRequestHeadersOnly(boolean bHeadersOnly) {\n        this.debugRequestHeadersOnly = bHeadersOnly;\n    }\n\n    /**\n     * @return \"debugRequestHeadersOnly\"\n     */\n    public boolean debugRequestHeadersOnly() {\n        return this.debugRequestHeadersOnly;\n    }\n\n    /**\n     * sets \"debugRequest\"\n     */\n    public void setDebugRequest(boolean bDebug) {\n        this.debugRequest = bDebug;\n    }\n\n    /**\n     * gets debugRequest\n     *\n     * @return debugRequest\n     */\n    public boolean debugRequest() {\n        return this.debugRequest;\n    }\n\n    /**\n     * removes \"routeHost\" key\n     */\n    public void removeRouteHost() {\n        remove(\"routeHost\");\n    }\n\n    /**\n     * sets routeHost\n     *\n     * @param routeHost a URL\n     */\n    public void setRouteHost(URL routeHost) {\n        set(\"routeHost\", routeHost);\n    }\n\n    /**\n     * @return \"routeHost\" URL\n     */\n    public URL getRouteHost() {\n        return (URL) get(\"routeHost\");\n    }\n\n    /**\n     * appends filter name and status to the filter execution history for the\n     * current request\n     */\n    public void addFilterExecutionSummary(String name, String status, long time) {\n        StringBuilder sb = getFilterExecutionSummary();\n        if (sb.length() > 0) {\n            sb.append(\", \");\n        }\n        sb.append(name)\n                .append('[')\n                .append(status)\n                .append(']')\n                .append('[')\n                .append(time)\n                .append(\"ms]\");\n    }\n\n    /**\n     * @return String that represents the filter execution history for the current request\n     */\n    public StringBuilder getFilterExecutionSummary() {\n        return (StringBuilder) get(KEY_FILTER_EXECS);\n    }\n\n    public boolean shouldSendErrorResponse() {\n        return this.shouldSendErrorResponse;\n    }\n\n    /**\n     * Set this to true to indicate that the Error endpoint should be applied after\n     * the end of the current filter processing phase.\n     *\n     */\n    public void setShouldSendErrorResponse(boolean should) {\n        this.shouldSendErrorResponse = should;\n    }\n\n    public boolean errorResponseSent() {\n        return this.errorResponseSent;\n    }\n\n    public void setErrorResponseSent(boolean should) {\n        this.errorResponseSent = should;\n    }\n\n    /**\n     * This can be used by filters for flagging if the server is getting overloaded, and then choose\n     * to disable/sample/rate-limit some optional features.\n     *\n     */\n    public boolean isInBrownoutMode() {\n        return brownoutMode;\n    }\n\n    /**\n     * Flag the server is getting overloaded.\n     * @deprecated use setInBrownoutMode(String reason)\n     */\n    @Deprecated\n    public void setInBrownoutMode() {\n        this.brownoutMode = true;\n    }\n\n    public void setInBrownoutMode(@NonNull String reason) {\n        this.brownoutMode = true;\n        put(CommonContextKeys.BROWNOUT_REASON, reason);\n    }\n\n    public @Nullable String getBrownoutReason() {\n        return get(CommonContextKeys.BROWNOUT_REASON);\n    }\n\n    /**\n     * This is typically set by a filter when wanting to reject a request, and also reduce load on the server\n     * by not processing any subsequent filters for this request.\n     */\n    public void stopFilterProcessing() {\n        shouldStopFilterProcessing = true;\n    }\n\n    public boolean shouldStopFilterProcessing() {\n        return shouldStopFilterProcessing;\n    }\n\n    /**\n     * returns the routeVIP; that is the Eureka \"vip\" of registered instances\n     *\n     */\n    public String getRouteVIP() {\n        return (String) get(KEY_VIP);\n    }\n\n    /**\n     * sets routeVIP; that is the Eureka \"vip\" of registered instances\n     */\n    public void setRouteVIP(String sVip) {\n        set(KEY_VIP, sVip);\n    }\n\n    public void setEndpoint(String endpoint) {\n        put(KEY_ENDPOINT, endpoint);\n    }\n\n    public String getEndpoint() {\n        return (String) get(KEY_ENDPOINT);\n    }\n\n    public void setEventProperty(String key, Object value) {\n        getEventProperties().put(key, value);\n    }\n\n    public Map<String, Object> getEventProperties() {\n        return (Map<String, Object>) this.get(KEY_EVENT_PROPS);\n    }\n\n    public List<FilterError> getFilterErrors() {\n        return (List<FilterError>) get(KEY_FILTER_ERRORS);\n    }\n\n    public void setOriginReportedDuration(int duration) {\n        put(\"_originReportedDuration\", duration);\n    }\n\n    public int getOriginReportedDuration() {\n        Object value = get(\"_originReportedDuration\");\n        if (value != null) {\n            return (Integer) value;\n        }\n        return -1;\n    }\n\n    public boolean isCancelled() {\n        return cancelled;\n    }\n\n    public void cancel() {\n        this.cancelled = true;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/SessionContextDecorator.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.context;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/25/15\n * Time: 4:09 PM\n */\npublic interface SessionContextDecorator {\n    public SessionContext decorate(SessionContext ctx);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/SessionContextFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.context;\n\nimport com.netflix.zuul.message.ZuulMessage;\nimport rx.Observable;\n\npublic interface SessionContextFactory<T, V> {\n    public ZuulMessage create(SessionContext context, T nativeRequest, V nativeResponse);\n\n    public Observable<ZuulMessage> write(ZuulMessage msg, V nativeResponse);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/context/ZuulSessionContextDecorator.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.context;\n\nimport com.netflix.netty.common.metrics.HttpBodySizeRecordingChannelHandler;\nimport com.netflix.util.UUIDFactory;\nimport com.netflix.util.concurrent.ConcurrentUUIDFactory;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.origins.OriginManager;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\n\n/**\n * Base Session Context Decorator\n *\n * Author: Arthur Gonigberg\n * Date: November 21, 2017\n */\n@Singleton\npublic class ZuulSessionContextDecorator implements SessionContextDecorator {\n\n    private static final UUIDFactory UUID_FACTORY = new ConcurrentUUIDFactory();\n\n    private final OriginManager originManager;\n\n    @Inject\n    public ZuulSessionContextDecorator(OriginManager originManager) {\n        this.originManager = originManager;\n    }\n\n    @Override\n    public SessionContext decorate(SessionContext ctx) {\n        // TODO split out commons parts from BaseSessionContextDecorator\n\n        ChannelHandlerContext nettyCtx =\n                (ChannelHandlerContext) ctx.get(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT);\n        if (nettyCtx == null) {\n            return null;\n        }\n\n        Channel channel = nettyCtx.channel();\n\n        // set injected origin manager\n        ctx.put(CommonContextKeys.ORIGIN_MANAGER, originManager);\n\n        // TODO\n        /*        // The throttle result info.\n        ThrottleResult throttleResult = channel.attr(HttpRequestThrottleChannelHandler.ATTR_THROTTLE_RESULT).get();\n        ctx.set(CommonContextKeys.THROTTLE_RESULT, throttleResult);*/\n\n        // Add a container for request attempts info.\n        ctx.put(CommonContextKeys.REQUEST_ATTEMPTS, new RequestAttempts());\n\n        // Providers for getting the size of read/written request and response body sizes from channel.\n        ctx.put(\n                CommonContextKeys.REQ_BODY_SIZE_PROVIDER,\n                HttpBodySizeRecordingChannelHandler.getCurrentInboundBodySize(channel));\n        ctx.put(\n                CommonContextKeys.RESP_BODY_SIZE_PROVIDER,\n                HttpBodySizeRecordingChannelHandler.getCurrentOutboundBodySize(channel));\n\n        CurrentPassport passport = CurrentPassport.fromChannel(channel);\n        ctx.put(CommonContextKeys.PASSPORT, passport);\n\n        ctx.setUUID(UUID_FACTORY.generateRandomUuid().toString());\n\n        return ctx;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/exception/ErrorType.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.exception;\n\nimport com.netflix.client.ClientException;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.zuul.stats.status.StatusCategory;\n\n/**\n * Error Type\n *\n * Author: Arthur Gonigberg\n * Date: November 28, 2017\n */\npublic interface ErrorType {\n\n    String PROP_PREFIX = \"zuul.error.outbound\";\n    DynamicIntProperty ERROR_TYPE_READ_TIMEOUT_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".readtimeout.status\", 504);\n    DynamicIntProperty ERROR_TYPE_CONNECT_ERROR_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".connecterror.status\", 502);\n    DynamicIntProperty ERROR_TYPE_SERVICE_UNAVAILABLE_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".serviceunavailable.status\", 503);\n    DynamicIntProperty ERROR_TYPE_ORIGIN_CONCURRENCY_EXCEEDED_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".originconcurrencyexceeded.status\", 503);\n    DynamicIntProperty ERROR_TYPE_ERROR_STATUS_RESPONSE_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".errorstatusresponse.status\", 500);\n    DynamicIntProperty ERROR_TYPE_NOSERVERS_STATUS = new DynamicIntProperty(PROP_PREFIX + \".noservers.status\", 502);\n    DynamicIntProperty ERROR_TYPE_ORIGIN_SERVER_MAX_CONNS_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".servermaxconns.status\", 503);\n    DynamicIntProperty ERROR_TYPE_ORIGIN_RESET_CONN_STATUS =\n            new DynamicIntProperty(PROP_PREFIX + \".originresetconnection.status\", 504);\n    DynamicIntProperty ERROR_TYPE_OTHER_STATUS = new DynamicIntProperty(PROP_PREFIX + \".other.status\", 500);\n\n    int getStatusCodeToReturn();\n\n    StatusCategory getStatusCategory();\n\n    ClientException.ErrorType getClientErrorType();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/exception/OutboundErrorType.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.exception;\n\nimport com.netflix.client.ClientException;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\n\n/**\n * Outbound Error Type\n *\n * Author: Arthur Gonigberg\n * Date: November 28, 2017\n */\npublic enum OutboundErrorType implements ErrorType {\n    READ_TIMEOUT(\n            ERROR_TYPE_READ_TIMEOUT_STATUS.get(),\n            ZuulStatusCategory.FAILURE_ORIGIN_READ_TIMEOUT,\n            ClientException.ErrorType.READ_TIMEOUT_EXCEPTION),\n    CONNECT_ERROR(\n            ERROR_TYPE_CONNECT_ERROR_STATUS.get(),\n            ZuulStatusCategory.FAILURE_ORIGIN_CONNECTIVITY,\n            ClientException.ErrorType.CONNECT_EXCEPTION),\n    SERVICE_UNAVAILABLE(\n            ERROR_TYPE_SERVICE_UNAVAILABLE_STATUS.get(),\n            ZuulStatusCategory.FAILURE_ORIGIN_THROTTLED,\n            ClientException.ErrorType.SERVER_THROTTLED),\n    ERROR_STATUS_RESPONSE(\n            ERROR_TYPE_ERROR_STATUS_RESPONSE_STATUS.get(),\n            ZuulStatusCategory.FAILURE_ORIGIN,\n            ClientException.ErrorType.GENERAL),\n    NO_AVAILABLE_SERVERS(\n            ERROR_TYPE_NOSERVERS_STATUS.get(),\n            ZuulStatusCategory.FAILURE_ORIGIN_NO_SERVERS,\n            ClientException.ErrorType.CONNECT_EXCEPTION),\n    ORIGIN_SERVER_MAX_CONNS(\n            ERROR_TYPE_ORIGIN_SERVER_MAX_CONNS_STATUS.get(),\n            ZuulStatusCategory.FAILURE_LOCAL_THROTTLED_ORIGIN_SERVER_MAXCONN,\n            ClientException.ErrorType.CLIENT_THROTTLED),\n    RESET_CONNECTION(\n            ERROR_TYPE_ORIGIN_RESET_CONN_STATUS.get(),\n            ZuulStatusCategory.FAILURE_ORIGIN_RESET_CONNECTION,\n            ClientException.ErrorType.CONNECT_EXCEPTION),\n    CLOSE_NOTIFY_CONNECTION(\n            502,\n            ZuulStatusCategory.FAILURE_ORIGIN_CLOSE_NOTIFY_CONNECTION,\n            ClientException.ErrorType.CONNECT_EXCEPTION),\n    CANCELLED(400, ZuulStatusCategory.FAILURE_CLIENT_CANCELLED, ClientException.ErrorType.SOCKET_TIMEOUT_EXCEPTION),\n    ORIGIN_CONCURRENCY_EXCEEDED(\n            ERROR_TYPE_ORIGIN_CONCURRENCY_EXCEEDED_STATUS.get(),\n            ZuulStatusCategory.FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY,\n            ClientException.ErrorType.SERVER_THROTTLED),\n    HEADER_FIELDS_TOO_LARGE(\n            431, ZuulStatusCategory.FAILURE_LOCAL_HEADER_FIELDS_TOO_LARGE, ClientException.ErrorType.GENERAL),\n    OTHER(ERROR_TYPE_OTHER_STATUS.get(), ZuulStatusCategory.FAILURE_LOCAL, ClientException.ErrorType.GENERAL);\n\n    private static final String NAME_PREFIX = \"ORIGIN_\";\n\n    private final int statusCodeToReturn;\n    private final StatusCategory statusCategory;\n    private final ClientException.ErrorType clientErrorType;\n\n    OutboundErrorType(\n            int statusCodeToReturn, StatusCategory statusCategory, ClientException.ErrorType clientErrorType) {\n        this.statusCodeToReturn = statusCodeToReturn;\n        this.statusCategory = statusCategory;\n        this.clientErrorType = clientErrorType;\n    }\n\n    @Override\n    public int getStatusCodeToReturn() {\n        return statusCodeToReturn;\n    }\n\n    @Override\n    public StatusCategory getStatusCategory() {\n        return statusCategory;\n    }\n\n    @Override\n    public ClientException.ErrorType getClientErrorType() {\n        return clientErrorType;\n    }\n\n    @Override\n    public String toString() {\n        return NAME_PREFIX + name();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/exception/OutboundException.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.exception;\n\nimport com.netflix.zuul.niws.RequestAttempt;\nimport com.netflix.zuul.niws.RequestAttempts;\n\n/**\n * Outbound Exception Decorator\n *\n * User: Mike Smith\n * Date: 10/21/15\n * Time: 11:46 AM\n */\npublic class OutboundException extends ZuulException {\n    private final ErrorType outboundErrorType;\n    private final RequestAttempts requestAttempts;\n\n    public OutboundException(ErrorType outboundErrorType, RequestAttempts requestAttempts) {\n        super(outboundErrorType.toString(), outboundErrorType.toString(), true);\n        this.outboundErrorType = outboundErrorType;\n        this.requestAttempts = requestAttempts;\n        this.setStatusCode(outboundErrorType.getStatusCodeToReturn());\n        this.dontLogAsError();\n    }\n\n    public OutboundException(ErrorType outboundErrorType, RequestAttempts requestAttempts, Throwable cause) {\n        super(outboundErrorType.toString(), cause.getMessage(), true);\n        this.outboundErrorType = outboundErrorType;\n        this.requestAttempts = requestAttempts;\n        this.setStatusCode(outboundErrorType.getStatusCodeToReturn());\n        this.dontLogAsError();\n    }\n\n    public RequestAttempt getFinalRequestAttempt() {\n        return requestAttempts == null ? null : requestAttempts.getFinalAttempt();\n    }\n\n    public ErrorType getOutboundErrorType() {\n        return outboundErrorType;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/exception/RequestExpiredException.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.exception;\n\n/**\n * @author Argha C\n * @since 4/26/23\n */\npublic class RequestExpiredException extends ZuulException {\n\n    public RequestExpiredException(String message) {\n        super(message, true);\n        setStatusCode(504);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/exception/ZuulException.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.exception;\n\n/**\n * All handled exceptions in Zuul are ZuulExceptions\n * @author Mikey Cohen\n * Date: 10/20/11\n * Time: 4:33 PM\n */\npublic class ZuulException extends RuntimeException {\n    private final String errorCause;\n    private int statusCode = 500;\n    private boolean shouldLogAsError = true;\n\n    /**\n     * Source Throwable, message, status code and info about the cause\n     * @param sMessage\n     * @param throwable\n     * @param errorCause\n     */\n    public ZuulException(String sMessage, Throwable throwable, String errorCause) {\n        super(sMessage, throwable);\n        this.errorCause = errorCause;\n    }\n\n    /**\n     * error message, status code and info about the cause\n     * @param sMessage\n     * @param errorCause\n     */\n    public ZuulException(String sMessage, String errorCause) {\n        this(sMessage, errorCause, false);\n    }\n\n    public ZuulException(String sMessage, String errorCause, boolean noStackTrace) {\n        super(sMessage, null, noStackTrace, !noStackTrace);\n        this.errorCause = errorCause;\n    }\n\n    public ZuulException(Throwable throwable, String sMessage, boolean noStackTrace) {\n        super(sMessage, throwable, noStackTrace, !noStackTrace);\n        this.errorCause = \"GENERAL\";\n    }\n\n    public ZuulException(Throwable throwable) {\n        super(throwable);\n        this.errorCause = \"GENERAL\";\n    }\n\n    public ZuulException(String sMessage) {\n        this(sMessage, false);\n    }\n\n    public ZuulException(String sMessage, boolean noStackTrace) {\n        super(sMessage, null, noStackTrace, !noStackTrace);\n        this.errorCause = \"GENERAL\";\n    }\n\n    public int getStatusCode() {\n        return statusCode;\n    }\n\n    public void setStatusCode(int statusCode) {\n        this.statusCode = statusCode;\n    }\n\n    public void dontLogAsError() {\n        shouldLogAsError = false;\n    }\n\n    public boolean shouldLogAsError() {\n        return shouldLogAsError;\n    }\n\n    public String getErrorCause() {\n        return errorCause;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/exception/ZuulFilterConcurrencyExceededException.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.exception;\n\nimport com.netflix.zuul.filters.ZuulFilter;\n\npublic class ZuulFilterConcurrencyExceededException extends ZuulException {\n\n    public ZuulFilterConcurrencyExceededException(ZuulFilter filter, int concurrencyLimit) {\n        super(filter.filterName() + \" exceeded concurrency limit of \" + concurrencyLimit, true);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/BaseFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.zuul.exception.ZuulFilterConcurrencyExceededException;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport io.netty.handler.codec.http.HttpContent;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Base abstract class for ZuulFilters. The base class defines abstract methods to define: filterType() - to classify a\n * filter by type. Standard types in Zuul are \"pre\" for pre-routing filtering, \"route\" for routing to an origin, \"post\"\n * for post-routing filters, \"error\" for error handling. We also support a \"static\" type for static responses see\n * StaticResponseFilter.\n * <p>\n * filterOrder() must also be defined for a filter. Filters may have the same  filterOrder if precedence is not\n * important for a filter. filterOrders do not need to be sequential.\n * <p>\n * ZuulFilters may be disabled using Archaius Properties.\n * <p>\n * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter()\n * property to false\n *\n * @author Mikey Cohen Date: 10/26/11 Time: 4:29 PM\n */\npublic abstract class BaseFilter<I extends ZuulMessage, O extends ZuulMessage> implements ZuulFilter<I, O> {\n\n    private final String baseName;\n    private final AtomicInteger concurrentCount;\n    private final Counter concurrencyRejections;\n    private final CachedDynamicBooleanProperty filterDisabled;\n    protected final CachedDynamicIntProperty filterConcurrencyCustom;\n    protected final CachedDynamicIntProperty filterConcurrencyDefault;\n    private final CachedDynamicBooleanProperty concurrencyProtectionEnabled;\n    private static final int DEFAULT_FILTER_CONCURRENCY_LIMIT = 4000;\n\n    protected BaseFilter() {\n        baseName = getClass().getSimpleName() + \".\" + filterType();\n        concurrentCount = SpectatorUtils.newGauge(\"zuul.filter.concurrency.current\", baseName, new AtomicInteger(0));\n        concurrencyRejections = SpectatorUtils.newCounter(\"zuul.filter.concurrency.rejected\", baseName);\n        filterDisabled = new CachedDynamicBooleanProperty(disablePropertyName(), false);\n        concurrencyProtectionEnabled =\n                new CachedDynamicBooleanProperty(\"zuul.filter.concurrency.protect.enabled\", true);\n        filterConcurrencyDefault =\n                new CachedDynamicIntProperty(\"zuul.filter.concurrency.limit.default\", DEFAULT_FILTER_CONCURRENCY_LIMIT);\n        filterConcurrencyCustom =\n                new CachedDynamicIntProperty(maxConcurrencyPropertyName(), DEFAULT_FILTER_CONCURRENCY_LIMIT);\n    }\n\n    @Override\n    public String filterName() {\n        return getClass().getName();\n    }\n\n    @Override\n    public boolean overrideStopFilterProcessing() {\n        return false;\n    }\n\n    /**\n     * The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable\n     */\n    public String disablePropertyName() {\n        return \"zuul.\" + baseName + \".disable\";\n    }\n\n    /**\n     * The name of the Archaius property for this filter's max concurrency. by default it is\n     * zuul.[classname].[filtertype].concurrency.limit\n     */\n    public String maxConcurrencyPropertyName() {\n        return \"zuul.\" + baseName + \".concurrency.limit\";\n    }\n\n    /**\n     * If true, the filter has been disabled by archaius and will not be run.\n     */\n    @Override\n    public boolean isDisabled() {\n        return filterDisabled.get();\n    }\n\n    @Override\n    public O getDefaultOutput(I input) {\n        return (O) input;\n    }\n\n    @Override\n    public FilterSyncType getSyncType() {\n        return FilterSyncType.ASYNC;\n    }\n\n    @Override\n    public String toString() {\n        return String.valueOf(filterType()) + \":\" + String.valueOf(filterName());\n    }\n\n    @Override\n    public boolean needsBodyBuffered(I input) {\n        return false;\n    }\n\n    @Override\n    public HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk) {\n        return chunk;\n    }\n\n    @Override\n    public void incrementConcurrency() throws ZuulFilterConcurrencyExceededException {\n        int limit = calculateConcurency();\n        if (concurrencyProtectionEnabled.get() && (concurrentCount.get() >= limit)) {\n            concurrencyRejections.increment();\n            throw new ZuulFilterConcurrencyExceededException(this, limit);\n        }\n        concurrentCount.incrementAndGet();\n    }\n\n    protected int calculateConcurency() {\n        int customLimit = filterConcurrencyCustom.get();\n        return customLimit != DEFAULT_FILTER_CONCURRENCY_LIMIT ? customLimit : filterConcurrencyDefault.get();\n    }\n\n    @Override\n    public void decrementConcurrency() {\n        concurrentCount.decrementAndGet();\n    }\n\n    public int getConcurrency() {\n        return concurrentCount.get();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/BaseSyncFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport com.netflix.zuul.message.ZuulMessage;\nimport rx.Observable;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/8/15\n * Time: 2:46 PM\n */\npublic abstract class BaseSyncFilter<I extends ZuulMessage, O extends ZuulMessage> extends BaseFilter<I, O>\n        implements SyncZuulFilter<I, O> {\n    /**\n     * A wrapper implementation of applyAsync() that is intended just to aggregate a non-blocking apply() method\n     * in an Observable.\n     *\n     * A subclass filter should override this method if doing any IO.\n     */\n    @Override\n    public Observable<O> applyAsync(I input) {\n        return Observable.just(this.apply(input));\n    }\n\n    @Override\n    public FilterSyncType getSyncType() {\n        return FilterSyncType.SYNC;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/Endpoint.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters;\n\nimport com.netflix.zuul.message.ZuulMessage;\n\n/**\n * User: Mike Smith\n * Date: 5/16/15\n * Time: 1:57 PM\n */\npublic abstract class Endpoint<I extends ZuulMessage, O extends ZuulMessage> extends BaseFilter<I, O> {\n    @Override\n    public int filterOrder() {\n        // Set all Endpoint filters to order of 0, because they are not processed sequentially like other filter types.\n        return 0;\n    }\n\n    @Override\n    public FilterType filterType() {\n        return FilterType.ENDPOINT;\n    }\n\n    @Override\n    public boolean shouldFilter(I msg) {\n        // Always true, because Endpoint filters are chosen by name instead.\n        return true;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/FilterError.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/7/15\n * Time: 10:19 AM\n */\npublic class FilterError implements Cloneable {\n    private final String filterName;\n    private final String filterType;\n    private Throwable exception = null;\n\n    public FilterError(String filterName, String filterType, Throwable exception) {\n        this.filterName = filterName;\n        this.filterType = filterType;\n        this.exception = exception;\n    }\n\n    public String getFilterName() {\n        return filterName;\n    }\n\n    public String getFilterType() {\n        return filterType;\n    }\n\n    public Throwable getException() {\n        return exception;\n    }\n\n    @Override\n    public Object clone() {\n        return new FilterError(filterName, filterType, exception);\n    }\n\n    @Override\n    public String toString() {\n        return \"FilterError{\" + \"filterName='\"\n                + filterName + '\\'' + \", filterType='\"\n                + filterType + '\\'' + \", exception=\"\n                + exception + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/FilterRegistry.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport java.util.Collection;\nimport javax.annotation.Nullable;\n\npublic interface FilterRegistry {\n    @Nullable\n    ZuulFilter<?, ?> get(String key);\n\n    int size();\n\n    Collection<ZuulFilter<?, ?>> getAllFilters();\n\n    /**\n     * Indicates if this registry can be modified.  Implementations should not change the return;\n     * they return the same value each time.\n     */\n    boolean isMutable();\n\n    /**\n     * Removes the filter from the registry, and returns it.   Returns {@code null} no such filter\n     * was found.  Callers should check {@link #isMutable()} before calling this method.\n     *\n     * @throws IllegalStateException if this registry is not mutable.\n     */\n    @Nullable\n    ZuulFilter<?, ?> remove(String key);\n\n    /**\n     * Stores the filter into the registry.  If an existing filter was present with the same key,\n     * it is removed.  Callers should check {@link #isMutable()} before calling this method.\n     *\n     * @throws IllegalStateException if this registry is not mutable.\n     */\n    void put(String key, ZuulFilter<?, ?> filter);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/FilterSyncType.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters;\n\n/**\n * User: Mike Smith\n * Date: 11/13/15\n * Time: 9:13 PM\n */\npublic enum FilterSyncType {\n    SYNC,\n    ASYNC\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/FilterType.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters;\n\nimport java.util.Locale;\n\n/**\n * User: Mike Smith\n * Date: 11/13/15\n * Time: 7:50 PM\n */\npublic enum FilterType {\n    INBOUND(\"in\"),\n    ENDPOINT(\"end\"),\n    OUTBOUND(\"out\");\n\n    private final String shortName;\n\n    private FilterType(String shortName) {\n        this.shortName = shortName;\n    }\n\n    @Override\n    public String toString() {\n        return shortName;\n    }\n\n    public static FilterType parse(String str) {\n        str = str.toLowerCase(Locale.ROOT);\n        switch (str) {\n            case \"in\":\n                return INBOUND;\n            case \"out\":\n                return OUTBOUND;\n            case \"end\":\n                return ENDPOINT;\n            default:\n                throw new IllegalArgumentException(\"Unknown filter type! type=\" + String.valueOf(str));\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/MutableFilterRegistry.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport jakarta.inject.Singleton;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport javax.annotation.Nullable;\n\n@Singleton\npublic final class MutableFilterRegistry implements FilterRegistry {\n    private final ConcurrentHashMap<String, ZuulFilter<?, ?>> filters = new ConcurrentHashMap<>();\n\n    @Nullable\n    @Override\n    public ZuulFilter<?, ?> remove(String key) {\n        return filters.remove(Objects.requireNonNull(key, \"key\"));\n    }\n\n    @Override\n    @Nullable\n    public ZuulFilter<?, ?> get(String key) {\n        return filters.get(Objects.requireNonNull(key, \"key\"));\n    }\n\n    @Override\n    public void put(String key, ZuulFilter<?, ?> filter) {\n        filters.putIfAbsent(Objects.requireNonNull(key, \"key\"), Objects.requireNonNull(filter, \"filter\"));\n    }\n\n    @Override\n    public int size() {\n        return filters.size();\n    }\n\n    @Override\n    public Collection<ZuulFilter<?, ?>> getAllFilters() {\n        return Collections.unmodifiableList(new ArrayList<>(filters.values()));\n    }\n\n    @Override\n    public boolean isMutable() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/ShouldFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport com.netflix.zuul.message.ZuulMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/7/15\n * Time: 3:31 PM\n */\npublic interface ShouldFilter<T extends ZuulMessage> {\n    /**\n     * a \"true\" return from this method means that the apply() method should be invoked\n     *\n     * @return true if the apply() method should be invoked. false will not invoke the apply() method\n     */\n    boolean shouldFilter(T msg);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/SyncZuulFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters;\n\nimport com.netflix.zuul.message.ZuulMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 11/16/15\n * Time: 2:07 PM\n */\npublic interface SyncZuulFilter<I extends ZuulMessage, O extends ZuulMessage> extends ZuulFilter<I, O> {\n    O apply(I input);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/SyncZuulFilterAdapter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters;\n\nimport com.netflix.zuul.message.ZuulMessage;\nimport io.netty.handler.codec.http.HttpContent;\nimport rx.Observable;\n\n/**\n * Base class to help implement SyncZuulFilter. Note that the class BaseSyncFilter does exist but it derives from\n * BaseFilter which in turn creates a new instance of CachedDynamicBooleanProperty for \"filterDisabled\" every time you\n * create a new instance of the ZuulFilter. Normally it is not too much of a concern as the instances of ZuulFilters\n * are \"effectively\" singleton and are cached by ZuulFilterLoader. However, if you ever have a need for instantiating a\n * new ZuulFilter instance per request - aka EdgeProxyEndpoint or Inbound/Outbound PassportStampingFilter creating new\n * instances of CachedDynamicBooleanProperty per instance of ZuulFilter will quickly kill your server's performance in\n * two ways -\n * a) Instances of CachedDynamicBooleanProperty are *very* heavy CPU wise to create due to extensive hookups machinery\n *    in their constructor\n * b) They leak memory as they add themselves to some ConcurrentHashMap and are never garbage collected.\n *\n * TL;DR use this as a base class for your ZuulFilter if you intend to create new instances of ZuulFilter\n * Created by saroskar on 6/8/17.\n */\npublic abstract class SyncZuulFilterAdapter<I extends ZuulMessage, O extends ZuulMessage>\n        implements SyncZuulFilter<I, O> {\n\n    @Override\n    public boolean isDisabled() {\n        return false;\n    }\n\n    @Override\n    public boolean shouldFilter(I msg) {\n        return true;\n    }\n\n    @Override\n    public int filterOrder() {\n        // Set all Endpoint filters to order of 0, because they are not processed sequentially like other filter types.\n        return 0;\n    }\n\n    @Override\n    public FilterType filterType() {\n        return FilterType.ENDPOINT;\n    }\n\n    @Override\n    public boolean overrideStopFilterProcessing() {\n        return false;\n    }\n\n    @Override\n    public Observable<O> applyAsync(I input) {\n        return Observable.just(apply(input));\n    }\n\n    @Override\n    public FilterSyncType getSyncType() {\n        return FilterSyncType.SYNC;\n    }\n\n    @Override\n    public boolean needsBodyBuffered(I input) {\n        return false;\n    }\n\n    @Override\n    public HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk) {\n        return chunk;\n    }\n\n    @Override\n    public void incrementConcurrency() {\n        // NOOP for sync filters\n    }\n\n    @Override\n    public void decrementConcurrency() {\n        // NOOP for sync filters\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/ZuulFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.FilterCategory;\nimport com.netflix.zuul.FilterConstraint;\nimport com.netflix.zuul.exception.ZuulFilterConcurrencyExceededException;\nimport com.netflix.zuul.message.ZuulMessage;\nimport io.netty.handler.codec.http.HttpContent;\nimport rx.Observable;\n\n/**\n * Base interface for ZuulFilters\n *\n * @author Mikey Cohen\n *         Date: 10/27/11\n *         Time: 3:03 PM\n */\npublic interface ZuulFilter<I extends ZuulMessage, O extends ZuulMessage> extends ShouldFilter<I> {\n    boolean isDisabled();\n\n    String filterName();\n\n    /**\n     * filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not\n     * important for a filter. filterOrders do not need to be sequential.\n     *\n     * @return the int order of a filter\n     */\n    default int filterOrder() {\n        Filter f = getClass().getAnnotation(Filter.class);\n        if (f != null) {\n            return f.order();\n        }\n        throw new UnsupportedOperationException(\"not implemented\");\n    }\n\n    /**\n     * to classify a filter by type. Standard types in Zuul are \"in\" for pre-routing filtering,\n     * \"end\" for routing to an origin, \"out\" for post-routing filters.\n     *\n     * @return FilterType\n     */\n    default FilterType filterType() {\n        Filter f = getClass().getAnnotation(Filter.class);\n        if (f != null) {\n            return f.type();\n        }\n        throw new UnsupportedOperationException(\"not implemented\");\n    }\n\n    /**\n     * Classify a filter by category.\n     *\n     * @return FilterCategory the classification of this filter\n     */\n    default FilterCategory category() {\n        Filter f = getClass().getAnnotation(Filter.class);\n        if (f != null) {\n            return f.category();\n        } else {\n            return FilterCategory.UNSPECIFIED;\n        }\n    }\n\n    default Class<? extends FilterConstraint>[] constraints() {\n        Filter annotation = getClass().getAnnotation(Filter.class);\n        if (annotation != null) {\n            return annotation.constraints();\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Whether this filter's shouldFilter() method should be checked, and apply() called, even\n     * if SessionContext.stopFilterProcessing has been set.\n     *\n     * @return boolean\n     */\n    boolean overrideStopFilterProcessing();\n\n    /**\n     * Called by zuul filter runner before sending request through this filter. The filter can throw\n     * ZuulFilterConcurrencyExceededException if it has reached its concurrent requests limit and does\n     * not wish to process the request. Generally only useful for async filters.\n     */\n    void incrementConcurrency() throws ZuulFilterConcurrencyExceededException;\n\n    /**\n     * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter\n     */\n    Observable<O> applyAsync(I input);\n\n    /**\n     * Called by zuul filter after request is processed by this filter.\n     *\n     */\n    void decrementConcurrency();\n\n    default FilterSyncType getSyncType() {\n        Filter f = getClass().getAnnotation(Filter.class);\n        if (f != null) {\n            return f.sync();\n        }\n        throw new UnsupportedOperationException(\"not implemented\");\n    }\n\n    /**\n     * Choose a default message to use if the applyAsync() method throws an exception.\n     *\n     * @return ZuulMessage\n     */\n    O getDefaultOutput(I input);\n\n    /**\n     * Filter indicates it needs to read and buffer whole body before it can operate on the messages by returning true.\n     * The decision can be made at runtime, looking at the request type. For example if the incoming message is a MSL\n     * message MSL decryption filter can return true here to buffer whole MSL message before it tries to decrypt it.\n     * @return true if this filter needs to read whole body before it can run, false otherwise\n     */\n    boolean needsBodyBuffered(I input);\n\n    /**\n     * Optionally transform HTTP content chunk received.\n     */\n    HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/common/GZipResponseFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.common;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.config.DynamicStringSetProperty;\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpOutboundSyncFilter;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpHeaderNames;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.util.Gzipper;\nimport com.netflix.zuul.util.HttpUtils;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport java.util.Locale;\n\n/**\n * General-purpose filter for gzipping/ungzipping response bodies if requested/needed.  This should be run as late as\n * possible to ensure final encoded body length is considered\n *\n * <p>You can just subclass this in your project, and use as-is.\n *\n * @author Mike Smith\n */\n@Filter(order = 110, type = FilterType.OUTBOUND)\npublic class GZipResponseFilter extends HttpOutboundSyncFilter {\n    private static final DynamicStringSetProperty GZIPPABLE_CONTENT_TYPES = new DynamicStringSetProperty(\n            \"zuul.gzip.contenttypes\",\n            \"text/html,application/x-javascript,text/css,application/javascript,text/javascript,text/plain,text/xml,\"\n                    + \"application/json,application/vnd.ms-fontobject,application/x-font-opentype,application/x-font-truetype,\"\n                    + \"application/x-font-ttf,application/xml,font/eot,font/opentype,font/otf,image/svg+xml,image/vnd.microsoft.icon,\"\n                    + \"text/event-stream\",\n            \",\");\n\n    // https://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits\n    private static final CachedDynamicIntProperty MIN_BODY_SIZE_FOR_GZIP =\n            new CachedDynamicIntProperty(\"zuul.min.gzip.body.size\", 860);\n\n    private static final CachedDynamicBooleanProperty ENABLED =\n            new CachedDynamicBooleanProperty(\"zuul.response.gzip.filter.enabled\", true);\n\n    @Override\n    public boolean shouldFilter(HttpResponseMessage response) {\n        if (!ENABLED.get() || !response.hasBody() || response.getContext().isInBrownoutMode()) {\n            return false;\n        }\n\n        if (response.getContext().get(CommonContextKeys.GZIPPER) != null) {\n            return true;\n        }\n\n        // A flag on SessionContext can be set to override normal mechanism of checking if client accepts gzip.;\n        HttpRequestInfo request = response.getInboundRequest();\n        Boolean overrideIsGzipRequested =\n                (Boolean) response.getContext().get(CommonContextKeys.OVERRIDE_GZIP_REQUESTED);\n        boolean isGzipRequested = (overrideIsGzipRequested == null)\n                ? HttpUtils.acceptsGzip(request.getHeaders())\n                : overrideIsGzipRequested;\n\n        // Check the headers to see if response is already gzipped.\n        Headers respHeaders = response.getHeaders();\n        boolean isResponseCompressed = HttpUtils.isCompressed(respHeaders);\n\n        // Decide what to do.;\n        boolean shouldGzip = isGzippableContentType(response)\n                && isGzipRequested\n                && !isResponseCompressed\n                && isRightSizeForGzip(response);\n        if (shouldGzip) {\n            response.getContext().set(CommonContextKeys.GZIPPER, getGzipper());\n        }\n        return shouldGzip;\n    }\n\n    protected Gzipper getGzipper() {\n        return new Gzipper();\n    }\n\n    @VisibleForTesting\n    boolean isRightSizeForGzip(HttpResponseMessage response) {\n        Integer bodySize = HttpUtils.getBodySizeIfKnown(response);\n        // bodySize == null is chunked encoding which is eligible for gzip compression\n        return (bodySize == null) || (bodySize >= MIN_BODY_SIZE_FOR_GZIP.get());\n    }\n\n    @Override\n    public HttpResponseMessage apply(HttpResponseMessage response) {\n        // set Gzip headers\n        Headers respHeaders = response.getHeaders();\n        respHeaders.set(HttpHeaderNames.CONTENT_ENCODING, \"gzip\");\n        respHeaders.remove(HttpHeaderNames.CONTENT_LENGTH);\n        return response;\n    }\n\n    private boolean isGzippableContentType(HttpResponseMessage response) {\n        String ct = response.getHeaders().getFirst(HttpHeaderNames.CONTENT_TYPE);\n        if (ct != null) {\n            int charsetIndex = ct.indexOf(';');\n            if (charsetIndex > 0) {\n                ct = ct.substring(0, charsetIndex);\n            }\n            return GZIPPABLE_CONTENT_TYPES.get().contains(ct.toLowerCase(Locale.ROOT));\n        }\n        return false;\n    }\n\n    @Override\n    public HttpContent processContentChunk(ZuulMessage resp, HttpContent chunk) {\n        Gzipper gzipper = (Gzipper) resp.getContext().get(CommonContextKeys.GZIPPER);\n        gzipper.write(chunk);\n        if (chunk instanceof LastHttpContent) {\n            gzipper.finish();\n            return new DefaultLastHttpContent(gzipper.getByteBuf());\n        } else {\n            return new DefaultHttpContent(gzipper.getByteBuf());\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/common/SurgicalDebugFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters.common;\n\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.config.DynamicStringProperty;\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.constants.ZuulConstants;\nimport com.netflix.zuul.constants.ZuulHeaders;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpInboundSyncFilter;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport java.util.Objects;\n\n/**\n * This is an abstract filter that will route requests that match the patternMatches() method to a debug Eureka \"VIP\" or\n * host specified by zuul.debug.vip or zuul.debug.host.\n *\n * @author Mikey Cohen\n * Date: 6/27/12\n * Time: 12:54 PM\n */\n@Filter(order = 99, type = FilterType.INBOUND)\npublic class SurgicalDebugFilter extends HttpInboundSyncFilter {\n\n    /**\n     * Returning true by the pattern or logic implemented in this method will route the request to the specified origin\n     *\n     * Override this method when using this filter to add your own pattern matching logic.\n     *\n     * @return true if this request should be routed to the debug origin\n     */\n    protected boolean patternMatches(HttpRequestMessage request) {\n        return false;\n    }\n\n    @Override\n    public int filterOrder() {\n        return 99;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage request) {\n\n        DynamicBooleanProperty debugFilterShutoff =\n                new DynamicBooleanProperty(ZuulConstants.ZUUL_DEBUGFILTERS_DISABLED, false);\n\n        if (debugFilterShutoff.get()) {\n            return false;\n        }\n\n        if (isDisabled()) {\n            return false;\n        }\n\n        String isSurgicalFilterRequest = request.getHeaders().getFirst(ZuulHeaders.X_ZUUL_SURGICAL_FILTER);\n        // don't apply filter if it was already applied\n        boolean notAlreadyFiltered = !Objects.equals(isSurgicalFilterRequest, \"true\");\n\n        return notAlreadyFiltered && patternMatches(request);\n    }\n\n    @Override\n    public HttpRequestMessage apply(HttpRequestMessage request) {\n        DynamicStringProperty routeVip = new DynamicStringProperty(ZuulConstants.ZUUL_DEBUG_VIP, null);\n        DynamicStringProperty routeHost = new DynamicStringProperty(ZuulConstants.ZUUL_DEBUG_HOST, null);\n\n        SessionContext ctx = request.getContext();\n\n        if (routeVip.get() != null || routeHost.get() != null) {\n\n            ctx.set(\"routeHost\", routeHost.get());\n            ctx.set(\"routeVIP\", routeVip.get());\n\n            request.getHeaders().set(ZuulHeaders.X_ZUUL_SURGICAL_FILTER, \"true\");\n\n            HttpQueryParams queryParams = request.getQueryParams();\n            queryParams.set(\"debugRequest\", \"true\");\n\n            ctx.setDebugRequest(true);\n            ctx.set(\"zuulToZuul\", true);\n        }\n        return request;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/EndpointLifecycle.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.endpoint;\n\n/**\n * Lifecycle contract for endpoints that manage their own async response flow.\n *\n * <p>Endpoints that acquire origin connections, manage streams, or otherwise hold\n * resources should implement this to ensure proper cleanup when a request\n * completes or errors.\n *\n * <p>The {@link #finish(boolean)} method is called by\n * {@link com.netflix.zuul.netty.filter.ZuulFilterChainHandler#fireEndpointFinish}\n * when the request lifecycle ends.\n */\npublic interface EndpointLifecycle {\n\n    /**\n     * Called when the request completes or errors, allowing the endpoint to release\n     * resources (connections, streams, etc.).\n     *\n     * @param error {@code true} if the request ended due to an error\n     */\n    void finish(boolean error);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/MissingEndpointHandlingFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.endpoint;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.SyncZuulFilterAdapter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Created by saroskar on 2/13/17.\n */\n@Filter(order = 0, type = FilterType.ENDPOINT)\npublic final class MissingEndpointHandlingFilter\n        extends SyncZuulFilterAdapter<HttpRequestMessage, HttpResponseMessage> {\n    private final String name;\n\n    private static final Logger LOG = LoggerFactory.getLogger(MissingEndpointHandlingFilter.class);\n\n    public MissingEndpointHandlingFilter(String name) {\n        this.name = name;\n    }\n\n    @Override\n    public HttpResponseMessage apply(HttpRequestMessage request) {\n        SessionContext zuulCtx = request.getContext();\n        zuulCtx.setErrorResponseSent(true);\n        String errMesg = \"Missing Endpoint filter, name = \" + name;\n        zuulCtx.setError(new ZuulException(errMesg, true));\n        LOG.error(errMesg);\n        return new HttpResponseMessageImpl(zuulCtx, request, 500);\n    }\n\n    @Override\n    public String filterName() {\n        return name;\n    }\n\n    @Override\n    public HttpResponseMessage getDefaultOutput(HttpRequestMessage input) {\n        return HttpResponseMessageImpl.defaultErrorResponse(input);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/endpoint/ProxyEndpoint.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.endpoint;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Sets;\nimport com.google.errorprone.annotations.ForOverride;\nimport com.netflix.client.ClientException;\nimport com.netflix.client.config.IClientConfigKey;\nimport com.netflix.config.DynamicIntegerSetProperty;\nimport com.netflix.netty.common.ByteBufUtil;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.Debug;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.exception.ErrorType;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.exception.OutboundException;\nimport com.netflix.zuul.exception.RequestExpiredException;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.SyncZuulFilterAdapter;\nimport com.netflix.zuul.message.HeaderName;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpHeaderNames;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.netty.NettyRequestAttemptFactory;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport com.netflix.zuul.netty.connectionpool.BasicRequestStat;\nimport com.netflix.zuul.netty.connectionpool.ClientTimeoutHandler;\nimport com.netflix.zuul.netty.connectionpool.DefaultOriginChannelInitializer;\nimport com.netflix.zuul.netty.connectionpool.PooledConnection;\nimport com.netflix.zuul.netty.connectionpool.RequestStat;\nimport com.netflix.zuul.netty.filter.FilterRunner;\nimport com.netflix.zuul.netty.server.ClientRequestReceiver;\nimport com.netflix.zuul.netty.server.MethodBinding;\nimport com.netflix.zuul.netty.server.OriginResponseReceiver;\nimport com.netflix.zuul.netty.timeouts.OriginTimeoutManager;\nimport com.netflix.zuul.niws.RequestAttempt;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.origins.NettyOrigin;\nimport com.netflix.zuul.origins.Origin;\nimport com.netflix.zuul.origins.OriginManager;\nimport com.netflix.zuul.origins.OriginName;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport com.netflix.zuul.util.HttpUtils;\nimport com.netflix.zuul.util.ProxyUtils;\nimport com.netflix.zuul.util.VipUtils;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport io.netty.util.concurrent.Future;\nimport io.netty.util.concurrent.GenericFutureListener;\nimport io.netty.util.concurrent.Promise;\nimport io.perfmark.PerfMark;\nimport io.perfmark.TaskCloseable;\nimport java.net.InetAddress;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Not thread safe! New instance of this class is created per HTTP/1.1 request proxied to the origin but NOT for each\n * attempt/retry. All the retry attempts for a given HTTP/1.1 request proxied share the same EdgeProxyEndpoint instance\n * Created by saroskar on 5/31/17.\n */\n@Filter(order = 0, type = FilterType.ENDPOINT)\npublic class ProxyEndpoint extends SyncZuulFilterAdapter<HttpRequestMessage, HttpResponseMessage>\n        implements EndpointLifecycle, GenericFutureListener<Future<PooledConnection>> {\n\n    private static final String ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY = \"_zuul_origin_attempt_ipaddr_map\";\n    private static final String ZUUL_ORIGIN_REQUEST_URI = \"_zuul_origin_request_uri\";\n\n    private final ChannelHandlerContext channelCtx;\n    private final FilterRunner<HttpResponseMessage, ?> responseFilters;\n    protected final AtomicReference<DiscoveryResult> chosenServer;\n    protected final AtomicReference<InetAddress> chosenHostAddr;\n\n    /* Individual request related state */\n    protected final HttpRequestMessage zuulRequest;\n    protected final SessionContext context;\n\n    @Nullable\n    protected final NettyOrigin origin;\n\n    protected final RequestAttempts requestAttempts;\n    protected final CurrentPassport passport;\n    protected final NettyRequestAttemptFactory requestAttemptFactory;\n    protected final OriginTimeoutManager originTimeoutManager;\n\n    protected MethodBinding<?> methodBinding;\n    protected HttpResponseMessage zuulResponse;\n    protected boolean startedSendingResponseToClient;\n    protected Duration timeLeftForAttempt;\n\n    /* Individual retry related state */\n    private volatile PooledConnection originConn;\n    private volatile OriginResponseReceiver originResponseReceiver;\n    private AtomicInteger concurrentReqCount;\n    private volatile boolean receivedChunkAfterProxyStarted;\n    protected int attemptNum;\n    protected RequestAttempt currentRequestAttempt;\n    protected List<RequestStat> requestStats = new ArrayList<>();\n    protected RequestStat currentRequestStat;\n\n    public static final Set<String> IDEMPOTENT_HTTP_METHODS = Sets.newHashSet(\"GET\", \"HEAD\", \"OPTIONS\");\n    private static final DynamicIntegerSetProperty RETRIABLE_STATUSES_FOR_IDEMPOTENT_METHODS =\n            new DynamicIntegerSetProperty(\"zuul.retry.allowed.statuses.idempotent\", \"500\");\n\n    /**\n     * Indicates how long Zuul should remember throttle events for an origin.  As of this writing, throttling is used\n     * to decide to cache request bodies.\n     */\n    private static final Set<HeaderName> REQUEST_HEADERS_TO_REMOVE =\n            Sets.newHashSet(HttpHeaderNames.CONNECTION, HttpHeaderNames.KEEP_ALIVE);\n\n    private static final Set<HeaderName> RESPONSE_HEADERS_TO_REMOVE =\n            Sets.newHashSet(HttpHeaderNames.CONNECTION, HttpHeaderNames.KEEP_ALIVE);\n    public static final String POOLED_ORIGIN_CONNECTION_KEY = \"_origin_pooled_conn\";\n    private static final Logger logger = LoggerFactory.getLogger(ProxyEndpoint.class);\n    private static final Counter NO_RETRY_INCOMPLETE_BODY =\n            SpectatorUtils.newCounter(\"zuul.no.retry\", \"incomplete_body\");\n    private static final Counter NO_RETRY_RESP_STARTED = SpectatorUtils.newCounter(\"zuul.no.retry\", \"resp_started\");\n\n    public ProxyEndpoint(\n            HttpRequestMessage inMesg,\n            ChannelHandlerContext ctx,\n            FilterRunner<HttpResponseMessage, ?> filters,\n            MethodBinding<?> methodBinding) {\n        this(inMesg, ctx, filters, methodBinding, new NettyRequestAttemptFactory());\n    }\n\n    public ProxyEndpoint(\n            HttpRequestMessage inMesg,\n            ChannelHandlerContext ctx,\n            FilterRunner<HttpResponseMessage, ?> filters,\n            MethodBinding<?> methodBinding,\n            NettyRequestAttemptFactory requestAttemptFactory) {\n        channelCtx = ctx;\n        responseFilters = filters;\n        zuulRequest = transformRequest(inMesg);\n        context = zuulRequest.getContext();\n        origin = getOrigin(zuulRequest);\n        originTimeoutManager = getTimeoutManager(origin);\n        requestAttempts = RequestAttempts.getFromSessionContext(context);\n        passport = CurrentPassport.fromSessionContext(context);\n        chosenServer = new AtomicReference<>(DiscoveryResult.EMPTY);\n        chosenHostAddr = new AtomicReference<>();\n        concurrentReqCount = new AtomicInteger();\n\n        this.methodBinding = methodBinding;\n        this.requestAttemptFactory = requestAttemptFactory;\n    }\n\n    public int getAttemptNum() {\n        return attemptNum;\n    }\n\n    public RequestAttempts getRequestAttempts() {\n        return requestAttempts;\n    }\n\n    protected RequestAttempt getCurrentRequestAttempt() {\n        return currentRequestAttempt;\n    }\n\n    public CurrentPassport getPassport() {\n        return passport;\n    }\n\n    public NettyOrigin getOrigin() {\n        return origin;\n    }\n\n    /**\n     * Get the implementing origin.\n     * <p>\n     * Note: this method gets called in the constructor so if overloading it or any methods called within, you cannot\n     * rely on your own constructor parameters.\n     */\n    @Nullable\n    protected NettyOrigin getOrigin(HttpRequestMessage request) {\n        SessionContext context = request.getContext();\n        OriginManager<NettyOrigin> originManager =\n                (OriginManager<NettyOrigin>) context.get(CommonContextKeys.ORIGIN_MANAGER);\n        if (Debug.debugRequest(context)) {\n\n            ImmutableList.Builder<String> routingLogEntries = context.get(CommonContextKeys.ROUTING_LOG);\n            if (routingLogEntries != null) {\n                for (String entry : routingLogEntries.build()) {\n                    Debug.addRequestDebug(context, \"RoutingLog: \" + entry);\n                }\n            }\n        }\n\n        String primaryRoute = context.getRouteVIP();\n        if (Strings.isNullOrEmpty(primaryRoute)) {\n            // If no vip selected, leave origin null, then later the handleNoOriginSelected() method will be invoked.\n            return null;\n        }\n\n        NettyOrigin origin = null;\n        // allow implementers to override the origin with custom injection logic\n        OriginName overrideOriginName = injectCustomOriginName(request);\n        if (overrideOriginName != null) {\n            // Use the custom vip instead if one has been provided.\n            origin = getOrCreateOrigin(originManager, overrideOriginName, request.reconstructURI(), context);\n        } else {\n            // This is the normal flow - that a RoutingFilter has assigned a route\n            OriginName originName = getOriginName(context);\n            origin = getOrCreateOrigin(originManager, originName, request.reconstructURI(), context);\n        }\n\n        verifyOrigin(context, request, origin);\n\n        // Update the routeVip on context to show the actual raw VIP from the clientConfig of the chosen Origin.\n        if (origin != null) {\n            context.set(\n                    CommonContextKeys.ACTUAL_VIP,\n                    origin.getClientConfig().get(IClientConfigKey.Keys.DeploymentContextBasedVipAddresses));\n            context.set(\n                    CommonContextKeys.ORIGIN_VIP_SECURE,\n                    origin.getClientConfig().get(IClientConfigKey.Keys.IsSecure));\n        }\n\n        return origin;\n    }\n\n    public HttpRequestMessage getZuulRequest() {\n        return zuulRequest;\n    }\n\n    // Unlink OriginResponseReceiver from origin channel pipeline so that we no longer receive events\n    private Channel unlinkFromOrigin() {\n        if (originResponseReceiver != null) {\n            originResponseReceiver.unlinkFromClientRequest();\n            originResponseReceiver = null;\n        }\n\n        if (concurrentReqCount.get() > 0) {\n            origin.recordProxyRequestEnd();\n            concurrentReqCount.decrementAndGet();\n        }\n\n        Channel origCh = null;\n        if (originConn != null) {\n            origCh = originConn.getChannel();\n            originConn = null;\n        }\n        return origCh;\n    }\n\n    private void releasePartialResponse(HttpResponse partialResponse) {\n        if (partialResponse != null && ReferenceCountUtil.refCnt(partialResponse) > 0) {\n            ReferenceCountUtil.safeRelease(partialResponse);\n        }\n    }\n\n    @Override\n    public void finish(boolean error) {\n        Channel origCh = unlinkFromOrigin();\n\n        while (concurrentReqCount.get() > 0) {\n            origin.recordProxyRequestEnd();\n            concurrentReqCount.decrementAndGet();\n        }\n\n        if (currentRequestStat != null) {\n            if (error) {\n                currentRequestStat.generalError();\n            }\n        }\n\n        // Publish each of the request stats (ie. one for each attempt).\n        if (!requestStats.isEmpty()) {\n            int indexFinal = requestStats.size() - 1;\n            for (int i = 0; i < requestStats.size(); i++) {\n                RequestStat stat = requestStats.get(i);\n\n                // Tag the final and non-final attempts.\n                stat.finalAttempt(i == indexFinal);\n\n                stat.finishIfNotAlready();\n            }\n        }\n\n        if (error && (origCh != null)) {\n            origCh.close();\n        }\n    }\n\n    /* Zuul filter methods */\n    @Override\n    public String filterName() {\n        return \"ProxyEndpoint\";\n    }\n\n    @Override\n    public HttpResponseMessage apply(HttpRequestMessage input) {\n        // If no Origin has been selected, then just return a 404 static response.\n        // handle any exception here\n        try {\n\n            if (origin == null) {\n                handleNoOriginSelected();\n                return null;\n            }\n\n            origin.onRequestExecutionStart(zuulRequest);\n            proxyRequestToOrigin();\n\n            // Doesn't return origin response to caller, calls invokeNext() internally in response filter chain\n            return null;\n        } catch (Exception ex) {\n            handleError(ex);\n            return null;\n        }\n    }\n\n    @Override\n    public HttpContent processContentChunk(ZuulMessage zuulReq, HttpContent chunk) {\n        if (originConn != null) {\n            if (chunk instanceof LastHttpContent && !receivedChunkAfterProxyStarted) {\n                // if everything except the LastHttpContent was buffered, then buffer the last chunk so this request\n                // is considered replayable\n                zuulReq.bufferBodyContents(chunk.retain());\n            }\n\n            // Connected to origin, stream request body without buffering\n            receivedChunkAfterProxyStarted = true;\n\n            ByteBufUtil.touch(chunk, \"ProxyEndpoint writing chunk to origin, request: \", zuulReq);\n            originConn.getChannel().writeAndFlush(chunk);\n            return null;\n        }\n\n        // Not connected to origin yet, let caller buffer the request body\n        ByteBufUtil.touch(chunk, \"ProxyEndpoint buffering chunk to origin, request: \", zuulReq);\n        return chunk;\n    }\n\n    @Override\n    public HttpResponseMessage getDefaultOutput(HttpRequestMessage input) {\n        return null;\n    }\n\n    public void invokeNext(HttpResponseMessage zuulResponse) {\n        try {\n            methodBinding.bind(() -> filterResponse(zuulResponse));\n        } catch (Exception ex) {\n            unlinkFromOrigin();\n            logger.error(\"Error in invokeNext resp\", ex);\n            channelCtx.fireExceptionCaught(ex);\n        }\n    }\n\n    public void invokeNext(HttpContent chunk) {\n        try {\n            ByteBufUtil.touch(chunk, \"ProxyEndpoint received chunk from origin, request: \", zuulRequest);\n            methodBinding.bind(() -> filterResponseChunk(chunk));\n        } catch (Exception ex) {\n            ByteBufUtil.touch(chunk, \"ProxyEndpoint exception processing chunk from origin, request: \", zuulRequest);\n            unlinkFromOrigin();\n            logger.error(\"Error in invokeNext content\", ex);\n            channelCtx.fireExceptionCaught(ex);\n        }\n    }\n\n    private void filterResponse(HttpResponseMessage zuulResponse) {\n        if (responseFilters != null) {\n            responseFilters.filter(zuulResponse);\n        } else {\n            channelCtx.fireChannelRead(zuulResponse);\n        }\n    }\n\n    private void filterResponseChunk(HttpContent chunk) {\n        if (context.isCancelled() || !channelCtx.channel().isActive()) {\n            SpectatorUtils.newCounter(\n                            \"zuul.origin.strayChunk\",\n                            origin == null ? \"none\" : origin.getName().getMetricId())\n                    .increment();\n            unlinkFromOrigin();\n            ReferenceCountUtil.safeRelease(chunk);\n            return;\n        }\n\n        if (chunk instanceof LastHttpContent) {\n            unlinkFromOrigin();\n        }\n\n        if (responseFilters != null) {\n            responseFilters.filter(zuulResponse, chunk);\n        } else {\n            channelCtx.fireChannelRead(chunk);\n        }\n    }\n\n    private void storeAndLogOriginRequestInfo() {\n        Map<String, Object> eventProps = context.getEventProperties();\n        // These two maps appear to be almost the same but are slightly different.   Also, the types in the map don't\n        // match exactly what needs to happen, so this is more of a To-Do.  ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY is\n        // supposed to be the mapping of IP addresses of the server.  This is (AFAICT) only used for logging.   It is\n        // an IP address semantically, but a String here.   The two should be swapped.\n        // ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY is almost always an IP address, but may some times be a hostname in\n        // case the discovery info is not an IP.\n        Map<Integer, String> attemptToIpAddressMap =\n                (Map<Integer, String>) eventProps.get(ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY);\n        Map<Integer, InetAddress> attemptToChosenHostMap = (Map<Integer, InetAddress>)\n                eventProps.get(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY.name());\n        if (attemptToIpAddressMap == null) {\n            attemptToIpAddressMap = new HashMap<>();\n        }\n        if (attemptToChosenHostMap == null) {\n            attemptToChosenHostMap = new HashMap<>();\n        }\n\n        // the chosen server can be null in the case of a timeout exception that skips acquiring a new origin connection\n        String ipAddr = origin.getIpAddrFromServer(chosenServer.get());\n        if (ipAddr != null) {\n            attemptToIpAddressMap.put(attemptNum, ipAddr);\n            eventProps.put(ZUUL_ORIGIN_ATTEMPT_IPADDR_MAP_KEY, attemptToIpAddressMap);\n        }\n\n        if (chosenHostAddr.get() != null) {\n            attemptToChosenHostMap.put(attemptNum, chosenHostAddr.get());\n            eventProps.put(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY.name(), attemptToChosenHostMap);\n            context.put(CommonContextKeys.ZUUL_ORIGIN_CHOSEN_HOST_ADDR_MAP_KEY, attemptToChosenHostMap);\n        }\n\n        eventProps.put(ZUUL_ORIGIN_REQUEST_URI, zuulRequest.getPathAndQuery());\n    }\n\n    protected void updateOriginRpsTrackers(NettyOrigin origin, int attempt) {\n        // override\n    }\n\n    private void proxyRequestToOrigin() {\n        Promise<PooledConnection> promise = null;\n        try {\n            attemptNum += 1;\n\n            /*\n             * Before connecting to the origin, we need to compute how much time we have left for this attempt. This\n             * method is also intended to validate deadline and timeouts boundaries for the request as a whole and could\n             * throw an exception, skipping the logic below.\n             */\n            timeLeftForAttempt = originTimeoutManager.computeReadTimeout(zuulRequest, attemptNum);\n\n            currentRequestStat = createRequestStat();\n            origin.preRequestChecks(zuulRequest);\n            concurrentReqCount.incrementAndGet();\n\n            // update RPS trackers\n            updateOriginRpsTrackers(origin, attemptNum);\n\n            // We pass this AtomicReference<Server> here and the origin impl will assign the chosen server to it.\n            promise = origin.connectToOrigin(\n                    zuulRequest, channelCtx.channel().eventLoop(), attemptNum, passport, chosenServer, chosenHostAddr);\n\n            storeAndLogOriginRequestInfo();\n            currentRequestAttempt =\n                    origin.newRequestAttempt(chosenServer.get(), chosenHostAddr.get(), context, attemptNum);\n            requestAttempts.add(currentRequestAttempt);\n            passport.add(PassportState.ORIGIN_CONN_ACQUIRE_START);\n\n            if (promise.isDone()) {\n                operationComplete(promise);\n            } else {\n                promise.addListener(this);\n            }\n        } catch (Exception ex) {\n            if (ex instanceof RequestExpiredException) {\n                logger.debug(\"Request deadline expired while connecting to origin, UUID {}\", context.getUUID(), ex);\n            } else {\n                logger.error(\"Error while connecting to origin, UUID {}\", context.getUUID(), ex);\n            }\n            storeAndLogOriginRequestInfo();\n            if (promise != null && !promise.isDone()) {\n                promise.setFailure(ex);\n            } else {\n                errorFromOrigin(ex);\n            }\n        }\n    }\n\n    /**\n     * Override to track your own request stats.\n     */\n    protected RequestStat createRequestStat() {\n        BasicRequestStat basicRequestStat = new BasicRequestStat();\n        requestStats.add(basicRequestStat);\n        RequestStat.putInSessionContext(basicRequestStat, context);\n        return basicRequestStat;\n    }\n\n    @Override\n    public void operationComplete(Future<PooledConnection> connectResult) {\n        // MUST run this within bindingcontext to support ThreadVariables.\n        try {\n            methodBinding.bind(() -> {\n                DiscoveryResult server = chosenServer.get();\n\n                /* TODO(argha-c): This reliance on mutable update of the `chosenServer` must be improved.\n                 * @see DiscoveryResult.EMPTY indicates that the loadbalancer found no available servers.\n                 */\n                if (!Objects.equals(server, DiscoveryResult.EMPTY)) {\n                    if (currentRequestStat != null) {\n                        currentRequestStat.server(server);\n                    }\n\n                    origin.onRequestStartWithServer(zuulRequest, server, attemptNum);\n                }\n\n                // Handle the connection establishment result.\n                if (connectResult.isSuccess()) {\n                    onOriginConnectSucceeded(connectResult.getNow(), timeLeftForAttempt);\n                } else {\n                    onOriginConnectFailed(connectResult.cause());\n                }\n            });\n        } catch (Throwable ex) {\n            logger.error(\n                    \"Uncaught error in operationComplete(). Closing the server channel now. {}\",\n                    ChannelUtils.channelInfoForLogging(channelCtx.channel()),\n                    ex);\n\n            unlinkFromOrigin();\n\n            // Fire exception here to ensure that server channel gets closed, so clients don't hang.\n            channelCtx.fireExceptionCaught(ex);\n        }\n    }\n\n    private void onOriginConnectSucceeded(PooledConnection conn, Duration readTimeout) {\n        passport.add(PassportState.ORIGIN_CONN_ACQUIRE_END);\n\n        if (context.isCancelled()) {\n            logger.info(\"Client cancelled after successful origin connect: {}\", conn.getChannel());\n\n            // conn isn't actually busy so we can put it in the pool\n            conn.setConnectionState(PooledConnection.ConnectionState.WRITE_READY);\n            conn.release();\n        } else {\n            // Update the RequestAttempt to reflect the readTimeout chosen.\n            currentRequestAttempt.setReadTimeout(readTimeout.toMillis());\n\n            // Start sending the request to origin now.\n            writeClientRequestToOrigin(conn, readTimeout);\n        }\n    }\n\n    private void onOriginConnectFailed(Throwable cause) {\n        passport.add(PassportState.ORIGIN_CONN_ACQUIRE_FAILED);\n        if (!context.isCancelled()) {\n            errorFromOrigin(cause);\n        }\n    }\n\n    private void writeClientRequestToOrigin(PooledConnection conn, Duration readTimeout) {\n        Channel ch = conn.getChannel();\n        passport.setOnChannel(ch);\n\n        // set read timeout on origin channel\n        ch.attr(ClientTimeoutHandler.ORIGIN_RESPONSE_READ_TIMEOUT).set(readTimeout);\n\n        context.put(CommonContextKeys.ORIGIN_CHANNEL, ch);\n        context.set(POOLED_ORIGIN_CONNECTION_KEY, conn);\n\n        preWriteToOrigin(chosenServer.get(), zuulRequest);\n\n        ChannelPipeline pipeline = ch.pipeline();\n        originResponseReceiver = getOriginResponseReceiver();\n        pipeline.addBefore(\n                DefaultOriginChannelInitializer.CONNECTION_POOL_HANDLER,\n                OriginResponseReceiver.CHANNEL_HANDLER_NAME,\n                originResponseReceiver);\n\n        ch.write(zuulRequest);\n        writeBufferedBodyContent(zuulRequest, ch);\n        ch.flush();\n\n        // Get ready to read origin's response\n        syncClientAndOriginChannels(channelCtx.channel(), ch);\n        ch.read();\n\n        originConn = conn;\n        channelCtx.read();\n    }\n\n    protected void syncClientAndOriginChannels(Channel clientChannel, Channel originChannel) {\n        // Add override for custom syncing between client and origin channels.\n    }\n\n    protected OriginResponseReceiver getOriginResponseReceiver() {\n        return new OriginResponseReceiver(this);\n    }\n\n    protected void preWriteToOrigin(DiscoveryResult chosenServer, HttpRequestMessage zuulRequest) {\n        // override for custom metrics or processing\n    }\n\n    private static void writeBufferedBodyContent(HttpRequestMessage zuulRequest, Channel channel) {\n        zuulRequest.getBodyContents().forEach((chunk) -> {\n            channel.write(chunk.retain());\n        });\n    }\n\n    protected boolean isRemoteZuulRetriesBelowRetryLimit(int maxAllowedRetries) {\n        // override for custom header checking..\n        return true;\n    }\n\n    protected boolean isBelowRetryLimit() {\n        int maxAllowedRetries = origin.getMaxRetriesForRequest(context);\n        return (attemptNum <= maxAllowedRetries) && isRemoteZuulRetriesBelowRetryLimit(maxAllowedRetries);\n    }\n\n    public void errorFromOrigin(Throwable ex) {\n        try {\n            // Flag that there was an origin server related error for the loadbalancer to choose\n            // whether to circuit-trip this server.\n            if (originConn != null) {\n                // NOTE: if originConn is null, then these stats will have been incremented within\n                // PerServerConnectionPool\n                // so don't need to be here.\n                originConn.getServer().incrementSuccessiveConnectionFailureCount();\n                originConn.getServer().addToFailureCount();\n\n                originConn.flagShouldClose();\n            }\n\n            // detach from current origin\n            Channel originCh = unlinkFromOrigin();\n\n            methodBinding.bind(() -> processErrorFromOrigin(ex, originCh));\n        } catch (Exception e) {\n            channelCtx.fireExceptionCaught(ex);\n        }\n    }\n\n    private void processErrorFromOrigin(Throwable ex, Channel origCh) {\n        try {\n            SessionContext zuulCtx = context;\n            ErrorType err = requestAttemptFactory.mapNettyToOutboundErrorType(ex);\n\n            // Be cautious about how much we log about errors from origins, as it can have perf implications at high\n            // rps.\n            if (zuulCtx.isInBrownoutMode()) {\n                // Don't include the stacktrace or the channel info.\n                logger.warn(\n                        \"{}, origin = {}: {}\", err.getStatusCategory().name(), origin.getName(), String.valueOf(ex));\n            } else {\n                String origChInfo = (origCh != null) ? ChannelUtils.channelInfoForLogging(origCh) : \"\";\n                if (logger.isInfoEnabled()) {\n                    // Include the stacktrace.\n                    logger.warn(\n                            \"{}, origin = {}, origin channel info = {}\",\n                            err.getStatusCategory().name(),\n                            origin.getName(),\n                            origChInfo,\n                            ex);\n                } else {\n                    logger.warn(\n                            \"{}, origin = {}, {}, origin channel info = {}\",\n                            err.getStatusCategory().name(),\n                            origin.getName(),\n                            String.valueOf(ex),\n                            origChInfo);\n                }\n            }\n\n            // Update the NIWS stat.\n            if (currentRequestStat != null) {\n                currentRequestStat.failAndSetErrorCode(err);\n            }\n\n            // Update RequestAttempt info.\n            if (currentRequestAttempt != null) {\n                currentRequestAttempt.complete(-1, currentRequestStat.duration(), ex);\n            }\n\n            postErrorProcessing(ex, zuulCtx, err, chosenServer.get(), attemptNum);\n\n            ClientException niwsEx = new ClientException(\n                    ClientException.ErrorType.valueOf(err.getClientErrorType().name()));\n            if (!Objects.equals(chosenServer.get(), DiscoveryResult.EMPTY)) {\n                origin.onRequestExceptionWithServer(zuulRequest, chosenServer.get(), attemptNum, niwsEx);\n            }\n\n            boolean retryable = isRetryable(err);\n            if (retryable) {\n                origin.adjustRetryPolicyIfNeeded(zuulRequest);\n            }\n\n            if (retryable && isBelowRetryLimit()) {\n                // retry request with different origin\n                passport.add(PassportState.ORIGIN_RETRY_START);\n                proxyRequestToOrigin();\n            } else {\n                // Record the exception in context. An error filter should later run which can translate this into an\n                // app-specific error response if needed.\n                zuulCtx.setError(ex);\n                zuulCtx.setShouldSendErrorResponse(true);\n\n                StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(zuulCtx, err.getStatusCategory());\n                origin.recordFinalError(zuulRequest, ex);\n                origin.onRequestExecutionFailed(zuulRequest, chosenServer.get(), attemptNum - 1, niwsEx);\n\n                // Send error response to client\n                handleError(ex);\n            }\n        } catch (Exception e) {\n            // Use original origin returned exception\n            handleError(ex);\n        }\n    }\n\n    protected void postErrorProcessing(\n            Throwable ex, SessionContext zuulCtx, ErrorType err, DiscoveryResult chosenServer, int attemptNum) {\n        // override for custom processing\n    }\n\n    private void handleError(Throwable cause) {\n        ZuulException ze = (cause instanceof ZuulException)\n                ? (ZuulException) cause\n                : requestAttemptFactory.mapNettyToOutboundException(cause, context);\n        logger.debug(\"Proxy endpoint failed.\", cause);\n        if (!startedSendingResponseToClient) {\n            startedSendingResponseToClient = true;\n            zuulResponse = new HttpResponseMessageImpl(context, zuulRequest, ze.getStatusCode());\n            zuulResponse\n                    .getHeaders()\n                    .add(\n                            \"Connection\",\n                            \"close\"); // TODO - why close the connection? maybe don't always want this to happen ...\n            zuulResponse.finishBufferedBodyIfIncomplete();\n            invokeNext(zuulResponse);\n        } else {\n            channelCtx.fireExceptionCaught(ze);\n        }\n    }\n\n    private void handleNoOriginSelected() {\n        StatusCategoryUtils.setStatusCategory(context, ZuulStatusCategory.SUCCESS_LOCAL_NO_ROUTE);\n        startedSendingResponseToClient = true;\n        zuulResponse = new HttpResponseMessageImpl(context, zuulRequest, 404);\n        zuulResponse.finishBufferedBodyIfIncomplete();\n        invokeNext(zuulResponse);\n    }\n\n    protected boolean isRetryable(ErrorType err) {\n        if ((err == OutboundErrorType.RESET_CONNECTION)\n                || (err == OutboundErrorType.CONNECT_ERROR)\n                || (err == OutboundErrorType.READ_TIMEOUT\n                        && IDEMPOTENT_HTTP_METHODS.contains(\n                                zuulRequest.getMethod().toUpperCase(Locale.ROOT)))) {\n            return isRequestReplayable();\n        }\n        return false;\n    }\n\n    /**\n     * Request is replayable on a different origin IFF\n     * A) we have not started to send response back to the client  AND\n     * B) we have not lost any of its body chunks\n     */\n    protected boolean isRequestReplayable() {\n        if (startedSendingResponseToClient) {\n            NO_RETRY_RESP_STARTED.increment();\n            return false;\n        }\n        if (!zuulRequest.hasCompleteBody()) {\n            NO_RETRY_INCOMPLETE_BODY.increment();\n            return false;\n        }\n        return true;\n    }\n\n    public void responseFromOrigin(HttpResponse originResponse) {\n        try (TaskCloseable ignore = PerfMark.traceTask(\"ProxyEndpoint.responseFromOrigin\")) {\n            PerfMark.attachTag(\"uuid\", zuulRequest, r -> r.getContext().getUUID());\n            PerfMark.attachTag(\"path\", zuulRequest, HttpRequestInfo::getPath);\n            ByteBufUtil.touch(originResponse, \"ProxyEndpoint handling response from origin, request: \", zuulRequest);\n            methodBinding.bind(() -> processResponseFromOrigin(originResponse));\n        } catch (Exception ex) {\n            unlinkFromOrigin();\n            releasePartialResponse(originResponse);\n            logger.error(\"Error in responseFromOrigin\", ex);\n            channelCtx.fireExceptionCaught(ex);\n        }\n    }\n\n    private void processResponseFromOrigin(HttpResponse originResponse) {\n        if (originResponse.status().code() >= 500) {\n            handleOriginNonSuccessResponse(originResponse, chosenServer.get());\n        } else {\n            handleOriginSuccessResponse(originResponse, chosenServer.get());\n        }\n    }\n\n    protected void handleOriginSuccessResponse(HttpResponse originResponse, DiscoveryResult chosenServer) {\n        origin.recordSuccessResponse();\n        if (originConn != null) {\n            originConn.getServer().clearSuccessiveConnectionFailureCount();\n        }\n        int respStatus = originResponse.status().code();\n        long duration = 0;\n        if (currentRequestStat != null) {\n            currentRequestStat.updateWithHttpStatusCode(respStatus);\n            duration = currentRequestStat.duration();\n        }\n        if (currentRequestAttempt != null) {\n            currentRequestAttempt.complete(respStatus, duration, null);\n        }\n        // separate nfstatus for 404 so that we can notify origins\n        ByteBufUtil.touch(originResponse, \"ProxyEndpoint handling successful response, request: \", zuulRequest);\n        StatusCategory statusCategory =\n                respStatus == 404 ? ZuulStatusCategory.SUCCESS_NOT_FOUND : ZuulStatusCategory.SUCCESS;\n        zuulResponse = buildZuulHttpResponse(originResponse, statusCategory, context.getError());\n        invokeNext(zuulResponse);\n    }\n\n    private HttpResponseMessage buildZuulHttpResponse(\n            HttpResponse httpResponse, StatusCategory statusCategory, Throwable ex) {\n        startedSendingResponseToClient = true;\n\n        // Translate the netty HttpResponse into a zuul HttpResponseMessage.\n        SessionContext zuulCtx = context;\n        int respStatus = httpResponse.status().code();\n        HttpResponseMessage zuulResponse = new HttpResponseMessageImpl(zuulCtx, zuulRequest, respStatus);\n\n        Headers respHeaders = zuulResponse.getHeaders();\n        for (Map.Entry<String, String> entry : httpResponse.headers()) {\n            respHeaders.add(entry.getKey(), entry.getValue());\n        }\n\n        // Try to decide if this response has a body or not based on the headers (as we won't yet have\n        // received any of the content).\n        // NOTE that we also later may override this if it is Chunked encoding, but we receive\n        // a LastHttpContent without any prior HttpContent's.\n        if (HttpUtils.hasChunkedTransferEncodingHeader(zuulResponse)\n                || HttpUtils.hasNonZeroContentLengthHeader(zuulResponse)) {\n            zuulResponse.setHasBody(true);\n        }\n\n        // Store this original response info for future reference (ie. for metrics and access logging purposes).\n        zuulResponse.storeInboundResponse();\n        channelCtx.channel().attr(ClientRequestReceiver.ATTR_ZUUL_RESP).set(zuulResponse);\n\n        if (httpResponse instanceof DefaultFullHttpResponse) {\n            ByteBufUtil.touch(\n                    httpResponse, \"ProxyEndpoint converting Netty response to Zuul response, request: \", zuulRequest);\n            ByteBuf chunk = ((DefaultFullHttpResponse) httpResponse).content();\n            zuulResponse.bufferBodyContents(new DefaultLastHttpContent(chunk));\n        }\n\n        // Invoke any Ribbon execution listeners.\n        // Request was a success even if server may have responded with an error code 5XX.\n        if (originConn != null) {\n            origin.onRequestExecutionSuccess(zuulRequest, zuulResponse, originConn.getServer(), attemptNum);\n        }\n\n        // Collect some info about the received response.\n        origin.recordFinalResponse(zuulResponse);\n        origin.recordFinalError(zuulRequest, ex);\n        StatusCategoryUtils.setStatusCategory(zuulCtx, statusCategory);\n        zuulCtx.setError(ex);\n        zuulCtx.put(\"origin_http_status\", Integer.toString(respStatus));\n\n        return transformResponse(zuulResponse);\n    }\n\n    private HttpResponseMessage transformResponse(HttpResponseMessage resp) {\n        RESPONSE_HEADERS_TO_REMOVE.stream().forEach(s -> resp.getHeaders().remove(s));\n        return resp;\n    }\n\n    protected void handleOriginNonSuccessResponse(HttpResponse originResponse, DiscoveryResult chosenServer) {\n        int respStatus = originResponse.status().code();\n        OutboundException obe;\n        StatusCategory statusCategory;\n        ClientException.ErrorType niwsErrorType;\n\n        if (respStatus == 503) {\n            statusCategory = ZuulStatusCategory.FAILURE_ORIGIN_THROTTLED;\n            niwsErrorType = ClientException.ErrorType.SERVER_THROTTLED;\n            obe = new OutboundException(OutboundErrorType.SERVICE_UNAVAILABLE, requestAttempts);\n            if (currentRequestStat != null) {\n                currentRequestStat.updateWithHttpStatusCode(respStatus);\n                currentRequestStat.serviceUnavailable();\n            }\n        } else {\n            statusCategory = ZuulStatusCategory.FAILURE_ORIGIN;\n            niwsErrorType = ClientException.ErrorType.GENERAL;\n            obe = new OutboundException(OutboundErrorType.ERROR_STATUS_RESPONSE, requestAttempts);\n            if (currentRequestStat != null) {\n                currentRequestStat.updateWithHttpStatusCode(respStatus);\n                currentRequestStat.generalError();\n            }\n        }\n        obe.setStatusCode(respStatus);\n\n        long duration = 0;\n        if (currentRequestStat != null) {\n            duration = currentRequestStat.duration();\n        }\n\n        if (currentRequestAttempt != null) {\n            currentRequestAttempt.complete(respStatus, duration, obe);\n        }\n\n        // Flag this error with the ExecutionListener.\n        origin.onRequestExceptionWithServer(zuulRequest, chosenServer, attemptNum, new ClientException(niwsErrorType));\n\n        boolean retryable5xxResponse = isRetryable5xxResponse(zuulRequest, originResponse);\n        if (retryable5xxResponse) {\n            origin.originRetryPolicyAdjustmentIfNeeded(zuulRequest, originResponse);\n            origin.adjustRetryPolicyIfNeeded(zuulRequest);\n        }\n\n        if (retryable5xxResponse && isBelowRetryLimit()) {\n            logger.debug(\n                    \"Retrying: status={}, attemptNum={}, maxRetries={}, startedSendingResponseToClient={},\"\n                            + \" hasCompleteBody={}, method={}\",\n                    respStatus,\n                    attemptNum,\n                    origin.getMaxRetriesForRequest(context),\n                    startedSendingResponseToClient,\n                    zuulRequest.hasCompleteBody(),\n                    zuulRequest.getMethod());\n            // detach from current origin.\n            ByteBufUtil.touch(originResponse, \"ProxyEndpoint handling non-success retry, request: \", zuulRequest);\n            unlinkFromOrigin();\n            releasePartialResponse(originResponse);\n\n            // ensure body reader indexes are reset so retry is able to access the body buffer\n            // otherwise when the body is read by netty (in writeBufferedBodyContent) the body will appear empty\n            zuulRequest.resetBodyReader();\n\n            // retry request with different origin\n            passport.add(PassportState.ORIGIN_RETRY_START);\n            proxyRequestToOrigin();\n        } else {\n            SessionContext zuulCtx = context;\n            logger.info(\n                    \"Sending error to client: status={}, attemptNum={}, maxRetries={},\"\n                            + \" startedSendingResponseToClient={}, hasCompleteBody={}, method={}\",\n                    respStatus,\n                    attemptNum,\n                    origin.getMaxRetriesForRequest(zuulCtx),\n                    startedSendingResponseToClient,\n                    zuulRequest.hasCompleteBody(),\n                    zuulRequest.getMethod());\n            // This is a final response after all retries that will go to the client\n            ByteBufUtil.touch(originResponse, \"ProxyEndpoint handling non-success response, request: \", zuulRequest);\n            zuulResponse = buildZuulHttpResponse(originResponse, statusCategory, obe);\n            invokeNext(zuulResponse);\n        }\n    }\n\n    public boolean isRetryable5xxResponse(\n            HttpRequestMessage zuulRequest, HttpResponse originResponse) { // int retryNum, int maxRetries) {\n        if (isRequestReplayable()) {\n            int status = originResponse.status().code();\n            if (status == 503 || originIndicatesRetryableInternalServerError(originResponse)) {\n                return true;\n            }\n            // Retry if this is an idempotent http method AND status code was retriable for idempotent methods.\n            else if (RETRIABLE_STATUSES_FOR_IDEMPOTENT_METHODS.get().contains(status)\n                    && IDEMPOTENT_HTTP_METHODS.contains(zuulRequest.getMethod().toUpperCase(Locale.ROOT))) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    protected boolean originIndicatesRetryableInternalServerError(HttpResponse response) {\n        // override for custom origin headers for retry\n        return false;\n    }\n\n    /* static utility methods */\n\n    protected HttpRequestMessage transformRequest(HttpRequestMessage requestMsg) {\n        HttpRequestMessage massagedRequest = massageRequestURI(requestMsg);\n\n        Headers headers = massagedRequest.getHeaders();\n        REQUEST_HEADERS_TO_REMOVE.forEach(headerName -> headers.remove(headerName.getName()));\n\n        addCustomRequestHeaders(headers);\n\n        // Add X-Forwarded headers if not already there.\n        ProxyUtils.addXForwardedHeaders(massagedRequest);\n\n        return massagedRequest;\n    }\n\n    protected void addCustomRequestHeaders(Headers headers) {\n        // override to add custom headers\n    }\n\n    @VisibleForTesting\n    static HttpRequestMessage massageRequestURI(HttpRequestMessage request) {\n        SessionContext context = request.getContext();\n        String modifiedPath;\n        HttpQueryParams modifiedQueryParams = null;\n        String uri = null;\n\n        if (context.get(\"requestURI\") != null) {\n            uri = (String) context.get(\"requestURI\");\n        }\n\n        // If another filter has specified an overrideURI, then use that instead of requested URI.\n        Object override = context.get(\"overrideURI\");\n        if (override != null) {\n            uri = override.toString();\n        }\n\n        if (uri != null) {\n            int index = uri.indexOf('?');\n            if (index != -1) {\n                // Strip the query string off of the URI.\n                String paramString = uri.substring(index + 1);\n                modifiedPath = uri.substring(0, index);\n                modifiedQueryParams = HttpQueryParams.parse(paramString);\n            } else {\n                modifiedPath = uri;\n            }\n\n            request.setPath(modifiedPath);\n            if (modifiedQueryParams != null) {\n                request.setQueryParams(modifiedQueryParams);\n            }\n        }\n\n        return request;\n    }\n\n    @Nonnull\n    protected OriginName getOriginName(SessionContext context) {\n        String clientName = getClientName(context);\n        return OriginName.fromVip(context.getRouteVIP(), clientName);\n    }\n\n    @Nonnull\n    protected String getClientName(SessionContext context) {\n        // make sure the restClientName will never be a raw VIP in cases where it's the fallback for another route\n        // assignment\n        String restClientVIP = context.getRouteVIP();\n        boolean useFullName = context.getBoolean(CommonContextKeys.USE_FULL_VIP_NAME);\n        return useFullName ? restClientVIP : VipUtils.getVIPPrefix(restClientVIP);\n    }\n\n    /**\n     * Inject your own custom VIP based on your own processing\n     * <p>\n     * Note: this method gets called in the constructor so if overloading it or any methods called within, you cannot\n     * rely on your own constructor parameters.\n     *\n     * @return {@code null} if unused.\n     */\n    @Nullable\n    protected OriginName injectCustomOriginName(HttpRequestMessage request) {\n        // override for custom vip injection\n        return null;\n    }\n\n    private NettyOrigin getOrCreateOrigin(\n            OriginManager<NettyOrigin> originManager, OriginName originName, String uri, SessionContext ctx) {\n        NettyOrigin origin = originManager.getOrigin(originName, uri, ctx);\n        if (origin == null) {\n            // If no pre-registered and configured RestClient found for this VIP, then register one using default NIWS\n            // properties.\n            logger.debug(\n                    \"Attempting to register RestClient for client that has not been configured. originName={}, uri={}\",\n                    originName,\n                    uri);\n            origin = originManager.createOrigin(originName, uri, ctx);\n        }\n        return origin;\n    }\n\n    private void verifyOrigin(SessionContext context, HttpRequestMessage request, Origin primaryOrigin) {\n        if (primaryOrigin == null) {\n            String vip = context.getRouteVIP();\n            // If no origin found then add specific error-cause metric tag, and throw an exception with 404 status.\n            StatusCategoryUtils.setStatusCategory(\n                    context,\n                    ZuulStatusCategory.SUCCESS_LOCAL_NO_ROUTE,\n                    \"Unable to find an origin client matching `\" + vip + \"` to handle request\");\n            String causeName = \"RESTCLIENT_NOTFOUND\";\n            originNotFound(context, causeName);\n            ZuulException ze = new ZuulException(\n                    \"No origin found for request. name=\" + vip + \", uri=\" + request.reconstructURI(), causeName);\n            ze.setStatusCode(404);\n            throw ze;\n        }\n    }\n\n    @ForOverride\n    protected void originNotFound(SessionContext context, String causeName) {\n        // override for metrics or custom processing\n    }\n\n    @ForOverride\n    protected OriginTimeoutManager getTimeoutManager(NettyOrigin origin) {\n        return new OriginTimeoutManager(origin);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/http/HttpInboundFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters.http;\n\nimport com.netflix.zuul.filters.BaseFilter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/29/15\n * Time: 3:22 PM\n */\npublic abstract class HttpInboundFilter extends BaseFilter<HttpRequestMessage, HttpRequestMessage> {\n    @Override\n    public FilterType filterType() {\n        return FilterType.INBOUND;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/http/HttpInboundSyncFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters.http;\n\nimport com.netflix.zuul.filters.BaseSyncFilter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/29/15\n * Time: 3:22 PM\n */\npublic abstract class HttpInboundSyncFilter extends BaseSyncFilter<HttpRequestMessage, HttpRequestMessage> {\n    @Override\n    public FilterType filterType() {\n        return FilterType.INBOUND;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/http/HttpOutboundFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters.http;\n\nimport com.netflix.zuul.filters.BaseFilter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/29/15\n * Time: 3:23 PM\n */\npublic abstract class HttpOutboundFilter extends BaseFilter<HttpResponseMessage, HttpResponseMessage> {\n    @Override\n    public FilterType filterType() {\n        return FilterType.OUTBOUND;\n    }\n\n    @Override\n    public HttpResponseMessage getDefaultOutput(HttpResponseMessage input) {\n        return input;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/http/HttpOutboundSyncFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters.http;\n\nimport com.netflix.zuul.filters.BaseSyncFilter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/29/15\n * Time: 3:23 PM\n */\npublic abstract class HttpOutboundSyncFilter extends BaseSyncFilter<HttpResponseMessage, HttpResponseMessage> {\n    @Override\n    public FilterType filterType() {\n        return FilterType.OUTBOUND;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/http/HttpSyncEndpoint.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.http;\n\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.zuul.filters.Endpoint;\nimport com.netflix.zuul.filters.SyncZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport rx.Observable;\nimport rx.Subscriber;\n\n/**\n * User: Mike Smith\n * Date: 6/16/15\n * Time: 12:23 AM\n */\npublic abstract class HttpSyncEndpoint extends Endpoint<HttpRequestMessage, HttpResponseMessage>\n        implements SyncZuulFilter<HttpRequestMessage, HttpResponseMessage> {\n    // Feature flag for enabling this while we get some real data for the impact.\n    private static final CachedDynamicBooleanProperty WAIT_FOR_LASTCONTENT =\n            new CachedDynamicBooleanProperty(\"zuul.endpoint.sync.wait_for_lastcontent\", true);\n\n    private static final String KEY_FOR_SUBSCRIBER = \"_HttpSyncEndpoint_subscriber\";\n\n    @Override\n    public HttpResponseMessage getDefaultOutput(HttpRequestMessage request) {\n        return HttpResponseMessageImpl.defaultErrorResponse(request);\n    }\n\n    @Override\n    public Observable<HttpResponseMessage> applyAsync(HttpRequestMessage input) {\n        if (WAIT_FOR_LASTCONTENT.get() && !input.hasCompleteBody()) {\n            // Return an observable that won't complete until after we have received the LastContent from client (ie.\n            // that we've\n            // received the whole request body), so that we can't potentially corrupt the clients' http state on this\n            // connection.\n            return Observable.create(subscriber -> {\n                ZuulMessage response = this.apply(input);\n                ResponseState state = new ResponseState(response, subscriber);\n                input.getContext().set(KEY_FOR_SUBSCRIBER, state);\n            });\n        } else {\n            return Observable.just(this.apply(input));\n        }\n    }\n\n    @Override\n    public HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk) {\n        // Only call onNext() after we've received the LastContent of request from client.\n        if (chunk instanceof LastHttpContent) {\n            ResponseState state = (ResponseState) zuulMessage.getContext().get(KEY_FOR_SUBSCRIBER);\n            if (state != null) {\n                state.subscriber.onNext(state.response);\n                state.subscriber.onCompleted();\n                zuulMessage.getContext().remove(KEY_FOR_SUBSCRIBER);\n            }\n        }\n        return super.processContentChunk(zuulMessage, chunk);\n    }\n\n    @Override\n    public void incrementConcurrency() {\n        // NOOP, since this is supposed to be a SYNC filter in spirit\n    }\n\n    @Override\n    public void decrementConcurrency() {\n        // NOOP, since this is supposed to be a SYNC filter in spirit\n    }\n\n    private static class ResponseState {\n        final ZuulMessage response;\n        final Subscriber subscriber;\n\n        public ResponseState(ZuulMessage response, Subscriber subscriber) {\n            this.response = response;\n            this.subscriber = subscriber;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/passport/InboundPassportStampingFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.passport;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.passport.PassportState;\n\n/**\n * Created by saroskar on 3/14/17.\n */\n@Filter(order = 0, type = FilterType.INBOUND)\npublic final class InboundPassportStampingFilter extends PassportStampingFilter<HttpRequestMessage> {\n\n    public InboundPassportStampingFilter(PassportState stamp) {\n        super(stamp);\n    }\n\n    @Override\n    public FilterType filterType() {\n        return FilterType.INBOUND;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/passport/OutboundPassportStampingFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.passport;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.passport.PassportState;\n\n/**\n * Created by saroskar on 3/14/17.\n */\n@Filter(order = 0, type = FilterType.OUTBOUND)\npublic final class OutboundPassportStampingFilter extends PassportStampingFilter<HttpResponseMessage> {\n\n    public OutboundPassportStampingFilter(PassportState stamp) {\n        super(stamp);\n    }\n\n    @Override\n    public FilterType filterType() {\n        return FilterType.OUTBOUND;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/filters/passport/PassportStampingFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.passport;\n\nimport com.netflix.zuul.filters.SyncZuulFilterAdapter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\n\n/**\n * Created by saroskar on 2/18/17.\n */\npublic abstract class PassportStampingFilter<T extends ZuulMessage> extends SyncZuulFilterAdapter<T, T> {\n\n    private final PassportState stamp;\n    private final String name;\n\n    public PassportStampingFilter(PassportState stamp) {\n        this.stamp = stamp;\n        this.name = filterType().name() + \"-\" + stamp.name() + \"-Filter\";\n    }\n\n    @Override\n    public String filterName() {\n        return name;\n    }\n\n    @Override\n    public T getDefaultOutput(T input) {\n        return input;\n    }\n\n    @Override\n    public T apply(T input) {\n        CurrentPassport.fromSessionContext(input.getContext()).add(stamp);\n        return input;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/logging/Http2FrameLoggingPerClientIpHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.logging;\n\nimport com.netflix.config.DynamicStringSetProperty;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.http2.DynamicHttp2FrameLogger;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\n\n/**\n * Be aware that this will only work correctly for devices connected _directly_ to Zuul - ie. connected\n * through an ELB TCP Listener. And not through FTL either.\n */\npublic class Http2FrameLoggingPerClientIpHandler extends ChannelInboundHandlerAdapter {\n    private static final DynamicStringSetProperty IPS =\n            new DynamicStringSetProperty(\"server.http2.frame.logging.ips\", \"\");\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        try {\n            String clientIP = ctx.channel()\n                    .attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS)\n                    .get();\n\n            if (IPS.get().contains(clientIP)) {\n                ctx.channel().attr(DynamicHttp2FrameLogger.ATTR_ENABLE).set(Boolean.TRUE);\n                ctx.pipeline().remove(this);\n            }\n        } finally {\n            super.channelRead(ctx, msg);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/Header.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message;\n\n/**\n * Represents a single header from a {@link Headers} object.\n */\npublic final class Header {\n    private final HeaderName name;\n    private final String value;\n\n    public Header(HeaderName name, String value) {\n        if (name == null) {\n            throw new NullPointerException(\"Header name cannot be null!\");\n        }\n        this.name = name;\n        this.value = value;\n    }\n\n    public String getKey() {\n        return name.getName();\n    }\n\n    public HeaderName getName() {\n        return name;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n\n        Header header = (Header) o;\n\n        if (!name.equals(header.name)) {\n            return false;\n        }\n        return !(value != null ? !value.equals(header.value) : header.value != null);\n    }\n\n    @Override\n    public int hashCode() {\n        int result = name.hashCode();\n        result = 31 * result + (value != null ? value.hashCode() : 0);\n        return result;\n    }\n\n    @Override\n    public String toString() {\n        return String.format(\"%s: %s\", name, value);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/HeaderName.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message;\n\nimport java.util.Locale;\n\n/**\n * Immutable, case-insensitive wrapper around Header name.\n *\n * User: Mike Smith\n * Date: 7/29/15\n * Time: 1:07 PM\n */\npublic final class HeaderName {\n    private final String name;\n    private final String normalised;\n    private final int hashCode;\n\n    public HeaderName(String name) {\n        if (name == null) {\n            throw new NullPointerException(\"HeaderName cannot be null!\");\n        }\n        this.name = name;\n        this.normalised = normalize(name);\n        this.hashCode = this.normalised.hashCode();\n    }\n\n    HeaderName(String name, String normalised) {\n        this.name = name;\n        this.normalised = normalised;\n        this.hashCode = normalised.hashCode();\n    }\n\n    /**\n     * Gets the original, non-normalized name for this header.\n     */\n    public String getName() {\n        return name;\n    }\n\n    public String getNormalised() {\n        return normalised;\n    }\n\n    static String normalize(String s) {\n        return s.toLowerCase(Locale.ROOT);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (!(o instanceof HeaderName)) {\n            return false;\n        }\n        HeaderName that = (HeaderName) o;\n        return this.normalised.equals(that.normalised);\n    }\n\n    @Override\n    public int hashCode() {\n        return hashCode;\n    }\n\n    @Override\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/Headers.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.message;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.zuul.exception.ZuulException;\nimport java.util.AbstractMap.SimpleImmutableEntry;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.BiConsumer;\nimport java.util.function.Predicate;\nimport javax.annotation.Nullable;\n\n/**\n * An abstraction over a collection of http headers. Allows multiple headers with same name, and header names are\n * compared case insensitively.\n *\n * There are methods for getting and setting headers by String AND by HeaderName. When possible, use the HeaderName\n * variants and cache the HeaderName instances somewhere, to avoid case-insensitive String comparisons.\n */\npublic final class Headers {\n    private static final int ABSENT = -1;\n\n    private final List<String> originalNames;\n    private final List<String> names;\n    private final List<String> values;\n\n    private static final Counter invalidHeaderCounter =\n            Spectator.globalRegistry().counter(\"zuul.header.invalid.char\");\n\n    public static Headers copyOf(Headers original) {\n        return new Headers(Objects.requireNonNull(original, \"original\"));\n    }\n\n    public Headers() {\n        originalNames = new ArrayList<>();\n        names = new ArrayList<>();\n        values = new ArrayList<>();\n    }\n\n    public Headers(int initialSize) {\n        originalNames = new ArrayList<>(initialSize);\n        names = new ArrayList<>(initialSize);\n        values = new ArrayList<>(initialSize);\n    }\n\n    private Headers(Headers original) {\n        originalNames = new ArrayList<>(original.originalNames);\n        names = new ArrayList<>(original.names);\n        values = new ArrayList<>(original.values);\n    }\n\n    /**\n     * Get the first value found for this key even if there are multiple. If none, then\n     * return {@code null}.\n     */\n    @Nullable\n    public String getFirst(String headerName) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        return getFirstNormal(normalName);\n    }\n\n    /**\n     * Get the first value found for this key even if there are multiple. If none, then\n     * return {@code null}.\n     */\n    @Nullable\n    public String getFirst(HeaderName headerName) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        return getFirstNormal(normalName);\n    }\n\n    /**\n     * Get the first value found for this key even if there are multiple. If none, then\n     * return the specified defaultValue.\n     */\n    public String getFirst(String headerName, String defaultValue) {\n        Objects.requireNonNull(defaultValue, \"defaultValue\");\n        String value = getFirst(headerName);\n        if (value != null) {\n            return value;\n        }\n        return defaultValue;\n    }\n\n    /**\n     * Get the first value found for this key even if there are multiple. If none, then\n     * return the specified defaultValue.\n     */\n    public String getFirst(HeaderName headerName, String defaultValue) {\n        Objects.requireNonNull(defaultValue, \"defaultValue\");\n        String value = getFirst(headerName);\n        if (value != null) {\n            return value;\n        }\n        return defaultValue;\n    }\n\n    @Nullable\n    private String getFirstNormal(String name) {\n        for (int i = 0; i < size(); i++) {\n            if (name(i).equals(name)) {\n                return value(i);\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Returns all header values associated with the name.\n     */\n    public List<String> getAll(String headerName) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        return getAllNormal(normalName);\n    }\n\n    /**\n     * Returns all header values associated with the name.\n     */\n    public List<String> getAll(HeaderName headerName) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        return getAllNormal(normalName);\n    }\n\n    private List<String> getAllNormal(String normalName) {\n        List<String> results = null;\n        for (int i = 0; i < size(); i++) {\n            if (name(i).equals(normalName)) {\n                if (results == null) {\n                    results = new ArrayList<>(1);\n                }\n                results.add(value(i));\n            }\n        }\n        if (results == null) {\n            return Collections.emptyList();\n        } else {\n            return Collections.unmodifiableList(results);\n        }\n    }\n\n    /**\n     * Iterates over the header entries with the given consumer.  The first argument will be the normalised header\n     * name as returned by {@link HeaderName#getNormalised()}.  The second argument will be the value.  Do not modify\n     * the headers during iteration.\n     */\n    public void forEachNormalised(BiConsumer<? super String, ? super String> entryConsumer) {\n        for (int i = 0; i < size(); i++) {\n            entryConsumer.accept(name(i), value(i));\n        }\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry.\n     *\n     * If value is {@code null}, then not added, but any existing header of same name is removed.\n     */\n    public void set(String headerName, @Nullable String value) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        setNormal(headerName, normalName, value);\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry.\n     *\n     * If value is {@code null}, then not added, but any existing header of same name is removed.\n     */\n    public void set(HeaderName headerName, String value) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        setNormal(headerName.getName(), normalName, value);\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry and validate.\n     *\n     * If value is {@code null}, then not added, but any existing header of same name is removed.\n     *\n     * @throws ZuulException on invalid name or value\n     */\n    public void setAndValidate(String headerName, @Nullable String value) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        setNormal(validateField(headerName), validateField(normalName), validateField(value));\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry and validate.\n     *\n     * If value is {@code null}, then not added, but any existing header of same name is removed.\n     *\n     * @throws ZuulException on invalid name or value\n     */\n    public void setAndValidate(HeaderName headerName, String value) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        setNormal(validateField(headerName.getName()), validateField(normalName), validateField(value));\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry if the key and entry are valid.\n     *\n     * If value is {@code null}, then not added, but any existing header of same name is removed.\n     */\n    public void setIfValid(HeaderName headerName, String value) {\n        Objects.requireNonNull(headerName, \"headerName\");\n        if (isValid(headerName.getName()) && isValid(value)) {\n            String normalName = headerName.getNormalised();\n            setNormal(headerName.getName(), normalName, value);\n        }\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry if the key and entry are valid.\n     *\n     * If value is {@code null}, then not added, but any existing header of same name is removed.\n     */\n    public void setIfValid(String headerName, @Nullable String value) {\n        Objects.requireNonNull(headerName, \"headerName\");\n        if (isValid(headerName) && isValid(value)) {\n            String normalName = HeaderName.normalize(headerName);\n            setNormal(headerName, normalName, value);\n        }\n    }\n\n    private void setNormal(String originalName, String normalName, @Nullable String value) {\n        int i = findNormal(normalName);\n        if (i == ABSENT) {\n            if (value != null) {\n                addNormal(originalName, normalName, value);\n            }\n            return;\n        }\n        if (value != null) {\n            value(i, value);\n            originalName(i, originalName);\n            i++;\n        }\n        clearMatchingStartingAt(i, normalName, /* removed= */ null);\n    }\n\n    /**\n     * Returns the first index entry that has a matching name.  Returns {@link #ABSENT} if absent.\n     */\n    private int findNormal(String normalName) {\n        for (int i = 0; i < size(); i++) {\n            if (name(i).equals(normalName)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * Removes entries that match the name, starting at the given index.\n     */\n    private void clearMatchingStartingAt(int i, String normalName, @Nullable Collection<? super String> removed) {\n        // This works by having separate read and write indexes, that iterate along the list.\n        // Values that don't match are moved to the front, leaving garbage values in place.\n        // At the end, all values at and values are garbage and are removed.\n        int w = i;\n        for (int r = i; r < size(); r++) {\n            if (!name(r).equals(normalName)) {\n                originalName(w, originalName(r));\n                name(w, name(r));\n                value(w, value(r));\n                w++;\n            } else if (removed != null) {\n                removed.add(value(r));\n            }\n        }\n        truncate(w);\n    }\n\n    /**\n     * Adds the name and value to the headers, except if the name is already present.  Unlike\n     * {@link #set(String, String)}, this method does not accept a {@code null} value.\n     *\n     * @return if the value was successfully added.\n     */\n    public boolean setIfAbsent(String headerName, String value) {\n        Objects.requireNonNull(value, \"value\");\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        return setIfAbsentNormal(headerName, normalName, value);\n    }\n\n    /**\n     * Adds the name and value to the headers, except if the name is already present.  Unlike\n     * {@link #set(HeaderName, String)}, this method does not accept a {@code null} value.\n     *\n     * @return if the value was successfully added.\n     */\n    public boolean setIfAbsent(HeaderName headerName, String value) {\n        Objects.requireNonNull(value, \"value\");\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        return setIfAbsentNormal(headerName.getName(), normalName, value);\n    }\n\n    private boolean setIfAbsentNormal(String originalName, String normalName, String value) {\n        int i = findNormal(normalName);\n        if (i != ABSENT) {\n            return false;\n        }\n        addNormal(originalName, normalName, value);\n        return true;\n    }\n\n    /**\n     * Validates and adds the name and value to the headers, except if the name is already present.  Unlike\n     * {@link #set(String, String)}, this method does not accept a {@code null} value.\n     *\n     * @return if the value was successfully added.\n     */\n    public boolean setIfAbsentAndValid(String headerName, String value) {\n        Objects.requireNonNull(value, \"value\");\n        Objects.requireNonNull(headerName, \"headerName\");\n        if (isValid(headerName) && isValid(value)) {\n            String normalName = HeaderName.normalize(headerName);\n            return setIfAbsentNormal(headerName, normalName, value);\n        }\n        return false;\n    }\n\n    /**\n     * Validates and adds the name and value to the headers, except if the name is already present.  Unlike\n     * {@link #set(HeaderName, String)}, this method does not accept a {@code null} value.\n     *\n     * @return if the value was successfully added.\n     */\n    public boolean setIfAbsentAndValid(HeaderName headerName, String value) {\n        Objects.requireNonNull(value, \"value\");\n        Objects.requireNonNull(headerName, \"headerName\");\n        if (isValid(headerName.getName()) && isValid(value)) {\n            String normalName = headerName.getNormalised();\n            return setIfAbsentNormal(headerName.getName(), normalName, value);\n        }\n        return false;\n    }\n\n    /**\n     * Adds the name and value to the headers.\n     */\n    public void add(String headerName, String value) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        Objects.requireNonNull(value, \"value\");\n        addNormal(headerName, normalName, value);\n    }\n\n    /**\n     * Adds the name and value to the headers.\n     */\n    public void add(HeaderName headerName, String value) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        Objects.requireNonNull(value, \"value\");\n        addNormal(headerName.getName(), normalName, value);\n    }\n\n    /**\n     * Adds the name and value to the headers and validate.\n     *\n     * @throws ZuulException on invalid name or value\n     */\n    public void addAndValidate(String headerName, String value) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        Objects.requireNonNull(value, \"value\");\n        addNormal(validateField(headerName), validateField(normalName), validateField(value));\n    }\n\n    /**\n     * Adds the name and value to the headers and validate\n     *\n     * @throws ZuulException on invalid name or value\n     */\n    public void addAndValidate(HeaderName headerName, String value) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        Objects.requireNonNull(value, \"value\");\n        addNormal(validateField(headerName.getName()), validateField(normalName), validateField(value));\n    }\n\n    /**\n     * Adds the name and value to the headers if valid\n     */\n    public void addIfValid(String headerName, String value) {\n        Objects.requireNonNull(headerName, \"headerName\");\n        Objects.requireNonNull(value, \"value\");\n        if (isValid(headerName) && isValid(value)) {\n            String normalName = HeaderName.normalize(headerName);\n            addNormal(headerName, normalName, value);\n        }\n    }\n\n    /**\n     * Adds the name and value to the headers if valid\n     */\n    public void addIfValid(HeaderName headerName, String value) {\n        Objects.requireNonNull(headerName, \"headerName\");\n        Objects.requireNonNull(value, \"value\");\n        if (isValid(headerName.getName()) && isValid(value)) {\n            String normalName = headerName.getNormalised();\n            addNormal(headerName.getName(), normalName, value);\n        }\n    }\n\n    /**\n     * Adds all the headers into this headers object.\n     */\n    public void putAll(Headers headers) {\n        for (int i = 0; i < headers.size(); i++) {\n            addNormal(headers.originalName(i), headers.name(i), headers.value(i));\n        }\n    }\n\n    /**\n     * Removes the header entries that match the given header name, and returns them as a list.\n     */\n    public List<String> remove(String headerName) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        return removeNormal(normalName);\n    }\n\n    /**\n     * Removes the header entries that match the given header name, and returns them as a list.\n     */\n    public List<String> remove(HeaderName headerName) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        return removeNormal(normalName);\n    }\n\n    private List<String> removeNormal(String normalName) {\n        List<String> removed = new ArrayList<>();\n        clearMatchingStartingAt(0, normalName, removed);\n        return Collections.unmodifiableList(removed);\n    }\n\n    /**\n     * Removes all header entries that match the given predicate.   Do not access the header list from inside the\n     * {@link Predicate#test} body.\n     *\n     * @return if any elements were removed.\n     */\n    public boolean removeIf(Predicate<? super Map.Entry<HeaderName, String>> filter) {\n        Objects.requireNonNull(filter, \"filter\");\n        boolean removed = false;\n        int w = 0;\n        for (int r = 0; r < size(); r++) {\n            if (filter.test(new SimpleImmutableEntry<>(new HeaderName(originalName(r), name(r)), value(r)))) {\n                removed = true;\n            } else {\n                originalName(w, originalName(r));\n                name(w, name(r));\n                value(w, value(r));\n                w++;\n            }\n        }\n        truncate(w);\n        return removed;\n    }\n\n    /**\n     * Returns the collection of headers.\n     */\n    public Collection<Header> entries() {\n        List<Header> entries = new ArrayList<>(size());\n        for (int i = 0; i < size(); i++) {\n            entries.add(new Header(new HeaderName(originalName(i), name(i)), value(i)));\n        }\n        return Collections.unmodifiableList(entries);\n    }\n\n    /**\n     * Returns a set of header names found in this headers object.  If there are duplicate header names, the first\n     * one present takes precedence.\n     */\n    public Set<HeaderName> keySet() {\n        Set<HeaderName> headerNames = new LinkedHashSet<>(size());\n        for (int i = 0; i < size(); i++) {\n            HeaderName headerName = new HeaderName(originalName(i), name(i));\n            // We actually do need to check contains before adding to the set because the original name may change.\n            // In this case, the first name wins.\n            if (!headerNames.contains(headerName)) {\n                headerNames.add(headerName);\n            }\n        }\n        return Collections.unmodifiableSet(headerNames);\n    }\n\n    /**\n     * Returns if there is a header entry that matches the given name.\n     */\n    public boolean contains(String headerName) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        return findNormal(normalName) != ABSENT;\n    }\n\n    /**\n     * Returns if there is a header entry that matches the given name.\n     */\n    public boolean contains(HeaderName headerName) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        return findNormal(normalName) != ABSENT;\n    }\n\n    /**\n     * Returns if there is a header entry that matches the given name and value.\n     */\n    public boolean contains(String headerName, String value) {\n        String normalName = HeaderName.normalize(Objects.requireNonNull(headerName, \"headerName\"));\n        Objects.requireNonNull(value, \"value\");\n        return containsNormal(normalName, value);\n    }\n\n    /**\n     * Returns if there is a header entry that matches the given name and value.\n     */\n    public boolean contains(HeaderName headerName, String value) {\n        String normalName = Objects.requireNonNull(headerName, \"headerName\").getNormalised();\n        Objects.requireNonNull(value, \"value\");\n        return containsNormal(normalName, value);\n    }\n\n    private boolean containsNormal(String normalName, String value) {\n        for (int i = 0; i < size(); i++) {\n            if (name(i).equals(normalName) && value(i).equals(value)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Returns the number of header entries.\n     */\n    public int size() {\n        return names.size();\n    }\n\n    /**\n     * This method should only be used for testing, as it is expensive to call.\n     */\n    @Override\n    @VisibleForTesting\n    public int hashCode() {\n        return asMap().hashCode();\n    }\n\n    /**\n     * Equality on headers is not clearly defined, but this method makes an attempt to do so.   This method should\n     * only be used for testing, as it is expensive to call.  Two headers object are considered equal if they have\n     * the same, normalized header names, and have the corresponding header values in the same order.\n     */\n    @Override\n    @VisibleForTesting\n    public boolean equals(Object obj) {\n        if (obj == this) {\n            return true;\n        }\n        if (!(obj instanceof Headers)) {\n            return false;\n        }\n        Headers other = (Headers) obj;\n\n        return asMap().equals(other.asMap());\n    }\n\n    private Map<String, List<String>> asMap() {\n        Map<String, List<String>> map = new LinkedHashMap<>(size());\n        for (int i = 0; i < size(); i++) {\n            map.computeIfAbsent(name(i), k -> new ArrayList<>(1)).add(value(i));\n        }\n        // Return an unwrapped collection since it should not ever be returned on the API.\n        return map;\n    }\n\n    /**\n     * This is used for debugging.  It is fairly expensive to construct, so don't call it on a hot path.\n     */\n    @Override\n    public String toString() {\n        return asMap().toString();\n    }\n\n    private String originalName(int i) {\n        return originalNames.get(i);\n    }\n\n    private void originalName(int i, String originalName) {\n        originalNames.set(i, originalName);\n    }\n\n    private String name(int i) {\n        return names.get(i);\n    }\n\n    private void name(int i, String name) {\n        names.set(i, name);\n    }\n\n    private String value(int i) {\n        return values.get(i);\n    }\n\n    private void value(int i, String val) {\n        values.set(i, val);\n    }\n\n    private void addNormal(String originalName, String normalName, String value) {\n        originalNames.add(originalName);\n        names.add(normalName);\n        values.add(value);\n    }\n\n    /**\n     * Removes all elements at and after the given index.\n     */\n    private void truncate(int i) {\n        for (int k = size() - 1; k >= i; k--) {\n            originalNames.remove(k);\n            names.remove(k);\n            values.remove(k);\n        }\n    }\n\n    /**\n     * Checks if the given value is compliant with our RFC 7230 based check\n     */\n    private static boolean isValid(@Nullable String value) {\n        if (value == null || findInvalid(value) == ABSENT) {\n            return true;\n        }\n        invalidHeaderCounter.increment();\n        return false;\n    }\n\n    /**\n     * Checks if the input value is compliant with our RFC 7230 based check\n     * Returns input value if valid, raises ZuulException otherwise\n     */\n    private static String validateField(@Nullable String value) {\n        if (value != null) {\n            int pos = findInvalid(value);\n            if (pos != ABSENT) {\n                invalidHeaderCounter.increment();\n                throw new ZuulException(\"Invalid header field: char \" + (int) value.charAt(pos) + \" in string \" + value\n                        + \" does not comply with RFC 7230\");\n            }\n        }\n        return value;\n    }\n\n    /**\n     * Validated the input value based on RFC 7230 but more lenient.\n     * Currently, only ASCII control characters are considered invalid.\n     *\n     * Returns the index of first invalid character. Returns {@link #ABSENT} if absent.\n     */\n    private static int findInvalid(String value) {\n        for (int i = 0; i < value.length(); i++) {\n            char c = value.charAt(i);\n            // ASCII non-control characters, per RFC 7230 but slightly more lenient\n            if (c < 31 || c == 127) {\n                return i;\n            }\n        }\n        return ABSENT;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessage.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport javax.annotation.Nullable;\n\n/**\n * Represents a message that propagates through the Zuul filter chain.\n */\npublic interface ZuulMessage extends Cloneable {\n\n    /**\n     * Returns the session context of this message.\n     */\n    SessionContext getContext();\n\n    /**\n     * Returns the headers for this message.  They may be request or response headers, depending on the underlying type\n     * of this object.  For some messages, there may be no headers, such as with chunked requests or responses.  In this\n     * case, a non-{@code null} default headers value will be returned.\n     */\n    Headers getHeaders();\n\n    /**\n     * Sets the headers for this message.\n     *\n     * @throws NullPointerException if newHeaders is {@code null}.\n     */\n    void setHeaders(Headers newHeaders);\n\n    /**\n     * Returns if this message has an attached body.   For requests, this is typically an HTTP POST body.  For\n     * responses, this is typically the HTTP response.\n     */\n    boolean hasBody();\n\n    /**\n     *  Declares that this message has a body.   This method is automatically called when {@link #bufferBodyContents}\n     *  is invoked.\n     */\n    void setHasBody(boolean hasBody);\n\n    /**\n     * Returns the message body.\n     * This is the entire buffered body, regardless of whether the underlying body chunks have been read or not.\n     * If there is no message body, this returns {@code null}.\n     */\n    @Nullable\n    byte[] getBody();\n\n    /**\n     * Returns the length of the entire buffered message body, or {@code 0} if there isn't a message present.\n     */\n    int getBodyLength();\n\n    /**\n     * Sets the message body.  Note: if the {@code body} is {@code null}, this may not reset the body presence as\n     * returned by {@link #hasBody}.  The body is considered complete after calling this method.\n     */\n    void setBody(@Nullable byte[] body);\n\n    /**\n     * Sets the message body as UTF-8 encoded text.   Note that this does NOT set any headers related to the\n     * Content-Type; callers must set or reset the content type to UTF-8.  The body is considered complete after\n     * calling this method.\n     */\n    void setBodyAsText(@Nullable String bodyText);\n\n    /**\n     * Appends an HTTP content chunk to this message.  Callers should be careful not to add multiple chunks that\n     * implement {@link LastHttpContent}.\n     *\n     * @throws NullPointerException if {@code chunk} is {@code null}.\n     */\n    void bufferBodyContents(HttpContent chunk);\n\n    /**\n     * Returns the HTTP content chunks that are part of this message.  Callers should avoid retaining the return value,\n     * as the contents may change with subsequent body mutations.\n     */\n    Iterable<HttpContent> getBodyContents();\n\n    /**\n     * Sets the message body to be complete if it was not already so.\n     *\n     * @return {@code true} if the body was not yet complete, or else false.\n     */\n    boolean finishBufferedBodyIfIncomplete();\n\n    /**\n     * Indicates that the message contains a content chunk the implements {@link LastHttpContent}.\n     */\n    boolean hasCompleteBody();\n\n    /**\n     * Passes the body content chunks through the given filter, and sets them back into this message.\n     */\n    void runBufferedBodyContentThroughFilter(ZuulFilter<?, ?> filter);\n\n    /**\n     * Clears the content chunks of this body, calling {@code release()} in the process.  Users SHOULD call this method\n     * when the body content is no longer needed.\n     */\n    void disposeBufferedBody();\n\n    /**\n     * Gets the body of this message as UTF-8 text, or {@code null} if there is no body.\n     */\n    @Nullable\n    String getBodyAsText();\n\n    /**\n     * Reset the chunked body reader indexes. Users SHOULD call this method before retrying requests\n     * as the chunked body buffer will have had the reader indexes changed during channel writes.\n     */\n    void resetBodyReader();\n\n    /**\n     * Returns the maximum body size that this message is willing to hold.  This value value should be more than the\n     * sum of lengths of the body chunks.  The max body size may not be strictly enforced, and is informational.\n     */\n    int getMaxBodySize();\n\n    /**\n     * Returns a copy of this message.\n     */\n    ZuulMessage clone();\n\n    /**\n     * Returns a string that represents this message which is suitable for debugging.\n     */\n    String getInfoForLogging();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/ZuulMessageImpl.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.message;\n\nimport com.google.common.base.Charsets;\nimport com.google.common.base.Strings;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.config.DynamicPropertyFactory;\nimport com.netflix.netty.common.ByteBufUtil;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.http.HttpHeaderNames;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/20/15\n * Time: 3:10 PM\n */\npublic class ZuulMessageImpl implements ZuulMessage {\n    protected static final DynamicIntProperty MAX_BODY_SIZE_PROP =\n            DynamicPropertyFactory.getInstance().getIntProperty(\"zuul.message.body.max.size\", 25 * 1000 * 1024);\n\n    protected final SessionContext context;\n    protected Headers headers;\n\n    private boolean hasBody;\n    private boolean bodyBufferedCompletely;\n    private final List<HttpContent> bodyChunks;\n\n    public ZuulMessageImpl(SessionContext context) {\n        this(context, new Headers());\n    }\n\n    public ZuulMessageImpl(SessionContext context, Headers headers) {\n        this.context = context == null ? new SessionContext() : context;\n        this.headers = headers == null ? new Headers() : headers;\n        this.bodyChunks = new ArrayList<>(16);\n    }\n\n    @Override\n    public SessionContext getContext() {\n        return context;\n    }\n\n    @Override\n    public Headers getHeaders() {\n        return headers;\n    }\n\n    @Override\n    public void setHeaders(Headers newHeaders) {\n        this.headers = newHeaders;\n    }\n\n    @Override\n    public int getMaxBodySize() {\n        return MAX_BODY_SIZE_PROP.get();\n    }\n\n    @Override\n    public void setHasBody(boolean hasBody) {\n        this.hasBody = hasBody;\n    }\n\n    @Override\n    public boolean hasBody() {\n        return hasBody;\n    }\n\n    @Override\n    public boolean hasCompleteBody() {\n        return bodyBufferedCompletely;\n    }\n\n    @Override\n    public void bufferBodyContents(HttpContent chunk) {\n        setHasBody(true);\n        ByteBufUtil.touch(chunk, \"ZuulMessage buffering body content.\");\n        bodyChunks.add(chunk);\n        if (chunk instanceof LastHttpContent) {\n            ByteBufUtil.touch(chunk, \"ZuulMessage buffering body content complete.\");\n            bodyBufferedCompletely = true;\n        }\n    }\n\n    private void setContentLength(int length) {\n        headers.remove(HttpHeaderNames.TRANSFER_ENCODING);\n        headers.set(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(length));\n    }\n\n    @Override\n    public void setBodyAsText(String bodyText) {\n        disposeBufferedBody();\n        if (!Strings.isNullOrEmpty(bodyText)) {\n            byte[] bytes = bodyText.getBytes(Charsets.UTF_8);\n            bufferBodyContents(new DefaultLastHttpContent(Unpooled.wrappedBuffer(bytes)));\n            setContentLength(bytes.length);\n        } else {\n            bufferBodyContents(new DefaultLastHttpContent());\n            setContentLength(0);\n        }\n    }\n\n    @Override\n    public void setBody(byte[] body) {\n        disposeBufferedBody();\n        if (body != null && body.length > 0) {\n            ByteBuf content = Unpooled.copiedBuffer(body);\n            bufferBodyContents(new DefaultLastHttpContent(content));\n            setContentLength(body.length);\n        } else {\n            bufferBodyContents(new DefaultLastHttpContent());\n            setContentLength(0);\n        }\n    }\n\n    @Override\n    public String getBodyAsText() {\n        byte[] body = getBody();\n        return (body != null && body.length > 0) ? new String(getBody(), Charsets.UTF_8) : null;\n    }\n\n    @Override\n    public byte[] getBody() {\n        if (bodyChunks.size() == 0) {\n            return null;\n        }\n\n        int size = this.getBodyLength();\n        byte[] body = new byte[size];\n        int offset = 0;\n        for (HttpContent chunk : bodyChunks) {\n            ByteBuf content = chunk.content();\n            int len = content.writerIndex(); // writer idx tracks the total readable bytes in the buffer\n            content.getBytes(0, body, offset, len);\n            offset += len;\n        }\n        return body;\n    }\n\n    @Override\n    public int getBodyLength() {\n        int size = 0;\n        for (HttpContent chunk : bodyChunks) {\n            // writer index tracks the total number of bytes written to the buffer regardless of buffer reads\n            size += chunk.content().writerIndex();\n        }\n        return size;\n    }\n\n    @Override\n    public Iterable<HttpContent> getBodyContents() {\n        return Collections.unmodifiableList(bodyChunks);\n    }\n\n    @Override\n    public void resetBodyReader() {\n        for (HttpContent chunk : bodyChunks) {\n            chunk.content().resetReaderIndex();\n        }\n    }\n\n    @Override\n    public boolean finishBufferedBodyIfIncomplete() {\n        if (!bodyBufferedCompletely) {\n            bufferBodyContents(new DefaultLastHttpContent());\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public void disposeBufferedBody() {\n        bodyChunks.forEach(chunk -> {\n            if ((chunk != null) && (chunk.refCnt() > 0)) {\n                ByteBufUtil.touch(chunk, \"ZuulMessage disposing buffered body\");\n                chunk.release();\n            }\n        });\n        bodyChunks.clear();\n    }\n\n    @Override\n    public void runBufferedBodyContentThroughFilter(ZuulFilter<?, ?> filter) {\n        // Loop optimized for the common case: Most filters' processContentChunk() return\n        // original chunk passed in as is without any processing\n        String filterName = filter.filterName();\n        for (int i = 0; i < bodyChunks.size(); i++) {\n            HttpContent origChunk = bodyChunks.get(i);\n            ByteBufUtil.touch(origChunk, \"ZuulMessage processing chunk, filter: \", filterName);\n            HttpContent filteredChunk = filter.processContentChunk(this, origChunk);\n            ByteBufUtil.touch(filteredChunk, \"ZuulMessage processing filteredChunk, filter: \", filterName);\n            if ((filteredChunk != null) && (filteredChunk != origChunk)) {\n                // filter actually did some processing, set the new chunk in and release the old chunk.\n                bodyChunks.set(i, filteredChunk);\n                int refCnt = origChunk.refCnt();\n                if (refCnt > 0) {\n                    origChunk.release(refCnt);\n                }\n            }\n        }\n    }\n\n    @Override\n    public ZuulMessage clone() {\n        ZuulMessageImpl copy = new ZuulMessageImpl(context.clone(), Headers.copyOf(headers));\n        this.bodyChunks.forEach(chunk -> {\n            chunk.retain();\n            copy.bufferBodyContents(chunk);\n        });\n        return copy;\n    }\n\n    /**\n     * Override this in more specific subclasses to add request/response info for logging purposes.\n     */\n    @Override\n    public String getInfoForLogging() {\n        return \"ZuulMessage\";\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/Cookies.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * User: Mike Smith\n * Date: 6/18/15\n * Time: 12:04 AM\n */\npublic class Cookies {\n    private final Map<String, List<Cookie>> map = new HashMap<>();\n    private final List<Cookie> all = new ArrayList<>();\n\n    public void add(Cookie cookie) {\n        map.computeIfAbsent(cookie.name(), k -> new ArrayList<>(1)).add(cookie);\n        all.add(cookie);\n    }\n\n    public List<Cookie> getAll() {\n        return all;\n    }\n\n    public Set<String> getNames() {\n        return Collections.unmodifiableSet(map.keySet());\n    }\n\n    public List<Cookie> get(String name) {\n        return map.get(name);\n    }\n\n    public Cookie getFirst(String name) {\n        List<Cookie> found = map.get(name);\n        if (found == null || found.isEmpty()) {\n            return null;\n        }\n        return found.get(0);\n    }\n\n    public String getFirstValue(String name) {\n        Cookie c = getFirst(name);\n        String value;\n        if (c != null) {\n            value = c.value();\n        } else {\n            value = null;\n        }\n        return value;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpHeaderNames.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.config.DynamicPropertyFactory;\nimport com.netflix.zuul.message.HeaderName;\n\n/**\n * A cache of both constants for common HTTP header names, and custom added header names.\n *\n * Primarily to be used as a performance optimization for avoiding repeatedly doing lower-casing and\n * case-insensitive comparisons of StringS.\n *\n * User: Mike Smith\n * Date: 8/5/15\n * Time: 12:33 PM\n */\npublic final class HttpHeaderNames {\n    private static final DynamicIntProperty MAX_CACHE_SIZE = DynamicPropertyFactory.getInstance()\n            .getIntProperty(\"com.netflix.zuul.message.http.HttpHeaderNames.maxCacheSize\", 30);\n\n    private static final HttpHeaderNamesCache HEADER_NAME_CACHE = new HttpHeaderNamesCache(100, MAX_CACHE_SIZE.get());\n\n    public static final HeaderName COOKIE = HEADER_NAME_CACHE.get(\"Cookie\");\n    public static final HeaderName SET_COOKIE = HEADER_NAME_CACHE.get(\"Set-Cookie\");\n\n    public static final HeaderName DATE = HEADER_NAME_CACHE.get(\"Date\");\n    public static final HeaderName CONNECTION = HEADER_NAME_CACHE.get(\"Connection\");\n    public static final HeaderName KEEP_ALIVE = HEADER_NAME_CACHE.get(\"Keep-Alive\");\n    public static final HeaderName HOST = HEADER_NAME_CACHE.get(\"Host\");\n    public static final HeaderName SERVER = HEADER_NAME_CACHE.get(\"Server\");\n    public static final HeaderName VIA = HEADER_NAME_CACHE.get(\"Via\");\n    public static final HeaderName USER_AGENT = HEADER_NAME_CACHE.get(\"User-Agent\");\n    public static final HeaderName REFERER = HEADER_NAME_CACHE.get(\"Referer\");\n    public static final HeaderName ORIGIN = HEADER_NAME_CACHE.get(\"Origin\");\n    public static final HeaderName LOCATION = HEADER_NAME_CACHE.get(\"Location\");\n    public static final HeaderName UPGRADE = HEADER_NAME_CACHE.get(\"Upgrade\");\n\n    public static final HeaderName CONTENT_TYPE = HEADER_NAME_CACHE.get(\"Content-Type\");\n    public static final HeaderName CONTENT_LENGTH = HEADER_NAME_CACHE.get(\"Content-Length\");\n    public static final HeaderName CONTENT_ENCODING = HEADER_NAME_CACHE.get(\"Content-Encoding\");\n    public static final HeaderName ACCEPT = HEADER_NAME_CACHE.get(\"Accept\");\n    public static final HeaderName ACCEPT_ENCODING = HEADER_NAME_CACHE.get(\"Accept-Encoding\");\n    public static final HeaderName ACCEPT_LANGUAGE = HEADER_NAME_CACHE.get(\"Accept-Language\");\n    public static final HeaderName TRANSFER_ENCODING = HEADER_NAME_CACHE.get(\"Transfer-Encoding\");\n    public static final HeaderName TE = HEADER_NAME_CACHE.get(\"TE\");\n    public static final HeaderName RANGE = HEADER_NAME_CACHE.get(\"Range\");\n    public static final HeaderName ACCEPT_RANGES = HEADER_NAME_CACHE.get(\"Accept-Ranges\");\n    public static final HeaderName ALLOW = HEADER_NAME_CACHE.get(\"Allow\");\n    public static final HeaderName VARY = HEADER_NAME_CACHE.get(\"Vary\");\n\n    public static final HeaderName LAST_MODIFIED = HEADER_NAME_CACHE.get(\"Last-Modified\");\n    public static final HeaderName ETAG = HEADER_NAME_CACHE.get(\"ETag\");\n    public static final HeaderName EXPIRES = HEADER_NAME_CACHE.get(\"Expires\");\n    public static final HeaderName CACHE_CONTROL = HEADER_NAME_CACHE.get(\"Cache-Control\");\n    public static final HeaderName EDGE_CONTROL = HEADER_NAME_CACHE.get(\"Edge-Control\");\n    public static final HeaderName PRAGMA = HEADER_NAME_CACHE.get(\"Pragma\");\n\n    public static final HeaderName X_FORWARDED_HOST = HEADER_NAME_CACHE.get(\"X-Forwarded-Host\");\n    public static final HeaderName X_FORWARDED_FOR = HEADER_NAME_CACHE.get(\"X-Forwarded-For\");\n    public static final HeaderName X_FORWARDED_PORT = HEADER_NAME_CACHE.get(\"X-Forwarded-Port\");\n    public static final HeaderName X_FORWARDED_PROTO = HEADER_NAME_CACHE.get(\"X-Forwarded-Proto\");\n    public static final HeaderName X_FORWARDED_PROTO_VERSION = HEADER_NAME_CACHE.get(\"X-Forwarded-Proto-Version\");\n\n    public static final HeaderName ACCESS_CONTROL_ALLOW_ORIGIN = HEADER_NAME_CACHE.get(\"Access-Control-Allow-Origin\");\n    public static final HeaderName ACCESS_CONTROL_ALLOW_CREDENTIALS =\n            HEADER_NAME_CACHE.get(\"Access-Control-Allow-Credentials\");\n    public static final HeaderName ACCESS_CONTROL_ALLOW_HEADERS = HEADER_NAME_CACHE.get(\"Access-Control-Allow-Headers\");\n    public static final HeaderName ACCESS_CONTROL_ALLOW_METHODS = HEADER_NAME_CACHE.get(\"Access-Control-Allow-Methods\");\n    public static final HeaderName ACCESS_CONTROL_REQUEST_HEADERS =\n            HEADER_NAME_CACHE.get(\"Access-Control-Request-Headers\");\n    public static final HeaderName ACCESS_CONTROL_EXPOSE_HEADERS =\n            HEADER_NAME_CACHE.get(\"Access-Control-Expose-Headers\");\n    public static final HeaderName ACCESS_CONTROL_MAX_AGE_HEADERS = HEADER_NAME_CACHE.get(\"Access-Control-Max-Age\");\n    public static final HeaderName STRICT_TRANSPORT_SECURITY = HEADER_NAME_CACHE.get(\"Strict-Transport-Security\");\n    public static final HeaderName LINK = HEADER_NAME_CACHE.get(\"Link\");\n\n    /**\n     * Looks up the name in the cache, and if does not exist, then creates and adds a new one\n     * (up to the max cache size).\n     *\n     * @param name\n     * @return HeaderName - never null.\n     */\n    public static HeaderName get(String name) {\n        return HEADER_NAME_CACHE.get(name);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpHeaderNamesCache.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport com.netflix.zuul.message.HeaderName;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * User: Mike Smith\n * Date: 8/5/15\n * Time: 1:08 PM\n */\npublic class HttpHeaderNamesCache {\n    private final ConcurrentHashMap<String, HeaderName> cache;\n    private final int maxSize;\n\n    public HttpHeaderNamesCache(int initialSize, int maxSize) {\n        this.cache = new ConcurrentHashMap<>(initialSize);\n        this.maxSize = maxSize;\n    }\n\n    public boolean isFull() {\n        return cache.size() >= maxSize;\n    }\n\n    public HeaderName get(String name) {\n        // Check in the static cache for this headername if available.\n        // NOTE: we do this lookup case-sensitively, as doing case-INSENSITIVELY removes the purpose of\n        // caching the object in the first place (ie. the expensive operation we want to avoid by caching\n        // is the case-insensitive string comparisons).\n        HeaderName hn = cache.get(name);\n        if (hn == null) {\n            // Here we're accepting that the isFull check is not happening atomically with the put, as we don't mind\n            // too much if the cache overfills a bit.\n            if (isFull()) {\n                hn = new HeaderName(name);\n            } else {\n                hn = cache.computeIfAbsent(name, (newName) -> new HeaderName(newName));\n            }\n        }\n        return hn;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpQueryParams.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.message.http;\n\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableListMultimap;\nimport com.google.common.collect.Iterables;\nimport com.google.common.collect.LinkedListMultimap;\nimport com.google.common.collect.ListMultimap;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.StringTokenizer;\n\n/**\n * User: michaels\n * Date: 2/24/15\n * Time: 10:58 AM\n */\npublic class HttpQueryParams implements Cloneable {\n    private final ListMultimap<String, String> delegate;\n    private final boolean immutable;\n    private final Map<String, Boolean> trailingEquals;\n\n    public HttpQueryParams() {\n        delegate = LinkedListMultimap.create();\n        immutable = false;\n        trailingEquals = new HashMap<>();\n    }\n\n    private HttpQueryParams(ListMultimap<String, String> delegate) {\n        this.delegate = delegate;\n        immutable = ImmutableListMultimap.class.isAssignableFrom(delegate.getClass());\n        trailingEquals = new HashMap<>();\n    }\n\n    public static HttpQueryParams parse(String queryString) {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        if (queryString == null) {\n            return queryParams;\n        }\n\n        StringTokenizer st = new StringTokenizer(queryString, \"&\");\n        int i;\n        while (st.hasMoreTokens()) {\n            String s = st.nextToken();\n            i = s.indexOf(\"=\");\n            // key-value query param\n            if (i > 0) {\n                String name = s.substring(0, i);\n                String value = s.substring(i + 1);\n\n                try {\n                    name = URLDecoder.decode(name, StandardCharsets.UTF_8);\n                    value = URLDecoder.decode(value, StandardCharsets.UTF_8);\n                } catch (Exception e) {\n                    // do nothing\n                }\n\n                queryParams.add(name, value);\n\n                // respect trailing equals for key-only params\n                if (s.endsWith(\"=\") && value.isEmpty()) {\n                    queryParams.setTrailingEquals(name, true);\n                }\n            }\n            // key only\n            else if (!s.isEmpty()) {\n                String name = s;\n\n                try {\n                    name = URLDecoder.decode(name, StandardCharsets.UTF_8);\n                } catch (Exception e) {\n                    // do nothing\n                }\n\n                queryParams.add(name, \"\");\n            }\n        }\n\n        return queryParams;\n    }\n\n    /**\n     * Get the first value found for this key even if there are multiple. If none, then\n     * return null.\n     */\n    public String getFirst(String name) {\n        List<String> values = delegate.get(name);\n        if (!values.isEmpty()) {\n            return values.get(0);\n        }\n        return null;\n    }\n\n    public List<String> get(String name) {\n        return delegate.get(name.toLowerCase(Locale.ROOT));\n    }\n\n    public boolean contains(String name) {\n        return delegate.containsKey(name);\n    }\n\n    public boolean contains(String name, String value) {\n        return delegate.containsEntry(name, value);\n    }\n\n    /**\n     * Per https://tools.ietf.org/html/rfc7230#page-19, query params are to be treated as case sensitive.\n     * However, as a utility, this exists to allow us to do a case insensitive match on demand.\n     */\n    public boolean containsIgnoreCase(String name) {\n        return delegate.containsKey(name) || delegate.containsKey(name.toLowerCase(Locale.ROOT));\n    }\n\n    /**\n     * Replace any/all entries with this key, with this single entry.\n     */\n    public void set(String name, String value) {\n        delegate.removeAll(name);\n        delegate.put(name, value);\n    }\n\n    public void add(String name, String value) {\n        delegate.put(name, value);\n    }\n\n    public void removeAll(String name) {\n        delegate.removeAll(name);\n    }\n\n    public void clear() {\n        delegate.clear();\n    }\n\n    public Collection<Map.Entry<String, String>> entries() {\n        return delegate.entries();\n    }\n\n    public Set<String> keySet() {\n        return delegate.keySet();\n    }\n\n    public String toEncodedString() {\n        StringBuilder sb = new StringBuilder();\n        for (Map.Entry<String, String> entry : entries()) {\n            sb.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));\n            if (!Strings.isNullOrEmpty(entry.getValue())) {\n                sb.append('=');\n                sb.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));\n            } else if (isTrailingEquals(entry.getKey())) {\n                sb.append('=');\n            }\n            sb.append('&');\n        }\n\n        // Remove trailing '&'.\n        if (!sb.isEmpty() && sb.charAt(sb.length() - 1) == '&') {\n            sb.deleteCharAt(sb.length() - 1);\n        }\n        return sb.toString();\n    }\n\n    @Override\n    public String toString() {\n        StringBuilder sb = new StringBuilder();\n        for (Map.Entry<String, String> entry : entries()) {\n            sb.append(entry.getKey());\n            if (!Strings.isNullOrEmpty(entry.getValue())) {\n                sb.append('=');\n                sb.append(entry.getValue());\n            }\n            sb.append('&');\n        }\n\n        // Remove trailing '&'.\n        if (!sb.isEmpty() && sb.charAt(sb.length() - 1) == '&') {\n            sb.deleteCharAt(sb.length() - 1);\n        }\n        return sb.toString();\n    }\n\n    @Override\n    protected HttpQueryParams clone() {\n        HttpQueryParams copy = new HttpQueryParams();\n        copy.delegate.putAll(this.delegate);\n        return copy;\n    }\n\n    public HttpQueryParams immutableCopy() {\n        return new HttpQueryParams(ImmutableListMultimap.copyOf(delegate));\n    }\n\n    public boolean isImmutable() {\n        return immutable;\n    }\n\n    public boolean isTrailingEquals(String key) {\n        return trailingEquals.getOrDefault(key, false);\n    }\n\n    public void setTrailingEquals(String key, boolean trailingEquals) {\n        this.trailingEquals.put(key, trailingEquals);\n    }\n\n    @Override\n    public int hashCode() {\n        return delegate.hashCode();\n    }\n\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == null) {\n            return false;\n        }\n        if (!(obj instanceof HttpQueryParams)) {\n            return false;\n        }\n\n        HttpQueryParams hqp2 = (HttpQueryParams) obj;\n        return Iterables.elementsEqual(delegate.entries(), hqp2.delegate.entries());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestInfo.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport java.util.Optional;\n\n/**\n * User: Mike Smith\n * Date: 7/15/15\n * Time: 1:18 PM\n */\npublic interface HttpRequestInfo extends ZuulMessage {\n    String getProtocol();\n\n    String getMethod();\n\n    String getPath();\n\n    HttpQueryParams getQueryParams();\n\n    String getPathAndQuery();\n\n    @Override\n    Headers getHeaders();\n\n    String getClientIp();\n\n    String getScheme();\n\n    int getPort();\n\n    String getServerName();\n\n    @Override\n    int getMaxBodySize();\n\n    @Override\n    String getInfoForLogging();\n\n    String getOriginalHost();\n\n    String getOriginalScheme();\n\n    String getOriginalProtocol();\n\n    int getOriginalPort();\n\n    /**\n     * Reflects the actual destination port that the client intended to communicate with,\n     * in preference to the port Zuul was listening on. In the case where proxy protocol is\n     * enabled, this should reflect the destination IP encoded in the TCP payload by the load balancer.\n     */\n    default Optional<Integer> getClientDestinationPort() {\n        throw new UnsupportedOperationException();\n    }\n\n    String reconstructURI();\n\n    /** Parse and lazily cache the request cookies. */\n    Cookies parseCookies();\n\n    /**\n     * Force parsing/re-parsing of the cookies. May want to do this if headers\n     * have been mutated since cookies were first parsed.\n     */\n    Cookies reParseCookies();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestMessage.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport com.netflix.zuul.message.ZuulMessage;\n\n/**\n * User: Mike Smith\n * Date: 7/15/15\n * Time: 5:36 PM\n */\npublic interface HttpRequestMessage extends HttpRequestInfo {\n    void setProtocol(String protocol);\n\n    void setMethod(String method);\n\n    void setPath(String path);\n\n    void setScheme(String scheme);\n\n    void setServerName(String serverName);\n\n    @Override\n    ZuulMessage clone();\n\n    void storeInboundRequest();\n\n    HttpRequestInfo getInboundRequest();\n\n    void setQueryParams(HttpQueryParams queryParams);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpRequestMessageImpl.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.message.http;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.config.DynamicStringProperty;\nimport com.netflix.util.Pair;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.ZuulMessageImpl;\nimport com.netflix.zuul.util.HttpUtils;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.handler.codec.http.cookie.ServerCookieDecoder;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URLDecoder;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels\n * Date: 2/24/15\n * Time: 10:54 AM\n */\npublic class HttpRequestMessageImpl implements HttpRequestMessage {\n    private static final Logger LOG = LoggerFactory.getLogger(HttpRequestMessageImpl.class);\n\n    private static final CachedDynamicBooleanProperty STRICT_HOST_HEADER_VALIDATION =\n            new CachedDynamicBooleanProperty(\"zuul.HttpRequestMessage.host.header.strict.validation\", true);\n\n    private static final CachedDynamicIntProperty MAX_BODY_SIZE_PROP =\n            new CachedDynamicIntProperty(\"zuul.HttpRequestMessage.body.max.size\", 15 * 1000 * 1024);\n    private static final CachedDynamicBooleanProperty CLEAN_COOKIES =\n            new CachedDynamicBooleanProperty(\"zuul.HttpRequestMessage.cookies.clean\", false);\n\n    /** \":::\"-delimited list of regexes to strip out of the cookie headers. */\n    private static final DynamicStringProperty REGEX_PTNS_TO_STRIP_PROP =\n            new DynamicStringProperty(\"zuul.request.cookie.cleaner.strip\", \" Secure,\");\n\n    private static final List<Pattern> RE_STRIP;\n\n    static {\n        RE_STRIP = new ArrayList<>();\n        for (String ptn : REGEX_PTNS_TO_STRIP_PROP.get().split(\":::\", -1)) {\n            RE_STRIP.add(Pattern.compile(ptn));\n        }\n    }\n\n    private static final String URI_SCHEME_SEP = \"://\";\n    private static final String URI_SCHEME_HTTP = \"http\";\n    private static final String URI_SCHEME_HTTPS = \"https\";\n\n    private final boolean immutable;\n    private final ZuulMessage message;\n    private String protocol;\n    private String method;\n    private String path;\n    private String decodedPath;\n    private HttpQueryParams queryParams;\n    private String clientIp;\n    private String scheme;\n    private int port;\n    private String serverName;\n    private final SocketAddress clientRemoteAddress;\n\n    private HttpRequestInfo inboundRequest = null;\n    private Cookies parsedCookies = null;\n\n    // These attributes are populated only if immutable=true.\n    private String reconstructedUri = null;\n    private String pathAndQuery = null;\n    private String infoForLogging = null;\n    private String originalHost = null;\n\n    private static final SocketAddress UNDEFINED_CLIENT_DEST_ADDRESS = new SocketAddress() {\n        @Override\n        public String toString() {\n            return \"Undefined destination address.\";\n        }\n    };\n\n    public HttpRequestMessageImpl(\n            SessionContext context,\n            String protocol,\n            String method,\n            String path,\n            HttpQueryParams queryParams,\n            Headers headers,\n            String clientIp,\n            String scheme,\n            int port,\n            String serverName) {\n        this(\n                context,\n                protocol,\n                method,\n                path,\n                queryParams,\n                headers,\n                clientIp,\n                scheme,\n                port,\n                serverName,\n                UNDEFINED_CLIENT_DEST_ADDRESS,\n                false);\n    }\n\n    public HttpRequestMessageImpl(\n            SessionContext context,\n            String protocol,\n            String method,\n            String path,\n            HttpQueryParams queryParams,\n            Headers headers,\n            String clientIp,\n            String scheme,\n            int port,\n            String serverName,\n            SocketAddress clientRemoteAddress,\n            boolean immutable) {\n        this.immutable = immutable;\n        this.message = new ZuulMessageImpl(context, headers);\n        this.protocol = protocol;\n        this.method = method;\n        this.path = path;\n        try {\n            this.decodedPath = URLDecoder.decode(path, \"UTF-8\");\n        } catch (Exception e) {\n            // fail to decode URI\n            // just set decodedPath to original path\n            this.decodedPath = path;\n        }\n        // Don't allow this to be null.\n        this.queryParams = queryParams == null ? new HttpQueryParams() : queryParams;\n        this.clientIp = clientIp;\n        this.scheme = scheme;\n        this.port = port;\n        this.serverName = serverName;\n        this.clientRemoteAddress = clientRemoteAddress;\n    }\n\n    private void immutableCheck() {\n        if (immutable) {\n            throw new IllegalStateException(\n                    \"This HttpRequestMessageImpl is immutable. No mutating operations allowed!\");\n        }\n    }\n\n    @Override\n    public SessionContext getContext() {\n        return message.getContext();\n    }\n\n    @Override\n    public Headers getHeaders() {\n        return message.getHeaders();\n    }\n\n    @Override\n    public void setHeaders(Headers newHeaders) {\n        immutableCheck();\n        message.setHeaders(newHeaders);\n    }\n\n    @Override\n    public void setHasBody(boolean hasBody) {\n        message.setHasBody(hasBody);\n    }\n\n    @Override\n    public boolean hasBody() {\n        return message.hasBody();\n    }\n\n    @Override\n    public void bufferBodyContents(HttpContent chunk) {\n        message.bufferBodyContents(chunk);\n    }\n\n    @Override\n    public void setBodyAsText(String bodyText) {\n        message.setBodyAsText(bodyText);\n    }\n\n    @Override\n    public void setBody(byte[] body) {\n        message.setBody(body);\n    }\n\n    @Override\n    public boolean finishBufferedBodyIfIncomplete() {\n        return message.finishBufferedBodyIfIncomplete();\n    }\n\n    @Override\n    public Iterable<HttpContent> getBodyContents() {\n        return message.getBodyContents();\n    }\n\n    @Override\n    public void runBufferedBodyContentThroughFilter(ZuulFilter filter) {\n        message.runBufferedBodyContentThroughFilter(filter);\n    }\n\n    @Override\n    public String getBodyAsText() {\n        return message.getBodyAsText();\n    }\n\n    @Override\n    public byte[] getBody() {\n        return message.getBody();\n    }\n\n    @Override\n    public int getBodyLength() {\n        return message.getBodyLength();\n    }\n\n    @Override\n    public void resetBodyReader() {\n        message.resetBodyReader();\n    }\n\n    @Override\n    public boolean hasCompleteBody() {\n        return message.hasCompleteBody();\n    }\n\n    @Override\n    public void disposeBufferedBody() {\n        message.disposeBufferedBody();\n    }\n\n    @Override\n    public String getProtocol() {\n        return protocol;\n    }\n\n    @Override\n    public void setProtocol(String protocol) {\n        immutableCheck();\n        this.protocol = protocol;\n    }\n\n    @Override\n    public String getMethod() {\n        return method;\n    }\n\n    @Override\n    public void setMethod(String method) {\n        immutableCheck();\n        this.method = method;\n    }\n\n    @Override\n    public String getPath() {\n        if (Objects.equals(message.getContext().get(CommonContextKeys.ZUUL_USE_DECODED_URI), Boolean.TRUE)) {\n            return decodedPath;\n        }\n        return path;\n    }\n\n    public String getDecodedPath() {\n        return decodedPath;\n    }\n\n    @Override\n    public void setPath(String path) {\n        immutableCheck();\n        this.path = path;\n        this.decodedPath = path;\n    }\n\n    @Override\n    public HttpQueryParams getQueryParams() {\n        return queryParams;\n    }\n\n    @Override\n    public String getPathAndQuery() {\n        // If this instance is immutable, then lazy-cache.\n        if (immutable) {\n            if (pathAndQuery == null) {\n                pathAndQuery = generatePathAndQuery();\n            }\n            return pathAndQuery;\n        } else {\n            return generatePathAndQuery();\n        }\n    }\n\n    protected String generatePathAndQuery() {\n        if (queryParams != null && queryParams.entries().size() > 0) {\n            return getPath() + \"?\" + queryParams.toEncodedString();\n        } else {\n            return getPath();\n        }\n    }\n\n    @Override\n    public String getClientIp() {\n        return clientIp;\n    }\n\n    @Deprecated\n    @VisibleForTesting\n    void setClientIp(String clientIp) {\n        immutableCheck();\n        this.clientIp = clientIp;\n    }\n\n    /**\n     * Note: this is meant to be used typically in cases where TLS termination happens on instance.\n     * For CLB/ALB fronted traffic, consider using {@link #getOriginalScheme()} instead.\n     */\n    @Override\n    public String getScheme() {\n        return scheme;\n    }\n\n    @Override\n    public void setScheme(String scheme) {\n        immutableCheck();\n        this.scheme = scheme;\n    }\n\n    @Override\n    public int getPort() {\n        return port;\n    }\n\n    @Deprecated\n    @VisibleForTesting\n    void setPort(int port) {\n        immutableCheck();\n        this.port = port;\n    }\n\n    @Override\n    public String getServerName() {\n        return serverName;\n    }\n\n    @Override\n    public void setServerName(String serverName) {\n        immutableCheck();\n        this.serverName = serverName;\n    }\n\n    @Override\n    public Cookies parseCookies() {\n        if (parsedCookies == null) {\n            parsedCookies = reParseCookies();\n        }\n        return parsedCookies;\n    }\n\n    @Override\n    public Cookies reParseCookies() {\n        Cookies cookies = new Cookies();\n        for (String aCookieHeader : getHeaders().getAll(HttpHeaderNames.COOKIE)) {\n            try {\n                if (CLEAN_COOKIES.get()) {\n                    aCookieHeader = cleanCookieHeader(aCookieHeader);\n                }\n\n                List<Cookie> decoded = ServerCookieDecoder.LAX.decodeAll(aCookieHeader);\n                for (Cookie cookie : decoded) {\n                    cookies.add(cookie);\n                }\n            } catch (Exception e) {\n                LOG.warn(\n                        \"Error parsing request Cookie header. cookie={}, request-info={}\",\n                        aCookieHeader,\n                        getInfoForLogging(),\n                        e);\n            }\n        }\n        parsedCookies = cookies;\n        return cookies;\n    }\n\n    @VisibleForTesting\n    static String cleanCookieHeader(String cookie) {\n        for (Pattern stripPtn : RE_STRIP) {\n            Matcher matcher = stripPtn.matcher(cookie);\n            if (matcher.find()) {\n                cookie = matcher.replaceAll(\"\");\n            }\n        }\n        return cookie;\n    }\n\n    @Override\n    public int getMaxBodySize() {\n        return MAX_BODY_SIZE_PROP.get();\n    }\n\n    @Override\n    public ZuulMessage clone() {\n        HttpRequestMessageImpl clone = new HttpRequestMessageImpl(\n                message.getContext().clone(),\n                protocol,\n                method,\n                path,\n                queryParams.clone(),\n                Headers.copyOf(message.getHeaders()),\n                clientIp,\n                scheme,\n                port,\n                serverName,\n                clientRemoteAddress,\n                immutable);\n        if (getInboundRequest() != null) {\n            clone.inboundRequest = (HttpRequestInfo) getInboundRequest().clone();\n        }\n        return clone;\n    }\n\n    protected HttpRequestInfo copyRequestInfo() {\n\n        HttpRequestMessageImpl req = new HttpRequestMessageImpl(\n                message.getContext(),\n                protocol,\n                method,\n                path,\n                queryParams.immutableCopy(),\n                Headers.copyOf(message.getHeaders()),\n                clientIp,\n                scheme,\n                port,\n                serverName,\n                clientRemoteAddress,\n                true);\n        req.setHasBody(hasBody());\n        return req;\n    }\n\n    @Override\n    public void storeInboundRequest() {\n        inboundRequest = copyRequestInfo();\n    }\n\n    @Override\n    public HttpRequestInfo getInboundRequest() {\n        return inboundRequest;\n    }\n\n    @Override\n    public void setQueryParams(HttpQueryParams queryParams) {\n        immutableCheck();\n        this.queryParams = queryParams;\n    }\n\n    @Override\n    public String getInfoForLogging() {\n        // If this instance is immutable, then lazy-cache generating this info.\n        if (immutable) {\n            if (infoForLogging == null) {\n                infoForLogging = generateInfoForLogging();\n            }\n            return infoForLogging;\n        } else {\n            return generateInfoForLogging();\n        }\n    }\n\n    protected String generateInfoForLogging() {\n        HttpRequestInfo req = getInboundRequest() == null ? this : getInboundRequest();\n        StringBuilder sb = new StringBuilder()\n                .append(\"uri=\")\n                .append(req.reconstructURI())\n                .append(\", method=\")\n                .append(req.getMethod())\n                .append(\", clientip=\")\n                .append(HttpUtils.getClientIP(req));\n        return sb.toString();\n    }\n\n    /**\n     * The originally request host. This will NOT include port.\n     *\n     * The Host header may contain port, but in this method we strip it out for consistency - use the\n     * getOriginalPort method for that.\n     */\n    @Override\n    public String getOriginalHost() {\n        try {\n            if (originalHost == null) {\n                originalHost = getOriginalHost(getHeaders(), getServerName());\n            }\n            return originalHost;\n        } catch (URISyntaxException e) {\n            throw new IllegalArgumentException(e);\n        }\n    }\n\n    @VisibleForTesting\n    static String getOriginalHost(Headers headers, String serverName) throws URISyntaxException {\n        String xForwardedHost = headers.getFirst(HttpHeaderNames.X_FORWARDED_HOST);\n        if (xForwardedHost != null) {\n            return xForwardedHost;\n        }\n        Pair<String, Integer> host = parseHostHeader(headers);\n        if (host.first() != null) {\n            return host.first();\n        }\n        return serverName;\n    }\n\n    @Override\n    public String getOriginalScheme() {\n        String scheme = getHeaders().getFirst(HttpHeaderNames.X_FORWARDED_PROTO);\n        if (scheme == null) {\n            scheme = getScheme();\n        }\n        return scheme;\n    }\n\n    @Override\n    public String getOriginalProtocol() {\n        String proto = getHeaders().getFirst(HttpHeaderNames.X_FORWARDED_PROTO_VERSION);\n        if (proto == null) {\n            proto = getProtocol();\n        }\n        return proto;\n    }\n\n    @Override\n    public int getOriginalPort() {\n        return getOriginalPort(getContext(), getHeaders(), getPort());\n    }\n\n    @VisibleForTesting\n    static int getOriginalPort(SessionContext context, Headers headers, int serverPort) {\n        if (context.containsKey(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS)) {\n            return ((InetSocketAddress) context.get(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS)).getPort();\n        }\n        String portStr = headers.getFirst(HttpHeaderNames.X_FORWARDED_PORT);\n        if (portStr != null && !portStr.isEmpty()) {\n            return Integer.parseInt(portStr);\n        }\n\n        try {\n            // Check if port was specified on a Host header.\n            Pair<String, Integer> host = parseHostHeader(headers);\n            if (host.second() != -1) {\n                return host.second();\n            }\n        } catch (URISyntaxException e) {\n            LOG.debug(\"Invalid host header, falling back to serverPort\", e);\n        }\n\n        return serverPort;\n    }\n\n    @Override\n    public Optional<Integer> getClientDestinationPort() {\n        if (clientRemoteAddress instanceof InetSocketAddress inetSocketAddress) {\n            return Optional.of(inetSocketAddress.getPort());\n        } else {\n            return Optional.empty();\n        }\n    }\n\n    /**\n     * Attempt to parse the Host header from the collection of headers\n     * and return the hostname and port components.\n     *\n     * @return Hostname and Port pair.\n     *         Hostname may be null. Port may be -1 when no valid port is found in the host header.\n     */\n    private static Pair<String, Integer> parseHostHeader(Headers headers) throws URISyntaxException {\n        String host = headers.getFirst(HttpHeaderNames.HOST);\n        if (host == null) {\n            return new Pair<>(null, -1);\n        }\n\n        try {\n            // attempt to use default URI parsing - this can fail when not strictly following RFC2396,\n            // for example, having underscores in host names will fail parsing\n            URI uri = new URI(/* scheme= */ null, host, /* path= */ null, /* query= */ null, /* fragment= */ null);\n            if (uri.getHost() != null) {\n                return new Pair<>(uri.getHost(), uri.getPort());\n            }\n        } catch (URISyntaxException e) {\n            LOG.debug(\"URI parsing failed\", e);\n        }\n\n        if (STRICT_HOST_HEADER_VALIDATION.get()) {\n            throw new URISyntaxException(host, \"Invalid host\");\n        }\n\n        // fallback to using a colon split\n        // valid IPv6 addresses would have been handled already so any colon is safely assumed a port separator\n        String[] components = host.split(\":\", -1);\n        if (components.length > 2) {\n            // handle case with unbracketed IPv6 addresses\n            return new Pair<>(null, -1);\n        }\n\n        String parsedHost = components[0];\n        int parsedPort = -1;\n        if (components.length > 1) {\n            try {\n                parsedPort = Integer.parseInt(components[1]);\n            } catch (NumberFormatException e) {\n                // ignore failing to parse port numbers and fallback to default port\n                LOG.debug(\"Parsing of host port component failed\", e);\n            }\n        }\n        return new Pair<>(parsedHost, parsedPort);\n    }\n\n    /**\n     * Attempt to reconstruct the full URI that the client used.\n     *\n     * @return String\n     */\n    @Override\n    public String reconstructURI() {\n        // If this instance is immutable, then lazy-cache reconstructing the uri.\n        if (immutable) {\n            if (reconstructedUri == null) {\n                reconstructedUri = _reconstructURI();\n            }\n            return reconstructedUri;\n        } else {\n            return _reconstructURI();\n        }\n    }\n\n    protected String _reconstructURI() {\n        try {\n            StringBuilder uri = new StringBuilder(100);\n\n            String scheme = getOriginalScheme().toLowerCase(Locale.ROOT);\n            uri.append(scheme);\n            uri.append(URI_SCHEME_SEP).append(getOriginalHost());\n\n            int port = getOriginalPort();\n            if ((scheme.equals(URI_SCHEME_HTTP) && port == 80) || (scheme.equals(URI_SCHEME_HTTPS) && port == 443)) {\n                // Don't need to include port.\n            } else {\n                uri.append(':').append(port);\n            }\n\n            uri.append(getPathAndQuery());\n\n            return uri.toString();\n        } catch (IllegalArgumentException e) {\n            // This is not really so bad, just debug log it and move on.\n            LOG.debug(\"Error reconstructing request URI!\", e);\n            return \"\";\n        } catch (Exception e) {\n            LOG.error(\"Error reconstructing request URI!\", e);\n            return \"\";\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"HttpRequestMessageImpl{\" + \"immutable=\"\n                + immutable + \", message=\"\n                + message + \", protocol='\"\n                + protocol + '\\'' + \", method='\"\n                + method + '\\'' + \", path='\"\n                + path + '\\'' + \", queryParams=\"\n                + queryParams + \", clientIp='\"\n                + clientIp + '\\'' + \", scheme='\"\n                + scheme + '\\'' + \", port=\"\n                + port + \", serverName='\"\n                + serverName + '\\'' + \", inboundRequest=\"\n                + inboundRequest + \", parsedCookies=\"\n                + parsedCookies + \", reconstructedUri='\"\n                + reconstructedUri + '\\'' + \", pathAndQuery='\"\n                + pathAndQuery + '\\'' + \", originalHost='\"\n                + originalHost + '\\'' + \", infoForLogging='\"\n                + infoForLogging + '\\'' + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseInfo.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport com.netflix.zuul.message.ZuulMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/6/15\n * Time: 5:27 PM\n */\npublic interface HttpResponseInfo extends ZuulMessage {\n    int getStatus();\n\n    /** The immutable request that was originally received from client. */\n    HttpRequestInfo getInboundRequest();\n\n    @Override\n    ZuulMessage clone();\n\n    @Override\n    String getInfoForLogging();\n\n    Cookies parseSetCookieHeader(String setCookieValue);\n\n    boolean hasSetCookieWithName(String cookieName);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseMessage.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport io.netty.handler.codec.http.cookie.Cookie;\n\n/**\n * User: Mike Smith\n * Date: 7/16/15\n * Time: 12:45 AM\n */\npublic interface HttpResponseMessage extends HttpResponseInfo {\n    void setStatus(int status);\n\n    @Override\n    int getMaxBodySize();\n\n    void addSetCookie(Cookie cookie);\n\n    void setSetCookie(Cookie cookie);\n\n    boolean removeExistingSetCookie(String cookieName);\n\n    /** The mutable request that will be sent to Origin. */\n    HttpRequestMessage getOutboundRequest();\n\n    /** The immutable response that was received from Origin. */\n    HttpResponseInfo getInboundResponse();\n\n    /** This should be called after response received from Origin, to store\n     * a copy of the response as-is. */\n    void storeInboundResponse();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/http/HttpResponseMessageImpl.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.message.http;\n\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.config.DynamicPropertyFactory;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.Header;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.ZuulMessageImpl;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.cookie.ClientCookieDecoder;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.handler.codec.http.cookie.ServerCookieEncoder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels\n * Date: 2/24/15\n * Time: 10:54 AM\n */\npublic class HttpResponseMessageImpl implements HttpResponseMessage {\n    private static final DynamicIntProperty MAX_BODY_SIZE_PROP = DynamicPropertyFactory.getInstance()\n            .getIntProperty(\"zuul.HttpResponseMessage.body.max.size\", 25 * 1000 * 1024);\n    private static final Logger LOG = LoggerFactory.getLogger(HttpResponseMessageImpl.class);\n\n    private final ZuulMessage message;\n    private final HttpRequestMessage outboundRequest;\n    private int status;\n    private HttpResponseInfo inboundResponse = null;\n\n    public HttpResponseMessageImpl(SessionContext context, HttpRequestMessage request, int status) {\n        this(context, new Headers(), request, status);\n    }\n\n    public HttpResponseMessageImpl(SessionContext context, Headers headers, HttpRequestMessage request, int status) {\n        this.message = new ZuulMessageImpl(context, headers);\n        this.outboundRequest = request;\n        if (this.outboundRequest.getInboundRequest() == null) {\n            LOG.warn(\n                    \"HttpResponseMessage created with a request that does not have a stored inboundRequest! Probably a\"\n                            + \" bug in the filter that is creating this response.\",\n                    new RuntimeException(\"Invalid HttpRequestMessage\"));\n        }\n        this.status = status;\n    }\n\n    public static HttpResponseMessage defaultErrorResponse(HttpRequestMessage request) {\n        HttpResponseMessage resp = new HttpResponseMessageImpl(request.getContext(), request, 500);\n        resp.finishBufferedBodyIfIncomplete();\n        return resp;\n    }\n\n    @Override\n    public Headers getHeaders() {\n        return message.getHeaders();\n    }\n\n    @Override\n    public SessionContext getContext() {\n        return message.getContext();\n    }\n\n    @Override\n    public void setHeaders(Headers newHeaders) {\n        message.setHeaders(newHeaders);\n    }\n\n    @Override\n    public void setHasBody(boolean hasBody) {\n        message.setHasBody(hasBody);\n    }\n\n    @Override\n    public boolean hasBody() {\n        return message.hasBody();\n    }\n\n    @Override\n    public void bufferBodyContents(HttpContent chunk) {\n        message.bufferBodyContents(chunk);\n    }\n\n    @Override\n    public void setBodyAsText(String bodyText) {\n        message.setBodyAsText(bodyText);\n    }\n\n    @Override\n    public void setBody(byte[] body) {\n        message.setBody(body);\n    }\n\n    @Override\n    public String getBodyAsText() {\n        return message.getBodyAsText();\n    }\n\n    @Override\n    public byte[] getBody() {\n        return message.getBody();\n    }\n\n    @Override\n    public int getBodyLength() {\n        return message.getBodyLength();\n    }\n\n    @Override\n    public boolean hasCompleteBody() {\n        return message.hasCompleteBody();\n    }\n\n    @Override\n    public boolean finishBufferedBodyIfIncomplete() {\n        return message.finishBufferedBodyIfIncomplete();\n    }\n\n    @Override\n    public Iterable<HttpContent> getBodyContents() {\n        return message.getBodyContents();\n    }\n\n    @Override\n    public void resetBodyReader() {\n        message.resetBodyReader();\n    }\n\n    @Override\n    public void runBufferedBodyContentThroughFilter(ZuulFilter filter) {\n        message.runBufferedBodyContentThroughFilter(filter);\n    }\n\n    @Override\n    public void disposeBufferedBody() {\n        message.disposeBufferedBody();\n    }\n\n    @Override\n    public HttpRequestInfo getInboundRequest() {\n        return outboundRequest.getInboundRequest();\n    }\n\n    @Override\n    public HttpRequestMessage getOutboundRequest() {\n        return outboundRequest;\n    }\n\n    @Override\n    public int getStatus() {\n        return status;\n    }\n\n    @Override\n    public void setStatus(int status) {\n        this.status = status;\n    }\n\n    @Override\n    public int getMaxBodySize() {\n        return MAX_BODY_SIZE_PROP.get();\n    }\n\n    @Override\n    public Cookies parseSetCookieHeader(String setCookieValue) {\n        Cookies cookies = new Cookies();\n        cookies.add(ClientCookieDecoder.STRICT.decode(setCookieValue));\n        return cookies;\n    }\n\n    @Override\n    public boolean hasSetCookieWithName(String cookieName) {\n        for (String setCookieValue : getHeaders().getAll(HttpHeaderNames.SET_COOKIE)) {\n            Cookie cookie = ClientCookieDecoder.STRICT.decode(setCookieValue);\n            if (cookie.name().equalsIgnoreCase(cookieName)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean removeExistingSetCookie(String cookieName) {\n        String cookieNamePrefix = cookieName + \"=\";\n        boolean dirty = false;\n        Headers filtered = new Headers();\n        for (Header hdr : getHeaders().entries()) {\n            if (hdr.getName().equals(HttpHeaderNames.SET_COOKIE)) {\n                String value = hdr.getValue();\n\n                // Strip out this set-cookie as requested.\n                if (value.startsWith(cookieNamePrefix)) {\n                    // Don't copy it.\n                    dirty = true;\n                } else {\n                    // Copy all other headers.\n                    filtered.add(hdr.getName(), hdr.getValue());\n                }\n            } else {\n                // Copy all other headers.\n                filtered.add(hdr.getName(), hdr.getValue());\n            }\n        }\n\n        if (dirty) {\n            setHeaders(filtered);\n        }\n        return dirty;\n    }\n\n    @Override\n    public void addSetCookie(Cookie cookie) {\n        getHeaders().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.LAX.encode(cookie));\n    }\n\n    @Override\n    public void setSetCookie(Cookie cookie) {\n        getHeaders().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.LAX.encode(cookie));\n    }\n\n    @Override\n    public ZuulMessage clone() {\n        // TODO - not sure if should be cloning the outbound request object here or not....\n        HttpResponseMessageImpl clone = new HttpResponseMessageImpl(\n                getContext().clone(), Headers.copyOf(getHeaders()), getOutboundRequest(), getStatus());\n        if (getInboundResponse() != null) {\n            clone.inboundResponse = (HttpResponseInfo) getInboundResponse().clone();\n        }\n        return clone;\n    }\n\n    protected HttpResponseInfo copyResponseInfo() {\n        HttpResponseMessageImpl response = new HttpResponseMessageImpl(\n                getContext(), Headers.copyOf(getHeaders()), getOutboundRequest(), getStatus());\n        response.setHasBody(hasBody());\n        return response;\n    }\n\n    @Override\n    public String toString() {\n        return \"HttpResponseMessageImpl{\" + \"message=\"\n                + message + \", outboundRequest=\"\n                + outboundRequest + \", status=\"\n                + status + \", inboundResponse=\"\n                + inboundResponse + '}';\n    }\n\n    @Override\n    public void storeInboundResponse() {\n        inboundResponse = copyResponseInfo();\n    }\n\n    @Override\n    public HttpResponseInfo getInboundResponse() {\n        return inboundResponse;\n    }\n\n    @Override\n    public String getInfoForLogging() {\n        HttpRequestInfo req = getInboundRequest() == null ? getOutboundRequest() : getInboundRequest();\n        StringBuilder sb = new StringBuilder()\n                .append(req.getInfoForLogging())\n                .append(\",proxy-status=\")\n                .append(getStatus());\n        return sb.toString();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/message/util/HttpRequestBuilder.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.util;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpVersion;\nimport java.util.Objects;\n\n/**\n * Builder for a zuul http request. *exclusively* for use in unit tests.\n *\n * For default values initialized in the constructor:\n * <pre>\n * {@code new HttpRequestBuilder(context).withDefaults();}\n *</pre>\n *\n * For overrides :\n * <pre>\n * {@code new HttpRequestBuilder(context).withHeaders(httpHeaders).withQueryParams(requestParams).build();}\n * </pre>\n * @author Argha C\n * @since 5/11/21\n */\npublic final class HttpRequestBuilder {\n    private final SessionContext sessionContext;\n    private final String protocol;\n    private String method;\n    private String path;\n    private HttpQueryParams queryParams;\n    private Headers headers;\n    private final String clientIp;\n    private final String scheme;\n    private int port;\n    private String serverName;\n    private boolean isBuilt;\n\n    public HttpRequestBuilder(SessionContext context) {\n        sessionContext = Objects.requireNonNull(context);\n        protocol = HttpVersion.HTTP_1_1.text();\n        method = \"get\";\n        path = \"/\";\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        clientIp = \"::1\";\n        scheme = \"https\";\n        port = 443;\n        isBuilt = false;\n    }\n\n    /**\n     * Builds a request with basic defaults\n     *\n     * @return `HttpRequestMessage`\n     */\n    public HttpRequestMessage withDefaults() {\n        return build();\n    }\n\n    public HttpRequestBuilder withHost(String hostName) {\n        serverName = Objects.requireNonNull(hostName);\n        return this;\n    }\n\n    public HttpRequestBuilder withHeaders(Headers requestHeaders) {\n        headers = Objects.requireNonNull(requestHeaders);\n        return this;\n    }\n\n    public HttpRequestBuilder withQueryParams(HttpQueryParams requestParams) {\n        this.queryParams = Objects.requireNonNull(requestParams);\n        return this;\n    }\n\n    public HttpRequestBuilder withMethod(HttpMethod httpMethod) {\n        method = Objects.requireNonNull(httpMethod).name();\n        return this;\n    }\n\n    public HttpRequestBuilder withUri(String uri) {\n        path = Objects.requireNonNull(uri);\n        return this;\n    }\n\n    public HttpRequestBuilder withPort(int port) {\n        this.port = port;\n        return this;\n    }\n\n    /**\n     * Used to build a request with overridden values\n     *\n     * @return `HttpRequestMessage`\n     */\n    public HttpRequestMessage build() {\n        if (isBuilt) {\n            throw new IllegalStateException(\"Builder must only be invoked once!\");\n        }\n        isBuilt = true;\n        return new HttpRequestMessageImpl(\n                sessionContext, protocol, method, path, queryParams, headers, clientIp, scheme, port, serverName);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/metrics/OriginStats.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.metrics;\n\n/**\n * User: michaels@netflix.com\n * Date: 3/20/15\n * Time: 5:55 PM\n */\npublic interface OriginStats {\n    public void started();\n\n    public void completed(boolean success, long totalTimeMS);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/metrics/OriginStatsFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.metrics;\n\n/**\n * User: michaels@netflix.com\n * Date: 3/20/15\n * Time: 6:14 PM\n */\npublic interface OriginStatsFactory {\n    public OriginStats create(String name);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnCounter.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.monitoring;\n\nimport com.netflix.spectator.api.Gauge;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.channel.Channel;\nimport io.netty.util.AttributeKey;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * A counter for connection stats.  Not thread-safe.\n */\n@SuppressWarnings(\"ErroneousBitwiseExpression\")\npublic final class ConnCounter {\n\n    private static final Logger logger = LoggerFactory.getLogger(ConnCounter.class);\n\n    private static final AttributeKey<ConnCounter> CONN_COUNTER = AttributeKey.newInstance(\"zuul.conncounter\");\n\n    private static final int LOCK_COUNT = 256;\n    private static final int LOCK_MASK = LOCK_COUNT - 1;\n\n    private static final Attrs EMPTY = Attrs.newInstance();\n\n    /**\n     * An array of locks to guard the gauges.   This is the same as Guava's Striped, but avoids the dep.\n     * <p>\n     * This can be removed after https://github.com/Netflix/spectator/issues/862 is fixed.\n     */\n    private static final Object[] locks = new Object[LOCK_COUNT];\n\n    static {\n        assert (LOCK_COUNT & LOCK_MASK) == 0;\n        for (int i = 0; i < locks.length; i++) {\n            locks[i] = new Object();\n        }\n    }\n\n    private final Registry registry;\n    private final Channel chan;\n    private final Id metricBase;\n\n    private String lastCountKey;\n\n    private final Map<String, Gauge> counts = new HashMap<>();\n\n    private ConnCounter(Registry registry, Channel chan, Id metricBase) {\n        this.registry = Objects.requireNonNull(registry);\n        this.chan = Objects.requireNonNull(chan);\n        this.metricBase = Objects.requireNonNull(metricBase);\n    }\n\n    public static ConnCounter install(Channel chan, Registry registry, Id metricBase) {\n        ConnCounter counter = new ConnCounter(registry, chan, metricBase);\n        if (!chan.attr(CONN_COUNTER).compareAndSet(null, counter)) {\n            throw new IllegalStateException(\"pre-existing counter already present\");\n        }\n        return counter;\n    }\n\n    public static ConnCounter from(Channel chan) {\n        Objects.requireNonNull(chan);\n        ConnCounter counter = chan.attr(CONN_COUNTER).get();\n        if (counter != null) {\n            return counter;\n        }\n        if (chan.parent() != null && (counter = chan.parent().attr(CONN_COUNTER).get()) != null) {\n            return counter;\n        }\n        throw new IllegalStateException(\"no counter on channel\");\n    }\n\n    public void increment(String event) {\n        increment(event, EMPTY);\n    }\n\n    public void increment(String event, Attrs extraDimensions) {\n        Objects.requireNonNull(event);\n        Objects.requireNonNull(extraDimensions);\n        if (counts.containsKey(event)) {\n            // TODO(carl-mastrangelo): make this throw IllegalStateException after verifying this doesn't happen.\n            logger.warn(\"Duplicate conn counter increment {}\", event);\n            return;\n        }\n        Attrs connDims = chan.attr(Server.CONN_DIMENSIONS).get();\n        Map<String, String> dimTags = new HashMap<>(connDims.size() + extraDimensions.size());\n\n        connDims.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v)));\n        extraDimensions.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v)));\n\n        dimTags.put(\"from\", lastCountKey != null ? lastCountKey : \"nascent\");\n        lastCountKey = event;\n        Id id = registry.createId(metricBase.name() + '.' + event)\n                .withTags(metricBase.tags())\n                .withTags(dimTags);\n        Gauge gauge = registry.gauge(id);\n\n        synchronized (getLock(id)) {\n            double current = gauge.value();\n            gauge.set(Double.isNaN(current) ? 1 : current + 1);\n        }\n        counts.put(event, gauge);\n    }\n\n    public double getCurrentActiveConns() {\n        return counts.containsKey(\"active\") ? counts.get(\"active\").value() : 0.0;\n    }\n\n    public void decrement(String event) {\n        Objects.requireNonNull(event);\n        Gauge gauge = counts.remove(event);\n        if (gauge == null) {\n            // TODO(carl-mastrangelo): make this throw IllegalStateException after verifying this doesn't happen.\n            logger.warn(\"Missing conn counter increment {}\", event);\n            return;\n        }\n        synchronized (getLock(gauge.id())) {\n            // Noop gauges break this assertion in tests, but the type is package private.   Check to make sure\n            // the gauge has a value, or by implementation cannot have a value.\n            assert !Double.isNaN(gauge.value())\n                    || gauge.getClass().getName().equals(\"com.netflix.spectator.api.NoopGauge\");\n            gauge.set(gauge.value() - 1);\n        }\n    }\n\n    // This is here to pick the correct lock stripe.   This avoids multiple threads synchronizing on the\n    // same lock in the common case.   This can go away once there is an atomic gauge update implemented\n    // in spectator.\n    private static Object getLock(Id id) {\n        return locks[id.hashCode() & LOCK_MASK];\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/monitoring/ConnTimer.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.monitoring;\n\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.channel.Channel;\nimport io.netty.util.AttributeKey;\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport javax.annotation.Nullable;\n\n/**\n * A timer for connection stats.  Not thread-safe.\n */\npublic final class ConnTimer {\n\n    private static final DynamicBooleanProperty PRECISE_TIMING =\n            new DynamicBooleanProperty(\"zuul.conn.precise_timing\", false);\n\n    private static final AttributeKey<ConnTimer> CONN_TIMER = AttributeKey.newInstance(\"zuul.conntimer\");\n\n    private static final Duration MIN_CONN_TIMING = Duration.ofNanos(1024);\n    private static final Duration MAX_CONN_TIMING = Duration.ofDays(366);\n\n    private static final Attrs EMPTY = Attrs.newInstance();\n\n    private final Registry registry;\n    private final Channel chan;\n    // TODO(carl-mastrangelo): make this changeable.\n    private final Id metricBase;\n\n    @Nullable\n    private final Id preciseMetricBase;\n\n    private final Map<String, Long> timings = new LinkedHashMap<>();\n\n    private ConnTimer(Registry registry, Channel chan, Id metricBase) {\n        this.registry = Objects.requireNonNull(registry);\n        this.chan = Objects.requireNonNull(chan);\n        this.metricBase = Objects.requireNonNull(metricBase);\n        if (PRECISE_TIMING.get()) {\n            preciseMetricBase = registry.createId(metricBase.name() + \".pct\").withTags(metricBase.tags());\n        } else {\n            preciseMetricBase = null;\n        }\n    }\n\n    public static ConnTimer install(Channel chan, Registry registry, Id metricBase) {\n        ConnTimer timer = new ConnTimer(registry, chan, metricBase);\n        if (!chan.attr(CONN_TIMER).compareAndSet(null, timer)) {\n            throw new IllegalStateException(\"pre-existing timer already present\");\n        }\n        return timer;\n    }\n\n    public static ConnTimer from(Channel chan) {\n        Objects.requireNonNull(chan);\n        ConnTimer timer = chan.attr(CONN_TIMER).get();\n        if (timer != null) {\n            return timer;\n        }\n        if (chan.parent() != null && (timer = chan.parent().attr(CONN_TIMER).get()) != null) {\n            return timer;\n        }\n        throw new IllegalStateException(\"no timer on channel\");\n    }\n\n    public void record(Long now, String event) {\n        record(now, event, EMPTY);\n    }\n\n    public void record(Long now, String event, Attrs extraDimensions) {\n        if (timings.containsKey(event)) {\n            return;\n        }\n        Objects.requireNonNull(now);\n        Objects.requireNonNull(event);\n        Objects.requireNonNull(extraDimensions);\n\n        Attrs connDims = chan.attr(Server.CONN_DIMENSIONS).get();\n        Map<String, String> dimTags = new HashMap<>(connDims.size() + extraDimensions.size());\n\n        connDims.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v)));\n        extraDimensions.forEach((k, v) -> dimTags.put(k.name(), String.valueOf(v)));\n\n        // Note: this is effectively O(n^2) because it will be called for each event in the connection\n        // setup.  It should be bounded to at most 10 or so.\n        timings.forEach((from, stamp) -> {\n            long durationNanos = now - stamp;\n            if (durationNanos == 0) {\n                // This may happen if an event is double listed, or if the timer is not accurate enough to record\n                // it.\n                return;\n            }\n            registry.timer(buildId(metricBase, from, event, dimTags)).record(durationNanos, TimeUnit.NANOSECONDS);\n            if (preciseMetricBase != null) {\n                PercentileTimer.builder(registry)\n                        .withId(buildId(preciseMetricBase, from, event, dimTags))\n                        .withRange(MIN_CONN_TIMING, MAX_CONN_TIMING)\n                        .build()\n                        .record(durationNanos, TimeUnit.NANOSECONDS);\n            }\n        });\n        timings.put(event, now);\n    }\n\n    private Id buildId(Id base, String from, String to, Map<String, String> tags) {\n        return registry.createId(metricBase.name() + '.' + from + '-' + to)\n                .withTags(base.tags())\n                .withTags(tags);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/monitoring/MonitoringHelper.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.monitoring;\n\n/**\n * Dummy implementations of CounterFactory, TracerFactory, and Tracer\n * @author mhawthorne\n */\npublic class MonitoringHelper {\n\n    public static final void initMocks() {\n        TracerFactory.initialize(new TracerFactoryImpl());\n    }\n\n    private static final class TracerFactoryImpl extends TracerFactory {\n        @Override\n        public Tracer startMicroTracer(String name) {\n            return new TracerImpl();\n        }\n    }\n\n    private static final class TracerImpl implements Tracer {\n        @Override\n        public void setName(String name) {}\n\n        @Override\n        public void stopAndLog() {}\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/monitoring/Tracer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.monitoring;\n\n/**\n * Time based monitoring metric.\n *\n * @author mhawthorne\n */\npublic interface Tracer {\n\n    /**\n     * Stops and Logs a time based tracer\n     *\n     */\n    void stopAndLog();\n\n    /**\n     * Sets the name for the time based tracer\n     *\n     * @param name a <code>String</code> value\n     */\n    void setName(String name);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/monitoring/TracerFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.monitoring;\n\n/**\n * Abstraction layer to provide time-based monitoring.\n *\n * @author mhawthorne\n */\npublic abstract class TracerFactory {\n\n    private static TracerFactory INSTANCE;\n\n    /**\n     * sets a TracerFactory Implementation\n     *\n     * @param f a <code>TracerFactory</code> value\n     */\n    public static final void initialize(TracerFactory f) {\n        INSTANCE = f;\n    }\n\n    /**\n     * Returns the singleton TracerFactory\n     *\n     * @return a <code>TracerFactory</code> value\n     */\n    public static final TracerFactory instance() {\n        if (INSTANCE == null) {\n            throw new IllegalStateException(String.format(\"%s not initialized\", TracerFactory.class.getSimpleName()));\n        }\n        return INSTANCE;\n    }\n\n    public abstract Tracer startMicroTracer(String name);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/ChannelUtils.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty;\n\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.Channel;\n\npublic class ChannelUtils {\n    public static String channelInfoForLogging(Channel ch) {\n        if (ch == null) {\n            return \"null\";\n        }\n\n        String passport = CurrentPassport.fromChannel(ch).toString();\n        StringBuilder builder = new StringBuilder(256 + passport.length());\n        builder.append(\"Channel: \")\n                .append(ch)\n                .append(\", active=\")\n                .append(ch.isActive())\n                .append(\", open=\")\n                .append(ch.isOpen())\n                .append(\", registered=\")\n                .append(ch.isRegistered())\n                .append(\", writable=\")\n                .append(ch.isWritable())\n                .append(\", Passport: \")\n                .append(passport);\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/NettyRequestAttemptFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.exception.ErrorType;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.exception.OutboundException;\nimport com.netflix.zuul.netty.connectionpool.OriginConnectException;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.origins.OriginConcurrencyExceededException;\nimport io.netty.channel.unix.Errors;\nimport io.netty.handler.codec.http2.Http2Exception.HeaderListSizeException;\nimport io.netty.handler.timeout.ReadTimeoutException;\nimport java.nio.channels.ClosedChannelException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class NettyRequestAttemptFactory {\n\n    private static final Logger LOG = LoggerFactory.getLogger(NettyRequestAttemptFactory.class);\n\n    public ErrorType mapNettyToOutboundErrorType(Throwable t) {\n        if (t instanceof ReadTimeoutException) {\n            return OutboundErrorType.READ_TIMEOUT;\n        }\n\n        if (t instanceof OriginConcurrencyExceededException) {\n            return OutboundErrorType.ORIGIN_CONCURRENCY_EXCEEDED;\n        }\n\n        if (t instanceof OriginConnectException) {\n            return ((OriginConnectException) t).getErrorType();\n        }\n\n        if (t instanceof OutboundException) {\n            return ((OutboundException) t).getOutboundErrorType();\n        }\n\n        if (t instanceof Errors.NativeIoException\n                && ((Errors.NativeIoException) t).expectedErr() == Errors.ERRNO_ECONNRESET_NEGATIVE) {\n            // This is a \"Connection reset by peer\" which we see fairly often happening when Origin servers are\n            // overloaded.\n            LOG.warn(\"ERRNO_ECONNRESET_NEGATIVE mapped to RESET_CONNECTION\", t);\n            return OutboundErrorType.RESET_CONNECTION;\n        }\n\n        if (t instanceof ClosedChannelException) {\n            return OutboundErrorType.RESET_CONNECTION;\n        }\n\n        if (t instanceof HeaderListSizeException) {\n            return OutboundErrorType.HEADER_FIELDS_TOO_LARGE;\n        }\n\n        Throwable cause = t.getCause();\n        if (cause instanceof IllegalStateException && cause.getMessage().contains(\"server\")) {\n            LOG.warn(\"IllegalStateException mapped to NO_AVAILABLE_SERVERS\", cause);\n            return OutboundErrorType.NO_AVAILABLE_SERVERS;\n        }\n\n        return OutboundErrorType.OTHER;\n    }\n\n    public OutboundException mapNettyToOutboundException(Throwable t, SessionContext context) {\n        if (t instanceof OutboundException) {\n            return (OutboundException) t;\n        }\n\n        // Map this throwable to zuul's OutboundException.\n        ErrorType errorType = mapNettyToOutboundErrorType(t);\n        RequestAttempts attempts = RequestAttempts.getFromSessionContext(context);\n        if (errorType == OutboundErrorType.OTHER) {\n            return new OutboundException(errorType, attempts, t);\n        }\n        return new OutboundException(errorType, attempts);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/RequestCancelledEvent.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty;\n\n/**\n * User: michaels@netflix.com\n * Date: 4/13/17\n * Time: 6:09 PM\n */\npublic class RequestCancelledEvent {}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/SpectatorUtils.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty;\n\nimport com.netflix.spectator.api.CompositeRegistry;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.Timer;\n\npublic final class SpectatorUtils {\n    private SpectatorUtils() {}\n\n    public static Counter newCounter(String name, String id) {\n        return Spectator.globalRegistry().counter(name, \"id\", id);\n    }\n\n    public static Counter newCounter(String name, String id, String... tags) {\n        String[] allTags = getTagsWithId(id, tags);\n        return Spectator.globalRegistry().counter(name, allTags);\n    }\n\n    public static Timer newTimer(String name, String id) {\n        return Spectator.registry().timer(name, \"id\", id);\n    }\n\n    public static Timer newTimer(String name, String id, String... tags) {\n        return Spectator.globalRegistry().timer(name, getTagsWithId(id, tags));\n    }\n\n    public static <T extends Number> T newGauge(String name, String id, T number) {\n        CompositeRegistry registry = Spectator.globalRegistry();\n        Id gaugeId = registry.createId(name, \"id\", id);\n        return registry.gauge(gaugeId, number);\n    }\n\n    public static <T extends Number> T newGauge(String name, String id, T number, String... tags) {\n        CompositeRegistry registry = Spectator.globalRegistry();\n        Id gaugeId = registry.createId(name, getTagsWithId(id, tags));\n        return registry.gauge(gaugeId, number);\n    }\n\n    private static String[] getTagsWithId(String id, String[] tags) {\n        String[] allTags = new String[tags.length + 2];\n        System.arraycopy(tags, 0, allTags, 0, tags.length);\n        allTags[allTags.length - 2] = \"id\";\n        allTags[allTags.length - 1] = id;\n        return allTags;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/BasicRequestStat.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.google.common.base.Stopwatch;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.exception.ErrorType;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * @author michaels\n */\npublic class BasicRequestStat implements RequestStat {\n\n    private volatile boolean isFinished;\n    private volatile Stopwatch stopwatch;\n\n    public BasicRequestStat() {\n        this.isFinished = false;\n        this.stopwatch = Stopwatch.createStarted();\n    }\n\n    @Override\n    public RequestStat server(DiscoveryResult server) {\n        return this;\n    }\n\n    @Override\n    public boolean isFinished() {\n        return isFinished;\n    }\n\n    @Override\n    public long duration() {\n        long ms = stopwatch.elapsed(TimeUnit.MILLISECONDS);\n        return ms > 0 ? ms : 0;\n    }\n\n    @Override\n    public void serviceUnavailable() {\n        failAndSetErrorCode(OutboundErrorType.SERVICE_UNAVAILABLE);\n    }\n\n    @Override\n    public void generalError() {\n        failAndSetErrorCode(OutboundErrorType.OTHER);\n    }\n\n    @Override\n    public void failAndSetErrorCode(ErrorType error) {\n        // override to implement metric tracking\n    }\n\n    @Override\n    public void updateWithHttpStatusCode(int httpStatusCode) {\n        // override to implement metric tracking\n    }\n\n    @Override\n    public void finalAttempt(boolean finalAttempt) {}\n\n    @Override\n    public boolean finishIfNotAlready() {\n        if (isFinished) {\n            return false;\n        }\n        stopwatch.stop();\n\n        publishMetrics();\n\n        isFinished = true;\n        return true;\n    }\n\n    protected void publishMetrics() {\n        // override to publish metrics here\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientChannelManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.EventLoop;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetAddress;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/8/16\n * Time: 12:36 PM\n */\npublic interface ClientChannelManager {\n    void init();\n\n    boolean isAvailable();\n\n    int getInflightRequestsCount();\n\n    void shutdown();\n\n    default void gracefulShutdown() {\n        shutdown();\n    }\n\n    boolean release(PooledConnection conn);\n\n    Promise<PooledConnection> acquire(EventLoop eventLoop);\n\n    Promise<PooledConnection> acquire(\n            EventLoop eventLoop,\n            Object key,\n            CurrentPassport passport,\n            AtomicReference<DiscoveryResult> selectedServer,\n            AtomicReference<? super InetAddress> selectedHostAddr);\n\n    boolean isCold();\n\n    boolean remove(PooledConnection conn);\n\n    int getConnsInPool();\n\n    int getConnsInUse();\n\n    ConnectionPoolConfig getConfig();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ClientTimeoutHandler.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.AttributeKey;\nimport java.time.Duration;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Client Timeout Handler\n *\n * Author: Arthur Gonigberg\n * Date: July 01, 2019\n */\npublic final class ClientTimeoutHandler {\n    private static final Logger LOG = LoggerFactory.getLogger(ClientTimeoutHandler.class);\n\n    public static final AttributeKey<Duration> ORIGIN_RESPONSE_READ_TIMEOUT =\n            AttributeKey.newInstance(\"originResponseReadTimeout\");\n\n    public static final class InboundHandler extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            try {\n                if (msg instanceof LastHttpContent) {\n                    LOG.debug(\n                            \"[{}] Removing read timeout handler\", ctx.channel().id());\n                    PooledConnection.getFromChannel(ctx.channel()).removeReadTimeoutHandler();\n                }\n            } finally {\n                super.channelRead(ctx, msg);\n            }\n        }\n    }\n\n    public static final class OutboundHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            try {\n                if (!(msg instanceof LastHttpContent)) {\n                    return;\n                }\n\n                Duration timeout =\n                        ctx.channel().attr(ORIGIN_RESPONSE_READ_TIMEOUT).get();\n                if (timeout != null) {\n                    promise.addListener(e -> {\n                        if (e.isSuccess()) {\n                            LOG.debug(\n                                    \"[{}] Adding read timeout handler: {}\",\n                                    ctx.channel().id(),\n                                    timeout.toMillis());\n                            PooledConnection.getFromChannel(ctx.channel()).startReadTimeoutHandler(timeout);\n                        }\n                    });\n                }\n            } finally {\n                super.write(ctx, msg, promise);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfig.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.zuul.origins.OriginName;\n\n/**\n * Created by saroskar on 3/24/16.\n */\npublic interface ConnectionPoolConfig {\n\n    /* Origin name from connection pool */\n    OriginName getOriginName();\n\n    /* Max number of requests per connection before it needs to be recycled */\n    int getMaxRequestsPerConnection();\n\n    /* Max connections per host */\n    int maxConnectionsPerHost();\n\n    int perServerWaterline();\n\n    /* Origin client TCP configuration options */\n    int getConnectTimeout();\n\n    /* number of milliseconds connection can stay idle in a connection pool before it is closed */\n    int getIdleTimeout();\n\n    int getTcpReceiveBufferSize();\n\n    int getTcpSendBufferSize();\n\n    int getNettyWriteBufferHighWaterMark();\n\n    int getNettyWriteBufferLowWaterMark();\n\n    boolean getTcpKeepAlive();\n\n    boolean getTcpNoDelay();\n\n    boolean getNettyAutoRead();\n\n    boolean isSecure();\n\n    boolean useIPAddrForServer();\n\n    default boolean isCloseOnCircuitBreakerEnabled() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfigImpl.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.client.config.CommonClientConfigKey;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.client.config.IClientConfigKey;\nimport com.netflix.zuul.origins.OriginName;\nimport java.util.Objects;\n\n/**\n * Created by saroskar on 3/24/16.\n */\npublic class ConnectionPoolConfigImpl implements ConnectionPoolConfig {\n\n    static final int DEFAULT_BUFFER_SIZE = 32 * 1024;\n    static final int DEFAULT_CONNECT_TIMEOUT = 500;\n    static final int DEFAULT_IDLE_TIMEOUT = 60000;\n    static final int DEFAULT_MAX_CONNS_PER_HOST = 50;\n    static final int DEFAULT_PER_SERVER_WATERLINE = 4;\n    static final int DEFAULT_MAX_REQUESTS_PER_CONNECTION = 1000;\n    static final boolean DEFAULT_TCP_NO_DELAY = true;\n\n    // TODO(argha-c): Document why these values were chosen, as opposed to defaults of 32k/64k\n    static final int DEFAULT_WRITE_BUFFER_HIGH_WATER_MARK = 32 * 1024;\n    static final int DEFAULT_WRITE_BUFFER_LOW_WATER_MARK = 8 * 1024;\n\n    /**\n     * NOTE that each eventloop has its own connection pool per host, and this is applied per event-loop.\n     */\n    public static final IClientConfigKey<Integer> PER_SERVER_WATERLINE =\n            new CommonClientConfigKey<>(\"PerServerWaterline\") {};\n\n    public static final IClientConfigKey<Boolean> CLOSE_ON_CIRCUIT_BREAKER =\n            new CommonClientConfigKey<>(\"CloseOnCircuitBreaker\") {};\n\n    public static final IClientConfigKey<Integer> MAX_REQUESTS_PER_CONNECTION =\n            new CommonClientConfigKey<>(\"MaxRequestsPerConnection\") {};\n\n    public static final IClientConfigKey<Boolean> TCP_KEEP_ALIVE = new CommonClientConfigKey<>(\"TcpKeepAlive\") {};\n\n    public static final IClientConfigKey<Boolean> TCP_NO_DELAY = new CommonClientConfigKey<>(\"TcpNoDelay\") {};\n\n    public static final IClientConfigKey<Boolean> AUTO_READ = new CommonClientConfigKey<>(\"AutoRead\") {};\n\n    public static final IClientConfigKey<Integer> WRITE_BUFFER_HIGH_WATER_MARK =\n            new CommonClientConfigKey<>(\"WriteBufferHighWaterMark\") {};\n\n    public static final IClientConfigKey<Integer> WRITE_BUFFER_LOW_WATER_MARK =\n            new CommonClientConfigKey<>(\"WriteBufferLowWaterMark\") {};\n\n    private final OriginName originName;\n    private final IClientConfig clientConfig;\n\n    public ConnectionPoolConfigImpl(OriginName originName, IClientConfig clientConfig) {\n        this.originName = Objects.requireNonNull(originName, \"originName\");\n        this.clientConfig = clientConfig;\n    }\n\n    @Override\n    public OriginName getOriginName() {\n        return originName;\n    }\n\n    @Override\n    public int getConnectTimeout() {\n        return clientConfig.getPropertyAsInteger(IClientConfigKey.Keys.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);\n    }\n\n    @Override\n    public int getMaxRequestsPerConnection() {\n        return clientConfig.getPropertyAsInteger(MAX_REQUESTS_PER_CONNECTION, DEFAULT_MAX_REQUESTS_PER_CONNECTION);\n    }\n\n    @Override\n    public int maxConnectionsPerHost() {\n        return clientConfig.getPropertyAsInteger(\n                IClientConfigKey.Keys.MaxConnectionsPerHost, DEFAULT_MAX_CONNS_PER_HOST);\n    }\n\n    @Override\n    public int perServerWaterline() {\n        return clientConfig.getPropertyAsInteger(PER_SERVER_WATERLINE, DEFAULT_PER_SERVER_WATERLINE);\n    }\n\n    @Override\n    public int getIdleTimeout() {\n        return clientConfig.getPropertyAsInteger(\n                IClientConfigKey.Keys.ConnIdleEvictTimeMilliSeconds, DEFAULT_IDLE_TIMEOUT);\n    }\n\n    @Override\n    public boolean getTcpKeepAlive() {\n        return clientConfig.getPropertyAsBoolean(TCP_KEEP_ALIVE, false);\n    }\n\n    @Override\n    public boolean getTcpNoDelay() {\n        return clientConfig.getPropertyAsBoolean(TCP_NO_DELAY, DEFAULT_TCP_NO_DELAY);\n    }\n\n    @Override\n    public int getTcpReceiveBufferSize() {\n        return clientConfig.getPropertyAsInteger(IClientConfigKey.Keys.ReceiveBufferSize, DEFAULT_BUFFER_SIZE);\n    }\n\n    @Override\n    public int getTcpSendBufferSize() {\n        return clientConfig.getPropertyAsInteger(IClientConfigKey.Keys.SendBufferSize, DEFAULT_BUFFER_SIZE);\n    }\n\n    @Override\n    public int getNettyWriteBufferHighWaterMark() {\n        return clientConfig.getPropertyAsInteger(WRITE_BUFFER_HIGH_WATER_MARK, DEFAULT_WRITE_BUFFER_HIGH_WATER_MARK);\n    }\n\n    @Override\n    public int getNettyWriteBufferLowWaterMark() {\n        return clientConfig.getPropertyAsInteger(WRITE_BUFFER_LOW_WATER_MARK, DEFAULT_WRITE_BUFFER_LOW_WATER_MARK);\n    }\n\n    @Override\n    public boolean getNettyAutoRead() {\n        return clientConfig.getPropertyAsBoolean(AUTO_READ, false);\n    }\n\n    @Override\n    public boolean isSecure() {\n        return clientConfig.getPropertyAsBoolean(IClientConfigKey.Keys.IsSecure, false);\n    }\n\n    @Override\n    public boolean useIPAddrForServer() {\n        return clientConfig.getPropertyAsBoolean(IClientConfigKey.Keys.UseIPAddrForServer, true);\n    }\n\n    @Override\n    public boolean isCloseOnCircuitBreakerEnabled() {\n        return clientConfig.getPropertyAsBoolean(CLOSE_ON_CIRCUIT_BREAKER, true);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\n\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.origins.OriginName;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.ssl.SslCloseCompletionEvent;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport java.util.Objects;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 6/23/16\n * Time: 1:57 PM\n */\n@ChannelHandler.Sharable\npublic class ConnectionPoolHandler extends ChannelDuplexHandler {\n    private static final Logger LOG = LoggerFactory.getLogger(ConnectionPoolHandler.class);\n\n    private final ConnectionPoolMetrics metrics;\n    private final OriginName originName;\n\n    @Deprecated\n    public ConnectionPoolHandler(OriginName originName) {\n        this(ConnectionPoolMetrics.create(Objects.requireNonNull(originName), Spectator.globalRegistry()));\n    }\n\n    public ConnectionPoolHandler(ConnectionPoolMetrics metrics) {\n        this.originName = metrics.originName();\n        this.metrics = metrics;\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        // First let other handlers do their thing ...\n        // super.userEventTriggered(ctx, evt);\n\n        if (evt instanceof IdleStateEvent) {\n            // Log some info about this.\n            metrics.idleCounter().increment();\n            String msg = \"Origin channel for origin - \" + originName + \" - idle timeout has fired. \"\n                    + ChannelUtils.channelInfoForLogging(ctx.channel());\n            closeConnection(ctx, msg);\n        } else if (evt instanceof CompleteEvent completeEvt) {\n            // The HttpLifecycleChannelHandler instance will fire this event when either a response has finished being\n            // written, or\n            // the channel is no longer active or disconnected.\n            // Return the connection to pool.\n            CompleteReason reason = completeEvt.getReason();\n            if (reason == CompleteReason.SESSION_COMPLETE) {\n                PooledConnection conn = PooledConnection.getFromChannel(ctx.channel());\n                if (conn != null) {\n                    if (\"close\".equalsIgnoreCase(getConnectionHeader(completeEvt))) {\n                        String msg = \"Origin channel for origin - \" + originName\n                                + \" - completed because of expired keep-alive. \"\n                                + ChannelUtils.channelInfoForLogging(ctx.channel());\n                        metrics.headerCloseCounter().increment();\n                        closeConnection(ctx, msg);\n                    } else {\n                        conn.setConnectionState(PooledConnection.ConnectionState.WRITE_READY);\n                        conn.release();\n                    }\n                }\n            } else {\n                String msg = \"Origin channel for origin - \" + originName + \" - completed with reason \" + reason.name()\n                        + \", \" + ChannelUtils.channelInfoForLogging(ctx.channel());\n                closeConnection(ctx, msg);\n            }\n        } else if (evt instanceof SslCloseCompletionEvent event) {\n            metrics.sslCloseCompletionCounter().increment();\n            String msg = \"Origin channel for origin - \" + originName + \" - received SslCloseCompletionEvent \" + event\n                    + \". \" + ChannelUtils.channelInfoForLogging(ctx.channel());\n            closeConnection(ctx, msg);\n        }\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        // super.exceptionCaught(ctx, cause);\n        metrics.errorCounter().increment();\n        String mesg = \"Exception on Origin channel for origin - \" + originName + \". \"\n                + ChannelUtils.channelInfoForLogging(ctx.channel()) + \" - \"\n                + cause.getClass().getCanonicalName()\n                + \": \" + cause.getMessage();\n        closeConnection(ctx, mesg);\n\n        if (LOG.isDebugEnabled()) {\n            LOG.debug(mesg, cause);\n        }\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        // super.channelInactive(ctx);\n        metrics.inactiveCounter().increment();\n        String msg = \"Client channel for origin - \" + originName + \" - inactive event has fired. \"\n                + ChannelUtils.channelInfoForLogging(ctx.channel());\n        closeConnection(ctx, msg);\n    }\n\n    private void closeConnection(ChannelHandlerContext ctx, String msg) {\n        PooledConnection conn = PooledConnection.getFromChannel(ctx.channel());\n        if (conn != null) {\n            if (LOG.isDebugEnabled()) {\n                msg = msg + \" Closing the PooledConnection and releasing. conn={}\";\n                LOG.debug(msg, conn);\n            }\n            flagCloseAndReleaseConnection(conn);\n        } else {\n            // If somehow we don't have a PooledConnection instance attached to this channel, then\n            // close the channel directly.\n            LOG.warn(\"{} But no PooledConnection attribute. So just closing Channel.\", msg);\n            ctx.close();\n        }\n    }\n\n    private void flagCloseAndReleaseConnection(PooledConnection pooledConnection) {\n        if (pooledConnection.isInPool()) {\n            pooledConnection.closeAndRemoveFromPool();\n        } else {\n            pooledConnection.flagShouldClose();\n            pooledConnection.release();\n        }\n    }\n\n    private static String getConnectionHeader(CompleteEvent completeEvt) {\n        HttpResponse response = completeEvt.getResponse();\n        if (response != null) {\n            return response.headers().get(HttpHeaderNames.CONNECTION);\n        }\n\n        return null;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolMetrics.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport com.netflix.zuul.origins.OriginName;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * @author Justin Guerra\n * @since 2/26/25\n */\npublic record ConnectionPoolMetrics(\n        OriginName originName,\n        Counter createNewConnCounter,\n        Counter createConnSucceededCounter,\n        Counter createConnFailedCounter,\n        Counter closeConnCounter,\n        Counter closeAbovePoolHighWaterMarkCounter,\n        Counter closeExpiredConnLifetimeCounter,\n        Counter requestConnCounter,\n        Counter reuseConnCounter,\n        Counter releaseConnCounter,\n        Counter alreadyClosedCounter,\n        Counter connTakenFromPoolIsNotOpen,\n        Counter maxConnsPerHostExceededCounter,\n        Counter closeWrtBusyConnCounter,\n        Counter circuitBreakerClose,\n        PercentileTimer connEstablishTimer,\n        AtomicInteger connsInPool,\n        AtomicInteger connsInUse,\n        Counter idleCounter,\n        Counter inactiveCounter,\n        Counter errorCounter,\n        Counter headerCloseCounter,\n        Counter sslCloseCompletionCounter) {\n\n    public static ConnectionPoolMetrics create(OriginName originName, Registry registry) {\n        Counter createNewConnCounter = newCounter(\"connectionpool_create\", originName, registry);\n        Counter createConnSucceededCounter = newCounter(\"connectionpool_create_success\", originName, registry);\n        Counter createConnFailedCounter = newCounter(\"connectionpool_create_fail\", originName, registry);\n\n        Counter closeConnCounter = newCounter(\"connectionpool_close\", originName, registry);\n        Counter closeAbovePoolHighWaterMarkCounter =\n                newCounter(\"connectionpool_closeAbovePoolHighWaterMark\", originName, registry);\n        Counter closeExpiredConnLifetimeCounter =\n                newCounter(\"connectionpool_closeExpiredConnLifetime\", originName, registry);\n        Counter requestConnCounter = newCounter(\"connectionpool_request\", originName, registry);\n        Counter reuseConnCounter = newCounter(\"connectionpool_reuse\", originName, registry);\n        Counter releaseConnCounter = newCounter(\"connectionpool_release\", originName, registry);\n        Counter alreadyClosedCounter = newCounter(\"connectionpool_alreadyClosed\", originName, registry);\n        Counter connTakenFromPoolIsNotOpen = newCounter(\"connectionpool_fromPoolIsClosed\", originName, registry);\n        Counter maxConnsPerHostExceededCounter =\n                newCounter(\"connectionpool_maxConnsPerHostExceeded\", originName, registry);\n        Counter closeWrtBusyConnCounter = newCounter(\"connectionpool_closeWrtBusyConnCounter\", originName, registry);\n        Counter circuitBreakerClose = newCounter(\"connectionpool_closeCircuitBreaker\", originName, registry);\n\n        Counter idleCounter = newCounter(\"connectionpool_idle\", originName, registry);\n        Counter inactiveCounter = newCounter(\"connectionpool_inactive\", originName, registry);\n        Counter errorCounter = newCounter(\"connectionpool_error\", originName, registry);\n        Counter headerCloseCounter = newCounter(\"connectionpool_headerClose\", originName, registry);\n        Counter sslCloseCompletionCounter = newCounter(\"connectionpool_sslClose\", originName, registry);\n\n        PercentileTimer connEstablishTimer = PercentileTimer.get(\n                registry, registry.createId(\"connectionpool_createTiming\", \"id\", originName.getMetricId()));\n\n        AtomicInteger connsInPool = newGauge(\"connectionpool_inPool\", originName, registry);\n        AtomicInteger connsInUse = newGauge(\"connectionpool_inUse\", originName, registry);\n\n        return new ConnectionPoolMetrics(\n                originName,\n                createNewConnCounter,\n                createConnSucceededCounter,\n                createConnFailedCounter,\n                closeConnCounter,\n                closeAbovePoolHighWaterMarkCounter,\n                closeExpiredConnLifetimeCounter,\n                requestConnCounter,\n                reuseConnCounter,\n                releaseConnCounter,\n                alreadyClosedCounter,\n                connTakenFromPoolIsNotOpen,\n                maxConnsPerHostExceededCounter,\n                closeWrtBusyConnCounter,\n                circuitBreakerClose,\n                connEstablishTimer,\n                connsInPool,\n                connsInUse,\n                idleCounter,\n                inactiveCounter,\n                errorCounter,\n                headerCloseCounter,\n                sslCloseCompletionCounter);\n    }\n\n    private static Counter newCounter(String metricName, OriginName originName, Registry registry) {\n        return registry.counter(metricName, \"id\", originName.getMetricId());\n    }\n\n    private static AtomicInteger newGauge(String metricName, OriginName originName, Registry registry) {\n        return PolledMeter.using(registry)\n                .withName(metricName)\n                .withTag(\"id\", originName.getMetricId())\n                .monitorValue(new AtomicInteger());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.net.InetAddresses;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.discovery.DynamicServerResolver;\nimport com.netflix.zuul.discovery.ResolverResult;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport com.netflix.zuul.netty.insights.PassportStateHttpClientHandler;\nimport com.netflix.zuul.netty.server.OriginResponseReceiver;\nimport com.netflix.zuul.origins.OriginName;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.resolver.Resolver;\nimport com.netflix.zuul.resolver.ResolverListener;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.EventLoop;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/8/16\n * Time: 12:39 PM\n */\npublic class DefaultClientChannelManager implements ClientChannelManager {\n    public static final String IDLE_STATE_HANDLER_NAME = \"idleStateHandler\";\n    private static final Logger LOG = LoggerFactory.getLogger(DefaultClientChannelManager.class);\n\n    protected final Resolver<DiscoveryResult> dynamicServerResolver;\n    protected final ConnectionPoolConfig connPoolConfig;\n    protected final IClientConfig clientConfig;\n    protected final Registry registry;\n    protected final OriginName originName;\n    protected final ConcurrentHashMap<DiscoveryResult, IConnectionPool> perServerPools;\n    protected final ConnectionPoolMetrics metrics;\n\n    protected NettyClientConnectionFactory clientConnFactory;\n    protected OriginChannelInitializer channelInitializer;\n\n    private volatile boolean shuttingDown = false;\n\n    public DefaultClientChannelManager(OriginName originName, IClientConfig clientConfig, Registry registry) {\n        this(originName, clientConfig, new DynamicServerResolver(clientConfig), registry);\n    }\n\n    public DefaultClientChannelManager(\n            OriginName originName, IClientConfig clientConfig, Resolver<DiscoveryResult> resolver, Registry registry) {\n        this.originName = Objects.requireNonNull(originName, \"originName\");\n        this.dynamicServerResolver = resolver;\n\n        this.clientConfig = clientConfig;\n        this.registry = registry;\n        this.perServerPools = new ConcurrentHashMap<>(200);\n\n        this.connPoolConfig = new ConnectionPoolConfigImpl(originName, this.clientConfig);\n\n        this.metrics = ConnectionPoolMetrics.create(originName, registry);\n    }\n\n    @Override\n    public void init() {\n        dynamicServerResolver.setListener(new ServerPoolListener());\n        // Load channel initializer and conn factory.\n        // We don't do this within the constructor because some subclass may not be initialized until post-construct.\n        this.channelInitializer = createChannelInitializer(clientConfig, connPoolConfig, registry);\n        this.clientConnFactory = createNettyClientConnectionFactory(connPoolConfig, channelInitializer);\n    }\n\n    protected OriginChannelInitializer createChannelInitializer(\n            IClientConfig clientConfig, ConnectionPoolConfig connPoolConfig, Registry registry) {\n        return new DefaultOriginChannelInitializer(connPoolConfig, registry);\n    }\n\n    protected NettyClientConnectionFactory createNettyClientConnectionFactory(\n            ConnectionPoolConfig connPoolConfig, ChannelInitializer<? extends Channel> clientConnInitializer) {\n        return new NettyClientConnectionFactory(connPoolConfig, clientConnInitializer);\n    }\n\n    @Override\n    public ConnectionPoolConfig getConfig() {\n        return connPoolConfig;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return dynamicServerResolver.hasServers();\n    }\n\n    @Override\n    public boolean isCold() {\n        return false;\n    }\n\n    @Override\n    public int getInflightRequestsCount() {\n        return this.channelInitializer.getHttpMetricsHandler().getInflightRequestsCount();\n    }\n\n    @Override\n    public void shutdown() {\n        this.shuttingDown = true;\n\n        dynamicServerResolver.shutdown();\n\n        for (IConnectionPool pool : perServerPools.values()) {\n            pool.shutdown();\n        }\n    }\n\n    /**\n     * Gracefully shuts down a DefaultClientChannelManager by allowing in-flight requests to finish before closing the connections.\n     * Idle connections in the connection pools are closed, and any connections associated with an in-flight request\n     * will be closed upon trying to return the connection to the pool\n     */\n    @Override\n    public void gracefulShutdown() {\n        LOG.info(\"Starting a graceful shutdown of {}\", clientConfig.getClientName());\n        shuttingDown = true;\n        dynamicServerResolver.shutdown();\n        perServerPools.values().forEach(IConnectionPool::drain);\n    }\n\n    @Override\n    public boolean release(PooledConnection conn) {\n\n        conn.stopRequestTimer();\n        metrics.releaseConnCounter().increment();\n        metrics.connsInUse().decrementAndGet();\n\n        DiscoveryResult discoveryResult = conn.getServer();\n        updateServerStatsOnRelease(conn);\n\n        boolean released = false;\n\n        if (conn.isShouldClose()) {\n            // Close and discard the connection, as it has been flagged (possibly due to receiving a non-channel error\n            // like a 503).\n            conn.setInPool(false);\n            conn.close();\n            LOG.debug(\n                    \"[{}] closing conn flagged to be closed\", conn.getChannel().id());\n        } else if (isConnectionExpired(conn.getUsageCount())) {\n            conn.setInPool(false);\n            conn.close();\n            metrics.closeExpiredConnLifetimeCounter().increment();\n            LOG.debug(\n                    \"[{}] closing conn lifetime expired, usage: {}\",\n                    conn.getChannel().id(),\n                    conn.getUsageCount());\n        } else if (connPoolConfig.isCloseOnCircuitBreakerEnabled() && discoveryResult.isCircuitBreakerTripped()) {\n            LOG.debug(\n                    \"[{}] closing conn, server circuit breaker tripped\",\n                    conn.getChannel().id());\n            metrics.circuitBreakerClose().increment();\n            // Don't put conns for currently circuit-tripped servers back into the pool.\n            conn.setInPool(false);\n            conn.close();\n        } else if (!conn.isActive()) {\n            LOG.debug(\"[{}] conn inactive, cleaning up\", conn.getChannel().id());\n            // Connection is already closed, so discard.\n            metrics.alreadyClosedCounter().increment();\n            // make sure to decrement OpenConnectionCounts\n            conn.updateServerStats();\n            conn.setInPool(false);\n        } else {\n            releaseHandlers(conn);\n\n            // Attempt to return connection to the pool.\n            IConnectionPool pool = perServerPools.get(discoveryResult);\n            if (pool != null) {\n                released = pool.release(conn);\n            } else {\n                // The pool for this server no longer exists (maybe due to it falling out of\n                // discovery).\n                conn.setInPool(false);\n                released = false;\n                conn.close();\n            }\n\n            LOG.debug(\"PooledConnection released: {}\", conn);\n        }\n\n        return released;\n    }\n\n    protected boolean isConnectionExpired(long usageCount) {\n        // if the connection has been around too long (i.e. too many requests), then close it\n        // TODO(argha-c): Document what is a reasonable default here, and the class of origins that optimizes for\n        return usageCount > connPoolConfig.getMaxRequestsPerConnection();\n    }\n\n    protected void updateServerStatsOnRelease(PooledConnection conn) {\n        DiscoveryResult discoveryResult = conn.getServer();\n        discoveryResult.decrementActiveRequestsCount();\n        discoveryResult.incrementNumRequests();\n    }\n\n    protected void releaseHandlers(PooledConnection conn) {\n        ChannelPipeline pipeline = conn.getChannel().pipeline();\n        removeHandlerFromPipeline(OriginResponseReceiver.CHANNEL_HANDLER_NAME, pipeline);\n        // The Outbound handler is always after the inbound handler, so look for it.\n        ChannelHandlerContext passportStateHttpClientHandlerCtx =\n                pipeline.context(PassportStateHttpClientHandler.OutboundHandler.class);\n        pipeline.addAfter(\n                passportStateHttpClientHandlerCtx.name(),\n                IDLE_STATE_HANDLER_NAME,\n                new IdleStateHandler(0, 0, connPoolConfig.getIdleTimeout(), TimeUnit.MILLISECONDS));\n    }\n\n    public static void removeHandlerFromPipeline(String handlerName, ChannelPipeline pipeline) {\n        if (pipeline.get(handlerName) != null) {\n            pipeline.remove(handlerName);\n        }\n    }\n\n    @Override\n    public boolean remove(PooledConnection conn) {\n        if (conn == null) {\n            return false;\n        }\n        if (!conn.isInPool()) {\n            return false;\n        }\n\n        // Attempt to remove the connection from the pool.\n        IConnectionPool pool = perServerPools.get(conn.getServer());\n        if (pool != null) {\n            return pool.remove(conn);\n        } else {\n            // The pool for this server no longer exists (maybe due to it failing out of\n            // discovery).\n            conn.setInPool(false);\n            metrics.connsInPool().decrementAndGet();\n            return false;\n        }\n    }\n\n    @Override\n    public Promise<PooledConnection> acquire(EventLoop eventLoop) {\n        return acquire(eventLoop, null, CurrentPassport.create(), new AtomicReference<>(), new AtomicReference<>());\n    }\n\n    @Override\n    public Promise<PooledConnection> acquire(\n            EventLoop eventLoop,\n            @Nullable Object key,\n            CurrentPassport passport,\n            AtomicReference<DiscoveryResult> selectedServer,\n            AtomicReference<? super InetAddress> selectedHostAddr) {\n\n        if (shuttingDown) {\n            Promise<PooledConnection> promise = eventLoop.newPromise();\n            promise.setFailure(new IllegalStateException(\"ConnectionPool is shutting down now.\"));\n            return promise;\n        }\n\n        // Choose the next load-balanced server.\n        DiscoveryResult chosenServer = dynamicServerResolver.resolve(key);\n\n        // (argha-c): Always ensure the selected server is updated, since the call chain relies on this mutation.\n        selectedServer.set(chosenServer);\n        if (Objects.equals(chosenServer, DiscoveryResult.EMPTY)) {\n            Promise<PooledConnection> promise = eventLoop.newPromise();\n            promise.setFailure(\n                    new OriginConnectException(\"No servers available\", OutboundErrorType.NO_AVAILABLE_SERVERS));\n            return promise;\n        }\n\n        // Now get the connection-pool for this server.\n        IConnectionPool pool = perServerPools.computeIfAbsent(chosenServer, s -> {\n            SocketAddress finalServerAddr = pickAddress(chosenServer);\n            ClientChannelManager clientChannelMgr = this;\n            PooledConnectionFactory pcf = createPooledConnectionFactory(\n                    chosenServer, clientChannelMgr, metrics.closeConnCounter(), metrics.closeWrtBusyConnCounter());\n\n            // Create a new pool for this server.\n            return createConnectionPool(\n                    chosenServer,\n                    finalServerAddr,\n                    clientConnFactory,\n                    pcf,\n                    connPoolConfig,\n                    clientConfig,\n                    metrics.createNewConnCounter(),\n                    metrics.createConnSucceededCounter(),\n                    metrics.createConnFailedCounter(),\n                    metrics.requestConnCounter(),\n                    metrics.reuseConnCounter(),\n                    metrics.connTakenFromPoolIsNotOpen(),\n                    metrics.closeAbovePoolHighWaterMarkCounter(),\n                    metrics.maxConnsPerHostExceededCounter(),\n                    metrics.connEstablishTimer(),\n                    metrics.connsInPool(),\n                    metrics.connsInUse());\n        });\n\n        return pool.acquire(eventLoop, passport, selectedHostAddr);\n    }\n\n    protected PooledConnectionFactory createPooledConnectionFactory(\n            DiscoveryResult chosenServer,\n            ClientChannelManager clientChannelMgr,\n            Counter closeConnCounter,\n            Counter closeWrtBusyConnCounter) {\n        return ch ->\n                new PooledConnection(ch, chosenServer, clientChannelMgr, closeConnCounter, closeWrtBusyConnCounter);\n    }\n\n    protected IConnectionPool createConnectionPool(\n            DiscoveryResult discoveryResult,\n            SocketAddress serverAddr,\n            NettyClientConnectionFactory clientConnFactory,\n            PooledConnectionFactory pcf,\n            ConnectionPoolConfig connPoolConfig,\n            IClientConfig clientConfig,\n            Counter createNewConnCounter,\n            Counter createConnSucceededCounter,\n            Counter createConnFailedCounter,\n            Counter requestConnCounter,\n            Counter reuseConnCounter,\n            Counter connTakenFromPoolIsNotOpen,\n            Counter closeAbovePoolHighWaterMarkCounter,\n            Counter maxConnsPerHostExceededCounter,\n            PercentileTimer connEstablishTimer,\n            AtomicInteger connsInPool,\n            AtomicInteger connsInUse) {\n        return new PerServerConnectionPool(\n                discoveryResult,\n                serverAddr,\n                clientConnFactory,\n                pcf,\n                connPoolConfig,\n                clientConfig,\n                createNewConnCounter,\n                createConnSucceededCounter,\n                createConnFailedCounter,\n                requestConnCounter,\n                reuseConnCounter,\n                connTakenFromPoolIsNotOpen,\n                closeAbovePoolHighWaterMarkCounter,\n                maxConnsPerHostExceededCounter,\n                connEstablishTimer,\n                connsInPool,\n                connsInUse);\n    }\n\n    final class ServerPoolListener implements ResolverListener<DiscoveryResult> {\n        @Override\n        public void onChange(List<DiscoveryResult> removedSet) {\n            if (!removedSet.isEmpty()) {\n                LOG.debug(\n                        \"Removing connection pools for missing servers. name = {}. {} servers gone.\",\n                        originName,\n                        removedSet.size());\n                for (DiscoveryResult s : removedSet) {\n                    IConnectionPool pool = perServerPools.remove(s);\n                    if (pool != null) {\n                        pool.shutdown();\n                    }\n                }\n            }\n        }\n    }\n\n    @Override\n    public int getConnsInPool() {\n        return metrics.connsInPool().get();\n    }\n\n    @Override\n    public int getConnsInUse() {\n        return metrics.connsInUse().get();\n    }\n\n    protected ConcurrentHashMap<DiscoveryResult, IConnectionPool> getPerServerPools() {\n        return perServerPools;\n    }\n\n    @VisibleForTesting\n    static SocketAddress pickAddressInternal(ResolverResult chosenServer, @Nullable OriginName originName) {\n        String rawHost;\n        int port;\n        rawHost = chosenServer.getHost();\n        port = chosenServer.getPort();\n        InetSocketAddress serverAddr;\n        try {\n            InetAddress ipAddr = InetAddresses.forString(rawHost);\n            serverAddr = new InetSocketAddress(ipAddr, port);\n        } catch (IllegalArgumentException e1) {\n            LOG.warn(\"NettyClientConnectionFactory got an unresolved address, addr: {}\", rawHost);\n            Counter unresolvedDiscoveryHost = SpectatorUtils.newCounter(\n                    \"unresolvedDiscoveryHost\", originName == null ? \"unknownOrigin\" : originName.getTarget());\n            unresolvedDiscoveryHost.increment();\n            try {\n                serverAddr = new InetSocketAddress(rawHost, port);\n            } catch (RuntimeException e2) {\n                e1.addSuppressed(e2);\n                throw e1;\n            }\n        }\n\n        return serverAddr;\n    }\n\n    /**\n     * Given a server chosen from the load balancer, pick the appropriate address to connect to.\n     */\n    protected SocketAddress pickAddress(DiscoveryResult chosenServer) {\n        return pickAddressInternal(chosenServer, connPoolConfig.getOriginName());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/DefaultOriginChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.netty.common.HttpClientLifecycleChannelHandler;\nimport com.netflix.netty.common.metrics.HttpMetricsChannelHandler;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.netty.insights.PassportStateHttpClientHandler;\nimport com.netflix.zuul.netty.insights.PassportStateOriginHandler;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport com.netflix.zuul.netty.ssl.ClientSslContextFactory;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http.HttpClientCodec;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport io.netty.handler.ssl.SslContext;\n\n/**\n * Default Origin Channel Initializer\n *\n * Author: Arthur Gonigberg\n * Date: December 01, 2017\n */\npublic class DefaultOriginChannelInitializer extends OriginChannelInitializer {\n\n    public static final String ORIGIN_NETTY_LOGGER = \"originNettyLogger\";\n    public static final String CONNECTION_POOL_HANDLER = \"connectionPoolHandler\";\n    private final ConnectionPoolConfig connectionPoolConfig;\n    private final SslContext sslContext;\n    protected final ConnectionPoolHandler connectionPoolHandler;\n    protected final HttpMetricsChannelHandler httpMetricsHandler;\n    protected final LoggingHandler nettyLogger;\n\n    public DefaultOriginChannelInitializer(ConnectionPoolConfig connPoolConfig, Registry spectatorRegistry) {\n        this.connectionPoolConfig = connPoolConfig;\n        String niwsClientName = connectionPoolConfig.getOriginName().getNiwsClientName();\n        this.connectionPoolHandler = new ConnectionPoolHandler(\n                ConnectionPoolMetrics.create(connPoolConfig.getOriginName(), spectatorRegistry));\n        this.httpMetricsHandler = new HttpMetricsChannelHandler(spectatorRegistry, \"client\", niwsClientName);\n        this.nettyLogger = new LoggingHandler(\"zuul.origin.nettylog.\" + niwsClientName, LogLevel.INFO);\n        this.sslContext = getClientSslContext(spectatorRegistry);\n    }\n\n    @Override\n    protected void initChannel(Channel ch) throws Exception {\n        ChannelPipeline pipeline = ch.pipeline();\n\n        pipeline.addLast(new PassportStateOriginHandler.InboundHandler());\n        pipeline.addLast(new PassportStateOriginHandler.OutboundHandler());\n\n        if (connectionPoolConfig.isSecure()) {\n            pipeline.addLast(\"ssl\", sslContext.newHandler(ch.alloc()));\n        }\n\n        pipeline.addLast(\n                BaseZuulChannelInitializer.HTTP_CODEC_HANDLER_NAME,\n                new HttpClientCodec(\n                        BaseZuulChannelInitializer.MAX_INITIAL_LINE_LENGTH.get(),\n                        BaseZuulChannelInitializer.MAX_HEADER_SIZE.get(),\n                        BaseZuulChannelInitializer.MAX_CHUNK_SIZE.get(),\n                        false,\n                        false));\n        pipeline.addLast(new PassportStateHttpClientHandler.InboundHandler());\n        pipeline.addLast(new PassportStateHttpClientHandler.OutboundHandler());\n        pipeline.addLast(ORIGIN_NETTY_LOGGER, nettyLogger);\n        pipeline.addLast(httpMetricsHandler);\n        addMethodBindingHandler(pipeline);\n        pipeline.addLast(HttpClientLifecycleChannelHandler.INBOUND_CHANNEL_HANDLER);\n        pipeline.addLast(HttpClientLifecycleChannelHandler.OUTBOUND_CHANNEL_HANDLER);\n        pipeline.addLast(new ClientTimeoutHandler.InboundHandler());\n        pipeline.addLast(new ClientTimeoutHandler.OutboundHandler());\n        pipeline.addLast(CONNECTION_POOL_HANDLER, connectionPoolHandler);\n    }\n\n    /**\n     * This method can be overridden to create your own custom SSL context\n     *\n     * @param spectatorRegistry metrics registry\n     * @return Netty SslContext\n     */\n    protected SslContext getClientSslContext(Registry spectatorRegistry) {\n        return new ClientSslContextFactory(spectatorRegistry).getClientSslContext();\n    }\n\n    /**\n     * This method can be overridden to add your own MethodBinding handler for preserving thread locals or thread variables.\n     *\n     * This should be a handler that binds downstream channelRead and userEventTriggered with the\n     * MethodBinding class. It should be added using the pipeline.addLast method.\n     *\n     * @param pipeline the channel pipeline\n     */\n    protected void addMethodBindingHandler(ChannelPipeline pipeline) {}\n\n    @Override\n    public HttpMetricsChannelHandler getHttpMetricsHandler() {\n        return httpMetricsHandler;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/IConnectionPool.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.EventLoop;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetAddress;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/8/16\n * Time: 1:10 PM\n */\npublic interface IConnectionPool {\n    Promise<PooledConnection> acquire(\n            EventLoop eventLoop, CurrentPassport passport, AtomicReference<? super InetAddress> selectedHostAddr);\n\n    boolean release(PooledConnection conn);\n\n    boolean remove(PooledConnection conn);\n\n    void shutdown();\n\n    default void drain() {\n        shutdown();\n    }\n\n    boolean isAvailable();\n\n    int getConnsInUse();\n\n    int getConnsInPool();\n\n    ConnectionPoolConfig getConfig();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/NettyClientConnectionFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.zuul.netty.server.Server;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoop;\nimport io.netty.channel.WriteBufferWaterMark;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Objects;\n\n/**\n * Created by saroskar on 3/16/16.\n */\npublic class NettyClientConnectionFactory {\n\n    private final ConnectionPoolConfig connPoolConfig;\n    private final ChannelInitializer<? extends Channel> channelInitializer;\n\n    public NettyClientConnectionFactory(\n            ConnectionPoolConfig connPoolConfig, ChannelInitializer<? extends Channel> channelInitializer) {\n        this.connPoolConfig = connPoolConfig;\n        this.channelInitializer = channelInitializer;\n    }\n\n    public ChannelFuture connect(\n            EventLoop eventLoop, SocketAddress socketAddress, CurrentPassport passport, IConnectionPool pool) {\n        Objects.requireNonNull(socketAddress, \"socketAddress\");\n        if (socketAddress instanceof InetSocketAddress) {\n            // This should be checked by the ClientConnectionManager\n            assert !((InetSocketAddress) socketAddress).isUnresolved() : socketAddress;\n        }\n        Bootstrap bootstrap = new Bootstrap()\n                .channel(Server.defaultOutboundChannelType.get())\n                .handler(channelInitializer)\n                .group(eventLoop)\n                .attr(CurrentPassport.CHANNEL_ATTR, passport)\n                .attr(PerServerConnectionPool.CHANNEL_ATTR, pool)\n                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connPoolConfig.getConnectTimeout())\n                .option(ChannelOption.SO_KEEPALIVE, connPoolConfig.getTcpKeepAlive())\n                .option(ChannelOption.TCP_NODELAY, connPoolConfig.getTcpNoDelay())\n                .option(ChannelOption.SO_SNDBUF, connPoolConfig.getTcpSendBufferSize())\n                .option(ChannelOption.SO_RCVBUF, connPoolConfig.getTcpReceiveBufferSize())\n                .option(\n                        ChannelOption.WRITE_BUFFER_WATER_MARK,\n                        new WriteBufferWaterMark(\n                                connPoolConfig.getNettyWriteBufferLowWaterMark(),\n                                connPoolConfig.getNettyWriteBufferHighWaterMark()))\n                .option(ChannelOption.AUTO_READ, connPoolConfig.getNettyAutoRead())\n                .remoteAddress(socketAddress);\n        return bootstrap.connect();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/OriginChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.netty.common.metrics.HttpMetricsChannelHandler;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelInitializer;\n\n/**\n * Origin Channel Initializer\n *\n * Author: Arthur Gonigberg\n * Date: December 01, 2017\n */\npublic abstract class OriginChannelInitializer extends ChannelInitializer<Channel> {\n\n    public abstract HttpMetricsChannelHandler getHttpMetricsHandler();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/OriginConnectException.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.zuul.exception.ErrorType;\n\n/**\n * Wrapper for exceptions failing to connect to origin with details on which server failed the attempt.\n */\npublic class OriginConnectException extends Exception {\n\n    private final ErrorType errorType;\n\n    public OriginConnectException(String message, ErrorType errorType) {\n        // ensure this exception does not fill its stacktrace, this causes a 10x slowdown\n        super(message, null, true, false);\n        this.errorType = errorType;\n    }\n\n    public OriginConnectException(String message, Throwable cause, ErrorType errorType) {\n        // ensure this exception does not fill its stacktrace, this causes a 10x slowdown\n        super(message, cause, true, false);\n        this.errorType = errorType;\n    }\n\n    public ErrorType getErrorType() {\n        return errorType;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PerServerConnectionPool.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Timer;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.EventLoop;\nimport io.netty.handler.codec.DecoderException;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Deque;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentLinkedDeque;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 7/8/16\n * Time: 1:09 PM\n */\npublic class PerServerConnectionPool implements IConnectionPool {\n    private static final Logger LOG = LoggerFactory.getLogger(PerServerConnectionPool.class);\n    public static final AttributeKey<IConnectionPool> CHANNEL_ATTR = AttributeKey.newInstance(\"_connection_pool\");\n    protected final ConcurrentHashMap<EventLoop, Deque<PooledConnection>> connectionsPerEventLoop =\n            new ConcurrentHashMap<>();\n    protected final PooledConnectionFactory pooledConnectionFactory;\n\n    protected final DiscoveryResult server;\n    protected final SocketAddress serverAddr;\n    protected final NettyClientConnectionFactory connectionFactory;\n    protected final ConnectionPoolConfig config;\n    protected final IClientConfig niwsClientConfig;\n\n    protected final Counter createNewConnCounter;\n    protected final Counter createConnSucceededCounter;\n    protected final Counter createConnFailedCounter;\n\n    protected final Counter requestConnCounter;\n    protected final Counter reuseConnCounter;\n    protected final Counter connTakenFromPoolIsNotOpen;\n    protected final Counter maxConnsPerHostExceededCounter;\n    protected final Counter closeAboveHighWaterMarkCounter;\n    protected final Timer connEstablishTimer;\n    protected final AtomicInteger connsInPool;\n    protected final AtomicInteger connsInUse;\n\n    /**\n     * This is the count of connections currently in progress of being established.\n     * They will only be added to connsInUse _after_ establishment has completed.\n     */\n    protected final AtomicInteger connCreationsInProgress;\n\n    protected volatile boolean draining;\n\n    public PerServerConnectionPool(\n            DiscoveryResult server,\n            SocketAddress serverAddr,\n            NettyClientConnectionFactory connectionFactory,\n            PooledConnectionFactory pooledConnectionFactory,\n            ConnectionPoolConfig config,\n            IClientConfig niwsClientConfig,\n            Counter createNewConnCounter,\n            Counter createConnSucceededCounter,\n            Counter createConnFailedCounter,\n            Counter requestConnCounter,\n            Counter reuseConnCounter,\n            Counter connTakenFromPoolIsNotOpen,\n            Counter closeAboveHighWaterMarkCounter,\n            Counter maxConnsPerHostExceededCounter,\n            Timer connEstablishTimer,\n            AtomicInteger connsInPool,\n            AtomicInteger connsInUse) {\n        this.server = server;\n        // Note: child classes can sometimes connect to different addresses than\n        this.serverAddr = Objects.requireNonNull(serverAddr, \"serverAddr\");\n        this.connectionFactory = connectionFactory;\n        this.pooledConnectionFactory = pooledConnectionFactory;\n        this.config = config;\n        this.niwsClientConfig = niwsClientConfig;\n        this.createNewConnCounter = createNewConnCounter;\n        this.createConnSucceededCounter = createConnSucceededCounter;\n        this.createConnFailedCounter = createConnFailedCounter;\n        this.requestConnCounter = requestConnCounter;\n        this.reuseConnCounter = reuseConnCounter;\n        this.connTakenFromPoolIsNotOpen = connTakenFromPoolIsNotOpen;\n        this.closeAboveHighWaterMarkCounter = closeAboveHighWaterMarkCounter;\n        this.maxConnsPerHostExceededCounter = maxConnsPerHostExceededCounter;\n        this.connEstablishTimer = connEstablishTimer;\n        this.connsInPool = connsInPool;\n        this.connsInUse = connsInUse;\n\n        this.connCreationsInProgress = new AtomicInteger(0);\n    }\n\n    @Override\n    public ConnectionPoolConfig getConfig() {\n        return this.config;\n    }\n\n    public IClientConfig getNiwsClientConfig() {\n        return niwsClientConfig;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return !draining;\n    }\n\n    /** function to run when a connection is acquired before returning it to caller. */\n    protected void onAcquire(PooledConnection conn, CurrentPassport passport) {\n        passport.setOnChannel(conn.getChannel());\n        removeIdleStateHandler(conn);\n\n        conn.setInUse();\n        LOG.debug(\"PooledConnection acquired: {}\", conn);\n    }\n\n    protected void removeIdleStateHandler(PooledConnection conn) {\n        DefaultClientChannelManager.removeHandlerFromPipeline(\n                DefaultClientChannelManager.IDLE_STATE_HANDLER_NAME,\n                conn.getChannel().pipeline());\n    }\n\n    @Override\n    public Promise<PooledConnection> acquire(\n            EventLoop eventLoop, CurrentPassport passport, AtomicReference<? super InetAddress> selectedHostAddr) {\n\n        if (draining) {\n            throw new IllegalStateException(\"Attempt to acquire connection while draining\");\n        }\n\n        requestConnCounter.increment();\n        updateServerStatsOnAcquire();\n\n        Promise<PooledConnection> promise = eventLoop.newPromise();\n\n        // Try getting a connection from the pool.\n        PooledConnection conn = tryGettingFromConnectionPool(eventLoop);\n        if (conn != null) {\n            // There was a pooled connection available, so use this one.\n            reusePooledConnection(passport, selectedHostAddr, conn, promise);\n        } else {\n            // connection pool empty, create new connection using client connection factory.\n            tryMakingNewConnection(eventLoop, promise, passport, selectedHostAddr);\n        }\n\n        return promise;\n    }\n\n    protected void reusePooledConnection(\n            CurrentPassport passport,\n            AtomicReference<? super InetAddress> selectedHostAddr,\n            PooledConnection conn,\n            Promise<PooledConnection> promise) {\n        conn.startRequestTimer();\n        conn.incrementUsageCount();\n        conn.getChannel().read();\n        onAcquire(conn, passport);\n        initPooledConnection(conn, promise);\n        selectedHostAddr.set(getSelectedHostString(serverAddr));\n    }\n\n    protected void updateServerStatsOnAcquire() {\n        server.incrementActiveRequestsCount();\n    }\n\n    public PooledConnection tryGettingFromConnectionPool(EventLoop eventLoop) {\n        PooledConnection conn;\n        Deque<PooledConnection> connections = getPoolForEventLoop(eventLoop);\n        while ((conn = connections.poll()) != null) {\n\n            conn.setInPool(false);\n\n            /* Check that the connection is still open. */\n            if (isValidFromPool(conn)) {\n                reuseConnCounter.increment();\n                connsInUse.incrementAndGet();\n                connsInPool.decrementAndGet();\n                return conn;\n            } else {\n                connTakenFromPoolIsNotOpen.increment();\n                connsInPool.decrementAndGet();\n                conn.close();\n            }\n        }\n        return null;\n    }\n\n    protected boolean isValidFromPool(PooledConnection conn) {\n        return conn.isActive() && conn.getChannel().isOpen();\n    }\n\n    protected void initPooledConnection(PooledConnection conn, Promise<PooledConnection> promise) {\n        // add custom init code by overriding this method\n        promise.setSuccess(conn);\n    }\n\n    protected Deque<PooledConnection> getPoolForEventLoop(EventLoop eventLoop) {\n        // We don't want to block under any circumstances, so can't use CHM.computeIfAbsent().\n        // Instead we accept the slight inefficiency of an unnecessary instantiation of a ConcurrentLinkedDeque.\n\n        Deque<PooledConnection> pool = connectionsPerEventLoop.get(eventLoop);\n        if (pool == null) {\n            pool = new ConcurrentLinkedDeque<>();\n            connectionsPerEventLoop.putIfAbsent(eventLoop, pool);\n        }\n        return pool;\n    }\n\n    protected void tryMakingNewConnection(\n            EventLoop eventLoop,\n            Promise<PooledConnection> promise,\n            CurrentPassport passport,\n            AtomicReference<? super InetAddress> selectedHostAddr) {\n\n        if (!isWithinConnectionLimit(promise)) {\n            return;\n        }\n\n        try {\n            createNewConnCounter.increment();\n            connCreationsInProgress.incrementAndGet();\n            passport.add(PassportState.ORIGIN_CH_CONNECTING);\n\n            selectedHostAddr.set(getSelectedHostString(serverAddr));\n\n            ChannelFuture cf = connectToServer(eventLoop, passport, serverAddr);\n\n            if (cf.isDone()) {\n                handleConnectCompletion(cf, promise, passport);\n            } else {\n                cf.addListener(future -> {\n                    try {\n                        handleConnectCompletion((ChannelFuture) future, promise, passport);\n                    } catch (Throwable e) {\n                        if (!promise.isDone()) {\n                            promise.setFailure(e);\n                        }\n                        LOG.warn(\n                                \"Error creating new connection! origin={}, host={}\",\n                                config.getOriginName(),\n                                server.getServerId());\n                    }\n                });\n            }\n        } catch (Throwable e) {\n            promise.setFailure(e);\n        }\n    }\n\n    protected boolean isWithinConnectionLimit(Promise<PooledConnection> promise) {\n        // Enforce MaxConnectionsPerHost config.\n        int maxConnectionsPerHost = config.maxConnectionsPerHost();\n        int openAndOpeningConnectionCount = server.getOpenConnectionsCount() + connCreationsInProgress.get();\n        if (maxConnectionsPerHost != -1 && openAndOpeningConnectionCount >= maxConnectionsPerHost) {\n            maxConnsPerHostExceededCounter.increment();\n            promise.setFailure(new OriginConnectException(\n                    \"maxConnectionsPerHost=\" + maxConnectionsPerHost + \", connectionsPerHost=\"\n                            + openAndOpeningConnectionCount,\n                    OutboundErrorType.ORIGIN_SERVER_MAX_CONNS));\n            LOG.warn(\n                    \"Unable to create new connection because at MaxConnectionsPerHost! maxConnectionsPerHost={},\"\n                            + \" connectionsPerHost={}, host={}origin={}\",\n                    maxConnectionsPerHost,\n                    openAndOpeningConnectionCount,\n                    server.getServerId(),\n                    config.getOriginName());\n            return false;\n        }\n        return true;\n    }\n\n    protected ChannelFuture connectToServer(EventLoop eventLoop, CurrentPassport passport, SocketAddress serverAddr) {\n        return connectionFactory.connect(eventLoop, serverAddr, passport, this);\n    }\n\n    protected void handleConnectCompletion(\n            ChannelFuture cf, Promise<PooledConnection> callerPromise, CurrentPassport passport) {\n        connCreationsInProgress.decrementAndGet();\n        updateServerStatsOnConnectCompletion(cf);\n        if (cf.isSuccess()) {\n            passport.add(PassportState.ORIGIN_CH_CONNECTED);\n            createConnSucceededCounter.increment();\n            connsInUse.incrementAndGet();\n            createConnection(cf, callerPromise, passport);\n        } else {\n            createConnFailedCounter.increment();\n\n            // unwrap DecoderExceptions to get a better indication of why decoding failed\n            // as decoding failures are not indicative of actual connection causes\n            if (cf.cause() instanceof DecoderException de && de.getCause() != null) {\n                callerPromise.setFailure(new OriginConnectException(\n                        de.getCause().getMessage(), de.getCause(), OutboundErrorType.CONNECT_ERROR));\n            } else {\n                callerPromise.setFailure(new OriginConnectException(\n                        cf.cause().getMessage(), cf.cause(), OutboundErrorType.CONNECT_ERROR));\n            }\n        }\n    }\n\n    protected void updateServerStatsOnConnectCompletion(ChannelFuture cf) {\n        if (cf.isSuccess()) {\n            server.incrementOpenConnectionsCount();\n        } else {\n            server.incrementSuccessiveConnectionFailureCount();\n            server.addToFailureCount();\n            server.decrementActiveRequestsCount();\n        }\n    }\n\n    protected void createConnection(\n            ChannelFuture cf, Promise<PooledConnection> callerPromise, CurrentPassport passport) {\n        PooledConnection conn = pooledConnectionFactory.create(cf.channel());\n\n        conn.incrementUsageCount();\n        conn.startRequestTimer();\n        conn.getChannel().read();\n        onAcquire(conn, passport);\n        callerPromise.setSuccess(conn);\n    }\n\n    @Override\n    public boolean release(PooledConnection conn) {\n        if (conn == null) {\n            return false;\n        }\n        if (conn.isInPool()) {\n            return false;\n        }\n\n        if (draining) {\n            LOG.debug(\n                    \"[{}] closing released connection during drain\",\n                    conn.getChannel().id());\n            conn.getChannel().close();\n            return false;\n        }\n\n        // Get the eventloop for this channel.\n        EventLoop eventLoop = conn.getChannel().eventLoop();\n        Deque<PooledConnection> connections = getPoolForEventLoop(eventLoop);\n\n        CurrentPassport passport = CurrentPassport.fromChannel(conn.getChannel());\n\n        // Discard conn if already at least above waterline in the pool already for this server.\n        if (isOverPerServerWaterline(connections.size())) {\n            closeAboveHighWaterMarkCounter.increment();\n            conn.close();\n            conn.setInPool(false);\n            return false;\n        }\n        // Attempt to return connection to the pool.\n        else if (connections.offer(conn)) {\n            conn.setInPool(true);\n            connsInPool.incrementAndGet();\n            passport.add(PassportState.ORIGIN_CH_POOL_RETURNED);\n            return true;\n        } else {\n            // If the pool is full, then close the conn and discard.\n            conn.close();\n            conn.setInPool(false);\n            return false;\n        }\n    }\n\n    protected boolean isOverPerServerWaterline(int connectionsInPool) {\n        int poolWaterline = config.perServerWaterline();\n        return poolWaterline > -1 && connectionsInPool >= poolWaterline;\n    }\n\n    @Override\n    public boolean remove(PooledConnection conn) {\n        if (conn == null) {\n            return false;\n        }\n        if (!conn.isInPool()) {\n            return false;\n        }\n\n        // Get the eventloop for this channel.\n        EventLoop eventLoop = conn.getChannel().eventLoop();\n\n        // Attempt to remove connection from the pool.\n        Deque<PooledConnection> connections = getPoolForEventLoop(eventLoop);\n        if (connections.remove(conn)) {\n            conn.setInPool(false);\n            connsInPool.decrementAndGet();\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    @Override\n    public void shutdown() {\n        for (Deque<PooledConnection> connections : connectionsPerEventLoop.values()) {\n            for (PooledConnection conn : connections) {\n                conn.close();\n            }\n        }\n    }\n\n    @Override\n    public void drain() {\n        if (draining) {\n            throw new IllegalStateException(\"Already draining\");\n        }\n\n        draining = true;\n        connectionsPerEventLoop.forEach((eventLoop, v) -> drainIdleConnectionsOnEventLoop(eventLoop));\n    }\n\n    @Override\n    public int getConnsInPool() {\n        return connsInPool.get();\n    }\n\n    @Override\n    public int getConnsInUse() {\n        return connsInUse.get();\n    }\n\n    @Nullable\n    protected InetAddress getSelectedHostString(SocketAddress addr) {\n        if (addr instanceof InetSocketAddress) {\n            return ((InetSocketAddress) addr).getAddress();\n        } else {\n            // If it's some other kind of address, just set it to empty\n            return null;\n        }\n    }\n\n    /**\n     * Closes idle connections in the connection pool for a given EventLoop. The closing is performed on the EventLoop\n     * thread since the connection pool is not thread safe.\n     *\n     * @param eventLoop - the event loop to drain\n     */\n    void drainIdleConnectionsOnEventLoop(EventLoop eventLoop) {\n        eventLoop.execute(() -> {\n            Deque<PooledConnection> connections = connectionsPerEventLoop.get(eventLoop);\n            if (connections == null) {\n                return;\n            }\n\n            for (PooledConnection connection : connections) {\n                // any connections in the Deque are idle since they are removed in tryGettingFromConnectionPool()\n                connection.setInPool(false);\n                LOG.debug(\"Closing connection {}\", connection);\n                connection.close();\n                connsInPool.decrementAndGet();\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PooledConnection.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.timeout.ReadTimeoutHandler;\nimport io.netty.util.AttributeKey;\nimport java.time.Duration;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Created by saroskar on 3/15/16.\n */\npublic class PooledConnection {\n\n    public static final AttributeKey<PooledConnection> CHANNEL_ATTR = AttributeKey.newInstance(\"_pooled_connection\");\n    public static final String READ_TIMEOUT_HANDLER_NAME = \"readTimeoutHandler\";\n\n    private final DiscoveryResult server;\n    private final Channel channel;\n    private final ClientChannelManager channelManager;\n    private final long creationTS;\n    private final Counter closeConnCounter;\n    private final Counter closeWrtBusyConnCounter;\n\n    private static final Logger LOG = LoggerFactory.getLogger(PooledConnection.class);\n\n    /**\n     * Connection State\n     */\n    public enum ConnectionState {\n        /**\n         * valid state in pool\n         */\n        WRITE_READY,\n        /**\n         * Can not be put in pool\n         */\n        WRITE_BUSY\n    }\n\n    private ConnectionState connectionState;\n    private long usageCount = 0;\n    private long reqStartTime;\n    private boolean inPool = false;\n    private boolean shouldClose = false;\n    protected boolean released = false;\n\n    public PooledConnection(\n            Channel channel,\n            DiscoveryResult server,\n            ClientChannelManager channelManager,\n            Counter closeConnCounter,\n            Counter closeWrtBusyConnCounter) {\n        this.channel = channel;\n        this.server = server;\n        this.channelManager = channelManager;\n        this.creationTS = System.currentTimeMillis();\n        this.closeConnCounter = closeConnCounter;\n        this.closeWrtBusyConnCounter = closeWrtBusyConnCounter;\n\n        this.connectionState = ConnectionState.WRITE_READY;\n\n        // Store ourself as an attribute on the underlying Channel.\n        channel.attr(CHANNEL_ATTR).set(this);\n    }\n\n    public void setInUse() {\n        this.connectionState = ConnectionState.WRITE_BUSY;\n        this.released = false;\n    }\n\n    public void setConnectionState(ConnectionState state) {\n        this.connectionState = state;\n    }\n\n    public static PooledConnection getFromChannel(Channel ch) {\n        return ch.attr(CHANNEL_ATTR).get();\n    }\n\n    public ConnectionPoolConfig getConfig() {\n        return this.channelManager.getConfig();\n    }\n\n    public DiscoveryResult getServer() {\n        return server;\n    }\n\n    public Channel getChannel() {\n        return channel;\n    }\n\n    public long getUsageCount() {\n        return usageCount;\n    }\n\n    public void incrementUsageCount() {\n        this.usageCount++;\n    }\n\n    public long getCreationTS() {\n        return creationTS;\n    }\n\n    public long getAgeInMillis() {\n        return System.currentTimeMillis() - creationTS;\n    }\n\n    public void startRequestTimer() {\n        reqStartTime = System.nanoTime();\n    }\n\n    public long stopRequestTimer() {\n        long responseTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - reqStartTime);\n        server.noteResponseTime((double) responseTime);\n        return responseTime;\n    }\n\n    public boolean isActive() {\n        return (channel.isActive() && channel.isRegistered());\n    }\n\n    public boolean isInPool() {\n        return inPool;\n    }\n\n    public void setInPool(boolean inPool) {\n        this.inPool = inPool;\n    }\n\n    public boolean isShouldClose() {\n        return shouldClose;\n    }\n\n    public void flagShouldClose() {\n        this.shouldClose = true;\n    }\n\n    public ChannelFuture close() {\n        server.decrementOpenConnectionsCount();\n        closeConnCounter.increment();\n        return channel.close();\n    }\n\n    public void updateServerStats() {\n        server.decrementOpenConnectionsCount();\n        server.stopPublishingStats();\n    }\n\n    public ChannelFuture closeAndRemoveFromPool() {\n        channelManager.remove(this);\n        return this.close();\n    }\n\n    public boolean release() {\n        if (released) {\n            return true;\n        }\n\n        if (isActive()) {\n            if (connectionState != ConnectionState.WRITE_READY) {\n                closeWrtBusyConnCounter.increment();\n            }\n        }\n\n        if (!isShouldClose() && connectionState != ConnectionState.WRITE_READY) {\n            CurrentPassport passport = CurrentPassport.fromChannel(channel);\n            LOG.info(\"Error - Attempt to put busy connection into the pool = {}, {}\", this, passport);\n            this.shouldClose = true;\n        }\n\n        // reset the connectionState\n        connectionState = ConnectionState.WRITE_READY;\n        released = true;\n        return channelManager.release(this);\n    }\n\n    public void removeReadTimeoutHandler() {\n        // Remove (and therefore destroy) the readTimeoutHandler when we release the\n        // channel back to the pool. As don't want it timing-out when it's not in use.\n        ChannelPipeline pipeline = getChannel().pipeline();\n        removeHandlerFromPipeline(READ_TIMEOUT_HANDLER_NAME, pipeline);\n    }\n\n    private void removeHandlerFromPipeline(String handlerName, ChannelPipeline pipeline) {\n        if (pipeline.get(handlerName) != null) {\n            pipeline.remove(handlerName);\n        }\n    }\n\n    public void startReadTimeoutHandler(Duration readTimeout) {\n        Channel channel = getChannel();\n        if (!channel.isActive()) {\n            LOG.debug(\"Tried to start read timeout handler, but channel is not active\");\n            return;\n        }\n        channel.pipeline()\n                .addBefore(\n                        DefaultOriginChannelInitializer.ORIGIN_NETTY_LOGGER,\n                        READ_TIMEOUT_HANDLER_NAME,\n                        new ReadTimeoutHandler(readTimeout.toMillis(), TimeUnit.MILLISECONDS));\n    }\n\n    ConnectionState getConnectionState() {\n        return connectionState;\n    }\n\n    boolean isReleased() {\n        return released;\n    }\n\n    @Override\n    public String toString() {\n        return \"PooledConnection{\" + \"channel=\" + channel + \", usageCount=\" + usageCount + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/PooledConnectionFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport io.netty.channel.Channel;\n\n/**\n * User: Mike Smith\n * Date: 7/9/16\n * Time: 2:25 PM\n */\npublic interface PooledConnectionFactory {\n    PooledConnection create(Channel ch);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/RequestStat.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.exception.ErrorType;\n\n/**\n * Request Stat\n *\n * Author: Arthur Gonigberg\n * Date: November 29, 2017\n */\npublic interface RequestStat {\n\n    String SESSION_CONTEXT_KEY = \"niwsRequestStat\";\n\n    static RequestStat putInSessionContext(RequestStat stat, SessionContext context) {\n        context.put(SESSION_CONTEXT_KEY, stat);\n        return stat;\n    }\n\n    static RequestStat getFromSessionContext(SessionContext context) {\n        return (RequestStat) context.get(SESSION_CONTEXT_KEY);\n    }\n\n    RequestStat server(DiscoveryResult server);\n\n    boolean isFinished();\n\n    long duration();\n\n    void serviceUnavailable();\n\n    void generalError();\n\n    void failAndSetErrorCode(ErrorType errorType);\n\n    void updateWithHttpStatusCode(int httpStatusCode);\n\n    void finalAttempt(boolean finalAttempt);\n\n    boolean finishIfNotAlready();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/connectionpool/ZuulNettyExceptionMapper.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\n/**\n * User: Mike Smith\n * Date: 7/13/16\n * Time: 6:02 PM\n */\npublic class ZuulNettyExceptionMapper {}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/BaseZuulFilterRunner.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.impl.Preconditions;\nimport com.netflix.zuul.ExecutionStatus;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.Debug;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.filters.FilterError;\nimport com.netflix.zuul.filters.FilterSyncType;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.SyncZuulFilter;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport com.netflix.zuul.netty.server.MethodBinding;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.perfmark.Link;\nimport io.perfmark.PerfMark;\nimport io.perfmark.TaskCloseable;\nimport jakarta.annotation.Nullable;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.annotation.concurrent.ThreadSafe;\nimport lombok.Getter;\nimport lombok.NonNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport rx.Observer;\nimport rx.functions.Action0;\nimport rx.functions.Action1;\n\n/**\n * Subclasses of this class are supposed to be thread safe\n *\n * Created by saroskar on 5/18/17.\n */\n@ThreadSafe\npublic abstract class BaseZuulFilterRunner<I extends ZuulMessage, O extends ZuulMessage> implements FilterRunner<I, O> {\n\n    private final FilterUsageNotifier usageNotifier;\n\n    @Getter\n    private final FilterRunner<O, ? extends ZuulMessage> nextStage;\n\n    private final String RUNNING_FILTER_IDX_SESSION_CTX_KEY;\n    private final String AWAITING_BODY_FLAG_SESSION_CTX_KEY;\n    private static final Logger logger = LoggerFactory.getLogger(BaseZuulFilterRunner.class);\n\n    private static final CachedDynamicIntProperty FILTER_EXCESSIVE_EXEC_TIME =\n            new CachedDynamicIntProperty(\"zuul.filters.excessive.execTime\", 500);\n\n    private final Registry registry;\n    private final Id filterExcessiveTimerId;\n    private final FilterConstraints filterConstraints;\n\n    protected BaseZuulFilterRunner(\n            FilterType filterType,\n            FilterUsageNotifier usageNotifier,\n            FilterRunner<O, ?> nextStage,\n            FilterConstraints filterConstraints,\n            Registry registry) {\n        this.usageNotifier = Preconditions.checkNotNull(usageNotifier, \"filter usage notifier\");\n        this.nextStage = nextStage;\n        this.RUNNING_FILTER_IDX_SESSION_CTX_KEY = filterType + \"RunningFilterIndex\";\n        this.AWAITING_BODY_FLAG_SESSION_CTX_KEY = filterType + \"IsAwaitingBody\";\n        this.registry = registry;\n        this.filterExcessiveTimerId = registry.createId(\"zuul.request.timing.filterExcessive\");\n        this.filterConstraints = filterConstraints;\n    }\n\n    @NonNull\n    public static ChannelHandlerContext getChannelHandlerContext(ZuulMessage mesg) {\n        return (ChannelHandlerContext) mesg.getContext().get(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT);\n    }\n\n    protected final AtomicInteger initRunningFilterIndex(I zuulMesg) {\n        AtomicInteger idx = new AtomicInteger(0);\n        zuulMesg.getContext().put(RUNNING_FILTER_IDX_SESSION_CTX_KEY, idx);\n        return idx;\n    }\n\n    protected final AtomicInteger getRunningFilterIndex(I zuulMesg) {\n        SessionContext ctx = zuulMesg.getContext();\n        return (AtomicInteger)\n                Preconditions.checkNotNull(ctx.get(RUNNING_FILTER_IDX_SESSION_CTX_KEY), \"runningFilterIndex\");\n    }\n\n    protected final boolean isFilterAwaitingBody(SessionContext context) {\n        return context.containsKey(AWAITING_BODY_FLAG_SESSION_CTX_KEY);\n    }\n\n    protected final void setFilterAwaitingBody(I zuulMesg, boolean flag) {\n        if (flag) {\n            zuulMesg.getContext().put(AWAITING_BODY_FLAG_SESSION_CTX_KEY, Boolean.TRUE);\n        } else {\n            zuulMesg.getContext().remove(AWAITING_BODY_FLAG_SESSION_CTX_KEY);\n        }\n    }\n\n    protected final void invokeNextStage(O zuulMesg, HttpContent chunk) {\n        if (nextStage != null) {\n            try (TaskCloseable ignored =\n                    PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".invokeNextStageChunk\")) {\n                addPerfMarkTags(zuulMesg);\n                nextStage.filter(zuulMesg, chunk);\n            }\n        } else {\n            // Next stage is Netty channel handler\n            try (TaskCloseable ignored =\n                    PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".fireChannelReadChunk\")) {\n                addPerfMarkTags(zuulMesg);\n                ChannelHandlerContext channelHandlerContext = getChannelHandlerContext(zuulMesg);\n                if (!channelHandlerContext.channel().isActive()) {\n                    zuulMesg.getContext().cancel();\n                    zuulMesg.disposeBufferedBody();\n                    SpectatorUtils.newCounter(\n                                    \"zuul.filterChain.chunk.hanging\",\n                                    zuulMesg.getClass().getSimpleName())\n                            .increment();\n                } else {\n                    channelHandlerContext.fireChannelRead(chunk);\n                }\n            }\n        }\n    }\n\n    protected final void invokeNextStage(O zuulMesg) {\n        if (nextStage != null) {\n            try (TaskCloseable ignored =\n                    PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".invokeNextStage\")) {\n                addPerfMarkTags(zuulMesg);\n                nextStage.filter(zuulMesg);\n            }\n        } else {\n            // Next stage is Netty channel handler\n            try (TaskCloseable ignored =\n                    PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".fireChannelRead\")) {\n                addPerfMarkTags(zuulMesg);\n                ChannelHandlerContext channelHandlerContext = getChannelHandlerContext(zuulMesg);\n                if (!channelHandlerContext.channel().isActive()) {\n                    zuulMesg.getContext().cancel();\n                    zuulMesg.disposeBufferedBody();\n                    SpectatorUtils.newCounter(\n                                    \"zuul.filterChain.message.hanging\",\n                                    zuulMesg.getClass().getSimpleName())\n                            .increment();\n                } else {\n                    channelHandlerContext.fireChannelRead(zuulMesg);\n                }\n            }\n        }\n    }\n\n    protected final void addPerfMarkTags(ZuulMessage inMesg) {\n        HttpRequestInfo req = null;\n        if (inMesg instanceof HttpRequestInfo) {\n            req = (HttpRequestInfo) inMesg;\n        }\n        if (inMesg instanceof HttpResponseMessage msg) {\n\n            req = msg.getOutboundRequest();\n            PerfMark.attachTag(\"statuscode\", msg.getStatus());\n        }\n        if (req != null) {\n            PerfMark.attachTag(\"path\", req, HttpRequestInfo::getPath);\n            PerfMark.attachTag(\"originalhost\", req, HttpRequestInfo::getOriginalHost);\n        }\n        PerfMark.attachTag(\"uuid\", inMesg, m -> m.getContext().getUUID());\n    }\n\n    protected final FilterExecutionResult<O> executeFilter(ZuulFilter<I, O> filter, I inMesg) {\n        long startTime = System.nanoTime();\n        ZuulMessage snapshot = inMesg.getContext().debugRouting() ? inMesg.clone() : null;\n\n        try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".filter\")) {\n            addPerfMarkTags(inMesg);\n\n            ExecutionStatus executionStatus = checkFilterPreconditions(filter, inMesg);\n            if (executionStatus != null) {\n                recordFilterCompletion(executionStatus, filter, startTime, inMesg, snapshot);\n                return FilterExecutionResult.completed(filter.getDefaultOutput(inMesg));\n            }\n\n            if (!isMessageBodyReadyForFilter(filter, inMesg)) {\n                setFilterAwaitingBody(inMesg, true);\n                logger.debug(\n                        \"Filter {} waiting for body, UUID {}\",\n                        filter.filterName(),\n                        inMesg.getContext().getUUID());\n                return FilterExecutionResult.pending();\n            }\n            setFilterAwaitingBody(inMesg, false);\n\n            if (snapshot != null) {\n                Debug.addRoutingDebug(\n                        inMesg.getContext(),\n                        \"Filter \" + filter.filterType().toString() + \" \" + filter.filterOrder() + \" \"\n                                + filter.filterName());\n            }\n\n            // run body contents accumulated so far through this filter\n            inMesg.runBufferedBodyContentThroughFilter(filter);\n\n            if (filter.getSyncType() == FilterSyncType.SYNC) {\n                return executeSyncFilter((SyncZuulFilter<I, O>) filter, inMesg, startTime, snapshot);\n            }\n\n            return executeAsyncFilter(filter, inMesg, startTime, snapshot);\n        } catch (Throwable t) {\n            O outMesg = handleFilterException(inMesg, filter, t);\n            outMesg.finishBufferedBodyIfIncomplete();\n            recordFilterCompletion(ExecutionStatus.FAILED, filter, startTime, inMesg, snapshot);\n            return FilterExecutionResult.completed(outMesg);\n        }\n    }\n\n    @Nullable\n    private ExecutionStatus checkFilterPreconditions(ZuulFilter<I, O> filter, I inMesg) {\n        if (filter.filterType() == FilterType.INBOUND && inMesg.getContext().shouldSendErrorResponse()) {\n            // Pass request down the pipeline, all the way to error endpoint if error response needs to be generated\n            return ExecutionStatus.SKIPPED;\n        }\n\n        try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".shouldSkipFilter\")) {\n            if (shouldSkipFilter(inMesg, filter)) {\n                return ExecutionStatus.SKIPPED;\n            }\n        }\n\n        if (filter.isDisabled()) {\n            return ExecutionStatus.DISABLED;\n        }\n\n        return null;\n    }\n\n    /**\n     * Execute a SyncZuulFilter apply on the current event loop thread.\n     */\n    private FilterExecutionResult<O> executeSyncFilter(\n            SyncZuulFilter<I, O> filter, I inMesg, long startTime, ZuulMessage snapshot) {\n        O outMesg;\n        try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".apply\")) {\n            addPerfMarkTags(inMesg);\n            outMesg = filter.apply(inMesg);\n        }\n        recordFilterCompletion(ExecutionStatus.SUCCESS, filter, startTime, inMesg, snapshot);\n        return FilterExecutionResult.completed((outMesg != null) ? outMesg : filter.getDefaultOutput(inMesg));\n    }\n\n    /**\n     * Execute a ZuulFilter's async apply, subscribing to resume the filter chain once the observable completes.\n     */\n    private FilterExecutionResult<O> executeAsyncFilter(\n            ZuulFilter<I, O> filter, I inMesg, long startTime, ZuulMessage snapshot) {\n        FilterChainResumer resumer;\n        filter.incrementConcurrency();\n        try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".applyAsync\")) {\n            Link nettyToSchedulerLink = PerfMark.linkOut();\n            resumer = new FilterChainResumer(inMesg, filter, snapshot, startTime);\n            filter.applyAsync(inMesg)\n                    .doOnSubscribe(() -> {\n                        try (TaskCloseable ignored2 =\n                                PerfMark.traceTask(filter, f -> f.filterName() + \".onSubscribeAsync\")) {\n                            PerfMark.linkIn(nettyToSchedulerLink);\n                        }\n                    })\n                    .doOnNext(resumer.onNextStarted(nettyToSchedulerLink))\n                    .doOnError(resumer.onErrorStarted(nettyToSchedulerLink))\n                    .doOnCompleted(resumer.onCompletedStarted(nettyToSchedulerLink))\n                    .observeOn(new EventExecutorScheduler(\n                            getChannelHandlerContext(inMesg).executor()))\n                    .doOnUnsubscribe(resumer::decrementConcurrency)\n                    .subscribe(resumer);\n        } catch (Throwable t) {\n            filter.decrementConcurrency();\n            throw t;\n        }\n\n        return FilterExecutionResult.pending();\n    }\n\n    /**\n     *  This is typically set by a filter when wanting to reject a request and also reduce load on the server by\n     *  not processing anymore filterChain\n     */\n    protected final boolean shouldSkipFilter(I inMesg, ZuulFilter<I, O> filter) {\n        if (filter.filterType() == FilterType.ENDPOINT) {\n            // Endpoints may not be skipped\n            return false;\n        }\n        SessionContext zuulCtx = inMesg.getContext();\n        if (zuulCtx.shouldStopFilterProcessing() && !filter.overrideStopFilterProcessing()) {\n            return true;\n        }\n        if (zuulCtx.isCancelled()) {\n            return true;\n        }\n\n        if (filterConstraints.isConstrained(inMesg, filter)) {\n            return true;\n        }\n        return !filter.shouldFilter(inMesg);\n    }\n\n    private boolean isMessageBodyReadyForFilter(ZuulFilter<I, O> filter, I inMesg) {\n        return inMesg.hasCompleteBody() || !filter.needsBodyBuffered(inMesg);\n    }\n\n    protected O handleFilterException(I inMesg, ZuulFilter<I, O> filter, Throwable ex) {\n        inMesg.getContext().setError(ex);\n        if (filter.filterType() == FilterType.ENDPOINT) {\n            inMesg.getContext().setShouldSendErrorResponse(true);\n        }\n        recordFilterError(inMesg, filter, ex);\n        return filter.getDefaultOutput(inMesg);\n    }\n\n    protected void recordFilterError(I inMesg, ZuulFilter<I, O> filter, Throwable t) {\n        // Add a log statement for this exception.\n        String errorMsg = \"Filter Exception: filter=\" + filter.filterName() + \", request-info=\"\n                + inMesg.getInfoForLogging() + \", msg=\" + String.valueOf(t.getMessage());\n        if (t instanceof ZuulException && !((ZuulException) t).shouldLogAsError()) {\n            logger.warn(errorMsg);\n        } else {\n            logger.error(errorMsg, t);\n        }\n\n        // Store this filter error for possible future use. But we still continue with next filter in the chain.\n        SessionContext zuulCtx = inMesg.getContext();\n        zuulCtx.getFilterErrors()\n                .add(new FilterError(filter.filterName(), filter.filterType().toString(), t));\n        if (zuulCtx.debugRouting()) {\n            Debug.addRoutingDebug(\n                    zuulCtx,\n                    \"Running Filter failed \" + filter.filterName() + \" type:\" + filter.filterType() + \" order:\"\n                            + filter.filterOrder() + \" \" + t.getMessage());\n        }\n    }\n\n    protected void recordFilterCompletion(\n            ExecutionStatus status,\n            ZuulFilter<I, O> filter,\n            long startTime,\n            ZuulMessage zuulMesg,\n            ZuulMessage startSnapshot) {\n\n        SessionContext zuulCtx = zuulMesg.getContext();\n        long execTimeNs = System.nanoTime() - startTime;\n        long execTimeMs = execTimeNs / 1_000_000L;\n        if (execTimeMs >= FILTER_EXCESSIVE_EXEC_TIME.get()) {\n            zuulCtx.setEventProperty(\"filter_execution_time_exceeded\", true);\n            registry.timer(filterExcessiveTimerId\n                            .withTag(\"id\", filter.filterName())\n                            .withTag(\"status\", status.name()))\n                    .record(execTimeMs, TimeUnit.MILLISECONDS);\n        }\n\n        // Record the execution summary in context.\n        switch (status) {\n            case FAILED:\n                if (logger.isDebugEnabled()) {\n                    zuulCtx.addFilterExecutionSummary(filter.filterName(), ExecutionStatus.FAILED.name(), execTimeMs);\n                }\n                break;\n            case SUCCESS:\n                if (logger.isDebugEnabled()) {\n                    zuulCtx.addFilterExecutionSummary(filter.filterName(), ExecutionStatus.SUCCESS.name(), execTimeMs);\n                }\n                if (startSnapshot != null) {\n                    // debugRouting == true\n                    Debug.addRoutingDebug(\n                            zuulCtx,\n                            \"Filter {\" + filter.filterName() + \" TYPE:\"\n                                    + filter.filterType().toString() + \" ORDER:\" + filter.filterOrder()\n                                    + \"} Execution time = \" + execTimeMs + \"ms\");\n                    Debug.compareContextState(filter.filterName(), zuulCtx, startSnapshot.getContext());\n                }\n                break;\n            default:\n                break;\n        }\n\n        logger.debug(\n                \"Filter {} completed with status {}, UUID {}\",\n                filter.filterName(),\n                status.name(),\n                zuulMesg.getContext().getUUID());\n        // Notify configured listener.\n        usageNotifier.notify(filter, status);\n    }\n\n    protected void handleException(ZuulMessage zuulMesg, String filterName, Exception ex) {\n        HttpRequestInfo zuulReq = null;\n        if (zuulMesg instanceof HttpRequestMessage) {\n            zuulReq = (HttpRequestMessage) zuulMesg;\n        } else if (zuulMesg instanceof HttpResponseMessage) {\n            zuulReq = ((HttpResponseMessage) zuulMesg).getInboundRequest();\n        }\n        String path = (zuulReq != null) ? zuulReq.getPathAndQuery() : \"-\";\n        String method = (zuulReq != null) ? zuulReq.getMethod() : \"-\";\n        String errMesg = \"Error with filter: \" + filterName + \", path: \" + path + \", method: \" + method;\n        logger.error(errMesg, ex);\n        getChannelHandlerContext(zuulMesg).fireExceptionCaught(ex);\n    }\n\n    protected abstract void resume(O zuulMesg);\n\n    protected MethodBinding<?> methodBinding(ZuulMessage zuulMesg) {\n        return MethodBinding.NO_OP_BINDING;\n    }\n\n    protected void resumeInBindingContext(O zuulMesg, String filterName) {\n        try {\n            methodBinding(zuulMesg).bind(() -> resume(zuulMesg));\n        } catch (Exception ex) {\n            handleException(zuulMesg, filterName, ex);\n        }\n    }\n\n    /**\n     * FilterExecutionResult indicates if the filter is still processing a request, such as waiting\n     * on an async filter execution or for a full body to buffer, or has completed.\n     */\n    protected sealed interface FilterExecutionResult<O> {\n        record Complete<O>(@Nullable O message) implements FilterExecutionResult<O> {}\n\n        record Pending<O>() implements FilterExecutionResult<O> {}\n\n        Pending<?> pending = new Pending<>();\n\n        @SuppressWarnings(\"unchecked\")\n        static <O> FilterExecutionResult<O> pending() {\n            return (FilterExecutionResult<O>) pending;\n        }\n\n        static <O> FilterExecutionResult<O> completed(O message) {\n            return new Complete<>(message);\n        }\n    }\n\n    private final class FilterChainResumer implements Observer<O> {\n        private final I inMesg;\n        private final ZuulFilter<I, O> filter;\n        private final long startTime;\n        private final ZuulMessage snapshot;\n        private final AtomicBoolean concurrencyDecremented;\n\n        private final AtomicReference<Link> onNextLinkOut = new AtomicReference<>();\n        private final AtomicReference<Link> onErrorLinkOut = new AtomicReference<>();\n        private final AtomicReference<Link> onCompletedLinkOut = new AtomicReference<>();\n\n        // no synchronization needed since onNext and onCompleted are always called on the same thread\n        private O outMesg;\n\n        public FilterChainResumer(I inMesg, ZuulFilter<I, O> filter, ZuulMessage snapshot, long startTime) {\n            this.inMesg = Preconditions.checkNotNull(inMesg, \"input message\");\n            this.filter = Preconditions.checkNotNull(filter, \"filter\");\n            this.snapshot = snapshot;\n            this.startTime = startTime;\n            this.concurrencyDecremented = new AtomicBoolean(false);\n        }\n\n        void decrementConcurrency() {\n            if (concurrencyDecremented.compareAndSet(false, true)) {\n                filter.decrementConcurrency();\n            }\n        }\n\n        @Override\n        public void onNext(O outMesg) {\n            try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".onNextAsync\")) {\n                PerfMark.linkIn(onNextLinkOut.get());\n                addPerfMarkTags(inMesg);\n                this.outMesg = outMesg;\n            } catch (Exception e) {\n                decrementConcurrency();\n                handleException(inMesg, filter.filterName(), e);\n            }\n        }\n\n        @Override\n        public void onError(Throwable ex) {\n            try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".onErrorAsync\")) {\n                PerfMark.linkIn(onErrorLinkOut.get());\n                decrementConcurrency();\n                recordFilterCompletion(ExecutionStatus.FAILED, filter, startTime, inMesg, snapshot);\n                O outMesg = handleFilterException(inMesg, filter, ex);\n                resumeInBindingContext(outMesg, filter.filterName());\n            } catch (Exception e) {\n                handleException(inMesg, filter.filterName(), e);\n            }\n        }\n\n        @Override\n        public void onCompleted() {\n            try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".onCompletedAsync\")) {\n                PerfMark.linkIn(onCompletedLinkOut.get());\n                decrementConcurrency();\n                if (outMesg == null) {\n                    outMesg = filter.getDefaultOutput(inMesg);\n                }\n                recordFilterCompletion(ExecutionStatus.SUCCESS, filter, startTime, inMesg, snapshot);\n                resumeInBindingContext(outMesg, filter.filterName());\n            } catch (Exception e) {\n                handleException(inMesg, filter.filterName(), e);\n            }\n        }\n\n        private Action1<O> onNextStarted(Link onNextLinkIn) {\n            return o -> {\n                try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".onNext\")) {\n                    PerfMark.linkIn(onNextLinkIn);\n                    onNextLinkOut.compareAndSet(null, PerfMark.linkOut());\n                }\n            };\n        }\n\n        private Action1<Throwable> onErrorStarted(Link onErrorLinkIn) {\n            return t -> {\n                try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".onError\")) {\n                    PerfMark.linkIn(onErrorLinkIn);\n                    onErrorLinkOut.compareAndSet(null, PerfMark.linkOut());\n                }\n            };\n        }\n\n        private Action0 onCompletedStarted(Link onCompletedLinkIn) {\n            return () -> {\n                try (TaskCloseable ignored = PerfMark.traceTask(filter, f -> f.filterName() + \".onCompleted\")) {\n                    PerfMark.linkIn(onCompletedLinkIn);\n                    onCompletedLinkOut.compareAndSet(null, PerfMark.linkOut());\n                }\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/EventExecutorScheduler.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport io.netty.util.concurrent.EventExecutor;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.lang.NotImplementedException;\nimport rx.Scheduler;\nimport rx.Subscription;\nimport rx.functions.Action0;\nimport rx.internal.schedulers.ScheduledAction;\nimport rx.subscriptions.Subscriptions;\n\n/**\n * A custom {@link Scheduler} for use with a {@link EventExecutor} that\n * 1) Ensures that every action is run on the EventExecutor thread\n * 2) avoids the unnecessary executions that occur from using {@link rx.internal.schedulers.ExecutorScheduler} if already\n * executing on the correct thread\n *\n * Should only be used with {@link io.netty.channel.SingleThreadEventLoop}\n *\n * @author Justin Guerra\n * @since 5/19/25\n */\npublic class EventExecutorScheduler extends Scheduler {\n\n    private final EventExecutor executor;\n\n    public EventExecutorScheduler(EventExecutor executor) {\n        this.executor = Objects.requireNonNull(executor);\n    }\n\n    @Override\n    public Worker createWorker() {\n        return new Worker() {\n\n            private volatile boolean unsubscribed;\n\n            @Override\n            public void unsubscribe() {\n                unsubscribed = true;\n            }\n\n            @Override\n            public boolean isUnsubscribed() {\n                return unsubscribed;\n            }\n\n            @Override\n            public Subscription schedule(Action0 action) {\n                if (executor.inEventLoop()) {\n                    action.call();\n                    return Subscriptions.unsubscribed();\n                } else {\n                    ScheduledAction sa = new ScheduledAction(action);\n                    executor.execute(sa);\n                    return sa;\n                }\n            }\n\n            @Override\n            public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {\n                throw new NotImplementedException();\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/FilterConstraints.java",
    "content": "/*\n * Copyright 2026 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport com.netflix.zuul.FilterConstraint;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.stream.Collectors;\nimport org.jspecify.annotations.NullMarked;\n\n/**\n * Class responsible for checking {@link FilterConstraint}.\n * Register this class with custom constraints by using {@link com.netflix.zuul.netty.server.ZuulDependencyKeys#filterConstraints}\n * in {@link com.netflix.zuul.netty.server.BaseZuulChannelInitializer}\n *\n * @author Justin Guerra\n * @since 1/9/26\n */\n@NullMarked\npublic class FilterConstraints {\n\n    @SuppressWarnings(\"unchecked\")\n    private static final Class<? extends FilterConstraint>[] NO_CONSTRAINTS = new Class[0];\n\n    private final Map<Class<? extends FilterConstraint>, FilterConstraint> lookup;\n    private final Map<String, Class<? extends FilterConstraint>[]> filterConstraints;\n\n    public FilterConstraints(List<FilterConstraint> constraints) {\n        this.lookup = constraints.stream().collect(Collectors.toUnmodifiableMap(FilterConstraint::getClass, c -> c));\n        this.filterConstraints = new ConcurrentHashMap<>();\n    }\n\n    /**\n     * Checks if any {@link FilterConstraint}'s are active for the given msg\n     */\n    public boolean isConstrained(ZuulMessage msg, ZuulFilter<?, ?> filter) {\n        Class<? extends FilterConstraint>[] constraints =\n                filterConstraints.computeIfAbsent(filter.getClass().getName(), f -> {\n                    Class<? extends FilterConstraint>[] filterConstraints = filter.constraints();\n                    return filterConstraints != null ? filterConstraints : NO_CONSTRAINTS;\n                });\n\n        for (Class<? extends FilterConstraint> constraint : constraints) {\n            FilterConstraint filterConstraint = lookup.get(constraint);\n            if (filterConstraint != null && filterConstraint.isConstrained(msg)) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/FilterRunner.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport com.netflix.zuul.message.ZuulMessage;\nimport io.netty.handler.codec.http.HttpContent;\n\n/**\n * Created by saroskar on 5/18/17.\n */\npublic interface FilterRunner<I extends ZuulMessage, O extends ZuulMessage> {\n\n    void filter(I zuulMesg);\n\n    void filter(I zuulMesg, HttpContent chunk);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulEndPointRunner.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport com.google.common.base.Strings;\nimport com.netflix.config.DynamicStringProperty;\nimport com.netflix.netty.common.ByteBufUtil;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.impl.Preconditions;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.Endpoint;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.SyncZuulFilterAdapter;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.filters.endpoint.EndpointLifecycle;\nimport com.netflix.zuul.filters.endpoint.MissingEndpointHandlingFilter;\nimport com.netflix.zuul.filters.endpoint.ProxyEndpoint;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport com.netflix.zuul.netty.server.MethodBinding;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport io.perfmark.PerfMark;\nimport io.perfmark.TaskCloseable;\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.ThreadSafe;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * This class is supposed to be thread safe and hence should not have any non final member variables\n * Created by saroskar on 5/18/17.\n */\n@ThreadSafe\npublic class ZuulEndPointRunner extends BaseZuulFilterRunner<HttpRequestMessage, HttpResponseMessage> {\n\n    private final FilterLoader filterLoader;\n\n    private static final Logger logger = LoggerFactory.getLogger(ZuulEndPointRunner.class);\n    public static final String PROXY_ENDPOINT_FILTER_NAME = ProxyEndpoint.class.getCanonicalName();\n    public static final DynamicStringProperty DEFAULT_ERROR_ENDPOINT =\n            new DynamicStringProperty(\"zuul.filters.error.default\", \"endpoint.ErrorResponse\");\n\n    public ZuulEndPointRunner(\n            FilterUsageNotifier usageNotifier,\n            FilterLoader filterLoader,\n            FilterRunner<HttpResponseMessage, HttpResponseMessage> respFilters,\n            FilterConstraints filterConstraints,\n            Registry registry) {\n        super(FilterType.ENDPOINT, usageNotifier, respFilters, filterConstraints, registry);\n        this.filterLoader = filterLoader;\n    }\n\n    @Nullable\n    public static ZuulFilter<HttpRequestMessage, HttpResponseMessage> getEndpoint(\n            @Nullable HttpRequestMessage zuulReq) {\n        if (zuulReq != null) {\n            return zuulReq.getContext().get(CommonContextKeys.ZUUL_ENDPOINT);\n        }\n        return null;\n    }\n\n    protected ZuulFilter<HttpRequestMessage, HttpResponseMessage> getEndpoint(\n            String endpointName, HttpRequestMessage zuulRequest) {\n        SessionContext zuulCtx = zuulRequest.getContext();\n\n        if (zuulCtx.getStaticResponse() != null) {\n            return STATIC_RESPONSE_ENDPOINT;\n        }\n\n        if (endpointName == null) {\n            return new MissingEndpointHandlingFilter(\"NO_ENDPOINT_NAME\");\n        }\n\n        if (endpointName.equals(PROXY_ENDPOINT_FILTER_NAME)) {\n            return newProxyEndpoint(zuulRequest);\n        }\n\n        Endpoint<HttpRequestMessage, HttpResponseMessage> filter = getEndpointFilter(endpointName);\n        if (filter == null) {\n            return new MissingEndpointHandlingFilter(endpointName);\n        }\n\n        return filter;\n    }\n\n    public static void setEndpoint(\n            HttpRequestMessage zuulReq, ZuulFilter<HttpRequestMessage, HttpResponseMessage> endpoint) {\n        zuulReq.getContext().put(CommonContextKeys.ZUUL_ENDPOINT, endpoint);\n    }\n\n    @Override\n    public void filter(HttpRequestMessage zuulReq) {\n        if (zuulReq.getContext().isCancelled()) {\n            PerfMark.event(getClass().getName(), \"filterCancelled\");\n            zuulReq.disposeBufferedBody();\n            logger.debug(\"Request was cancelled, UUID {}\", zuulReq.getContext().getUUID());\n            return;\n        }\n\n        String endpointName = getEndPointName(zuulReq.getContext());\n        try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".filter\")) {\n            Preconditions.checkNotNull(zuulReq, \"input message\");\n            addPerfMarkTags(zuulReq);\n\n            ZuulFilter<HttpRequestMessage, HttpResponseMessage> endpoint = getEndpoint(endpointName, zuulReq);\n            logger.debug(\n                    \"Got endpoint {}, UUID {}\",\n                    endpoint.filterName(),\n                    zuulReq.getContext().getUUID());\n            setEndpoint(zuulReq, endpoint);\n            FilterExecutionResult<HttpResponseMessage> result = executeFilter(endpoint, zuulReq);\n\n            if (result instanceof FilterExecutionResult.Complete<HttpResponseMessage>(HttpResponseMessage message)\n                    && !(endpoint instanceof EndpointLifecycle)) {\n                // EdgeProxyEndpoint calls invokeNextStage internally\n                logger.debug(\n                        \"Endpoint calling invokeNextStage, UUID {}\",\n                        zuulReq.getContext().getUUID());\n                invokeNextStage(message);\n            }\n        } catch (Exception ex) {\n            handleException(zuulReq, endpointName, ex);\n        }\n    }\n\n    @Override\n    public void filter(HttpRequestMessage zuulReq, HttpContent chunk) {\n        if (zuulReq.getContext().isCancelled()) {\n            chunk.release();\n            return;\n        }\n\n        String endpointName = \"-\";\n        try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".filterChunk\")) {\n            addPerfMarkTags(zuulReq);\n            ZuulFilter<HttpRequestMessage, HttpResponseMessage> endpoint =\n                    Preconditions.checkNotNull(getEndpoint(zuulReq), \"endpoint\");\n            endpointName = endpoint.filterName();\n\n            ByteBufUtil.touch(chunk, \"Endpoint processing chunk, ZuulMessage: \", zuulReq);\n            HttpContent newChunk = endpoint.processContentChunk(zuulReq, chunk);\n            if (newChunk != null) {\n                ByteBufUtil.touch(newChunk, \"Endpoint buffering newChunk, ZuulMessage: \", zuulReq);\n                // Endpoints do not directly forward content chunks to next stage in the filter chain.\n                zuulReq.bufferBodyContents(newChunk);\n\n                // deallocate original chunk if necessary\n                if (newChunk != chunk) {\n                    chunk.release();\n                }\n\n                if (isFilterAwaitingBody(zuulReq.getContext())\n                        && zuulReq.hasCompleteBody()\n                        && !(endpoint instanceof EndpointLifecycle)) {\n                    // whole body has arrived, resume filter chain\n                    ByteBufUtil.touch(newChunk, \"Endpoint body complete, resume chain, ZuulMessage: \", zuulReq);\n                    FilterExecutionResult<HttpResponseMessage> result = executeFilter(endpoint, zuulReq);\n                    if (result\n                            instanceof\n                            FilterExecutionResult.Complete<HttpResponseMessage>(HttpResponseMessage message)) {\n                        invokeNextStage(message);\n                    }\n                }\n            }\n        } catch (Exception ex) {\n            ReferenceCountUtil.safeRelease(chunk);\n            handleException(zuulReq, endpointName, ex);\n        }\n    }\n\n    @Override\n    protected void resume(HttpResponseMessage zuulMesg) {\n        try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".resume\")) {\n            if (zuulMesg.getContext().isCancelled()) {\n                return;\n            }\n            invokeNextStage(zuulMesg);\n        }\n    }\n\n    protected String getEndPointName(SessionContext zuulCtx) {\n        if (zuulCtx.shouldSendErrorResponse()) {\n            zuulCtx.setShouldSendErrorResponse(false);\n            zuulCtx.setErrorResponseSent(true);\n            String errEndPointName = zuulCtx.getErrorEndpoint();\n            return Strings.isNullOrEmpty(errEndPointName) ? DEFAULT_ERROR_ENDPOINT.get() : errEndPointName;\n        } else {\n            return zuulCtx.getEndpoint();\n        }\n    }\n\n    /**\n     * Override to inject your own proxy endpoint implementation\n     *\n     * @param zuulRequest - the request message\n     * @return the proxy endpoint\n     */\n    protected ZuulFilter<HttpRequestMessage, HttpResponseMessage> newProxyEndpoint(HttpRequestMessage zuulRequest) {\n        return new ProxyEndpoint(\n                zuulRequest, getChannelHandlerContext(zuulRequest), getNextStage(), MethodBinding.NO_OP_BINDING);\n    }\n\n    protected <I extends ZuulMessage, O extends ZuulMessage> Endpoint<I, O> getEndpointFilter(String endpointName) {\n        return (Endpoint<I, O>) filterLoader.getFilterByNameAndType(endpointName, FilterType.ENDPOINT);\n    }\n\n    protected static final ZuulFilter<HttpRequestMessage, HttpResponseMessage> STATIC_RESPONSE_ENDPOINT =\n            new SyncZuulFilterAdapter<HttpRequestMessage, HttpResponseMessage>() {\n                @Override\n                public HttpResponseMessage apply(HttpRequestMessage request) {\n                    HttpResponseMessage resp = request.getContext().getStaticResponse();\n                    resp.finishBufferedBodyIfIncomplete();\n                    return resp;\n                }\n\n                @Override\n                public String filterName() {\n                    return \"StaticResponseEndpoint\";\n                }\n\n                @Override\n                public HttpResponseMessage getDefaultOutput(HttpRequestMessage input) {\n                    return HttpResponseMessageImpl.defaultErrorResponse(input);\n                }\n            };\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport com.google.common.base.Preconditions;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport com.netflix.netty.common.HttpRequestReadTimeoutEvent;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.filters.endpoint.EndpointLifecycle;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.netty.RequestCancelledEvent;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport com.netflix.zuul.netty.server.ClientRequestReceiver;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.unix.Errors;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport io.netty.util.ReferenceCountUtil;\nimport java.nio.channels.ClosedChannelException;\nimport javax.net.ssl.SSLException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Created by saroskar on 5/18/17.\n */\npublic class ZuulFilterChainHandler extends ChannelInboundHandlerAdapter {\n\n    private final ZuulFilterChainRunner<HttpRequestMessage> requestFilterChain;\n    private final ZuulFilterChainRunner<HttpResponseMessage> responseFilterChain;\n    private HttpRequestMessage zuulRequest;\n\n    private static final Logger logger = LoggerFactory.getLogger(ZuulFilterChainHandler.class);\n\n    public ZuulFilterChainHandler(\n            ZuulFilterChainRunner<HttpRequestMessage> requestFilterChain,\n            ZuulFilterChainRunner<HttpResponseMessage> responseFilterChain) {\n        this.requestFilterChain = Preconditions.checkNotNull(requestFilterChain, \"request filter chain\");\n        this.responseFilterChain = Preconditions.checkNotNull(responseFilterChain, \"response filter chain\");\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof HttpRequestMessage) {\n            zuulRequest = (HttpRequestMessage) msg;\n\n            // Replace NETTY_SERVER_CHANNEL_HANDLER_CONTEXT in SessionContext\n            SessionContext zuulCtx = zuulRequest.getContext();\n            zuulCtx.put(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, ctx);\n\n            requestFilterChain.filter(zuulRequest);\n        } else if ((msg instanceof HttpContent) && (zuulRequest != null)) {\n            requestFilterChain.filter(zuulRequest, (HttpContent) msg);\n        } else {\n            logger.debug(\n                    \"Received unrecognized message type. {}\", msg.getClass().getName());\n            ReferenceCountUtil.release(msg);\n        }\n    }\n\n    @Override\n    public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof CompleteEvent completeEvent) {\n\n            fireEndpointFinish(\n                    completeEvent.getReason() != HttpLifecycleChannelHandler.CompleteReason.SESSION_COMPLETE, ctx);\n        } else if (evt instanceof HttpRequestReadTimeoutEvent) {\n            sendResponse(ZuulStatusCategory.FAILURE_CLIENT_TIMEOUT, 408, ctx);\n        } else if (evt instanceof IdleStateEvent) {\n            sendResponse(ZuulStatusCategory.FAILURE_LOCAL_IDLE_TIMEOUT, 504, ctx);\n        } else if (evt instanceof RequestCancelledEvent) {\n            if (zuulRequest != null) {\n                zuulRequest.getContext().cancel();\n                StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(\n                        zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_CANCELLED);\n            }\n            fireEndpointFinish(true, ctx);\n            ctx.close();\n        }\n        super.userEventTriggered(ctx, evt);\n    }\n\n    private void sendResponse(StatusCategory statusCategory, int status, ChannelHandlerContext ctx) {\n        if (zuulRequest == null) {\n            ctx.close();\n        } else {\n            SessionContext zuulCtx = zuulRequest.getContext();\n            zuulRequest.getContext().cancel();\n            StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(zuulCtx, statusCategory);\n            HttpResponseMessage zuulResponse = new HttpResponseMessageImpl(zuulCtx, zuulRequest, status);\n            Headers headers = zuulResponse.getHeaders();\n            headers.add(\"Connection\", \"close\");\n            headers.add(\"Content-Length\", \"0\");\n            zuulResponse.finishBufferedBodyIfIncomplete();\n            responseFilterChain.filter(zuulResponse);\n            fireEndpointFinish(true, ctx);\n        }\n    }\n\n    protected HttpRequestMessage getZuulRequest() {\n        return zuulRequest;\n    }\n\n    protected void fireEndpointFinish(boolean error, ChannelHandlerContext ctx) {\n        // make sure filter chain is not left hanging\n        finishResponseFilters(ctx);\n\n        ZuulFilter endpoint = ZuulEndPointRunner.getEndpoint(zuulRequest);\n        if (endpoint instanceof EndpointLifecycle lifecycleEndpoint) {\n            lifecycleEndpoint.finish(error);\n        }\n        zuulRequest = null;\n    }\n\n    private void finishResponseFilters(ChannelHandlerContext ctx) {\n        // check if there are any response filters awaiting a buffered body\n        if (zuulRequest != null && responseFilterChain.isFilterAwaitingBody(zuulRequest.getContext())) {\n            HttpResponseMessage zuulResponse =\n                    ctx.channel().attr(ClientRequestReceiver.ATTR_ZUUL_RESP).get();\n            if (zuulResponse != null) {\n                // fire a last content into the filter chain to unblock any filters awaiting a buffered body\n                responseFilterChain.filter(zuulResponse, new DefaultLastHttpContent());\n                SpectatorUtils.newCounter(\n                                \"zuul.filterChain.bodyBuffer.hanging\",\n                                zuulRequest.getContext().getRouteVIP())\n                        .increment();\n            }\n        }\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        if (cause instanceof SSLException) {\n            logger.debug(\"SSL exception not handled in filter chain\", cause);\n        } else {\n            logger.error(\n                    \"zuul filter chain handler caught exception on channel: {}\",\n                    ChannelUtils.channelInfoForLogging(ctx.channel()),\n                    cause);\n        }\n        if (zuulRequest != null && !isClientChannelClosed(cause)) {\n            SessionContext zuulCtx = zuulRequest.getContext();\n            zuulCtx.setError(cause);\n            zuulCtx.setShouldSendErrorResponse(true);\n            sendResponse(ZuulStatusCategory.FAILURE_LOCAL, 500, ctx);\n        } else {\n            fireEndpointFinish(true, ctx);\n            ctx.close();\n        }\n    }\n\n    // Race condition: channel.isActive() did not catch\n    // channel close..resulting in an i/o exception\n    private boolean isClientChannelClosed(Throwable cause) {\n        if (cause instanceof ClosedChannelException || cause instanceof Errors.NativeIoException) {\n            logger.error(\"ZuulFilterChainHandler::isClientChannelClosed - IO Exception\");\n            return true;\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/filter/ZuulFilterChainRunner.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport com.netflix.netty.common.ByteBufUtil;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.impl.Preconditions;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport io.perfmark.PerfMark;\nimport io.perfmark.TaskCloseable;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport javax.annotation.concurrent.ThreadSafe;\n\n/**\n * This class is supposed to be thread safe\n * Created by saroskar on 5/17/17.\n */\n@ThreadSafe\npublic class ZuulFilterChainRunner<T extends ZuulMessage> extends BaseZuulFilterRunner<T, T> {\n\n    private final ZuulFilter<T, T>[] filters;\n\n    public ZuulFilterChainRunner(\n            ZuulFilter<T, T>[] zuulFilters,\n            FilterUsageNotifier usageNotifier,\n            FilterRunner<T, ?> nextStage,\n            FilterConstraints filterConstraints,\n            Registry registry) {\n        super(zuulFilters[0].filterType(), usageNotifier, nextStage, filterConstraints, registry);\n        this.filters = zuulFilters;\n    }\n\n    public ZuulFilterChainRunner(\n            ZuulFilter<T, T>[] zuulFilters,\n            FilterUsageNotifier usageNotifier,\n            FilterConstraints filterConstraints,\n            Registry registry) {\n        this(zuulFilters, usageNotifier, null, filterConstraints, registry);\n    }\n\n    @Override\n    public void filter(T inMesg) {\n        try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".filter\")) {\n            addPerfMarkTags(inMesg);\n            runFilters(inMesg, initRunningFilterIndex(inMesg));\n        }\n    }\n\n    @Override\n    public void filter(T inMesg, HttpContent chunk) {\n        String filterName = \"-\";\n\n        try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".filterChunk\")) {\n            addPerfMarkTags(inMesg);\n            Preconditions.checkNotNull(inMesg, \"input message\");\n\n            AtomicInteger runningFilterIdx = getRunningFilterIndex(inMesg);\n            int limit = runningFilterIdx.get();\n            for (int i = 0; i < limit; i++) {\n                ZuulFilter<T, T> filter = filters[i];\n                filterName = filter.filterName();\n                if (!filter.isDisabled() && !shouldSkipFilter(inMesg, filter)) {\n                    ByteBufUtil.touch(chunk, \"Filter runner processing chunk, filter: \", filterName);\n                    HttpContent newChunk = filter.processContentChunk(inMesg, chunk);\n                    if (newChunk == null) {\n                        // Filter wants to break the chain and stop propagating this chunk any further\n                        return;\n                    }\n                    // deallocate original chunk if necessary\n                    if ((newChunk != chunk) && (chunk.refCnt() > 0)) {\n                        ByteBufUtil.touch(chunk, \"Filter runner processing newChunk, filter: \", filterName);\n                        chunk.release(chunk.refCnt());\n                    }\n                    chunk = newChunk;\n                }\n            }\n\n            if (limit >= filters.length) {\n                // Filter chain has run to end, pass down the channel pipeline\n                ByteBufUtil.touch(chunk, \"Filter runner chain complete, message: \", inMesg);\n                invokeNextStage(inMesg, chunk);\n            } else {\n                ByteBufUtil.touch(chunk, \"Filter runner buffering chunk, message: \", inMesg);\n                inMesg.bufferBodyContents(chunk);\n\n                boolean isAwaitingBody = isFilterAwaitingBody(inMesg.getContext());\n\n                // Record passport states for start and end of buffering bodies.\n                if (isAwaitingBody) {\n                    CurrentPassport passport = CurrentPassport.fromSessionContext(inMesg.getContext());\n                    if (inMesg.hasCompleteBody()) {\n                        if (inMesg instanceof HttpRequestMessage) {\n                            passport.addIfNotAlready(PassportState.FILTERS_INBOUND_BUF_END);\n                        } else if (inMesg instanceof HttpResponseMessage) {\n                            passport.addIfNotAlready(PassportState.FILTERS_OUTBOUND_BUF_END);\n                        }\n                    } else {\n                        if (inMesg instanceof HttpRequestMessage) {\n                            passport.addIfNotAlready(PassportState.FILTERS_INBOUND_BUF_START);\n                        } else if (inMesg instanceof HttpResponseMessage) {\n                            passport.addIfNotAlready(PassportState.FILTERS_OUTBOUND_BUF_START);\n                        }\n                    }\n                }\n\n                if (isAwaitingBody && inMesg.hasCompleteBody()) {\n                    // whole body has arrived, resume filter chain\n                    ByteBufUtil.touch(chunk, \"Filter body complete, resume chain, ZuulMessage: \", inMesg);\n                    runFilters(inMesg, runningFilterIdx);\n                }\n            }\n        } catch (Exception ex) {\n            ReferenceCountUtil.safeRelease(chunk);\n            handleException(inMesg, filterName, ex);\n        }\n    }\n\n    @Override\n    protected void resume(T inMesg) {\n        try (TaskCloseable ignored = PerfMark.traceTask(this, s -> s.getClass().getSimpleName() + \".resume\")) {\n            AtomicInteger runningFilterIdx = getRunningFilterIndex(inMesg);\n            runningFilterIdx.incrementAndGet();\n            runFilters(inMesg, runningFilterIdx);\n        }\n    }\n\n    private final void runFilters(T mesg, AtomicInteger runningFilterIdx) {\n        T inMesg = mesg;\n        String filterName = \"-\";\n        try {\n            Preconditions.checkNotNull(mesg, \"Input message\");\n            int i = runningFilterIdx.get();\n\n            while (i < filters.length) {\n                ZuulFilter<T, T> filter = filters[i];\n                filterName = filter.filterName();\n                FilterExecutionResult<T> result = executeFilter(filter, inMesg);\n                if (result instanceof FilterExecutionResult.Pending<T>) {\n                    return;\n                }\n                if (result instanceof FilterExecutionResult.Complete<T>(T message)) {\n                    inMesg = message;\n                }\n                i = runningFilterIdx.incrementAndGet();\n            }\n\n            // Filter chain has reached its end, pass result to the next stage\n            invokeNextStage(inMesg);\n        } catch (Exception ex) {\n            handleException(inMesg, filterName, ex);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportLoggingHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport com.netflix.config.CachedDynamicLongProperty;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.netty.common.metrics.HttpMetricsChannelHandler;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.monitoring.ConnCounter;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.netty.server.ClientRequestReceiver;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport com.netflix.zuul.passport.StartAndEnd;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/28/17\n * Time: 5:41 PM\n */\n@ChannelHandler.Sharable\npublic class PassportLoggingHandler extends ChannelInboundHandlerAdapter {\n    private static final Logger LOG = LoggerFactory.getLogger(PassportLoggingHandler.class);\n\n    private static final CachedDynamicLongProperty WARN_REQ_PROCESSING_TIME_NS =\n            new CachedDynamicLongProperty(\"zuul.passport.log.request.time.threshold\", 1000 * 1000 * 1000); // 1000 ms\n    private static final CachedDynamicLongProperty WARN_RESP_PROCESSING_TIME_NS =\n            new CachedDynamicLongProperty(\"zuul.passport.log.response.time.threshold\", 1000 * 1000 * 1000); // 1000 ms\n\n    private final Counter incompleteProxySessionCounter;\n\n    public PassportLoggingHandler(Registry spectatorRegistry) {\n        incompleteProxySessionCounter = spectatorRegistry.counter(\"server.http.session.incomplete\");\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        try {\n            super.userEventTriggered(ctx, evt);\n        } finally {\n            if (evt instanceof HttpLifecycleChannelHandler.CompleteEvent) {\n                try {\n                    logPassport(ctx.channel());\n                } catch (Exception e) {\n                    LOG.error(\"Error logging passport info after request completed!\", e);\n                }\n            }\n        }\n    }\n\n    private void logPassport(Channel channel) {\n        // Collect attributes.\n        CurrentPassport passport = CurrentPassport.fromChannel(channel);\n        HttpRequestMessage request = ClientRequestReceiver.getRequestFromChannel(channel);\n        HttpResponseMessage response = ClientRequestReceiver.getResponseFromChannel(channel);\n        SessionContext ctx = request == null ? null : request.getContext();\n\n        String topLevelRequestId = getRequestId(channel, ctx);\n\n        // Do some debug logging of the Passport.\n        if (LOG.isDebugEnabled()) {\n            LOG.debug(\n                    \"State after complete. , current-server-conns = {}, current-http-reqs = {}, status = {}, nfstatus\"\n                            + \" = {}, toplevelid = {}, req = {}, passport = {}\",\n                    ConnCounter.from(channel).getCurrentActiveConns(),\n                    HttpMetricsChannelHandler.getInflightRequestCountFromChannel(channel),\n                    (response == null ? getRequestId(channel, ctx) : response.getStatus()),\n                    String.valueOf(StatusCategoryUtils.getStatusCategory(ctx)),\n                    topLevelRequestId,\n                    request.getInfoForLogging(),\n                    String.valueOf(passport));\n        }\n\n        // Some logging of session states if certain criteria match:\n        if (LOG.isInfoEnabled()) {\n            if (passport.wasProxyAttempt()) {\n\n                if (passport.findStateBackwards(PassportState.OUT_RESP_LAST_CONTENT_SENDING) == null) {\n                    incompleteProxySessionCounter.increment();\n                    LOG.info(\n                            \"Incorrect final state! toplevelid = {}, {}\",\n                            topLevelRequestId,\n                            ChannelUtils.channelInfoForLogging(channel));\n                }\n            }\n\n            if (!passport.wasProxyAttempt()) {\n                if (ctx != null && !isHealthcheckRequest(request)) {\n                    // Why did we fail to attempt to proxy this request?\n                    RequestAttempts attempts = RequestAttempts.getFromSessionContext(ctx);\n                    LOG.debug(\n                            \"State after complete. , context-error = {}, current-http-reqs = {}, toplevelid = {}, req\"\n                                    + \" = {}, attempts = {}, passport = {}\",\n                            String.valueOf(ctx.getError()),\n                            HttpMetricsChannelHandler.getInflightRequestCountFromChannel(channel),\n                            topLevelRequestId,\n                            request.getInfoForLogging(),\n                            String.valueOf(attempts),\n                            String.valueOf(passport));\n                }\n            }\n\n            StartAndEnd inReqToOutResp = passport.findFirstStartAndLastEndStates(\n                    PassportState.IN_REQ_HEADERS_RECEIVED, PassportState.OUT_REQ_LAST_CONTENT_SENT);\n            if (passport.calculateTimeBetween(inReqToOutResp) > WARN_REQ_PROCESSING_TIME_NS.get()) {\n                LOG.info(\n                        \"Request processing took longer than threshold! toplevelid = {}, {}\",\n                        topLevelRequestId,\n                        ChannelUtils.channelInfoForLogging(channel));\n            }\n\n            StartAndEnd inRespToOutResp = passport.findLastStartAndFirstEndStates(\n                    PassportState.IN_RESP_HEADERS_RECEIVED, PassportState.OUT_RESP_LAST_CONTENT_SENT);\n            if (passport.calculateTimeBetween(inRespToOutResp) > WARN_RESP_PROCESSING_TIME_NS.get()) {\n                LOG.info(\n                        \"Response processing took longer than threshold! toplevelid = {}, {}\",\n                        topLevelRequestId,\n                        ChannelUtils.channelInfoForLogging(channel));\n            }\n        }\n    }\n\n    protected boolean isHealthcheckRequest(HttpRequestMessage req) {\n        return req.getPath().equals(\"/healthcheck\");\n    }\n\n    protected String getRequestId(Channel channel, SessionContext ctx) {\n        return ctx == null ? \"-\" : ctx.getUUID();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateHttpClientHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.LastHttpContent;\n\n/**\n * User: Mike Smith\n * Date: 9/24/16\n * Time: 2:41 PM\n */\npublic final class PassportStateHttpClientHandler {\n\n    private static CurrentPassport passport(ChannelHandlerContext ctx) {\n        return CurrentPassport.fromChannel(ctx.channel());\n    }\n\n    public static final class InboundHandler extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            try {\n                CurrentPassport passport = passport(ctx);\n\n                if (msg instanceof HttpResponse) {\n                    passport.add(PassportState.IN_RESP_HEADERS_RECEIVED);\n                }\n\n                if (msg instanceof LastHttpContent) {\n                    passport.add(PassportState.IN_RESP_LAST_CONTENT_RECEIVED);\n                } else if (msg instanceof HttpContent) {\n                    passport.add(PassportState.IN_RESP_CONTENT_RECEIVED);\n                }\n            } finally {\n                super.channelRead(ctx, msg);\n            }\n        }\n    }\n\n    public static final class OutboundHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            try {\n                CurrentPassport passport = passport(ctx);\n\n                if (msg instanceof HttpRequest) {\n                    passport.add(PassportState.OUT_REQ_HEADERS_SENDING);\n                    promise.addListener(new PassportStateListener(\n                            passport, PassportState.OUT_REQ_HEADERS_SENT, PassportState.OUT_REQ_HEADERS_ERROR_SENDING));\n                }\n\n                if (msg instanceof LastHttpContent) {\n                    passport.add(PassportState.OUT_REQ_LAST_CONTENT_SENDING);\n                    promise.addListener(new PassportStateListener(\n                            passport,\n                            PassportState.OUT_REQ_LAST_CONTENT_SENT,\n                            PassportState.OUT_REQ_LAST_CONTENT_ERROR_SENDING));\n                } else if (msg instanceof HttpContent) {\n                    passport.add(PassportState.OUT_REQ_CONTENT_SENDING);\n                    promise.addListener(new PassportStateListener(\n                            passport, PassportState.OUT_REQ_CONTENT_SENT, PassportState.OUT_REQ_CONTENT_ERROR_SENDING));\n                }\n            } finally {\n                super.write(ctx, msg, promise);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateHttpServerHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.LastHttpContent;\n\n/**\n * User: Mike Smith\n * Date: 9/24/16\n * Time: 2:41 PM\n */\npublic final class PassportStateHttpServerHandler {\n\n    private static CurrentPassport passport(ChannelHandlerContext ctx) {\n        return CurrentPassport.fromChannel(ctx.channel());\n    }\n\n    public static final class InboundHandler extends ChannelInboundHandlerAdapter {\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            // Get existing passport or create new if none already.\n            CurrentPassport passport = passport(ctx);\n\n            if (msg instanceof HttpRequest) {\n                // If the current passport for this channel already contains an inbound http request, then\n                // we know it's used, so discard and create a new one.\n                // NOTE: we do this because we want to include the initial conn estab + ssl handshake into the passport\n                // of the 1st request on a channel, but not on subsequent requests.\n                if (passport.findState(PassportState.IN_REQ_HEADERS_RECEIVED) != null) {\n                    passport = CurrentPassport.createForChannel(ctx.channel());\n                }\n\n                passport.add(PassportState.IN_REQ_HEADERS_RECEIVED);\n            }\n\n            if (msg instanceof LastHttpContent) {\n                passport.add(PassportState.IN_REQ_LAST_CONTENT_RECEIVED);\n            } else if (msg instanceof HttpContent) {\n                passport.add(PassportState.IN_REQ_CONTENT_RECEIVED);\n            }\n\n            super.channelRead(ctx, msg);\n        }\n\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n            try {\n                super.userEventTriggered(ctx, evt);\n            } finally {\n                if (evt instanceof HttpLifecycleChannelHandler.CompleteEvent) {\n                    CurrentPassport.clearFromChannel(ctx.channel());\n                }\n            }\n        }\n    }\n\n    public static final class OutboundHandler extends ChannelOutboundHandlerAdapter {\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            CurrentPassport passport = passport(ctx);\n\n            // Set into the SENDING state.\n            if (msg instanceof HttpResponse) {\n                passport.add(PassportState.OUT_RESP_HEADERS_SENDING);\n                promise.addListener(new PassportStateListener(\n                        passport, PassportState.OUT_RESP_HEADERS_SENT, PassportState.OUT_RESP_HEADERS_ERROR_SENDING));\n            }\n\n            if (msg instanceof LastHttpContent) {\n                passport.add(PassportState.OUT_RESP_LAST_CONTENT_SENDING);\n                promise.addListener(new PassportStateListener(\n                        passport,\n                        PassportState.OUT_RESP_LAST_CONTENT_SENT,\n                        PassportState.OUT_RESP_LAST_CONTENT_ERROR_SENDING));\n            } else if (msg instanceof HttpContent) {\n                passport.add(PassportState.OUT_RESP_CONTENT_SENDING);\n                promise.addListener(new PassportStateListener(\n                        passport, PassportState.OUT_RESP_CONTENT_SENT, PassportState.OUT_RESP_CONTENT_ERROR_SENDING));\n            }\n\n            // Continue with the write.\n            super.write(ctx, msg, promise);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateListener.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.util.concurrent.Future;\nimport io.netty.util.concurrent.GenericFutureListener;\n\npublic class PassportStateListener implements GenericFutureListener<Future<? super Void>> {\n    private final CurrentPassport passport;\n    private final PassportState successState;\n    private final PassportState failState;\n\n    public PassportStateListener(CurrentPassport passport, PassportState successState) {\n        this.passport = passport;\n        this.successState = successState;\n        this.failState = null;\n    }\n\n    public PassportStateListener(CurrentPassport passport, PassportState successState, PassportState failState) {\n        this.passport = passport;\n        this.successState = successState;\n        this.failState = failState;\n    }\n\n    @Override\n    public void operationComplete(Future future) throws Exception {\n        if (future.isSuccess()) {\n            passport.add(successState);\n        } else {\n            if (failState != null) {\n                // only capture a single failure state event,\n                // as sending content errors will fire for all content chunks,\n                // and we only need the first one\n                passport.addIfNotAlready(failState);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/insights/PassportStateOriginHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport java.net.SocketAddress;\n\n/**\n * User: Mike Smith\n * Date: 9/24/16\n * Time: 2:41 PM\n */\npublic final class PassportStateOriginHandler {\n    private static CurrentPassport passport(ChannelHandlerContext ctx) {\n        return CurrentPassport.fromChannel(ctx.channel());\n    }\n\n    public static final class InboundHandler extends ChannelInboundHandlerAdapter {\n\n        @Override\n        public void channelActive(ChannelHandlerContext ctx) throws Exception {\n            passport(ctx).add(PassportState.ORIGIN_CH_ACTIVE);\n            super.channelActive(ctx);\n        }\n\n        @Override\n        public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n            passport(ctx).add(PassportState.ORIGIN_CH_INACTIVE);\n            super.channelInactive(ctx);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            passport(ctx).add(PassportState.ORIGIN_CH_EXCEPTION);\n            super.exceptionCaught(ctx, cause);\n        }\n    }\n\n    public static final class OutboundHandler extends ChannelOutboundHandlerAdapter {\n\n        @Override\n        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            passport(ctx).add(PassportState.ORIGIN_CH_DISCONNECT);\n            super.disconnect(ctx, promise);\n        }\n\n        @Override\n        public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            passport(ctx).add(PassportState.ORIGIN_CH_CLOSE);\n            super.close(ctx, promise);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            passport(ctx).add(PassportState.ORIGIN_CH_EXCEPTION);\n            super.exceptionCaught(ctx, cause);\n        }\n\n        @Override\n        public void connect(\n                ChannelHandlerContext ctx,\n                SocketAddress remoteAddress,\n                SocketAddress localAddress,\n                ChannelPromise promise)\n                throws Exception {\n            // We would prefer to set this passport state here, but if we do then it will be run _after_ the http\n            // request\n            // has actually been written to the channel. Because another listener is added before this one.\n            // So instead we have to add this listener in the PerServerConnectionPool.handleConnectCompletion() method\n            // instead.\n            // passport.add(PassportState.ORIGIN_CH_CONNECTING);\n            // promise.addListener(new PassportStateListener(passport, PassportState.ORIGIN_CH_CONNECTED));\n\n            super.connect(ctx, remoteAddress, localAddress, promise);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/insights/ServerStateHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.unix.Errors;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: Mike Smith Date: 9/24/16 Time: 2:41 PM\n */\npublic final class ServerStateHandler {\n\n    private static final Logger logger = LoggerFactory.getLogger(ServerStateHandler.class);\n\n    private static CurrentPassport passport(ChannelHandlerContext ctx) {\n        return CurrentPassport.fromChannel(ctx.channel());\n    }\n\n    public static final class InboundHandler extends ChannelInboundHandlerAdapter {\n\n        private final Registry registry;\n        private final Counter totalConnections;\n        private final Counter connectionClosed;\n        private final Counter connectionErrors;\n\n        public InboundHandler(Registry registry, String metricId) {\n            this.registry = registry;\n            this.totalConnections = registry.counter(\"server.connections.connect\", \"id\", metricId);\n            this.connectionClosed = registry.counter(\"server.connections.close\", \"id\", metricId);\n            this.connectionErrors = registry.counter(\"server.connections.errors\", \"id\", metricId);\n        }\n\n        @Override\n        public void channelActive(ChannelHandlerContext ctx) throws Exception {\n            totalConnections.increment();\n            passport(ctx).add(PassportState.SERVER_CH_ACTIVE);\n\n            super.channelActive(ctx);\n        }\n\n        @Override\n        public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n            connectionClosed.increment();\n            passport(ctx).add(PassportState.SERVER_CH_INACTIVE);\n\n            super.channelInactive(ctx);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            connectionErrors.increment();\n            registry.counter(\n                            \"server.connection.exception.inbound\",\n                            \"handler\",\n                            \"ServerStateHandler.InboundHandler\",\n                            \"id\",\n                            cause.getClass().getSimpleName())\n                    .increment();\n            passport(ctx).add(PassportState.SERVER_CH_EXCEPTION);\n            logger.info(\"Connection error on Inbound\", cause);\n\n            super.exceptionCaught(ctx, cause);\n        }\n\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n            if (evt instanceof IdleStateEvent) {\n                CurrentPassport passport = CurrentPassport.fromChannel(ctx.channel());\n                if (passport != null) {\n                    passport.add(PassportState.SERVER_CH_IDLE_TIMEOUT);\n                }\n            }\n\n            super.userEventTriggered(ctx, evt);\n        }\n    }\n\n    public static final class OutboundHandler extends ChannelOutboundHandlerAdapter {\n\n        private final Registry registry;\n\n        public OutboundHandler(Registry registry) {\n            this.registry = registry;\n        }\n\n        @Override\n        public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            passport(ctx).add(PassportState.SERVER_CH_CLOSE);\n            super.close(ctx, promise);\n        }\n\n        @Override\n        public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n            passport(ctx).add(PassportState.SERVER_CH_DISCONNECT);\n            super.disconnect(ctx, promise);\n        }\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            passport(ctx).add(PassportState.SERVER_CH_EXCEPTION);\n            if (cause instanceof Errors.NativeIoException) {\n                logger.debug(\"PassportStateServerHandler Outbound NativeIoException\", cause);\n                registry.counter(\n                                \"server.connection.exception.outbound\",\n                                \"handler\",\n                                \"ServerStateHandler.OutboundHandler\",\n                                \"id\",\n                                cause.getClass().getSimpleName())\n                        .increment();\n\n            } else {\n                super.exceptionCaught(ctx, cause);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/ratelimiting/NullChannelHandlerProvider.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ratelimiting;\n\nimport io.netty.channel.ChannelHandler;\nimport jakarta.inject.Provider;\nimport jakarta.inject.Singleton;\n\n@Singleton\npublic class NullChannelHandlerProvider implements Provider<ChannelHandler> {\n    @Override\n    public ChannelHandler get() {\n        return null;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseServerStartup.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.google.errorprone.annotations.ForOverride;\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.config.ChainedDynamicProperty;\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.ChannelConfigValue;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.RequestCompleteHandler;\nimport com.netflix.zuul.context.SessionContextDecorator;\nimport com.netflix.zuul.netty.ratelimiting.NullChannelHandlerProvider;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.channel.group.DefaultChannelGroup;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.util.AsyncMapping;\nimport io.netty.util.concurrent.GlobalEventExecutor;\nimport jakarta.inject.Inject;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Map;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic abstract class BaseServerStartup {\n\n    protected static final Logger LOG = LoggerFactory.getLogger(BaseServerStartup.class);\n\n    protected final ServerStatusManager serverStatusManager;\n    protected final Registry registry;\n\n    @SuppressWarnings(\"unused\") // force initialization\n    protected final DirectMemoryMonitor directMemoryMonitor;\n\n    protected final EventLoopGroupMetrics eventLoopGroupMetrics;\n    protected final EventLoopConfig eventLoopConfig;\n    protected final EurekaClient discoveryClient;\n    protected final ApplicationInfoManager applicationInfoManager;\n    protected final AccessLogPublisher accessLogPublisher;\n    protected final SessionContextDecorator sessionCtxDecorator;\n    protected final RequestCompleteHandler reqCompleteHandler;\n    protected final FilterLoader filterLoader;\n    protected final FilterUsageNotifier usageNotifier;\n\n    private Map<NamedSocketAddress, ? extends ChannelInitializer<?>> addrsToChannelInitializers;\n    private ClientConnectionsShutdown clientConnectionsShutdown;\n    private Server server;\n\n    @Inject\n    public BaseServerStartup(\n            ServerStatusManager serverStatusManager,\n            FilterLoader filterLoader,\n            SessionContextDecorator sessionCtxDecorator,\n            FilterUsageNotifier usageNotifier,\n            RequestCompleteHandler reqCompleteHandler,\n            Registry registry,\n            DirectMemoryMonitor directMemoryMonitor,\n            EventLoopGroupMetrics eventLoopGroupMetrics,\n            EventLoopConfig eventLoopConfig,\n            EurekaClient discoveryClient,\n            ApplicationInfoManager applicationInfoManager,\n            AccessLogPublisher accessLogPublisher) {\n        this.serverStatusManager = serverStatusManager;\n        this.registry = registry;\n        this.directMemoryMonitor = directMemoryMonitor;\n        this.eventLoopGroupMetrics = eventLoopGroupMetrics;\n        this.eventLoopConfig = eventLoopConfig;\n        this.discoveryClient = discoveryClient;\n        this.applicationInfoManager = applicationInfoManager;\n        this.accessLogPublisher = accessLogPublisher;\n        this.sessionCtxDecorator = sessionCtxDecorator;\n        this.reqCompleteHandler = reqCompleteHandler;\n        this.filterLoader = filterLoader;\n        this.usageNotifier = usageNotifier;\n    }\n\n    public Server server() {\n        return server;\n    }\n\n    @Inject\n    public void init() throws Exception {\n        ChannelGroup clientChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);\n        clientConnectionsShutdown =\n                new ClientConnectionsShutdown(clientChannels, GlobalEventExecutor.INSTANCE, discoveryClient);\n\n        addrsToChannelInitializers = chooseAddrsAndChannels(clientChannels);\n\n        server = new Server(\n                registry,\n                serverStatusManager,\n                addrsToChannelInitializers,\n                clientConnectionsShutdown,\n                eventLoopGroupMetrics,\n                eventLoopConfig);\n    }\n\n    // TODO(carl-mastrangelo): remove this after 2.1.7\n\n    /**\n     * Use {@link #chooseAddrsAndChannels(ChannelGroup)} instead.\n     */\n    @Deprecated\n    protected Map<Integer, ChannelInitializer> choosePortsAndChannels(ChannelGroup clientChannels) {\n        throw new UnsupportedOperationException(\"unimplemented\");\n    }\n\n    @ForOverride\n    protected Map<NamedSocketAddress, ChannelInitializer<?>> chooseAddrsAndChannels(ChannelGroup clientChannels) {\n        @SuppressWarnings(\"unchecked\") // Channel init map has the wrong generics and we can't fix without api breakage.\n        Map<Integer, ChannelInitializer<?>> portMap =\n                (Map<Integer, ChannelInitializer<?>>) (Map) choosePortsAndChannels(clientChannels);\n        return Server.convertPortMap(portMap);\n    }\n\n    protected ChannelConfig defaultChannelDependencies(String listenAddressName) {\n        ChannelConfig channelDependencies = new ChannelConfig();\n        addChannelDependencies(channelDependencies, listenAddressName);\n        return channelDependencies;\n    }\n\n    protected ChannelConfig defaultChannelDependencies(ListenerSpec listenerSpec) {\n        ChannelConfig channelDependencies = new ChannelConfig();\n        addChannelDependencies(channelDependencies, listenerSpec);\n        return channelDependencies;\n    }\n\n    protected void addChannelDependencies(\n            ChannelConfig channelDeps,\n            @SuppressWarnings(\"unused\") String listenAddressName) { // listenAddressName may be overridden by subclasse\n        channelDeps.set(ZuulDependencyKeys.registry, registry);\n\n        channelDeps.set(ZuulDependencyKeys.applicationInfoManager, applicationInfoManager);\n        channelDeps.set(ZuulDependencyKeys.serverStatusManager, serverStatusManager);\n\n        channelDeps.set(ZuulDependencyKeys.accessLogPublisher, accessLogPublisher);\n\n        channelDeps.set(ZuulDependencyKeys.sessionCtxDecorator, sessionCtxDecorator);\n        channelDeps.set(ZuulDependencyKeys.requestCompleteHandler, reqCompleteHandler);\n        Counter httpRequestHeadersReadTimeoutCounter = registry.counter(\"server.http.request.headers.read.timeout\");\n        channelDeps.set(ZuulDependencyKeys.httpRequestHeadersReadTimeoutCounter, httpRequestHeadersReadTimeoutCounter);\n        PercentileTimer httpRequestHeadersReadTimer =\n                PercentileTimer.get(registry, registry.createId(\"server.http.request.headers.read.timer\"));\n        channelDeps.set(ZuulDependencyKeys.httpRequestHeadersReadTimer, httpRequestHeadersReadTimer);\n        Counter httpRequestReadTimeoutCounter = registry.counter(\"server.http.request.read.timeout\");\n        channelDeps.set(ZuulDependencyKeys.httpRequestReadTimeoutCounter, httpRequestReadTimeoutCounter);\n        channelDeps.set(ZuulDependencyKeys.filterLoader, filterLoader);\n        channelDeps.set(ZuulDependencyKeys.filterUsageNotifier, usageNotifier);\n\n        channelDeps.set(ZuulDependencyKeys.eventLoopGroupMetrics, eventLoopGroupMetrics);\n\n        channelDeps.set(ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider, new NullChannelHandlerProvider());\n        channelDeps.set(ZuulDependencyKeys.rateLimitingChannelHandlerProvider, new NullChannelHandlerProvider());\n    }\n\n    protected void addChannelDependencies(\n            ChannelConfig channelDeps,\n            @SuppressWarnings(\"unused\") ListenerSpec listenerSpec) { // listenerSpec may be overridden by subclasses\n        channelDeps.set(ZuulDependencyKeys.registry, registry);\n\n        channelDeps.set(ZuulDependencyKeys.applicationInfoManager, applicationInfoManager);\n        channelDeps.set(ZuulDependencyKeys.serverStatusManager, serverStatusManager);\n\n        channelDeps.set(ZuulDependencyKeys.accessLogPublisher, accessLogPublisher);\n\n        channelDeps.set(ZuulDependencyKeys.sessionCtxDecorator, sessionCtxDecorator);\n        channelDeps.set(ZuulDependencyKeys.requestCompleteHandler, reqCompleteHandler);\n        Counter httpRequestHeadersReadTimeoutCounter = registry.counter(\"server.http.request.headers.read.timeout\");\n        channelDeps.set(ZuulDependencyKeys.httpRequestHeadersReadTimeoutCounter, httpRequestHeadersReadTimeoutCounter);\n        PercentileTimer httpRequestHeadersReadTimer =\n                PercentileTimer.get(registry, registry.createId(\"server.http.request.headers.read.timer\"));\n        channelDeps.set(ZuulDependencyKeys.httpRequestHeadersReadTimer, httpRequestHeadersReadTimer);\n        Counter httpRequestReadTimeoutCounter = registry.counter(\"server.http.request.read.timeout\");\n        channelDeps.set(ZuulDependencyKeys.httpRequestReadTimeoutCounter, httpRequestReadTimeoutCounter);\n        channelDeps.set(ZuulDependencyKeys.filterLoader, filterLoader);\n        channelDeps.set(ZuulDependencyKeys.filterUsageNotifier, usageNotifier);\n\n        channelDeps.set(ZuulDependencyKeys.eventLoopGroupMetrics, eventLoopGroupMetrics);\n\n        channelDeps.set(ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider, new NullChannelHandlerProvider());\n        channelDeps.set(ZuulDependencyKeys.rateLimitingChannelHandlerProvider, new NullChannelHandlerProvider());\n    }\n\n    /**\n     * First looks for a property specific to the named listen address of the form -\n     * \"server.${addrName}.${propertySuffix}\". If none found, then looks for a server-wide property of the form -\n     * \"server.${propertySuffix}\".  If that is also not found, then returns the specified default value.\n     */\n    public static int chooseIntChannelProperty(String listenAddressName, String propertySuffix, int defaultValue) {\n        String globalPropertyName = \"server.\" + propertySuffix;\n        String listenAddressPropertyName = \"server.\" + listenAddressName + \".\" + propertySuffix;\n        Integer value = new DynamicIntProperty(listenAddressPropertyName, -999).get();\n        if (value == -999) {\n            value = new DynamicIntProperty(globalPropertyName, -999).get();\n            if (value == -999) {\n                value = defaultValue;\n            }\n        }\n        return value;\n    }\n\n    public static boolean chooseBooleanChannelProperty(\n            String listenAddressName, String propertySuffix, boolean defaultValue) {\n        String globalPropertyName = \"server.\" + propertySuffix;\n        String listenAddressPropertyName = \"server.\" + listenAddressName + \".\" + propertySuffix;\n\n        Boolean value = new ChainedDynamicProperty.DynamicBooleanPropertyThatSupportsNull(\n                        listenAddressPropertyName, null)\n                .get();\n        if (value == null) {\n            value = new DynamicBooleanProperty(globalPropertyName, defaultValue)\n                    .getDynamicProperty()\n                    .getBoolean();\n            if (value == null) {\n                value = defaultValue;\n            }\n        }\n        return value;\n    }\n\n    public static ChannelConfig defaultChannelConfig(String listenAddressName) {\n        ChannelConfig config = new ChannelConfig();\n\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxConnections,\n                chooseIntChannelProperty(\n                        listenAddressName, \"connection.max\", CommonChannelConfigKeys.maxConnections.defaultValue())));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxRequestsPerConnection,\n                chooseIntChannelProperty(listenAddressName, \"connection.max.requests\", 20000)));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxRequestsPerConnectionInBrownout,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"connection.max.requests.brownout\",\n                        CommonChannelConfigKeys.maxRequestsPerConnectionInBrownout.defaultValue())));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.connectionExpiry,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"connection.expiry\",\n                        CommonChannelConfigKeys.connectionExpiry.defaultValue())));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.httpRequestReadTimeout,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"http.request.read.timeout\",\n                        CommonChannelConfigKeys.httpRequestReadTimeout.defaultValue())));\n\n        int connectionIdleTimeout = chooseIntChannelProperty(\n                listenAddressName, \"connection.idle.timeout\", CommonChannelConfigKeys.idleTimeout.defaultValue());\n        config.add(new ChannelConfigValue<>(CommonChannelConfigKeys.idleTimeout, connectionIdleTimeout));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.serverTimeout, new ServerTimeout(connectionIdleTimeout)));\n\n        // For security, default to NEVER allowing XFF/Proxy headers from client.\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.allowProxyHeadersWhen, StripUntrustedProxyHeadersHandler.AllowWhen.NEVER));\n\n        config.set(CommonChannelConfigKeys.withProxyProtocol, true);\n        config.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.connCloseDelay,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"connection.close.delay\",\n                        CommonChannelConfigKeys.connCloseDelay.defaultValue())));\n\n        return config;\n    }\n\n    public static void addHttp2DefaultConfig(ChannelConfig config, String listenAddressName) {\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxConcurrentStreams,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"http2.max.concurrent.streams\",\n                        CommonChannelConfigKeys.maxConcurrentStreams.defaultValue())));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.initialWindowSize,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"http2.initialwindowsize\",\n                        CommonChannelConfigKeys.initialWindowSize.defaultValue())));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxHttp2HeaderTableSize,\n                chooseIntChannelProperty(listenAddressName, \"http2.maxheadertablesize\", 65536)));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxHttp2HeaderListSize,\n                chooseIntChannelProperty(listenAddressName, \"http2.maxheaderlistsize\", 32768)));\n\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.http2EncoderMaxResetFrames,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"http2.maxResetFrames\",\n                        CommonChannelConfigKeys.http2EncoderMaxResetFrames.defaultValue())));\n\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.http2EncoderMaxResetFramesWindow,\n                chooseIntChannelProperty(\n                        listenAddressName,\n                        \"http2.maxResetFramesWindow\",\n                        CommonChannelConfigKeys.http2EncoderMaxResetFramesWindow.defaultValue())));\n\n        // Override this to a lower value, as we'll be using ELB TCP listeners for h2, and therefore the connection\n        // is direct from each device rather than shared in an ELB pool.\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.maxRequestsPerConnection,\n                chooseIntChannelProperty(listenAddressName, \"connection.max.requests\", 4000)));\n\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.http2AllowGracefulDelayed,\n                chooseBooleanChannelProperty(listenAddressName, \"connection.close.graceful.delayed.allow\", true)));\n        config.add(new ChannelConfigValue<>(\n                CommonChannelConfigKeys.http2SwallowUnknownExceptionsOnConnClose,\n                chooseBooleanChannelProperty(listenAddressName, \"connection.close.swallow.unknown.exceptions\", false)));\n    }\n\n    // TODO(carl-mastrangelo): remove this after 2.1.7\n\n    /**\n     * Use {@link #logAddrConfigured(SocketAddress)} instead.\n     */\n    @Deprecated\n    protected void logPortConfigured(int port) {\n        logAddrConfigured(new InetSocketAddress(port));\n    }\n\n    // TODO(carl-mastrangelo): remove this after 2.1.7\n\n    /**\n     * Use {@link #logAddrConfigured(SocketAddress, ServerSslConfig)} instead.\n     */\n    @Deprecated\n    protected void logPortConfigured(int port, ServerSslConfig serverSslConfig) {\n        logAddrConfigured(new InetSocketAddress(port), serverSslConfig);\n    }\n\n    // TODO(carl-mastrangelo): remove this after 2.1.7\n\n    /**\n     * Use {@link #logAddrConfigured(SocketAddress, AsyncMapping)} instead.\n     */\n    @Deprecated\n    protected void logPortConfigured(int port, AsyncMapping<String, SslContext> sniMapping) {\n        logAddrConfigured(new InetSocketAddress(port), sniMapping);\n    }\n\n    protected final void logAddrConfigured(SocketAddress socketAddress) {\n        LOG.info(\"Configured address: {}\", socketAddress);\n    }\n\n    protected final void logAddrConfigured(SocketAddress socketAddress, @Nullable ServerSslConfig serverSslConfig) {\n        String msg = \"Configured address: \" + socketAddress;\n        if (serverSslConfig != null) {\n            msg = msg + \" with SSL config: \" + serverSslConfig;\n        }\n        LOG.info(msg);\n    }\n\n    protected final void logAddrConfigured(\n            SocketAddress socketAddress, @Nullable AsyncMapping<String, SslContext> sniMapping) {\n        String msg = \"Configured address: \" + socketAddress;\n        if (sniMapping != null) {\n            msg = msg + \" with SNI config: \" + sniMapping;\n        }\n        LOG.info(msg);\n    }\n\n    protected final void logSecureAddrConfigured(SocketAddress socketAddress, @Nullable Object securityConfig) {\n        LOG.info(\"Configured address: {} with security config {}\", socketAddress, securityConfig);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.google.common.base.Preconditions;\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.netty.common.CloseOnIdleStateHandler;\nimport com.netflix.netty.common.Http1ConnectionCloseHandler;\nimport com.netflix.netty.common.Http1ConnectionExpiryHandler;\nimport com.netflix.netty.common.HttpRequestReadTimeoutHandler;\nimport com.netflix.netty.common.HttpServerLifecycleChannelHandler;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.SslExceptionsHandler;\nimport com.netflix.netty.common.accesslog.AccessLogChannelHandler;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.metrics.HttpBodySizeRecordingChannelHandler;\nimport com.netflix.netty.common.metrics.HttpMetricsChannelHandler;\nimport com.netflix.netty.common.metrics.PerEventLoopMetricsChannelHandler;\nimport com.netflix.netty.common.proxyprotocol.ElbProxyProtocolChannelHandler;\nimport com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler;\nimport com.netflix.netty.common.throttle.MaxInboundConnectionsHandler;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.RequestCompleteHandler;\nimport com.netflix.zuul.context.SessionContextDecorator;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.filters.passport.InboundPassportStampingFilter;\nimport com.netflix.zuul.filters.passport.OutboundPassportStampingFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.filter.FilterConstraints;\nimport com.netflix.zuul.netty.filter.FilterRunner;\nimport com.netflix.zuul.netty.filter.ZuulEndPointRunner;\nimport com.netflix.zuul.netty.filter.ZuulFilterChainHandler;\nimport com.netflix.zuul.netty.filter.ZuulFilterChainRunner;\nimport com.netflix.zuul.netty.insights.PassportLoggingHandler;\nimport com.netflix.zuul.netty.insights.PassportStateHttpServerHandler;\nimport com.netflix.zuul.netty.insights.ServerStateHandler;\nimport com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler;\nimport com.netflix.zuul.netty.timeouts.HttpHeadersTimeoutHandler;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.logging.LoggingHandler;\nimport io.netty.handler.timeout.IdleStateHandler;\nimport io.netty.util.AttributeKey;\nimport java.util.List;\nimport java.util.SortedSet;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * User: Mike Smith\n * Date: 3/5/16\n * Time: 6:26 PM\n */\npublic abstract class BaseZuulChannelInitializer extends ChannelInitializer<Channel> {\n    public static final String HTTP_CODEC_HANDLER_NAME = \"codec\";\n    public static final AttributeKey<ChannelConfig> ATTR_CHANNEL_CONFIG = AttributeKey.newInstance(\"channel_config\");\n\n    protected static final LoggingHandler nettyLogger = new LoggingHandler(\"zuul.server.nettylog\", LogLevel.INFO);\n\n    public static final CachedDynamicIntProperty MAX_INITIAL_LINE_LENGTH =\n            new CachedDynamicIntProperty(\"server.http.decoder.maxInitialLineLength\", 16384);\n    public static final CachedDynamicIntProperty MAX_HEADER_SIZE =\n            new CachedDynamicIntProperty(\"server.http.decoder.maxHeaderSize\", 32768);\n    public static final CachedDynamicIntProperty MAX_CHUNK_SIZE =\n            new CachedDynamicIntProperty(\"server.http.decoder.maxChunkSize\", 32768);\n\n    public static final CachedDynamicBooleanProperty HTTP_REQUEST_HEADERS_READ_TIMEOUT_ENABLED =\n            new CachedDynamicBooleanProperty(\"server.http.request.headers.read.timeout.enabled\", false);\n\n    public static final CachedDynamicIntProperty HTTP_REQUEST_HEADERS_READ_TIMEOUT =\n            new CachedDynamicIntProperty(\"server.http.request.headers.read.timeout\", 10000);\n\n    /**\n     * The port that the server intends to listen on.  Subclasses should NOT use this field, as it may not be set, and\n     * may differ from the actual listening port.  For example:\n     *\n     * <ul>\n     *     <li>When binding the server to port `0`, the actual port will be different from the one provided here.\n     *     <li>If there is no port (such as in a LocalSocket, or DomainSocket), the port number may be `-1`.\n     * </ul>\n     *\n     * <p>Instead, subclasses should read the local address on channel initialization, and decide to take action then.\n     */\n    @Deprecated\n    protected final int port;\n\n    protected final String metricId;\n    protected final ChannelConfig channelConfig;\n    protected final ChannelConfig channelDependencies;\n    protected final int idleTimeout;\n    protected final int httpRequestReadTimeout;\n    protected final int maxRequestsPerConnection;\n    protected final int maxRequestsPerConnectionInBrownout;\n    protected final int connectionExpiry;\n    protected final int maxConnections;\n\n    protected final Registry registry;\n    protected final HttpMetricsChannelHandler httpMetricsHandler;\n    protected final PerEventLoopMetricsChannelHandler.Connections perEventLoopConnectionMetricsHandler;\n    protected final PerEventLoopMetricsChannelHandler.HttpRequests perEventLoopRequestsMetricsHandler;\n    protected final MaxInboundConnectionsHandler maxConnectionsHandler;\n    protected final AccessLogPublisher accessLogPublisher;\n    protected final PassportLoggingHandler passportLoggingHandler;\n    protected final boolean withProxyProtocol;\n    protected final StripUntrustedProxyHeadersHandler stripInboundProxyHeadersHandler;\n    // TODO\n    // protected final HttpRequestThrottleChannelHandler requestThrottleHandler;\n    protected final ChannelHandler rateLimitingChannelHandler;\n    protected final ChannelHandler sslClientCertCheckChannelHandler;\n    // protected final RequestRejectedChannelHandler requestRejectedChannelHandler;\n    protected final SessionContextDecorator sessionContextDecorator;\n    protected final RequestCompleteHandler requestCompleteHandler;\n    protected final Counter httpRequestHeadersReadTimeoutCounter;\n    protected final PercentileTimer httpRequestHeadersReadTimer;\n    protected final Counter httpRequestReadTimeoutCounter;\n    protected final FilterLoader filterLoader;\n    protected final FilterUsageNotifier filterUsageNotifier;\n    protected final SourceAddressChannelHandler sourceAddressChannelHandler;\n    protected final FilterConstraints filterConstraints;\n\n    /** A collection of all the active channels that we can use to things like graceful shutdown */\n    protected final ChannelGroup channels;\n\n    /**\n     * After calling this method, child classes should not reference {@link #port} any more.\n     */\n    protected BaseZuulChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        this(-1, metricId, channelConfig, channelDependencies, channels);\n    }\n\n    /**\n     * Call {@link #BaseZuulChannelInitializer(String, ChannelConfig, ChannelConfig, ChannelGroup)} instead.\n     */\n    @Deprecated\n    protected BaseZuulChannelInitializer(\n            int port, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        this(port, String.valueOf(port), channelConfig, channelDependencies, channels);\n    }\n\n    private BaseZuulChannelInitializer(\n            int port,\n            String metricId,\n            ChannelConfig channelConfig,\n            ChannelConfig channelDependencies,\n            ChannelGroup channels) {\n        this.port = port;\n        Preconditions.checkNotNull(metricId, \"metricId\");\n        this.metricId = metricId;\n        this.channelConfig = channelConfig;\n        this.channelDependencies = channelDependencies;\n        this.channels = channels;\n\n        this.accessLogPublisher = channelDependencies.get(ZuulDependencyKeys.accessLogPublisher);\n\n        this.withProxyProtocol = channelConfig.get(CommonChannelConfigKeys.withProxyProtocol);\n\n        this.idleTimeout = channelConfig.get(CommonChannelConfigKeys.idleTimeout);\n        this.httpRequestReadTimeout = channelConfig.get(CommonChannelConfigKeys.httpRequestReadTimeout);\n        this.registry = channelDependencies.get(ZuulDependencyKeys.registry);\n        this.httpMetricsHandler = new HttpMetricsChannelHandler(registry, \"server\", \"http-\" + metricId);\n\n        EventLoopGroupMetrics eventLoopGroupMetrics = channelDependencies.get(ZuulDependencyKeys.eventLoopGroupMetrics);\n        PerEventLoopMetricsChannelHandler perEventLoopMetricsHandler =\n                new PerEventLoopMetricsChannelHandler(eventLoopGroupMetrics);\n        this.perEventLoopConnectionMetricsHandler = perEventLoopMetricsHandler.new Connections();\n        this.perEventLoopRequestsMetricsHandler = perEventLoopMetricsHandler.new HttpRequests();\n\n        this.maxConnections = channelConfig.get(CommonChannelConfigKeys.maxConnections);\n        this.maxConnectionsHandler = new MaxInboundConnectionsHandler(registry, metricId, maxConnections);\n        this.maxRequestsPerConnection = channelConfig.get(CommonChannelConfigKeys.maxRequestsPerConnection);\n        this.maxRequestsPerConnectionInBrownout =\n                channelConfig.get(CommonChannelConfigKeys.maxRequestsPerConnectionInBrownout);\n        this.connectionExpiry = channelConfig.get(CommonChannelConfigKeys.connectionExpiry);\n\n        StripUntrustedProxyHeadersHandler.AllowWhen allowProxyHeadersWhen =\n                channelConfig.get(CommonChannelConfigKeys.allowProxyHeadersWhen);\n        this.stripInboundProxyHeadersHandler = new StripUntrustedProxyHeadersHandler(allowProxyHeadersWhen);\n\n        this.rateLimitingChannelHandler = channelDependencies\n                .get(ZuulDependencyKeys.rateLimitingChannelHandlerProvider)\n                .get();\n\n        this.sslClientCertCheckChannelHandler = channelDependencies\n                .get(ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider)\n                .get();\n\n        this.passportLoggingHandler = new PassportLoggingHandler(registry);\n\n        this.sessionContextDecorator = channelDependencies.get(ZuulDependencyKeys.sessionCtxDecorator);\n        this.requestCompleteHandler = channelDependencies.get(ZuulDependencyKeys.requestCompleteHandler);\n        this.httpRequestHeadersReadTimeoutCounter =\n                channelDependencies.get(ZuulDependencyKeys.httpRequestHeadersReadTimeoutCounter);\n        this.httpRequestHeadersReadTimer = channelDependencies.get(ZuulDependencyKeys.httpRequestHeadersReadTimer);\n        this.httpRequestReadTimeoutCounter = channelDependencies.get(ZuulDependencyKeys.httpRequestReadTimeoutCounter);\n\n        this.filterLoader = channelDependencies.get(ZuulDependencyKeys.filterLoader);\n        this.filterUsageNotifier = channelDependencies.get(ZuulDependencyKeys.filterUsageNotifier);\n\n        FilterConstraints filterConstraints = channelDependencies.get(ZuulDependencyKeys.filterConstraints);\n        this.filterConstraints = filterConstraints != null ? filterConstraints : new FilterConstraints(List.of());\n\n        this.sourceAddressChannelHandler = new SourceAddressChannelHandler();\n    }\n\n    protected void storeChannel(Channel ch) {\n        this.channels.add(ch);\n\n        // Also add the ChannelConfig as an attribute on each channel. So interested filters/channel-handlers can\n        // introspect\n        // and potentially act differently based on the config.\n        ch.attr(ATTR_CHANNEL_CONFIG).set(channelConfig);\n    }\n\n    protected void addPassportHandler(ChannelPipeline pipeline) {\n        pipeline.addLast(new ServerStateHandler.InboundHandler(registry, \"http-\" + metricId));\n        pipeline.addLast(new ServerStateHandler.OutboundHandler(registry));\n    }\n\n    protected void addTcpRelatedHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(sourceAddressChannelHandler);\n        pipeline.addLast(perEventLoopConnectionMetricsHandler);\n        new ElbProxyProtocolChannelHandler(registry, withProxyProtocol).addProxyProtocol(pipeline);\n\n        pipeline.addLast(maxConnectionsHandler);\n    }\n\n    protected void addHttp1Handlers(ChannelPipeline pipeline) {\n        pipeline.addLast(HTTP_CODEC_HANDLER_NAME, createHttpServerCodec());\n\n        pipeline.addLast(new Http1ConnectionCloseHandler());\n        pipeline.addLast(\n                \"conn_expiry_handler\",\n                new Http1ConnectionExpiryHandler(\n                        maxRequestsPerConnection, maxRequestsPerConnectionInBrownout, connectionExpiry));\n    }\n\n    protected HttpServerCodec createHttpServerCodec() {\n        return new HttpServerCodec(MAX_INITIAL_LINE_LENGTH.get(), MAX_HEADER_SIZE.get(), MAX_CHUNK_SIZE.get(), false);\n    }\n\n    protected void addHttpRelatedHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(new HttpHeadersTimeoutHandler.InboundHandler(\n                HTTP_REQUEST_HEADERS_READ_TIMEOUT_ENABLED::get,\n                HTTP_REQUEST_HEADERS_READ_TIMEOUT::get,\n                httpRequestHeadersReadTimeoutCounter,\n                httpRequestHeadersReadTimer));\n        pipeline.addLast(new PassportStateHttpServerHandler.InboundHandler());\n        pipeline.addLast(new PassportStateHttpServerHandler.OutboundHandler());\n        if (httpRequestReadTimeout > -1) {\n            HttpRequestReadTimeoutHandler.addLast(\n                    pipeline, httpRequestReadTimeout, TimeUnit.MILLISECONDS, httpRequestReadTimeoutCounter);\n        }\n        pipeline.addLast(new HttpServerLifecycleChannelHandler.HttpServerLifecycleInboundChannelHandler());\n        pipeline.addLast(new HttpServerLifecycleChannelHandler.HttpServerLifecycleOutboundChannelHandler());\n        pipeline.addLast(new HttpBodySizeRecordingChannelHandler.InboundChannelHandler());\n        pipeline.addLast(new HttpBodySizeRecordingChannelHandler.OutboundChannelHandler());\n        pipeline.addLast(httpMetricsHandler);\n        pipeline.addLast(perEventLoopRequestsMetricsHandler);\n\n        if (accessLogPublisher != null) {\n            pipeline.addLast(new AccessLogChannelHandler.AccessLogInboundChannelHandler(accessLogPublisher));\n            pipeline.addLast(new AccessLogChannelHandler.AccessLogOutboundChannelHandler());\n        }\n\n        pipeline.addLast(stripInboundProxyHeadersHandler);\n\n        if (rateLimitingChannelHandler != null) {\n            pipeline.addLast(rateLimitingChannelHandler);\n        }\n\n        // pipeline.addLast(requestRejectedChannelHandler);\n    }\n\n    protected void addTimeoutHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(new IdleStateHandler(0, 0, idleTimeout, TimeUnit.MILLISECONDS));\n        pipeline.addLast(new CloseOnIdleStateHandler(registry, metricId));\n    }\n\n    protected void addSslInfoHandlers(ChannelPipeline pipeline, boolean isSSlFromIntermediary) {\n        pipeline.addLast(\"ssl_info\", new SslHandshakeInfoHandler(registry, isSSlFromIntermediary));\n        pipeline.addLast(\"ssl_exceptions\", new SslExceptionsHandler(registry));\n    }\n\n    protected void addSslClientCertChecks(ChannelPipeline pipeline) {\n        if (channelConfig.get(ZuulDependencyKeys.SSL_CLIENT_CERT_CHECK_REQUIRED)) {\n            if (this.sslClientCertCheckChannelHandler == null) {\n                throw new IllegalArgumentException(\"A sslClientCertCheckChannelHandler is required!\");\n            }\n            pipeline.addLast(this.sslClientCertCheckChannelHandler);\n        }\n    }\n\n    protected void addZuulHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(\"logger\", nettyLogger);\n        pipeline.addLast(new ClientRequestReceiver(sessionContextDecorator));\n        pipeline.addLast(passportLoggingHandler);\n        addZuulFilterChainHandler(pipeline);\n        pipeline.addLast(new ClientResponseWriter(requestCompleteHandler, registry));\n    }\n\n    protected void addZuulFilterChainHandler(ChannelPipeline pipeline) {\n        ZuulFilter<HttpResponseMessage, HttpResponseMessage>[] responseFilters = getFilters(\n                new OutboundPassportStampingFilter(PassportState.FILTERS_OUTBOUND_START),\n                new OutboundPassportStampingFilter(PassportState.FILTERS_OUTBOUND_END));\n\n        // response filter chain\n        ZuulFilterChainRunner<HttpResponseMessage> responseFilterChain =\n                getFilterChainRunner(responseFilters, filterUsageNotifier);\n\n        // endpoint | response filter chain\n        FilterRunner<HttpRequestMessage, HttpResponseMessage> endPoint =\n                getEndpointRunner(responseFilterChain, filterUsageNotifier, filterLoader);\n\n        ZuulFilter<HttpRequestMessage, HttpRequestMessage>[] requestFilters = getFilters(\n                new InboundPassportStampingFilter(PassportState.FILTERS_INBOUND_START),\n                new InboundPassportStampingFilter(PassportState.FILTERS_INBOUND_END));\n\n        // request filter chain | end point | response filter chain\n        ZuulFilterChainRunner<HttpRequestMessage> requestFilterChain =\n                getFilterChainRunner(requestFilters, filterUsageNotifier, endPoint);\n\n        pipeline.addLast(new ZuulFilterChainHandler(requestFilterChain, responseFilterChain));\n    }\n\n    protected ZuulEndPointRunner getEndpointRunner(\n            ZuulFilterChainRunner<HttpResponseMessage> responseFilterChain,\n            FilterUsageNotifier filterUsageNotifier,\n            FilterLoader filterLoader) {\n        return new ZuulEndPointRunner(\n                filterUsageNotifier, filterLoader, responseFilterChain, filterConstraints, registry);\n    }\n\n    protected <T extends ZuulMessage> ZuulFilterChainRunner<T> getFilterChainRunner(\n            ZuulFilter<T, T>[] filters, FilterUsageNotifier filterUsageNotifier) {\n        return new ZuulFilterChainRunner<>(filters, filterUsageNotifier, filterConstraints, registry);\n    }\n\n    protected <T extends ZuulMessage, R extends ZuulMessage> ZuulFilterChainRunner<T> getFilterChainRunner(\n            ZuulFilter<T, T>[] filters, FilterUsageNotifier filterUsageNotifier, FilterRunner<T, R> filterRunner) {\n        return new ZuulFilterChainRunner<>(filters, filterUsageNotifier, filterRunner, filterConstraints, registry);\n    }\n\n    @SuppressWarnings(\"unchecked\") // For the conversion from getFiltersByType.  It's not safe, sorry.\n    public <T extends ZuulMessage> ZuulFilter<T, T>[] getFilters(ZuulFilter<T, T> start, ZuulFilter<T, T> stop) {\n        SortedSet<ZuulFilter<?, ?>> zuulFilters = filterLoader.getFiltersByType(start.filterType());\n        ZuulFilter<T, T>[] filters = new ZuulFilter[zuulFilters.size() + 2];\n        filters[0] = start;\n        int i = 1;\n        for (ZuulFilter<?, ?> filter : zuulFilters) {\n            // TODO(carl-mastrangelo): find some way to make this cast not needed.\n            filters[i++] = (ZuulFilter<T, T>) filter;\n        }\n        filters[filters.length - 1] = stop;\n        return filters;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientConnectionsShutdown.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.discovery.StatusChangeEvent;\nimport com.netflix.netty.common.ConnectionCloseChannelAttributes;\nimport com.netflix.netty.common.ConnectionCloseType;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.channel.group.ChannelGroupFuture;\nimport io.netty.util.concurrent.EventExecutor;\nimport io.netty.util.concurrent.Promise;\nimport io.netty.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * TODO: Change this class to be an instance per-port.\n * So that then the configuration can be different per-port, which is need for the combined FTL/Cloud clusters.\n * <p>\n * User: michaels@netflix.com\n * Date: 3/6/17\n * Time: 12:36 PM\n */\npublic class ClientConnectionsShutdown {\n\n    private static final Logger LOG = LoggerFactory.getLogger(ClientConnectionsShutdown.class);\n    private static final DynamicBooleanProperty ENABLED =\n            new DynamicBooleanProperty(\"server.outofservice.connections.shutdown\", false);\n    private static final DynamicIntProperty DELAY_AFTER_OUT_OF_SERVICE_MS =\n            new DynamicIntProperty(\"server.outofservice.connections.delay\", 2000);\n    private static final DynamicIntProperty GRACEFUL_CLOSE_TIMEOUT =\n            new DynamicIntProperty(\"server.outofservice.close.timeout\", 30);\n\n    public enum ShutdownType {\n        OUT_OF_SERVICE,\n        SHUTDOWN\n    }\n\n    private final ChannelGroup channels;\n    private final EventExecutor executor;\n    private final EurekaClient discoveryClient;\n\n    public ClientConnectionsShutdown(ChannelGroup channels, EventExecutor executor, EurekaClient discoveryClient) {\n        this.channels = channels;\n        this.executor = executor;\n        this.discoveryClient = discoveryClient;\n\n        if (discoveryClient != null) {\n            initDiscoveryListener();\n        }\n    }\n\n    private void initDiscoveryListener() {\n        this.discoveryClient.registerEventListener(event -> {\n            if (event instanceof StatusChangeEvent sce) {\n\n                LOG.info(\"Received {}\", sce);\n\n                if (sce.getPreviousStatus() == InstanceInfo.InstanceStatus.UP\n                        && (sce.getStatus() == InstanceInfo.InstanceStatus.OUT_OF_SERVICE\n                                || sce.getStatus() == InstanceInfo.InstanceStatus.DOWN)) {\n                    // TODO - Also should stop accepting any new client connections now too?\n\n                    // Schedule to gracefully close all the client connections.\n                    if (ENABLED.get()) {\n                        executor.schedule(\n                                () -> gracefullyShutdownClientChannels(ShutdownType.OUT_OF_SERVICE),\n                                DELAY_AFTER_OUT_OF_SERVICE_MS.get(),\n                                TimeUnit.MILLISECONDS);\n                    }\n                }\n            }\n        });\n    }\n\n    public Promise<Void> gracefullyShutdownClientChannels() {\n        return gracefullyShutdownClientChannels(ShutdownType.SHUTDOWN);\n    }\n\n    Promise<Void> gracefullyShutdownClientChannels(ShutdownType shutdownType) {\n        // Mark all active connections to be closed after next response sent.\n        LOG.warn(\"Flagging CLOSE_AFTER_RESPONSE on {} client channels.\", channels.size());\n\n        // racy situation if new connections are still coming in, but any channels created after newCloseFuture will\n        // be closed during the force close stage\n        ChannelGroupFuture closeFuture = channels.newCloseFuture();\n        for (Channel channel : channels) {\n            flagChannelForClose(channel, shutdownType);\n        }\n\n        LOG.info(\"Setting up scheduled task for {} with shutdownType: {}\", closeFuture, shutdownType);\n        Promise<Void> promise = executor.newPromise();\n        Runnable cancelTimeoutTask;\n        if (shutdownType == ShutdownType.SHUTDOWN) {\n            ScheduledFuture<?> timeoutTask = executor.schedule(\n                    () -> {\n                        LOG.warn(\"Force closing remaining {} active client channels.\", channels.size());\n                        channels.close().addListener(future -> {\n                            if (!future.isSuccess()) {\n                                LOG.error(\"Failed to close all connections\", future.cause());\n                            }\n                            if (!promise.isDone()) {\n                                promise.setSuccess(null);\n                            }\n                        });\n                    },\n                    GRACEFUL_CLOSE_TIMEOUT.get(),\n                    TimeUnit.SECONDS);\n            cancelTimeoutTask = () -> {\n                if (!timeoutTask.isDone()) {\n                    LOG.info(\"Timeout task canceled before completion.\");\n                    // close happened before the timeout\n                    timeoutTask.cancel(false);\n                }\n            };\n        } else {\n            cancelTimeoutTask = () -> {};\n        }\n\n        closeFuture.addListener(future -> {\n            LOG.info(\"CloseFuture completed successfully: {}\", future.isSuccess());\n            cancelTimeoutTask.run();\n            promise.setSuccess(null);\n        });\n\n        return promise;\n    }\n\n    protected void flagChannelForClose(Channel channel, ShutdownType shutdownType) {\n        ConnectionCloseType.setForChannel(channel, ConnectionCloseType.DELAYED_GRACEFUL);\n        ChannelPromise closePromise = channel.pipeline().newPromise();\n        channel.attr(ConnectionCloseChannelAttributes.CLOSE_AFTER_RESPONSE).set(closePromise);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientRequestReceiver.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\n\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.ssl.SslHandshakeInfo;\nimport com.netflix.netty.common.throttle.RejectionUtils;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.Debug;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.context.SessionContextDecorator;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.netty.server.http2.Http2OrHttpHandler;\nimport com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport com.netflix.zuul.util.HttpUtils;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.unix.Errors;\nimport io.netty.handler.codec.haproxy.HAProxyMessage;\nimport io.netty.handler.codec.http.DefaultFullHttpRequest;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpUtil;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.ReferenceCountUtil;\nimport io.perfmark.PerfMark;\nimport io.perfmark.TaskCloseable;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map.Entry;\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.net.ssl.SSLException;\nimport lombok.NonNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Created by saroskar on 1/6/17.\n */\npublic class ClientRequestReceiver extends ChannelDuplexHandler {\n\n    public static final AttributeKey<HttpRequestMessage> ATTR_ZUUL_REQ = AttributeKey.newInstance(\"_zuul_request\");\n    public static final AttributeKey<HttpResponseMessage> ATTR_ZUUL_RESP = AttributeKey.newInstance(\"_zuul_response\");\n    public static final AttributeKey<Boolean> ATTR_LAST_CONTENT_RECEIVED =\n            AttributeKey.newInstance(\"_last_content_received\");\n\n    private static final Logger LOG = LoggerFactory.getLogger(ClientRequestReceiver.class);\n    private static final String SCHEME_HTTP = \"http\";\n    private static final String SCHEME_HTTPS = \"https\";\n\n    // via @stephenhay https://mathiasbynens.be/demo/url-regex, groups added\n    // group 1: scheme, group 2: domain, group 3: path+query\n    private static final Pattern URL_REGEX = Pattern.compile(\"^(https?)://([^\\\\s/$.?#].[^\\\\s/]*)([^\\\\s]*)$\");\n\n    private final SessionContextDecorator decorator;\n\n    private HttpRequestMessage zuulRequest;\n    private HttpRequest clientRequest;\n\n    public ClientRequestReceiver(SessionContextDecorator decorator) {\n        this.decorator = decorator;\n    }\n\n    public static HttpRequestMessage getRequestFromChannel(Channel ch) {\n        return ch.attr(ATTR_ZUUL_REQ).get();\n    }\n\n    public static HttpResponseMessage getResponseFromChannel(Channel ch) {\n        return ch.attr(ATTR_ZUUL_RESP).get();\n    }\n\n    public static boolean isLastContentReceivedForChannel(Channel ch) {\n        Boolean value = ch.attr(ATTR_LAST_CONTENT_RECEIVED).get();\n        return value == null ? false : value;\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        try (TaskCloseable ignore = PerfMark.traceTask(\"CRR.channelRead\")) {\n            channelReadInternal(ctx, msg);\n        }\n    }\n\n    private void channelReadInternal(ChannelHandlerContext ctx, Object msg) {\n        // Flag that we have now received the LastContent for this request from the client.\n        // This is needed for ClientResponseReceiver to know whether it's yet safe to start writing\n        // a response to the client channel.\n        if (msg instanceof LastHttpContent) {\n            ctx.channel().attr(ATTR_LAST_CONTENT_RECEIVED).set(Boolean.TRUE);\n        }\n\n        if (msg instanceof HttpRequest) {\n            clientRequest = (HttpRequest) msg;\n\n            zuulRequest = buildZuulHttpRequest(clientRequest, ctx);\n\n            // Handle invalid HTTP requests.\n            if (clientRequest.decoderResult().isFailure()) {\n                String clientIp = Objects.requireNonNullElse(getClientIp(ctx.channel()), \"unknown\");\n                LOG.warn(\n                        \"Invalid http request. clientRequest = {}, clientIp = {}, info = {}\",\n                        clientRequest,\n                        clientIp,\n                        ChannelUtils.channelInfoForLogging(ctx.channel()),\n                        clientRequest.decoderResult().cause());\n                StatusCategoryUtils.setStatusCategory(\n                        zuulRequest.getContext(),\n                        ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST,\n                        \"Invalid request provided: Decode failure\");\n                RejectionUtils.rejectByClosingConnection(\n                        ctx, ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST, \"decodefailure\", clientRequest, null);\n                return;\n            } else if (zuulRequest.hasBody() && zuulRequest.getBodyLength() > zuulRequest.getMaxBodySize()) {\n                String errorMsg = \"Request too large. \"\n                        + \"clientRequest = \" + clientRequest.toString()\n                        + \", uri = \" + String.valueOf(clientRequest.uri())\n                        + \", info = \" + ChannelUtils.channelInfoForLogging(ctx.channel());\n                ZuulException ze = new ZuulException(errorMsg);\n                ze.setStatusCode(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE.code());\n                StatusCategoryUtils.setStatusCategory(\n                        zuulRequest.getContext(),\n                        ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST,\n                        \"Invalid request provided: Request body size \" + zuulRequest.getBodyLength()\n                                + \" is above limit of \" + zuulRequest.getMaxBodySize());\n                zuulRequest.getContext().setError(ze);\n                zuulRequest.getContext().setShouldSendErrorResponse(true);\n            } else if (zuulRequest\n                            .getHeaders()\n                            .getAll(HttpHeaderNames.HOST.toString())\n                            .size()\n                    > 1) {\n                LOG.debug(\n                        \"Multiple Host headers. clientRequest = {} , uri = {}, info = {}\",\n                        clientRequest,\n                        clientRequest.uri(),\n                        ChannelUtils.channelInfoForLogging(ctx.channel()));\n                ZuulException ze = new ZuulException(\"Multiple Host headers\");\n                ze.setStatusCode(HttpResponseStatus.BAD_REQUEST.code());\n                StatusCategoryUtils.setStatusCategory(\n                        zuulRequest.getContext(),\n                        ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST,\n                        \"Invalid request provided: Multiple Host headers\");\n                zuulRequest.getContext().setError(ze);\n                zuulRequest.getContext().setShouldSendErrorResponse(true);\n            }\n\n            handleExpect100Continue(ctx, clientRequest);\n\n            // Send the request down the filter pipeline\n            ctx.fireChannelRead(zuulRequest);\n        } else if (msg instanceof HttpContent) {\n            if ((zuulRequest != null) && !zuulRequest.getContext().isCancelled()) {\n                ctx.fireChannelRead(msg);\n            } else {\n                // We already sent response for this request, these are laggard request body chunks that are still\n                // arriving\n                ReferenceCountUtil.release(msg);\n            }\n        } else if (msg instanceof HAProxyMessage) {\n            // do nothing, should already be handled by ElbProxyProtocolHandler\n            LOG.debug(\"Received HAProxyMessage for Proxy Protocol IP: {}\", ((HAProxyMessage) msg).sourceAddress());\n            ReferenceCountUtil.release(msg);\n        } else {\n            LOG.debug(\"Received unrecognized message type. {}\", msg.getClass().getName());\n            ReferenceCountUtil.release(msg);\n        }\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof CompleteEvent) {\n            CompleteReason reason = ((CompleteEvent) evt).getReason();\n            if (zuulRequest != null) {\n                zuulRequest.getContext().cancel();\n                zuulRequest.disposeBufferedBody();\n                CurrentPassport passport = CurrentPassport.fromSessionContext(zuulRequest.getContext());\n                if ((passport != null) && (passport.findState(PassportState.OUT_RESP_LAST_CONTENT_SENT) == null)) {\n                    // Only log this state if the response does not seem to have completed normally.\n                    passport.add(PassportState.IN_REQ_CANCELLED);\n                }\n            }\n\n            if (reason == CompleteReason.INACTIVE && zuulRequest != null) {\n                this.handleClientChannelInactiveEvent(zuulRequest);\n            }\n\n            if (reason == CompleteReason.PIPELINE_REJECT && zuulRequest != null) {\n                StatusCategoryUtils.setStatusCategory(\n                        zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_PIPELINE_REJECT);\n            }\n\n            if (reason != CompleteReason.SESSION_COMPLETE && zuulRequest != null) {\n                SessionContext zuulCtx = zuulRequest.getContext();\n                if (clientRequest != null) {\n                    if (LOG.isInfoEnabled()) {\n                        // With http/2, the netty codec closes/completes the stream immediately after writing the\n                        // lastcontent\n                        // of response to the channel, which causes this CompleteEvent to fire before we have cleaned up\n                        // state. But\n                        // that's ok, so don't log in that case.\n                        if (Objects.equals(zuulRequest.getProtocol(), \"HTTP/2\")) {\n                            LOG.debug(\n                                    \"Client {} request UUID {} to {} completed with reason = {}, {}\",\n                                    clientRequest.method(),\n                                    zuulCtx.getUUID(),\n                                    clientRequest.uri(),\n                                    reason.name(),\n                                    ChannelUtils.channelInfoForLogging(ctx.channel()));\n                        }\n                    }\n                }\n                if (zuulCtx.debugRequest()) {\n                    LOG.debug(\"Endpoint = {}\", zuulCtx.getEndpoint());\n                    dumpDebugInfo(Debug.getRequestDebug(zuulCtx));\n                    dumpDebugInfo(Debug.getRoutingDebug(zuulCtx));\n                }\n            }\n\n            if (zuulRequest == null) {\n                Spectator.globalRegistry()\n                        .counter(\"zuul.client.complete.null\", \"reason\", String.valueOf(reason))\n                        .increment();\n            }\n\n            clientRequest = null;\n            zuulRequest = null;\n        }\n\n        super.userEventTriggered(ctx, evt);\n\n        if (evt instanceof CompleteEvent) {\n            Channel channel = ctx.channel();\n            channel.attr(ATTR_ZUUL_REQ).set(null);\n            channel.attr(ATTR_ZUUL_RESP).set(null);\n            channel.attr(ATTR_LAST_CONTENT_RECEIVED).set(null);\n        }\n    }\n\n    /**\n     * Handle the event when the client channel is moved to inactive,\n     * generally this occurs if the client cancels the request.\n     */\n    protected void handleClientChannelInactiveEvent(@NonNull HttpRequestMessage zuulRequest) {\n        // client closed connection prematurely\n        StatusCategoryUtils.setStatusCategory(zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_CANCELLED);\n    }\n\n    private static void dumpDebugInfo(List<String> debugInfo) {\n        debugInfo.forEach((dbg) -> LOG.debug(dbg));\n    }\n\n    private void handleExpect100Continue(ChannelHandlerContext ctx, HttpRequest req) {\n        if (HttpUtil.is100ContinueExpected(req)) {\n            PerfMark.event(\"CRR.handleExpect100Continue\");\n            ChannelFuture f =\n                    ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE));\n            f.addListener((s) -> {\n                if (!s.isSuccess()) {\n                    throw new ZuulException(s.cause(), \"Failed while writing 100-continue response\", true);\n                }\n            });\n            // Remove the Expect: 100-Continue header from request as we don't want to proxy it downstream.\n            req.headers().remove(HttpHeaderNames.EXPECT);\n            zuulRequest.getHeaders().remove(HttpHeaderNames.EXPECT.toString());\n        }\n    }\n\n    // Build a ZuulMessage from the netty request.\n    private HttpRequestMessage buildZuulHttpRequest(HttpRequest nativeRequest, ChannelHandlerContext clientCtx) {\n        PerfMark.attachTag(\"path\", nativeRequest, HttpRequest::uri);\n        // Setup the context for this request.\n        SessionContext context;\n        if (decorator != null) { // Optionally decorate the context.\n            SessionContext tempContext = new SessionContext();\n            // Store the netty channel in SessionContext.\n            tempContext.set(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, clientCtx);\n            context = decorator.decorate(tempContext);\n            // We expect the UUID is present after decoration\n            PerfMark.attachTag(\"uuid\", context, SessionContext::getUUID);\n        } else {\n            context = new SessionContext();\n        }\n\n        // Get the client IP (ignore XFF headers at this point, as that can be app specific).\n        Channel channel = clientCtx.channel();\n        String clientIp = getClientIp(channel);\n\n        // This is the only way I found to get the port of the request with netty...\n        int port =\n                channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).get();\n        String serverName = channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_ADDRESS)\n                .get();\n        SocketAddress clientDestinationAddress =\n                channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get();\n        InetSocketAddress proxyProtocolDestinationAddress = channel.attr(\n                        SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS)\n                .get();\n        if (proxyProtocolDestinationAddress != null) {\n            context.set(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS, proxyProtocolDestinationAddress);\n        }\n\n        // Store info about the SSL handshake if applicable, and choose the http scheme.\n        String scheme = SCHEME_HTTP;\n        SslHandshakeInfo sslHandshakeInfo =\n                channel.attr(SslHandshakeInfoHandler.ATTR_SSL_INFO).get();\n        if (sslHandshakeInfo != null) {\n            context.set(CommonContextKeys.SSL_HANDSHAKE_INFO, sslHandshakeInfo);\n            scheme = SCHEME_HTTPS;\n        }\n\n        // Decide if this is HTTP/1 or HTTP/2.\n        String protocol = channel.attr(Http2OrHttpHandler.PROTOCOL_NAME).get();\n        if (protocol == null) {\n            protocol = nativeRequest.protocolVersion().text();\n        }\n\n        // Strip off the query from the path.\n        String path = parsePath(nativeRequest.uri());\n\n        // Setup the req/resp message objects.\n        HttpRequestMessage request = new HttpRequestMessageImpl(\n                context,\n                protocol,\n                nativeRequest.method().asciiName().toString().toLowerCase(Locale.ROOT),\n                path,\n                copyQueryParams(nativeRequest),\n                copyHeaders(nativeRequest),\n                clientIp,\n                scheme,\n                port,\n                serverName,\n                clientDestinationAddress,\n                false);\n\n        // Try to decide if this request has a body or not based on the headers (as we won't yet have\n        // received any of the content).\n        // NOTE that we also later may override this if it is Chunked encoding, but we receive\n        // a LastHttpContent without any prior HttpContent's.\n        if (HttpUtils.hasChunkedTransferEncodingHeader(request) || HttpUtils.hasNonZeroContentLengthHeader(request)) {\n            request.setHasBody(true);\n        }\n\n        // Store this original request info for future reference (ie. for metrics and access logging purposes).\n        request.storeInboundRequest();\n\n        // Store the netty request for use later.\n        context.set(CommonContextKeys.NETTY_HTTP_REQUEST, nativeRequest);\n\n        // Store zuul request on netty channel for later use.\n        channel.attr(ATTR_ZUUL_REQ).set(request);\n\n        if (nativeRequest instanceof DefaultFullHttpRequest) {\n            ByteBuf chunk = ((DefaultFullHttpRequest) nativeRequest).content();\n            request.bufferBodyContents(new DefaultLastHttpContent(chunk));\n        }\n\n        return request;\n    }\n\n    protected String getClientIp(Channel channel) {\n        return channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get();\n    }\n\n    private String parsePath(String uri) {\n        String path;\n        try {\n            URI uriObject = new URI(uri);\n            uriObject = uriObject.normalize();\n            path = uriObject.getRawPath();\n            if (path == null) {\n                // If we have an opaque URI, match existing behavior of using the URI as the path.\n                return uri;\n            }\n            while (path.startsWith(\"/..\")) {\n                path = path.substring(3);\n            }\n            return path;\n        } catch (URISyntaxException ex) {\n            LOG.debug(\"URI syntax error\", ex);\n        }\n        // manual path parsing\n        // relative uri\n        if (uri.startsWith(\"/\")) {\n            path = uri;\n        } else {\n            Matcher m = URL_REGEX.matcher(uri);\n\n            // absolute uri\n            if (m.matches()) {\n                String match = m.group(3);\n                if (match == null) {\n                    // in case of no match, default to existing behavior\n                    path = uri;\n                } else {\n                    path = match;\n                }\n            }\n            // unknown value\n            else {\n                // in case of unknown value, default to existing behavior\n                path = uri;\n            }\n        }\n\n        int queryIndex = path.indexOf('?');\n        if (queryIndex > -1) {\n            path = path.substring(0, queryIndex);\n        }\n\n        while (path.startsWith(\"/..\")) {\n            path = path.substring(3);\n        }\n        return path;\n    }\n\n    private static Headers copyHeaders(HttpRequest req) {\n        Headers headers = new Headers(req.headers().size());\n        for (Iterator<Entry<String, String>> it = req.headers().iteratorAsString(); it.hasNext(); ) {\n            Entry<String, String> header = it.next();\n            headers.add(header.getKey(), header.getValue());\n        }\n        return headers;\n    }\n\n    public static HttpQueryParams copyQueryParams(HttpRequest nativeRequest) {\n        String uri = nativeRequest.uri();\n        int queryStart = uri.indexOf('?');\n        String query = queryStart == -1 ? null : uri.substring(queryStart + 1);\n        return HttpQueryParams.parse(query);\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        try (TaskCloseable ignored = PerfMark.traceTask(\"CRR.write\")) {\n            if (msg instanceof HttpResponse) {\n                promise.addListener((future) -> {\n                    if (!future.isSuccess()) {\n                        fireWriteError(\"response headers\", future.cause(), ctx);\n                    }\n                });\n                super.write(ctx, msg, promise);\n            } else if (msg instanceof HttpContent) {\n                promise.addListener((future) -> {\n                    if (!future.isSuccess()) {\n                        fireWriteError(\"response content\", future.cause(), ctx);\n                    }\n                });\n                super.write(ctx, msg, promise);\n            } else {\n                // should never happen\n                ReferenceCountUtil.release(msg);\n                throw new ZuulException(\n                        \"Attempt to write invalid content type to client: \"\n                                + msg.getClass().getSimpleName(),\n                        true);\n            }\n        }\n    }\n\n    private void fireWriteError(String requestPart, Throwable cause, ChannelHandlerContext ctx) {\n\n        String errMesg = String.format(\"Error writing %s to client\", requestPart);\n\n        if (cause instanceof java.nio.channels.ClosedChannelException\n                || cause instanceof Errors.NativeIoException\n                || cause instanceof SSLException\n                || (cause.getCause() != null && cause.getCause() instanceof SSLException)\n                || isStreamCancelled(cause)) {\n            LOG.debug(\"{} - client connection is closed.\", errMesg);\n            if (zuulRequest != null) {\n                zuulRequest.getContext().cancel();\n                StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(\n                        zuulRequest.getContext(), ZuulStatusCategory.FAILURE_CLIENT_CANCELLED);\n            }\n        } else {\n            LOG.error(errMesg, cause);\n            ctx.fireExceptionCaught(new ZuulException(cause, errMesg, true));\n        }\n    }\n\n    private boolean isStreamCancelled(Throwable cause) {\n        // Detect if the stream is cancelled or closed.\n        // If the stream was closed before the write occurred, then netty flags it with INTERNAL_ERROR code.\n        if (cause instanceof Http2Exception.StreamException) {\n            Http2Exception http2Exception = (Http2Exception) cause;\n            if (http2Exception.error() == Http2Error.INTERNAL_ERROR) {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ClientResponseWriter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport static com.netflix.netty.common.HttpLifecycleChannelHandler.StartEvent;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.RequestCompleteHandler;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.message.Header;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.DefaultHttpResponse;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpHeaderValues;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpUtil;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http2.HttpConversionUtil;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport io.netty.handler.timeout.ReadTimeoutException;\nimport io.netty.util.ReferenceCountUtil;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Created by saroskar on 2/26/17.\n */\npublic class ClientResponseWriter extends ChannelInboundHandlerAdapter {\n\n    private static final Registry NOOP_REGISTRY = new NoopRegistry();\n\n    private final RequestCompleteHandler requestCompleteHandler;\n    private final Counter responseBeforeReceivedLastContentCounter;\n\n    // state\n    private boolean isHandlingRequest;\n    private boolean startedSendingResponseToClient;\n    private boolean closeConnection;\n\n    // data\n    private HttpResponseMessage zuulResponse;\n\n    private static final Logger logger = LoggerFactory.getLogger(ClientResponseWriter.class);\n\n    public ClientResponseWriter(RequestCompleteHandler requestCompleteHandler) {\n        this(requestCompleteHandler, NOOP_REGISTRY);\n    }\n\n    public ClientResponseWriter(RequestCompleteHandler requestCompleteHandler, Registry registry) {\n        this.requestCompleteHandler = requestCompleteHandler;\n        this.responseBeforeReceivedLastContentCounter =\n                registry.counter(\"server.http.requests.responseBeforeReceivedLastContent\");\n    }\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        Channel channel = ctx.channel();\n\n        if (msg instanceof HttpResponseMessage resp) {\n\n            if (skipProcessing(resp)) {\n                return;\n            }\n\n            if (!isHandlingRequest || startedSendingResponseToClient) {\n                /* This can happen if we are already in the process of streaming response back to client OR NOT within active\n                  request/response cycle and something like IDLE or Request Read timeout occurs. In that case we have no way\n                  to recover other than closing the socket and cleaning up resources used by BOTH responses.\n                */\n                resp.disposeBufferedBody();\n                if (zuulResponse != null) {\n                    zuulResponse.disposeBufferedBody();\n                }\n                ctx.close(); // This will trigger CompleteEvent if one is needed\n                return;\n            }\n\n            startedSendingResponseToClient = true;\n            zuulResponse = resp;\n            if (\"close\".equalsIgnoreCase(zuulResponse.getHeaders().getFirst(\"Connection\"))) {\n                closeConnection = true;\n            }\n            channel.attr(ClientRequestReceiver.ATTR_ZUUL_RESP).set(zuulResponse);\n\n            if (channel.isActive()) {\n                // track if response is being written before receiving LastContent for requests with a body\n                if (!ClientRequestReceiver.isLastContentReceivedForChannel(channel)\n                        && !shouldAllowPreemptiveResponse(channel)\n                        && zuulResponse.getInboundRequest().hasBody()) {\n                    responseBeforeReceivedLastContentCounter.increment();\n                    logger.warn(\n                            \"Writing response to client channel before have received the LastContent of request! {},\"\n                                    + \" {}\",\n                            zuulResponse.getInboundRequest().getInfoForLogging(),\n                            ChannelUtils.channelInfoForLogging(channel));\n                }\n                // Write out and flush the response to the client channel.\n                channel.write(buildHttpResponse(zuulResponse));\n                writeBufferedBodyContent(zuulResponse, channel);\n                channel.flush();\n            } else {\n                resp.disposeBufferedBody();\n                channel.close();\n            }\n        } else if (msg instanceof HttpContent chunk) {\n\n            if (channel.isActive()) {\n                channel.writeAndFlush(chunk);\n            } else {\n                chunk.release();\n                channel.close();\n            }\n        } else {\n            // should never happen\n            ReferenceCountUtil.release(msg);\n            throw new ZuulException(\"Received invalid message from origin\", true);\n        }\n    }\n\n    protected boolean shouldAllowPreemptiveResponse(Channel channel) {\n        // If the request timed-out while being read, then there won't have been any LastContent, but that's ok because\n        // the connection will have to be discarded anyway.\n        StatusCategory status =\n                StatusCategoryUtils.getStatusCategory(ClientRequestReceiver.getRequestFromChannel(channel));\n        return status == ZuulStatusCategory.FAILURE_CLIENT_TIMEOUT;\n    }\n\n    protected boolean skipProcessing(HttpResponseMessage resp) {\n        // override if you need to skip processing of response\n        return false;\n    }\n\n    private static void writeBufferedBodyContent(HttpResponseMessage zuulResponse, Channel channel) {\n        zuulResponse.getBodyContents().forEach(chunk -> channel.write(chunk.retain()));\n    }\n\n    private HttpResponse buildHttpResponse(HttpResponseMessage zuulResp) {\n        HttpRequestInfo zuulRequest = zuulResp.getInboundRequest();\n        HttpVersion responseHttpVersion;\n        String inboundProtocol = zuulRequest.getProtocol();\n        if (inboundProtocol.startsWith(\"HTTP/1\")) {\n            responseHttpVersion = HttpVersion.valueOf(inboundProtocol);\n        } else {\n            // Default to 1.1. We do this to cope with HTTP/2 inbound requests.\n            responseHttpVersion = HttpVersion.HTTP_1_1;\n        }\n\n        // Create the main http response to send, with body.\n        DefaultHttpResponse nativeResponse = new DefaultHttpResponse(\n                responseHttpVersion, HttpResponseStatus.valueOf(zuulResp.getStatus()), false, false);\n\n        // Now set all of the response headers - note this is a multi-set in keeping with HTTP semantics\n        HttpHeaders nativeHeaders = nativeResponse.headers();\n        for (Header entry : zuulResp.getHeaders().entries()) {\n            nativeHeaders.add(entry.getKey(), entry.getValue());\n        }\n\n        // Netty does not automatically add Content-Length or Transfer-Encoding: chunked. So we add here if missing.\n        if (!HttpUtil.isContentLengthSet(nativeResponse) && !HttpUtil.isTransferEncodingChunked(nativeResponse)) {\n            nativeResponse.headers().add(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);\n        }\n\n        HttpRequest nativeReq = (HttpRequest) zuulResp.getContext().get(CommonContextKeys.NETTY_HTTP_REQUEST);\n        if (!closeConnection && HttpUtil.isKeepAlive(nativeReq)) {\n            HttpUtil.setKeepAlive(nativeResponse, true);\n        } else {\n            // Send a Connection: close response header (only needed for HTTP/1.0 but no harm in doing for 1.1 too).\n            nativeResponse.headers().set(\"Connection\", \"close\");\n        }\n\n        // TODO - temp hack for http/2 handling.\n        if (nativeReq.headers().contains(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text())) {\n            String streamId = nativeReq.headers().get(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());\n            nativeResponse.headers().set(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);\n        }\n\n        return nativeResponse;\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof StartEvent) {\n            isHandlingRequest = true;\n            startedSendingResponseToClient = false;\n            closeConnection = false;\n            zuulResponse = null;\n        } else if (evt instanceof CompleteEvent) {\n            HttpResponse response = ((CompleteEvent) evt).getResponse();\n            if (response != null) {\n                if (\"close\".equalsIgnoreCase(response.headers().get(\"Connection\"))) {\n                    closeConnection = true;\n                }\n            }\n            if (zuulResponse != null) {\n                zuulResponse.disposeBufferedBody();\n            }\n\n            // Do all the post-completion metrics and logging.\n            handleComplete(ctx.channel());\n\n            // Choose to either close the connection, or prepare it for next use.\n            CompleteEvent completeEvent = (CompleteEvent) evt;\n            CompleteReason reason = completeEvent.getReason();\n            if (reason == CompleteReason.SESSION_COMPLETE || reason == CompleteReason.INACTIVE) {\n                if (!closeConnection) {\n                    // Start reading next request over HTTP 1.1 persistent connection\n                    ctx.channel().read();\n                } else {\n                    ctx.close();\n                }\n            } else {\n                if (isHandlingRequest) {\n                    logger.debug(\n                            \"Received complete event while still handling the request. With reason: {} -- {}\",\n                            reason.name(),\n                            ChannelUtils.channelInfoForLogging(ctx.channel()));\n                }\n                ctx.close();\n            }\n\n            isHandlingRequest = false;\n        } else if (evt instanceof IdleStateEvent) {\n            logger.debug(\"Received IdleStateEvent.\");\n        } else {\n            logger.debug(\"ClientResponseWriter Received event {}\", evt);\n        }\n    }\n\n    private void handleComplete(Channel channel) {\n        try {\n            if (isHandlingRequest) {\n                completeMetrics(channel, zuulResponse);\n\n                // Notify requestComplete listener if configured.\n                HttpRequestMessage zuulRequest = ClientRequestReceiver.getRequestFromChannel(channel);\n                if ((requestCompleteHandler != null) && (zuulRequest != null)) {\n                    requestCompleteHandler.handle(zuulRequest.getInboundRequest(), zuulResponse);\n                }\n                zuulResponse = null;\n            }\n        } catch (Throwable ex) {\n            logger.error(\"Error in RequestCompleteHandler.\", ex);\n        }\n    }\n\n    protected void completeMetrics(Channel channel, HttpResponseMessage zuulResponse) {\n        // override for recording complete metrics\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        int status = 500;\n\n        if (cause instanceof ZuulException ze) {\n\n            status = ze.getStatusCode();\n            logger.error(\n                    \"Exception caught in ClientResponseWriter for channel {} \",\n                    ChannelUtils.channelInfoForLogging(ctx.channel()),\n                    cause);\n        } else if (cause instanceof ReadTimeoutException) {\n            logger.debug(\"Read timeout for channel {} \", ChannelUtils.channelInfoForLogging(ctx.channel()), cause);\n            status = 504;\n        } else {\n            logger.error(\"Exception caught in ClientResponseWriter: \", cause);\n        }\n\n        if (isHandlingRequest\n                && !startedSendingResponseToClient\n                && ctx.channel().isActive()) {\n            HttpResponse httpResponse =\n                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(status));\n            ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);\n            startedSendingResponseToClient = true;\n        } else {\n            ctx.close();\n        }\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        super.channelInactive(ctx);\n        ctx.close();\n    }\n\n    @VisibleForTesting\n    HttpResponseMessage getZuulResponse() {\n        return zuulResponse;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/DefaultEventLoopConfig.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server;\n\nimport com.netflix.config.DynamicIntProperty;\nimport jakarta.inject.Singleton;\n\n/**\n * Event loop configuration for the Zuul server.\n * By default, it configures a single acceptor thread with workers = logical cores available.\n */\n@Singleton\npublic class DefaultEventLoopConfig implements EventLoopConfig {\n    private static final DynamicIntProperty ACCEPTOR_THREADS =\n            new DynamicIntProperty(\"zuul.server.netty.threads.acceptor\", 1);\n    private static final DynamicIntProperty WORKER_THREADS =\n            new DynamicIntProperty(\"zuul.server.netty.threads.worker\", -1);\n    private static final int PROCESSOR_COUNT = Runtime.getRuntime().availableProcessors();\n\n    private final int eventLoopCount;\n    private final int acceptorCount;\n\n    public DefaultEventLoopConfig() {\n        eventLoopCount = WORKER_THREADS.get() > 0 ? WORKER_THREADS.get() : PROCESSOR_COUNT;\n        acceptorCount = ACCEPTOR_THREADS.get();\n    }\n\n    public DefaultEventLoopConfig(int eventLoopCount, int acceptorCount) {\n        this.eventLoopCount = eventLoopCount;\n        this.acceptorCount = acceptorCount;\n    }\n\n    @Override\n    public int eventLoopCount() {\n        return eventLoopCount;\n    }\n\n    @Override\n    public int acceptorCount() {\n        return acceptorCount;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/DirectMemoryMonitor.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport io.netty.util.internal.PlatformDependent;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport java.time.Duration;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 4/29/16\n * Time: 10:23 AM\n */\n@Singleton\npublic final class DirectMemoryMonitor {\n    private static final Logger LOG = LoggerFactory.getLogger(DirectMemoryMonitor.class);\n    private static final String PROP_PREFIX = \"zuul.directmemory\";\n    private static final DynamicIntProperty TASK_DELAY_PROP = new DynamicIntProperty(PROP_PREFIX + \".task.delay\", 10);\n\n    private final ScheduledExecutorService service;\n\n    @Inject\n    public DirectMemoryMonitor(Registry registry) {\n        service = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder()\n                .setDaemon(true)\n                .setNameFormat(\"dmm-%d\")\n                .build());\n\n        PolledMeter.using(registry)\n                .withName(PROP_PREFIX + \".reserved\")\n                .withDelay(Duration.ofSeconds(TASK_DELAY_PROP.get()))\n                .scheduleOn(service)\n                .monitorValue(DirectMemoryMonitor.class, DirectMemoryMonitor::getReservedMemory);\n\n        PolledMeter.using(registry)\n                .withName(PROP_PREFIX + \".max\")\n                .withDelay(Duration.ofSeconds(TASK_DELAY_PROP.get()))\n                .scheduleOn(service)\n                .monitorValue(DirectMemoryMonitor.class, DirectMemoryMonitor::getMaxMemory);\n    }\n\n    public DirectMemoryMonitor() {\n        // no-op constructor\n        this.service = null;\n    }\n\n    private static double getReservedMemory(Object discard) {\n        try {\n            return PlatformDependent.usedDirectMemory();\n        } catch (Throwable e) {\n            LOG.warn(\"Error in DirectMemoryMonitor task.\", e);\n        }\n        return -1;\n    }\n\n    private static double getMaxMemory(Object discard) {\n        try {\n            return PlatformDependent.maxDirectMemory();\n        } catch (Throwable e) {\n            LOG.warn(\"Error in DirectMemoryMonitor task.\", e);\n        }\n        return -1;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/EventLoopConfig.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server;\n\npublic interface EventLoopConfig {\n    int eventLoopCount();\n\n    int acceptorCount();\n\n    /**\n     * specifies the backlog (accept queue) size to use\n     */\n    default int getBacklogSize() {\n        return 128;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/Http1MutualSslChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.zuul.netty.ssl.SslContextFactory;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslHandler;\nimport javax.net.ssl.SSLException;\n\n/**\n * User: michaels@netflix.com\n * Date: 1/31/17\n * Time: 11:43 PM\n */\npublic class Http1MutualSslChannelInitializer extends BaseZuulChannelInitializer {\n    private final SslContextFactory sslContextFactory;\n    private final SslContext sslContext;\n    private final boolean isSSlFromIntermediary;\n\n    /**\n     * Use {@link #Http1MutualSslChannelInitializer(String, ChannelConfig, ChannelConfig, ChannelGroup)} instead.\n     */\n    @Deprecated\n    public Http1MutualSslChannelInitializer(\n            int port, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        this(String.valueOf(port), channelConfig, channelDependencies, channels);\n    }\n\n    public Http1MutualSslChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        super(metricId, channelConfig, channelDependencies, channels);\n\n        this.isSSlFromIntermediary = channelConfig.get(CommonChannelConfigKeys.isSSlFromIntermediary);\n\n        this.sslContextFactory = channelConfig.get(CommonChannelConfigKeys.sslContextFactory);\n        try {\n            sslContext = sslContextFactory.createBuilderForServer().build();\n        } catch (SSLException e) {\n            throw new RuntimeException(\"Error configuring SslContext!\", e);\n        }\n\n        // Enable TLS Session Tickets support.\n        sslContextFactory.enableSessionTickets(sslContext);\n\n        // Setup metrics tracking the OpenSSL stats.\n        sslContextFactory.configureOpenSslStatsMetrics(sslContext, metricId);\n    }\n\n    @Override\n    protected void initChannel(Channel ch) throws Exception {\n        SslHandler sslHandler = sslContext.newHandler(ch.alloc());\n        sslHandler.engine().setEnabledProtocols(sslContextFactory.getProtocols());\n\n        // Configure our pipeline of ChannelHandlerS.\n        ChannelPipeline pipeline = ch.pipeline();\n\n        storeChannel(ch);\n        addTimeoutHandlers(pipeline);\n        addPassportHandler(pipeline);\n        addTcpRelatedHandlers(pipeline);\n        pipeline.addLast(\"ssl\", sslHandler);\n        addSslInfoHandlers(pipeline, isSSlFromIntermediary);\n        addSslClientCertChecks(pipeline);\n        addHttp1Handlers(pipeline);\n        addHttpRelatedHandlers(pipeline);\n        addZuulHandlers(pipeline);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ListenerSpec.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\n/*\n * @author Argha C\n * @since 10/2/24\n */\nimport java.net.SocketAddress;\nimport java.util.Objects;\n\n/**\n * A specification of an address to listen on.\n */\npublic record ListenerSpec(String addressName, boolean defaultAddressEnabled, SocketAddress defaultAddressValue) {\n\n    public ListenerSpec {\n        Objects.requireNonNull(addressName, \"addressName\");\n        Objects.requireNonNull(defaultAddressValue, \"defaultAddressValue\");\n    }\n\n    /**\n     * The fast property name that indicates if this address is enabled.  This is used when overriding\n     * {@link #defaultAddressEnabled}.\n     */\n    public String addressEnabledPropertyName() {\n        return \"zuul.server.\" + addressName + \".enabled\";\n    }\n\n    /**\n     * The fast property to override the default port for the address name\n     */\n    @Deprecated\n    public String portPropertyName() {\n        return \"zuul.server.port.\" + addressName;\n    }\n\n    /**\n     * The fast property to override the default address name\n     */\n    public String addressPropertyName() {\n        return \"zuul.server.addr.\" + addressName;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/MethodBinding.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport java.util.concurrent.Callable;\nimport java.util.function.BiConsumer;\n\n/**\n * Utility used for binding context variables or thread variables, depending on requirements.\n *\n * Author: Arthur Gonigberg\n * Date: November 29, 2017\n */\npublic class MethodBinding<T> {\n    private final BiConsumer<Runnable, T> boundMethod;\n    private final Callable<T> bindingContextExtractor;\n\n    public static MethodBinding<?> NO_OP_BINDING = new MethodBinding<>((r, t) -> {}, () -> null);\n\n    public MethodBinding(BiConsumer<Runnable, T> boundMethod, Callable<T> bindingContextExtractor) {\n        this.boundMethod = boundMethod;\n        this.bindingContextExtractor = bindingContextExtractor;\n    }\n\n    public void bind(Runnable method) throws Exception {\n        T bindingContext = bindingContextExtractor.call();\n        if (bindingContext == null) {\n            method.run();\n        } else {\n            boundMethod.accept(method, bindingContext);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/NamedSocketAddress.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport java.net.SocketAddress;\nimport java.util.Objects;\nimport javax.annotation.CheckReturnValue;\n\npublic final class NamedSocketAddress extends SocketAddress {\n\n    private final String name;\n    private final SocketAddress delegate;\n\n    public NamedSocketAddress(String name, SocketAddress delegate) {\n        this.name = Objects.requireNonNull(name);\n        this.delegate = Objects.requireNonNull(delegate);\n    }\n\n    public String name() {\n        return name;\n    }\n\n    public SocketAddress unwrap() {\n        return delegate;\n    }\n\n    @CheckReturnValue\n    public NamedSocketAddress withNewSocket(SocketAddress delegate) {\n        return new NamedSocketAddress(this.name, delegate);\n    }\n\n    @Override\n    public String toString() {\n        return \"NamedSocketAddress{\" + \"name='\" + name + '\\'' + \", delegate=\" + delegate + '}';\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        NamedSocketAddress that = (NamedSocketAddress) o;\n        return Objects.equals(name, that.name) && Objects.equals(delegate, that.delegate);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(name, delegate);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/OriginResponseReceiver.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.exception.OutboundException;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.filters.endpoint.ProxyEndpoint;\nimport com.netflix.zuul.message.Header;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.netty.connectionpool.OriginConnectException;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.ssl.SslCloseCompletionEvent;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport io.netty.handler.timeout.ReadTimeoutException;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.ReferenceCountUtil;\nimport io.perfmark.PerfMark;\nimport io.perfmark.TaskCloseable;\nimport java.io.IOException;\nimport java.util.Locale;\nimport java.util.Objects;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Created by saroskar on 1/18/17.\n */\npublic class OriginResponseReceiver extends ChannelDuplexHandler {\n\n    protected volatile ProxyEndpoint edgeProxy;\n\n    private static final Logger logger = LoggerFactory.getLogger(OriginResponseReceiver.class);\n    private static final AttributeKey<Throwable> SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE =\n            AttributeKey.newInstance(\"_ssl_handshake_from_origin_throwable\");\n    private static final AttributeKey<Boolean> SSL_CLOSE_NOTIFY_SEEN =\n            AttributeKey.newInstance(\"_ssl_close_notify_seen\");\n    public static final String CHANNEL_HANDLER_NAME = \"_origin_response_receiver\";\n\n    public OriginResponseReceiver(ProxyEndpoint edgeProxy) {\n        this.edgeProxy = edgeProxy;\n    }\n\n    public void unlinkFromClientRequest() {\n        edgeProxy = null;\n    }\n\n    @Override\n    public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        try (TaskCloseable a = PerfMark.traceTask(\"ORR.channelRead\")) {\n            channelReadInternal(ctx, msg, true);\n        }\n    }\n\n    protected void channelReadInternal(ChannelHandlerContext ctx, Object msg, boolean triggerRead) throws Exception {\n        if (msg instanceof HttpResponse) {\n            if (edgeProxy != null) {\n                edgeProxy.responseFromOrigin((HttpResponse) msg);\n            } else if (ReferenceCountUtil.refCnt(msg) > 0) {\n                // this handles the case of a DefaultFullHttpResponse that could have content that needs to be released\n                ReferenceCountUtil.safeRelease(msg);\n            }\n\n            if (triggerRead) {\n                ctx.channel().read();\n            }\n        } else if (msg instanceof HttpContent chunk) {\n            if (edgeProxy != null) {\n                edgeProxy.invokeNext(chunk);\n            } else {\n                ReferenceCountUtil.safeRelease(chunk);\n            }\n\n            if (triggerRead) {\n                ctx.channel().read();\n            }\n        } else {\n            // should never happen\n            ReferenceCountUtil.release(msg);\n            Exception error = new IllegalStateException(\"Received invalid message from origin\");\n            if (edgeProxy != null) {\n                edgeProxy.errorFromOrigin(error);\n            }\n            ctx.fireExceptionCaught(error);\n        }\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof CompleteEvent completeEvent) {\n            CompleteReason reason = completeEvent.getReason();\n            if ((reason != CompleteReason.SESSION_COMPLETE) && (edgeProxy != null)) {\n                if (reason == CompleteReason.CLOSE\n                        && Objects.equals(\n                                ctx.channel().attr(SSL_CLOSE_NOTIFY_SEEN).get(), Boolean.TRUE)) {\n                    logger.warn(\n                            \"Origin request completed with close, after getting a SslCloseCompletionEvent event: {}\",\n                            ChannelUtils.channelInfoForLogging(ctx.channel()));\n                    edgeProxy.errorFromOrigin(new OriginConnectException(\n                            \"Origin connection close_notify\", OutboundErrorType.CLOSE_NOTIFY_CONNECTION));\n                } else {\n                    logger.error(\n                            \"Origin request completed with reason other than COMPLETE: {}, {}\",\n                            reason.name(),\n                            ChannelUtils.channelInfoForLogging(ctx.channel()));\n                    ZuulException ze = new ZuulException(\"CompleteEvent\", reason.name(), true);\n                    edgeProxy.errorFromOrigin(ze);\n                }\n            }\n\n            // First let this event propagate along the pipeline, before cleaning vars from the channel.\n            // See channelWrite() where these vars are first set onto the channel.\n            try {\n                super.userEventTriggered(ctx, evt);\n            } finally {\n                postCompleteHook(ctx, evt);\n            }\n        } else if (evt instanceof SslHandshakeCompletionEvent && !((SslHandshakeCompletionEvent) evt).isSuccess()) {\n            Throwable cause = ((SslHandshakeCompletionEvent) evt).cause();\n            ctx.channel().attr(SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE).set(cause);\n        } else if (evt instanceof IdleStateEvent) {\n            if (edgeProxy != null) {\n                logger.error(\n                        \"Origin request received IDLE event: {}\", ChannelUtils.channelInfoForLogging(ctx.channel()));\n                edgeProxy.errorFromOrigin(\n                        new OutboundException(OutboundErrorType.READ_TIMEOUT, edgeProxy.getRequestAttempts()));\n            }\n            super.userEventTriggered(ctx, evt);\n        } else if (evt instanceof SslCloseCompletionEvent) {\n            logger.debug(\"Received SslCloseCompletionEvent on {}\", ChannelUtils.channelInfoForLogging(ctx.channel()));\n            ctx.channel().attr(SSL_CLOSE_NOTIFY_SEEN).set(true);\n            super.userEventTriggered(ctx, evt);\n        } else {\n            super.userEventTriggered(ctx, evt);\n        }\n    }\n\n    /**\n     * Override to add custom post complete functionality\n     *\n     * @param ctx - channel handler context\n     * @param evt - netty event\n     * @throws Exception\n     */\n    protected void postCompleteHook(ChannelHandlerContext ctx, Object evt) throws Exception {}\n\n    private HttpRequest buildOriginHttpRequest(HttpRequestMessage zuulRequest) {\n        String method = zuulRequest.getMethod().toUpperCase(Locale.ROOT);\n        String uri = pathAndQueryString(zuulRequest);\n\n        customRequestProcessing(zuulRequest);\n\n        DefaultHttpRequest nettyReq =\n                new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.valueOf(method), uri, false);\n        // Copy headers across.\n        for (Header h : zuulRequest.getHeaders().entries()) {\n            nettyReq.headers().add(h.getKey(), h.getValue());\n        }\n\n        return nettyReq;\n    }\n\n    /**\n     * Override to add custom modifications to the request before it goes out\n     *\n     * @param headers\n     */\n    protected void customRequestProcessing(HttpRequestMessage headers) {}\n\n    private static String pathAndQueryString(HttpRequestMessage request) {\n        // parsing the params cleans up any empty/null params using the logic of the HttpQueryParams class\n        HttpQueryParams cleanParams =\n                HttpQueryParams.parse(request.getQueryParams().toEncodedString());\n        String cleanQueryStr = cleanParams.toEncodedString();\n        if (cleanQueryStr == null || cleanQueryStr.isEmpty()) {\n            return request.getPath();\n        } else {\n            return request.getPath() + \"?\" + cleanParams.toEncodedString();\n        }\n    }\n\n    @Override\n    public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        try (TaskCloseable ignore = PerfMark.traceTask(\"ORR.writeInternal\")) {\n            writeInternal(ctx, msg, promise);\n        }\n    }\n\n    private void writeInternal(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        if (!ctx.channel().isActive()) {\n            ReferenceCountUtil.release(msg);\n            return;\n        }\n\n        if (msg instanceof HttpRequestMessage) {\n            promise.addListener((future) -> {\n                if (!future.isSuccess()) {\n                    Throwable cause = ctx.channel()\n                            .attr(SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE)\n                            .get();\n                    if (cause != null) {\n                        // Set the specific SSL handshake error if the handlers have already caught them\n                        ctx.channel()\n                                .attr(SSL_HANDSHAKE_UNSUCCESS_FROM_ORIGIN_THROWABLE)\n                                .set(null);\n                        fireWriteError(\"request headers\", cause, ctx);\n                        logger.debug(\n                                \"SSLException is overridden by SSLHandshakeException caught in handler level. Original\"\n                                        + \" SSL exception message: \",\n                                future.cause());\n                    } else {\n                        fireWriteError(\"request headers\", future.cause(), ctx);\n                    }\n                }\n            });\n\n            HttpRequestMessage zuulReq = (HttpRequestMessage) msg;\n            preWriteHook(ctx, zuulReq);\n\n            super.write(ctx, buildOriginHttpRequest(zuulReq), promise);\n        } else if (msg instanceof HttpContent) {\n            promise.addListener((future) -> {\n                if (!future.isSuccess()) {\n                    fireWriteError(\"request content chunk\", future.cause(), ctx);\n                }\n            });\n            super.write(ctx, msg, promise);\n        } else {\n            // should never happen\n            ReferenceCountUtil.release(msg);\n            throw new ZuulException(\"Received invalid message from client\", true);\n        }\n    }\n\n    /**\n     * Override to add custom pre-write functionality\n     *\n     * @param ctx     channel handler context\n     * @param zuulReq request message to modify\n     */\n    protected void preWriteHook(ChannelHandlerContext ctx, HttpRequestMessage zuulReq) {}\n\n    private void fireWriteError(String requestPart, Throwable cause, ChannelHandlerContext ctx) {\n        String errMesg = \"Error while proxying \" + requestPart + \" to origin \";\n        if (edgeProxy != null) {\n            ProxyEndpoint ep = edgeProxy;\n            edgeProxy = null;\n            errMesg += ep.getOrigin().getName();\n            ep.errorFromOrigin(cause);\n        }\n        ctx.fireExceptionCaught(new ZuulException(cause, errMesg, true));\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        if (edgeProxy != null) {\n            if (cause instanceof ReadTimeoutException) {\n                edgeProxy.getPassport().add(PassportState.ORIGIN_CH_READ_TIMEOUT);\n                logger.debug(\n                        \"read timeout on origin channel {} \", ChannelUtils.channelInfoForLogging(ctx.channel()), cause);\n            } else if (cause instanceof IOException) {\n                edgeProxy.getPassport().add(PassportState.ORIGIN_CH_IO_EX);\n                logger.debug(\n                        \"I/O error on origin channel {} \", ChannelUtils.channelInfoForLogging(ctx.channel()), cause);\n            } else {\n                logger.error(\"Error from Origin connection:\", cause);\n            }\n            edgeProxy.errorFromOrigin(cause);\n        }\n        ctx.fireExceptionCaught(cause);\n    }\n\n    @Override\n    public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        if (edgeProxy != null) {\n            logger.debug(\"Origin channel inactive. channel-info={}\", ChannelUtils.channelInfoForLogging(ctx.channel()));\n            OriginConnectException ex =\n                    new OriginConnectException(\"Origin server inactive\", OutboundErrorType.RESET_CONNECTION);\n            edgeProxy.errorFromOrigin(ex);\n        }\n        super.channelInactive(ctx);\n        ctx.close();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/Server.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.netty.common.CategorizedThreadFactory;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.monitoring.ConnCounter;\nimport com.netflix.zuul.monitoring.ConnTimer;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.buffer.ByteBufAllocator;\nimport io.netty.buffer.ByteBufAllocatorMetric;\nimport io.netty.buffer.ByteBufAllocatorMetricProvider;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOption;\nimport io.netty.channel.EventLoopGroup;\nimport io.netty.channel.IoHandlerFactory;\nimport io.netty.channel.MultiThreadIoEventLoopGroup;\nimport io.netty.channel.ServerChannel;\nimport io.netty.channel.epoll.Epoll;\nimport io.netty.channel.epoll.EpollChannelOption;\nimport io.netty.channel.epoll.EpollIoHandler;\nimport io.netty.channel.epoll.EpollServerSocketChannel;\nimport io.netty.channel.epoll.EpollSocketChannel;\nimport io.netty.channel.kqueue.KQueue;\nimport io.netty.channel.kqueue.KQueueIoHandler;\nimport io.netty.channel.kqueue.KQueueServerSocketChannel;\nimport io.netty.channel.kqueue.KQueueSocketChannel;\nimport io.netty.channel.nio.NioIoHandler;\nimport io.netty.channel.socket.nio.NioServerSocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.channel.uring.IoUring;\nimport io.netty.channel.uring.IoUringIoHandler;\nimport io.netty.channel.uring.IoUringServerSocketChannel;\nimport io.netty.channel.uring.IoUringSocketChannel;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.concurrent.DefaultEventExecutorChooserFactory;\nimport io.netty.util.concurrent.EventExecutor;\nimport io.netty.util.concurrent.ThreadPerTaskExecutor;\nimport java.net.InetSocketAddress;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n *\n * NOTE: Shout-out to <a href=\"https://github.com/adamfisk/LittleProxy\">LittleProxy</a> which was great as a reference.\n *\n * User: michaels\n * Date: 11/8/14\n * Time: 8:39 PM\n */\npublic class Server {\n    /**\n     * This field is effectively a noop, as Epoll is enabled automatically if available.   This can be disabled by\n     * using the {@link #FORCE_NIO} property.\n     */\n    @Deprecated\n    public static final DynamicBooleanProperty USE_EPOLL =\n            new DynamicBooleanProperty(\"zuul.server.netty.socket.epoll\", false);\n\n    /**\n     * If {@code true}, The Zuul server will avoid autodetecting the transport type and use the default Java NIO\n     * transport.\n     */\n    private static final DynamicBooleanProperty FORCE_NIO =\n            new DynamicBooleanProperty(\"zuul.server.netty.socket.force_nio\", false);\n\n    private static final DynamicBooleanProperty FORCE_IO_URING =\n            new DynamicBooleanProperty(\"zuul.server.netty.socket.force_io_uring\", false);\n\n    private static final Logger LOG = LoggerFactory.getLogger(Server.class);\n\n    private static final DynamicBooleanProperty MANUAL_DISCOVERY_STATUS =\n            new DynamicBooleanProperty(\"zuul.server.netty.manual.discovery.status\", true);\n\n    private final Thread jvmShutdownHook;\n    private final Registry registry;\n    private ServerGroup serverGroup;\n    private final ClientConnectionsShutdown clientConnectionsShutdown;\n    private final ServerStatusManager serverStatusManager;\n    private final Map<NamedSocketAddress, ? extends ChannelInitializer<?>> addressesToInitializers;\n    /**\n     * Unlike the above, the socket addresses in this map are the *bound* addresses, rather than the requested ones.\n     */\n    private final Map<NamedSocketAddress, Channel> addressesToChannels = new LinkedHashMap<>();\n\n    private final EventLoopConfig eventLoopConfig;\n    private final Map<Integer, Counter> acceptCountersByPort = new ConcurrentHashMap<>();\n\n    /**\n     * This is a hack to expose the channel type to the origin channel.  It is NOT API stable and should not be\n     * referenced by non-Zuul code.\n     */\n    @Deprecated\n    public static final AtomicReference<Class<? extends Channel>> defaultOutboundChannelType = new AtomicReference<>();\n\n    /**\n     * Use {@link #Server(Registry, ServerStatusManager, Map, ClientConnectionsShutdown, EventLoopGroupMetrics,\n     * EventLoopConfig)}\n     * instead.\n     */\n    @SuppressWarnings(\"rawtypes\")\n    @Deprecated\n    public Server(\n            Map<Integer, ChannelInitializer> portsToChannelInitializers,\n            ServerStatusManager serverStatusManager,\n            ClientConnectionsShutdown clientConnectionsShutdown,\n            EventLoopGroupMetrics eventLoopGroupMetrics) {\n        this(\n                portsToChannelInitializers,\n                serverStatusManager,\n                clientConnectionsShutdown,\n                eventLoopGroupMetrics,\n                new DefaultEventLoopConfig());\n    }\n\n    /**\n     * Use {@link #Server(Registry, ServerStatusManager, Map, ClientConnectionsShutdown, EventLoopGroupMetrics,\n     * EventLoopConfig)}\n     * instead.\n     */\n    @SuppressWarnings({\"unchecked\", \"rawtypes\"\n    }) // Channel init map has the wrong generics and we can't fix without api breakage.\n    @Deprecated\n    public Server(\n            Map<Integer, ChannelInitializer> portsToChannelInitializers,\n            ServerStatusManager serverStatusManager,\n            ClientConnectionsShutdown clientConnectionsShutdown,\n            EventLoopGroupMetrics eventLoopGroupMetrics,\n            EventLoopConfig eventLoopConfig) {\n        this(\n                Spectator.globalRegistry(),\n                serverStatusManager,\n                convertPortMap((Map<Integer, ChannelInitializer<?>>) (Map) portsToChannelInitializers),\n                clientConnectionsShutdown,\n                eventLoopGroupMetrics,\n                eventLoopConfig);\n    }\n\n    public Server(\n            Registry registry,\n            ServerStatusManager serverStatusManager,\n            Map<NamedSocketAddress, ? extends ChannelInitializer<?>> addressesToInitializers,\n            ClientConnectionsShutdown clientConnectionsShutdown,\n            EventLoopGroupMetrics eventLoopGroupMetrics,\n            EventLoopConfig eventLoopConfig) {\n        this(\n                registry,\n                serverStatusManager,\n                addressesToInitializers,\n                clientConnectionsShutdown,\n                eventLoopGroupMetrics,\n                eventLoopConfig,\n                null);\n    }\n\n    public Server(\n            Registry registry,\n            ServerStatusManager serverStatusManager,\n            Map<NamedSocketAddress, ? extends ChannelInitializer<?>> addressesToInitializers,\n            ClientConnectionsShutdown clientConnectionsShutdown,\n            EventLoopGroupMetrics eventLoopGroupMetrics,\n            EventLoopConfig eventLoopConfig,\n            Thread jvmShutdownHook) {\n        this.registry = Objects.requireNonNull(registry);\n        this.addressesToInitializers = Collections.unmodifiableMap(new LinkedHashMap<>(addressesToInitializers));\n        this.serverStatusManager = Preconditions.checkNotNull(serverStatusManager, \"serverStatusManager\");\n        this.clientConnectionsShutdown =\n                Preconditions.checkNotNull(clientConnectionsShutdown, \"clientConnectionsShutdown\");\n        this.eventLoopConfig = Preconditions.checkNotNull(eventLoopConfig, \"eventLoopConfig\");\n        this.jvmShutdownHook =\n                jvmShutdownHook != null ? jvmShutdownHook : new Thread(this::stop, \"Zuul-JVM-shutdown-hook\");\n    }\n\n    public void stop() {\n        LOG.info(\"Shutting down Zuul.\");\n        serverGroup.stop();\n        LOG.info(\"Completed zuul shutdown.\");\n    }\n\n    public void start() {\n        if (jvmShutdownHook != null) {\n            Runtime.getRuntime().addShutdownHook(jvmShutdownHook);\n        }\n\n        serverGroup = new ServerGroup(\"Salamander\", eventLoopConfig.acceptorCount(), eventLoopConfig.eventLoopCount());\n        serverGroup.initializeTransport();\n        List<ChannelFuture> allBindFutures = new ArrayList<>(addressesToInitializers.size());\n\n        // Setup each of the channel initializers on requested ports.\n        for (Map.Entry<NamedSocketAddress, ? extends ChannelInitializer<?>> entry :\n                addressesToInitializers.entrySet()) {\n            NamedSocketAddress requestedNamedAddr = entry.getKey();\n            ChannelFuture nettyServerFuture = setupServerBootstrap(requestedNamedAddr, entry.getValue());\n            Channel chan = nettyServerFuture.channel();\n            addressesToChannels.put(requestedNamedAddr.withNewSocket(chan.localAddress()), chan);\n            allBindFutures.add(nettyServerFuture);\n        }\n\n        // All channels should share a single ByteBufAllocator instance.\n        // Add metrics to monitor that allocator's memory usage.\n        if (!allBindFutures.isEmpty()) {\n            ByteBufAllocator alloc = allBindFutures.get(0).channel().alloc();\n            if (alloc instanceof ByteBufAllocatorMetricProvider) {\n                ByteBufAllocatorMetric metrics = ((ByteBufAllocatorMetricProvider) alloc).metric();\n                PolledMeter.using(registry)\n                        .withId(registry.createId(\"zuul.nettybuffermem.live\", \"type\", \"heap\"))\n                        .monitorValue(metrics, ByteBufAllocatorMetric::usedHeapMemory);\n                PolledMeter.using(registry)\n                        .withId(registry.createId(\"zuul.nettybuffermem.live\", \"type\", \"direct\"))\n                        .monitorValue(metrics, ByteBufAllocatorMetric::usedDirectMemory);\n            }\n        }\n    }\n\n    public final void awaitTermination() throws InterruptedException {\n        for (Channel chan : addressesToChannels.values()) {\n            chan.closeFuture().sync();\n        }\n    }\n\n    public final List<NamedSocketAddress> getListeningAddresses() {\n        if (serverGroup == null) {\n            throw new IllegalStateException(\"Server has not been started\");\n        }\n        return Collections.unmodifiableList(new ArrayList<>(addressesToChannels.keySet()));\n    }\n\n    @VisibleForTesting\n    public void waitForEachEventLoop() throws InterruptedException, ExecutionException {\n        for (EventExecutor exec : serverGroup.clientToProxyWorkerPool) {\n            exec.submit(() -> {\n                        // Do nothing.\n                    })\n                    .get();\n        }\n    }\n\n    private ChannelFuture setupServerBootstrap(\n            NamedSocketAddress listenAddress, ChannelInitializer<?> channelInitializer) {\n        ServerBootstrap serverBootstrap =\n                new ServerBootstrap().group(serverGroup.clientToProxyBossPool, serverGroup.clientToProxyWorkerPool);\n\n        LOG.info(\"Proxy listening with {}\", serverGroup.channelType);\n        serverBootstrap.channel(serverGroup.channelType);\n\n        serverBootstrap.option(ChannelOption.SO_BACKLOG, eventLoopConfig.getBacklogSize());\n        serverBootstrap.childOption(ChannelOption.SO_LINGER, -1);\n        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);\n        serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);\n\n        // Apply transport specific socket options.\n        for (Map.Entry<ChannelOption<?>, ?> optionEntry : serverGroup.transportChannelOptions.entrySet()) {\n            applyServerOption(serverBootstrap, optionEntry.getKey(), optionEntry.getValue());\n        }\n\n        serverBootstrap.handler(new NewConnHandler());\n        serverBootstrap.childHandler(channelInitializer);\n        serverBootstrap.validate();\n\n        LOG.info(\"Binding to : {}\", listenAddress);\n\n        if (MANUAL_DISCOVERY_STATUS.get()) {\n            // Flag status as UP just before binding to the port.\n            serverStatusManager.localStatus(InstanceInfo.InstanceStatus.UP);\n        }\n\n        // Bind and start to accept incoming connections.\n        ChannelFuture bindFuture = serverBootstrap.bind(listenAddress.unwrap());\n\n        try {\n            return bindFuture.sync();\n        } catch (Exception e) {\n            // sync() sneakily throws a checked Exception, but doesn't declare it. This can happen if there is a bind\n            // failure, which is typically an IOException.  Just chain it and rethrow.\n            throw new RuntimeException(\"Failed to bind on addr \" + listenAddress, e);\n        }\n    }\n\n    /**\n     * Override for metrics or informational purposes\n     *\n     * @param clientToProxyBossPool - acceptor pool\n     * @param clientToProxyWorkerPool - worker pool\n     */\n    public void postEventLoopCreationHook(\n            EventLoopGroup clientToProxyBossPool, EventLoopGroup clientToProxyWorkerPool) {}\n\n    private final class ServerGroup {\n        /** A name for this ServerGroup to use in naming threads. */\n        private final String name;\n\n        private final int acceptorThreads;\n        private final int workerThreads;\n\n        private EventLoopGroup clientToProxyBossPool;\n        private EventLoopGroup clientToProxyWorkerPool;\n        private Class<? extends ServerChannel> channelType;\n        private Map<ChannelOption<?>, ?> transportChannelOptions;\n\n        private volatile boolean stopped = false;\n\n        private ServerGroup(String name, int acceptorThreads, int workerThreads) {\n            this.name = name;\n            this.acceptorThreads = acceptorThreads;\n            this.workerThreads = workerThreads;\n\n            Thread.setDefaultUncaughtExceptionHandler((t, e) -> LOG.error(\"Uncaught throwable\", e));\n        }\n\n        private void initializeTransport() {\n            Map<ChannelOption<?>, Object> extraOptions = new HashMap<>();\n            boolean useNio = FORCE_NIO.get();\n            boolean useIoUring = FORCE_IO_URING.get();\n\n            final IoHandlerFactory handlerFactory;\n            if (useIoUring && ioUringIsAvailable()) {\n                channelType = IoUringServerSocketChannel.class;\n                defaultOutboundChannelType.set(IoUringSocketChannel.class);\n                handlerFactory = IoUringIoHandler.newFactory();\n            } else if (!useNio && epollIsAvailable()) {\n                channelType = EpollServerSocketChannel.class;\n                defaultOutboundChannelType.set(EpollSocketChannel.class);\n                handlerFactory = EpollIoHandler.newFactory();\n                extraOptions.put(EpollChannelOption.TCP_DEFER_ACCEPT, -1);\n            } else if (!useNio && kqueueIsAvailable()) {\n                channelType = KQueueServerSocketChannel.class;\n                defaultOutboundChannelType.set(KQueueSocketChannel.class);\n                handlerFactory = KQueueIoHandler.newFactory();\n            } else {\n                channelType = NioServerSocketChannel.class;\n                defaultOutboundChannelType.set(NioSocketChannel.class);\n                handlerFactory = NioIoHandler.newFactory();\n            }\n\n            clientToProxyBossPool = new MultiThreadIoEventLoopGroup(\n                    acceptorThreads, new CategorizedThreadFactory(name + \"-ClientToZuulAcceptor\"), handlerFactory);\n\n            ThreadFactory workerThreadFactory = new CategorizedThreadFactory(name + \"-ClientToZuulWorker\");\n            Executor workerExecutor = new ThreadPerTaskExecutor(workerThreadFactory);\n            clientToProxyWorkerPool = new MultiThreadIoEventLoopGroup(\n                    workerThreads, workerExecutor, DefaultEventExecutorChooserFactory.INSTANCE, handlerFactory);\n\n            transportChannelOptions = Collections.unmodifiableMap(extraOptions);\n            postEventLoopCreationHook(clientToProxyBossPool, clientToProxyWorkerPool);\n        }\n\n        private synchronized void stop() {\n            LOG.info(\"Shutting down\");\n            if (stopped) {\n                LOG.info(\"Already stopped\");\n                return;\n            }\n\n            if (MANUAL_DISCOVERY_STATUS.get()) {\n                // Flag status as down.\n                // that we can flag to return DOWN here (would that then update Discovery? or still be a delay?)\n                serverStatusManager.localStatus(InstanceInfo.InstanceStatus.DOWN);\n            }\n\n            // Shutdown each of the client connections (blocks until complete).\n            // NOTE: ClientConnectionsShutdown can also be configured to gracefully close connections when the\n            // discovery status changes to DOWN. So if it has been configured that way, then this will be an additional\n            // call to gracefullyShutdownClientChannels(), which will be a noop.\n            clientConnectionsShutdown.gracefullyShutdownClientChannels().syncUninterruptibly();\n\n            LOG.info(\"Shutting down event loops\");\n            List<EventLoopGroup> allEventLoopGroups = new ArrayList<>();\n            allEventLoopGroups.add(clientToProxyBossPool);\n            allEventLoopGroups.add(clientToProxyWorkerPool);\n            for (EventLoopGroup group : allEventLoopGroups) {\n                group.shutdownGracefully();\n            }\n\n            for (EventLoopGroup group : allEventLoopGroups) {\n                try {\n                    group.awaitTermination(20, TimeUnit.SECONDS);\n                } catch (InterruptedException ie) {\n                    LOG.warn(\"Interrupted while shutting down event loop\");\n                }\n            }\n\n            stopped = true;\n            LOG.info(\"Done shutting down\");\n        }\n    }\n\n    /*\n     * Keys should be a short string usable in metrics.\n     */\n    public static final AttributeKey<Attrs> CONN_DIMENSIONS = AttributeKey.newInstance(\"zuulconndimensions\");\n\n    private final class NewConnHandler extends ChannelInboundHandlerAdapter {\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            Long now = System.nanoTime();\n            Channel child = (Channel) msg;\n\n            int localPort = child.localAddress() instanceof InetSocketAddress localAddr ? localAddr.getPort() : -1;\n            acceptCountersByPort\n                    .computeIfAbsent(\n                            localPort,\n                            p -> registry.counter(\n                                    registry.createId(\"zuul.conn.acceptor.accepts\", \"port\", String.valueOf(p))))\n                    .increment();\n\n            child.attr(CONN_DIMENSIONS).set(Attrs.newInstance());\n            ConnTimer timer = ConnTimer.install(child, registry, registry.createId(\"zuul.conn.client.timing\"));\n            timer.record(now, \"ACCEPT\");\n            ConnCounter.install(child, registry, registry.createId(\"zuul.conn.client.current\"));\n            super.channelRead(ctx, msg);\n        }\n    }\n\n    static Map<NamedSocketAddress, ChannelInitializer<?>> convertPortMap(\n            Map<Integer, ChannelInitializer<?>> portsToChannelInitializers) {\n        Map<NamedSocketAddress, ChannelInitializer<?>> addrsToInitializers =\n                new LinkedHashMap<>(portsToChannelInitializers.size());\n        for (Map.Entry<Integer, ChannelInitializer<?>> portToInitializer : portsToChannelInitializers.entrySet()) {\n            int portNumber = portToInitializer.getKey();\n            addrsToInitializers.put(\n                    new NamedSocketAddress(\"port\" + portNumber, new InetSocketAddress(portNumber)),\n                    portToInitializer.getValue());\n        }\n        return Collections.unmodifiableMap(addrsToInitializers);\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private static <T> void applyServerOption(ServerBootstrap bootstrap, ChannelOption<T> key, Object value) {\n        bootstrap.option(key, (T) value);\n    }\n\n    private static boolean epollIsAvailable() {\n        boolean available;\n        try {\n            available = Epoll.isAvailable();\n        } catch (NoClassDefFoundError e) {\n            LOG.debug(\"Epoll is unavailable, skipping\", e);\n            return false;\n        } catch (RuntimeException | Error e) {\n            LOG.warn(\"Epoll is unavailable, skipping\", e);\n            return false;\n        }\n        if (!available) {\n            LOG.debug(\"Epoll is unavailable, skipping\", Epoll.unavailabilityCause());\n        }\n        return available;\n    }\n\n    private static boolean ioUringIsAvailable() {\n        boolean available;\n        try {\n            available = IoUring.isAvailable();\n        } catch (NoClassDefFoundError e) {\n            LOG.debug(\"io_uring is unavailable, skipping\", e);\n            return false;\n        } catch (RuntimeException | Error e) {\n            LOG.warn(\"io_uring is unavailable, skipping\", e);\n            return false;\n        }\n        if (!available) {\n            LOG.debug(\"io_uring is unavailable, skipping\", IoUring.unavailabilityCause());\n        }\n        return available;\n    }\n\n    private static boolean kqueueIsAvailable() {\n        boolean available;\n        try {\n            available = KQueue.isAvailable();\n        } catch (NoClassDefFoundError e) {\n            LOG.debug(\"KQueue is unavailable, skipping\", e);\n            return false;\n        } catch (RuntimeException | Error e) {\n            LOG.warn(\"KQueue is unavailable, skipping\", e);\n            return false;\n        }\n        if (!available) {\n            LOG.debug(\"KQueue is unavailable, skipping\", KQueue.unavailabilityCause());\n        }\n        return available;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ServerTimeout.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\npublic class ServerTimeout {\n    private final int connectionIdleTimeout;\n\n    public ServerTimeout(int connectionIdleTimeout) {\n        this.connectionIdleTimeout = connectionIdleTimeout;\n    }\n\n    public int connectionIdleTimeout() {\n        return connectionIdleTimeout;\n    }\n\n    public int defaultRequestExpiryTimeout() {\n        // Note this is the timeout for the inbound request to zuul, not for each outbound attempt.\n        // It needs to align with the inbound connection idle timeout and/or the ELB idle timeout. So we\n        // set it here to 1 sec less than that.\n        return connectionIdleTimeout > 1000 ? connectionIdleTimeout - 1000 : 1000;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/SocketAddressProperty.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.config.StringDerivedProperty;\nimport io.netty.channel.unix.DomainSocketAddress;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Locale;\nimport java.util.concurrent.Callable;\nimport java.util.function.Supplier;\nimport javax.annotation.Nullable;\n\n/**\n * This class expresses an address that Zuul can bind to.  Similar to {@link\n * com.netflix.config.DynamicStringMapProperty} this class uses a similar key=value syntax, but only supports a single\n * pair.\n *\n * <p>To use this class, set a bind type such as {@link BindType#ANY} and assign it a port number like {@code 7001}.\n *     Sample usage:\n *     <ul>\n *         <li>{@code =7001} - equivalent to {@code ANY=7001}</li>\n *         <li>{@code ANY=7001} Binds on all IP addresses and IP stack for port 7001</li>\n *         <li>{@code IPV4_ANY=7001} Binds on all IPv4 address 0.0.0.0 for port 7001</li>\n *         <li>{@code IPV6_ANY=7001} Binds on all IPv6 address :: for port 7001</li>\n *         <li>{@code ANY_LOCAL=7001} Binds on localhost for all IP stacks for port 7001</li>\n *         <li>{@code IPV4_LOCAL=7001} Binds on IPv4 localhost (127.0.0.1) for port 7001</li>\n *         <li>{@code IPV6_LOCAL=7001} Binds on IPv6 localhost (::1) for port 7001</li>\n *         <li>{@code UDS=/var/run/zuul.sock} Binds a domain socket at /var/run/zuul.sock</li>\n *     </ul>\n *\n *  <p>Note that the local IPv4 binds only work for {@code 127.0.0.1}, and not any other loopback addresses.  Currently,\n *      all IP stack specific bind types only \"prefer\" a stack; it is up to the OS and the JVM to pick the the final\n *      address.\n */\npublic final class SocketAddressProperty extends StringDerivedProperty<SocketAddress> {\n\n    public enum BindType {\n        /**\n         * Supports any IP stack, for a given port.  This is the default behaviour.  This also indicates that the\n         * caller doesn't prefer a given IP stack.\n         */\n        ANY,\n        /**\n         * Binds on IPv4 {@code 0.0.0.0} address.\n         */\n        IPV4_ANY(() -> InetAddress.getByAddress(\"0.0.0.0\", new byte[] {0, 0, 0, 0})),\n        /**\n         * Binds on IPv6 {@code ::} address.\n         */\n        IPV6_ANY(() -> InetAddress.getByAddress(\"::\", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})),\n        /**\n         * Binds on any local address. This indicates that the caller doesn't prefer a given IP stack.\n         */\n        ANY_LOCAL(InetAddress::getLoopbackAddress),\n        /**\n         * Binds on the IPv4 {@code 127.0.0.1} localhost address.\n         */\n        IPV4_LOCAL(() -> InetAddress.getByAddress(\"localhost\", new byte[] {127, 0, 0, 1})),\n        /**\n         * Binds on the IPv6 {@code ::1} localhost address.\n         */\n        IPV6_LOCAL(() ->\n                InetAddress.getByAddress(\"localhost\", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})),\n        /**\n         * Binds on the Unix Domain Socket path.\n         */\n        UDS,\n        ;\n\n        @SuppressWarnings(\"ImmutableEnumChecker\") // Hopes and prayers that addressSupplier returns a constant.\n        @Nullable\n        private final Supplier<? extends InetAddress> addressSupplier;\n\n        BindType() {\n            addressSupplier = null;\n        }\n\n        BindType(Callable<? extends InetAddress> addressFn) {\n            this.addressSupplier = () -> {\n                try {\n                    return addressFn.call();\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            };\n        }\n    }\n\n    @VisibleForTesting\n    static final class Decoder implements com.google.common.base.Function<String, SocketAddress> {\n\n        static final Decoder INSTANCE = new Decoder();\n\n        @Override\n        public SocketAddress apply(String input) {\n            if (input == null || input.isEmpty()) {\n                throw new IllegalArgumentException(\"Invalid address\");\n            }\n\n            int equalsPosition = input.indexOf('=');\n            if (equalsPosition == -1) {\n                throw new IllegalArgumentException(\"Invalid address \" + input);\n            }\n            String rawBindType = equalsPosition != 0 ? input.substring(0, equalsPosition) : BindType.ANY.name();\n            BindType bindType = BindType.valueOf(rawBindType.toUpperCase(Locale.ROOT));\n            String rawAddress = input.substring(equalsPosition + 1);\n            int port;\n\n            switch (bindType) {\n                case ANY: // fallthrough\n                case IPV4_ANY: // fallthrough\n                case IPV6_ANY: // fallthrough\n                case ANY_LOCAL: // fallthrough\n                case IPV4_LOCAL: // fallthrough\n                case IPV6_LOCAL: // fallthrough\n                    try {\n                        port = Integer.parseInt(rawAddress);\n                    } catch (NumberFormatException e) {\n                        throw new IllegalArgumentException(\"Invalid Port \" + input, e);\n                    }\n                    break;\n                case UDS:\n                    port = -1;\n                    break;\n                default:\n                    throw new AssertionError(\"Missed cased: \" + bindType);\n            }\n\n            switch (bindType) {\n                case ANY:\n                    return new InetSocketAddress(port);\n                case IPV4_ANY: // fallthrough\n                case IPV6_ANY: // fallthrough\n                case ANY_LOCAL: // fallthrough\n                case IPV4_LOCAL: // fallthrough\n                case IPV6_LOCAL: // fallthrough\n                    return new InetSocketAddress(bindType.addressSupplier.get(), port);\n                case UDS:\n                    return new DomainSocketAddress(rawAddress);\n            }\n            throw new AssertionError(\"Missed cased: \" + bindType);\n        }\n    }\n\n    public SocketAddressProperty(String propName, SocketAddress defaultValue) {\n        super(propName, defaultValue, Decoder.INSTANCE);\n    }\n\n    public SocketAddressProperty(String propName, String defaultValue) {\n        this(propName, Decoder.INSTANCE.apply(defaultValue));\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ZuulDependencyKeys.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.channel.config.ChannelConfigKey;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.RequestCompleteHandler;\nimport com.netflix.zuul.context.SessionContextDecorator;\nimport com.netflix.zuul.netty.filter.FilterConstraints;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport io.netty.channel.ChannelHandler;\nimport jakarta.inject.Provider;\n\n/**\n * User: michaels@netflix.com\n * Date: 2/9/17\n * Time: 9:35 AM\n */\npublic class ZuulDependencyKeys {\n\n    public static final ChannelConfigKey<AccessLogPublisher> accessLogPublisher =\n            new ChannelConfigKey<>(\"accessLogPublisher\");\n    public static final ChannelConfigKey<EventLoopGroupMetrics> eventLoopGroupMetrics =\n            new ChannelConfigKey<>(\"eventLoopGroupMetrics\");\n    public static final ChannelConfigKey<Registry> registry = new ChannelConfigKey<>(\"registry\");\n    public static final ChannelConfigKey<SessionContextDecorator> sessionCtxDecorator =\n            new ChannelConfigKey<>(\"sessionCtxDecorator\");\n    public static final ChannelConfigKey<RequestCompleteHandler> requestCompleteHandler =\n            new ChannelConfigKey<>(\"requestCompleteHandler\");\n    public static final ChannelConfigKey<Counter> httpRequestHeadersReadTimeoutCounter =\n            new ChannelConfigKey<>(\"httpRequestHeadersReadTimeoutCounter\");\n    public static final ChannelConfigKey<PercentileTimer> httpRequestHeadersReadTimer =\n            new ChannelConfigKey<>(\"httpRequestHeadersReadTimer\");\n    public static final ChannelConfigKey<Counter> httpRequestReadTimeoutCounter =\n            new ChannelConfigKey<>(\"httpRequestReadTimeoutCounter\");\n    public static final ChannelConfigKey<FilterLoader> filterLoader = new ChannelConfigKey<>(\"filterLoader\");\n    public static final ChannelConfigKey<FilterUsageNotifier> filterUsageNotifier =\n            new ChannelConfigKey<>(\"filterUsageNotifier\");\n    public static final ChannelConfigKey<EurekaClient> discoveryClient = new ChannelConfigKey<>(\"discoveryClient\");\n    public static final ChannelConfigKey<ApplicationInfoManager> applicationInfoManager =\n            new ChannelConfigKey<>(\"applicationInfoManager\");\n    public static final ChannelConfigKey<ServerStatusManager> serverStatusManager =\n            new ChannelConfigKey<>(\"serverStatusManager\");\n    public static final ChannelConfigKey<Boolean> SSL_CLIENT_CERT_CHECK_REQUIRED =\n            new ChannelConfigKey<>(\"requiresSslClientCertCheck\", false);\n\n    public static final ChannelConfigKey<Provider<ChannelHandler>> rateLimitingChannelHandlerProvider =\n            new ChannelConfigKey<>(\"rateLimitingChannelHandlerProvider\");\n    public static final ChannelConfigKey<Provider<ChannelHandler>> sslClientCertCheckChannelHandlerProvider =\n            new ChannelConfigKey<>(\"sslClientCertCheckChannelHandlerProvider\");\n    public static final ChannelConfigKey<PushConnectionRegistry> pushConnectionRegistry =\n            new ChannelConfigKey<>(\"pushConnectionRegistry\");\n    public static final ChannelConfigKey<FilterConstraints> filterConstraints =\n            new ChannelConfigKey<>(\"filterConstraints\");\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ZuulServerChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\n\n/**\n * User: Mike Smith\n * Date: 3/5/16\n * Time: 6:44 PM\n */\npublic class ZuulServerChannelInitializer extends BaseZuulChannelInitializer {\n\n    public ZuulServerChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        super(metricId, channelConfig, channelDependencies, channels);\n    }\n\n    /**\n     * Use {@link #ZuulServerChannelInitializer(String, ChannelConfig, ChannelConfig, ChannelGroup)} instead.\n     */\n    @Deprecated\n    public ZuulServerChannelInitializer(\n            int port, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        this(String.valueOf(port), channelConfig, channelDependencies, channels);\n    }\n\n    @Override\n    protected void initChannel(Channel ch) throws Exception {\n        // Configure our pipeline of ChannelHandlerS.\n        ChannelPipeline pipeline = ch.pipeline();\n\n        storeChannel(ch);\n        addTimeoutHandlers(pipeline);\n        addPassportHandler(pipeline);\n        addTcpRelatedHandlers(pipeline);\n        addHttp1Handlers(pipeline);\n        addHttpRelatedHandlers(pipeline);\n        addZuulHandlers(pipeline);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/DummyChannelHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\n\n/**\n * Dummy Channel Handler\n *\n * Author: Arthur Gonigberg\n * Date: December 15, 2017\n */\npublic class DummyChannelHandler implements ChannelHandler {\n    @Override\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {}\n\n    @Override\n    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {}\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {}\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2Configuration.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport com.netflix.zuul.netty.ssl.SslContextFactory;\nimport io.netty.handler.ssl.ApplicationProtocolConfig;\nimport io.netty.handler.ssl.ApplicationProtocolNames;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport javax.net.ssl.SSLException;\n\npublic class Http2Configuration {\n\n    public static SslContext configureSSL(SslContextFactory sslContextFactory, String metricId) {\n        SslContextBuilder builder = sslContextFactory.createBuilderForServer();\n\n        String[] supportedProtocols = new String[] {ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1};\n        ApplicationProtocolConfig apn = new ApplicationProtocolConfig(\n                ApplicationProtocolConfig.Protocol.ALPN,\n                // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.\n                ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,\n                // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.\n                ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,\n                supportedProtocols);\n\n        SslContext sslContext;\n        try {\n            sslContext = builder.applicationProtocolConfig(apn).build();\n        } catch (SSLException e) {\n            throw new RuntimeException(\"Error configuring SslContext with ALPN!\", e);\n        }\n\n        // Enable TLS Session Tickets support.\n        sslContextFactory.enableSessionTickets(sslContext);\n\n        // Setup metrics tracking the OpenSSL stats.\n        sslContextFactory.configureOpenSslStatsMetrics(sslContext, metricId);\n\n        return sslContext;\n    }\n\n    /**\n     * This is meant to be use in cases where the server wishes not to advertise h2 as part of ALPN.\n     */\n    public static SslContext configureSSLWithH2Disabled(SslContextFactory sslContextFactory, String host) {\n\n        ApplicationProtocolConfig apn = new ApplicationProtocolConfig(\n                ApplicationProtocolConfig.Protocol.ALPN,\n                // NO_ADVERTISE is currently the only mode supported by both OpenSsl and JDK providers.\n                ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,\n                // ACCEPT is currently the only mode supported by both OpenSsl and JDK providers.\n                ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,\n                ApplicationProtocolNames.HTTP_1_1);\n        SslContext sslContext;\n        try {\n            sslContext = sslContextFactory\n                    .createBuilderForServer()\n                    .applicationProtocolConfig(apn)\n                    .build();\n        } catch (SSLException e) {\n            throw new RuntimeException(\"Error configuring SslContext with ALPN!\", e);\n        }\n\n        sslContextFactory.enableSessionTickets(sslContext);\n        sslContextFactory.configureOpenSslStatsMetrics(sslContext, host);\n        return sslContext;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2ConnectionErrorHandler.java",
    "content": "/**\n * Copyright 2023 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.http2;\n\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Logs and tracks connection errors. The actual\n * sending of the go-away and closing the connection is handled by netty in {@link io.netty.handler.codec.http2.Http2ConnectionHandler}\n * onConnectionError\n *\n * See also, {@link com.netflix.netty.common.channel.config.CommonChannelConfigKeys#http2CatchConnectionErrors}\n * @author Justin Guerra\n * @since 11/14/23\n */\npublic class Http2ConnectionErrorHandler extends ChannelInboundHandlerAdapter {\n\n    private static final Logger LOG = LoggerFactory.getLogger(Http2ConnectionErrorHandler.class);\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        if (cause instanceof Http2Exception http2Exception) {\n            LOG.debug(\"Received Http/2 connection error\", cause);\n            SpectatorUtils.newCounter(\n                            \"server.connection.http2.connection.exception\",\n                            http2Exception.error().name())\n                    .increment();\n        } else {\n            ctx.fireExceptionCaught(cause);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandler.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpUtil;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.handler.codec.http2.DefaultHttp2ResetFrame;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.util.ReferenceCountUtil;\nimport java.util.List;\n\n/**\n * Validates that HTTP/2 request content-length headers are consistent with the actual body.\n * This class is only suitable for use on HTTP/2 child channels.\n */\npublic final class Http2ContentLengthEnforcingHandler extends ChannelInboundHandlerAdapter {\n    private static final long UNSET_CONTENT_LENGTH = -1;\n\n    private long expectedContentLength = UNSET_CONTENT_LENGTH;\n\n    private long seenContentLength;\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof HttpRequest req && !validateRequest(req)) {\n            rejectAndRelease(ctx, msg);\n            return;\n        }\n\n        if (msg instanceof HttpContent httpContent && !validateContent(httpContent)) {\n            rejectAndRelease(ctx, msg);\n            return;\n        }\n\n        if (msg instanceof LastHttpContent && !validateEndOfStream()) {\n            rejectAndRelease(ctx, msg);\n            return;\n        }\n\n        super.channelRead(ctx, msg);\n    }\n\n    private boolean validateRequest(HttpRequest req) {\n        List<String> lengthHeaders = req.headers().getAll(HttpHeaderNames.CONTENT_LENGTH);\n        if (lengthHeaders.size() > 1) {\n            return false;\n        }\n\n        if (lengthHeaders.size() == 1) {\n            try {\n                expectedContentLength = Long.parseLong(lengthHeaders.getFirst());\n            } catch (NumberFormatException e) {\n                return false;\n            }\n            if (expectedContentLength < 0) {\n                return false;\n            }\n        }\n\n        return isContentLengthUnset() || !HttpUtil.isTransferEncodingChunked(req);\n    }\n\n    private boolean validateContent(HttpContent httpContent) {\n        incrementSeenContent(httpContent.content().readableBytes());\n        return isContentLengthUnset() || seenContentLength <= expectedContentLength;\n    }\n\n    private boolean validateEndOfStream() {\n        return isContentLengthUnset() || seenContentLength == expectedContentLength;\n    }\n\n    private void rejectAndRelease(ChannelHandlerContext ctx, Object msg) {\n        // TODO(carl-mastrangelo): this is not right, but meh.  Fix this to return a proper 400.\n        ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR));\n        ReferenceCountUtil.safeRelease(msg);\n    }\n\n    private boolean isContentLengthUnset() {\n        return expectedContentLength == UNSET_CONTENT_LENGTH;\n    }\n\n    private void incrementSeenContent(int length) {\n        seenContentLength = Math.addExact(seenContentLength, length);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.http2.DynamicHttp2FrameLogger;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport com.netflix.zuul.netty.server.psk.TlsPskHandler;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController;\nimport io.netty.handler.codec.http2.Http2Connection;\nimport io.netty.handler.codec.http2.Http2FrameCodec;\nimport io.netty.handler.codec.http2.Http2FrameCodecBuilder;\nimport io.netty.handler.codec.http2.Http2MultiplexHandler;\nimport io.netty.handler.codec.http2.Http2Settings;\nimport io.netty.handler.codec.http2.UniformStreamByteDistributor;\nimport io.netty.handler.logging.LogLevel;\nimport io.netty.handler.ssl.ApplicationProtocolNames;\nimport io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\nimport io.netty.util.AttributeKey;\nimport java.util.function.Consumer;\n\n/**\n * Http2 Or Http Handler\n * <p>\n * Author: Arthur Gonigberg\n * Date: December 15, 2017\n */\npublic class Http2OrHttpHandler extends ApplicationProtocolNegotiationHandler {\n    public static final AttributeKey<String> PROTOCOL_NAME = AttributeKey.valueOf(\"protocol_name\");\n    public static final String PROTOCOL_HTTP_1_1 = \"HTTP/1.1\";\n    public static final String PROTOCOL_HTTP_2 = \"HTTP/2\";\n\n    private static final String FALLBACK_APPLICATION_PROTOCOL = ApplicationProtocolNames.HTTP_1_1;\n\n    private static final DynamicHttp2FrameLogger FRAME_LOGGER =\n            new DynamicHttp2FrameLogger(LogLevel.DEBUG, Http2FrameCodec.class);\n\n    private final ChannelHandler http2StreamHandler;\n    private final int maxConcurrentStreams;\n    private final int initialWindowSize;\n    private final long maxHeaderTableSize;\n    private final long maxHeaderListSize;\n    private final boolean catchConnectionErrors;\n    private final boolean connectProtocolEnabled;\n\n    // controls the number of rst frames that will be sent to a client before closing the connection\n    private final int maxEncoderRstFrames;\n    private final int maxEncoderRstFramesWindow;\n    private final Consumer<ChannelPipeline> addHttpHandlerFn;\n\n    public Http2OrHttpHandler(\n            ChannelHandler http2StreamHandler,\n            ChannelConfig channelConfig,\n            Consumer<ChannelPipeline> addHttpHandlerFn) {\n        super(FALLBACK_APPLICATION_PROTOCOL);\n        this.http2StreamHandler = http2StreamHandler;\n        this.maxConcurrentStreams = channelConfig.get(CommonChannelConfigKeys.maxConcurrentStreams);\n        this.initialWindowSize = channelConfig.get(CommonChannelConfigKeys.initialWindowSize);\n        this.maxHeaderTableSize = channelConfig.get(CommonChannelConfigKeys.maxHttp2HeaderTableSize);\n        this.maxHeaderListSize = channelConfig.get(CommonChannelConfigKeys.maxHttp2HeaderListSize);\n        this.catchConnectionErrors = channelConfig.get(CommonChannelConfigKeys.http2CatchConnectionErrors);\n        this.maxEncoderRstFrames = channelConfig.get(CommonChannelConfigKeys.http2EncoderMaxResetFrames);\n        this.maxEncoderRstFramesWindow = channelConfig.get(CommonChannelConfigKeys.http2EncoderMaxResetFramesWindow);\n        this.connectProtocolEnabled = channelConfig.get(CommonChannelConfigKeys.http2ConnectProtocolEnabled);\n        this.addHttpHandlerFn = addHttpHandlerFn;\n    }\n\n    /**\n     * this method is inspired by ApplicationProtocolNegotiationHandler.userEventTriggered\n     */\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof SslHandshakeCompletionEvent handshakeEvent) {\n            if (handshakeEvent.isSuccess()) {\n                TlsPskHandler tlsPskHandler = ctx.channel().pipeline().get(TlsPskHandler.class);\n                if (tlsPskHandler != null) {\n                    // PSK mode\n                    try {\n                        String tlsPskApplicationProtocol = tlsPskHandler.getApplicationProtocol();\n                        configurePipeline(\n                                ctx,\n                                tlsPskApplicationProtocol != null\n                                        ? tlsPskApplicationProtocol\n                                        : FALLBACK_APPLICATION_PROTOCOL);\n                    } catch (Throwable cause) {\n                        exceptionCaught(ctx, cause);\n                    } finally {\n                        // Handshake failures are handled in exceptionCaught(...).\n                        if (handshakeEvent.isSuccess()) {\n                            removeSelfIfPresent(ctx);\n                        }\n                    }\n                } else {\n                    // non PSK mode\n                    super.userEventTriggered(ctx, evt);\n                }\n            } else {\n                // handshake failures\n                // TODO sunnys - handle PSK handshake failures\n                super.userEventTriggered(ctx, evt);\n            }\n        } else {\n            super.userEventTriggered(ctx, evt);\n        }\n    }\n\n    @Override\n    protected void configurePipeline(ChannelHandlerContext ctx, String protocol) throws Exception {\n        if (protocol.equals(ApplicationProtocolNames.HTTP_2)) {\n            ctx.channel().attr(PROTOCOL_NAME).set(PROTOCOL_HTTP_2);\n            configureHttp2(ctx.pipeline());\n            return;\n        }\n        if (protocol.equals(ApplicationProtocolNames.HTTP_1_1)) {\n            ctx.channel().attr(PROTOCOL_NAME).set(PROTOCOL_HTTP_1_1);\n            configureHttp1(ctx.pipeline());\n            return;\n        }\n\n        throw new IllegalStateException(\"unknown protocol: \" + protocol);\n    }\n\n    private void configureHttp2(ChannelPipeline pipeline) {\n\n        // setup the initial stream settings for the server to use.\n        Http2Settings settings = new Http2Settings()\n                .maxConcurrentStreams(maxConcurrentStreams)\n                .initialWindowSize(initialWindowSize)\n                .headerTableSize(maxHeaderTableSize)\n                .maxHeaderListSize(maxHeaderListSize)\n                .connectProtocolEnabled(connectProtocolEnabled);\n\n        Http2FrameCodec frameCodec = Http2FrameCodecBuilder.forServer()\n                .frameLogger(FRAME_LOGGER)\n                .initialSettings(settings)\n                .validateHeaders(true)\n                .encoderEnforceMaxRstFramesPerWindow(maxEncoderRstFrames, maxEncoderRstFramesWindow)\n                .build();\n        Http2Connection conn = frameCodec.connection();\n        // Use the uniform byte distributor until https://github.com/netty/netty/issues/10525 is fixed.\n        conn.remote()\n                .flowController(new DefaultHttp2RemoteFlowController(conn, new UniformStreamByteDistributor(conn)));\n\n        Http2MultiplexHandler multiplexHandler = new Http2MultiplexHandler(http2StreamHandler);\n\n        // The frame codec MUST be in the pipeline.\n        pipeline.addBefore(\"codec_placeholder\", null, frameCodec);\n        pipeline.replace(\"codec_placeholder\", BaseZuulChannelInitializer.HTTP_CODEC_HANDLER_NAME, multiplexHandler);\n        if (catchConnectionErrors) {\n            pipeline.addLast(new Http2ConnectionErrorHandler());\n        }\n    }\n\n    private void configureHttp1(ChannelPipeline pipeline) {\n        addHttpHandlerFn.accept(pipeline);\n    }\n\n    private void removeSelfIfPresent(ChannelHandlerContext ctx) {\n        ChannelPipeline pipeline = ctx.pipeline();\n        if (!ctx.isRemoved()) {\n            pipeline.remove(this);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2ResetFrameHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport com.netflix.zuul.netty.RequestCancelledEvent;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http2.Http2ResetFrame;\nimport io.netty.util.ReferenceCountUtil;\n\n/**\n * User: michaels@netflix.com\n * Date: 4/13/17\n * Time: 6:02 PM\n */\n@ChannelHandler.Sharable\npublic class Http2ResetFrameHandler extends ChannelInboundHandlerAdapter {\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof Http2ResetFrame) {\n            // Inform zuul to cancel the request.\n            ctx.fireUserEventTriggered(new RequestCancelledEvent());\n            ReferenceCountUtil.safeRelease(msg);\n        } else {\n            super.channelRead(ctx, msg);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2SslChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport com.google.common.base.Preconditions;\nimport com.netflix.netty.common.Http2ConnectionCloseHandler;\nimport com.netflix.netty.common.Http2ConnectionExpiryHandler;\nimport com.netflix.netty.common.SwallowSomeHttp2ExceptionsHandler;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.Http2MetricsChannelHandlers;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.zuul.logging.Http2FrameLoggingPerClientIpHandler;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport com.netflix.zuul.netty.ssl.SslContextFactory;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslHandler;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: Mike Smith\n * Date: 3/5/16\n * Time: 5:41 PM\n */\npublic final class Http2SslChannelInitializer extends BaseZuulChannelInitializer {\n    private static final Logger LOG = LoggerFactory.getLogger(Http2SslChannelInitializer.class);\n    private static final DummyChannelHandler DUMMY_HANDLER = new DummyChannelHandler();\n\n    private final ServerSslConfig serverSslConfig;\n    private final SslContext sslContext;\n    private final boolean isSSlFromIntermediary;\n    private final SwallowSomeHttp2ExceptionsHandler swallowSomeHttp2ExceptionsHandler;\n    private final String http2SslMetricId;\n\n    /**\n     * Use {@link #Http2SslChannelInitializer(String, ChannelConfig, ChannelConfig, ChannelGroup)} instead.\n     */\n    @Deprecated\n    public Http2SslChannelInitializer(\n            int port, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        this(String.valueOf(port), channelConfig, channelDependencies, channels);\n    }\n\n    public Http2SslChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        super(metricId, channelConfig, channelDependencies, channels);\n        this.http2SslMetricId = Preconditions.checkNotNull(metricId, \"metricId\");\n\n        this.swallowSomeHttp2ExceptionsHandler = new SwallowSomeHttp2ExceptionsHandler(registry);\n\n        this.serverSslConfig = channelConfig.get(CommonChannelConfigKeys.serverSslConfig);\n        this.isSSlFromIntermediary = channelConfig.get(CommonChannelConfigKeys.isSSlFromIntermediary);\n\n        SslContextFactory sslContextFactory = channelConfig.get(CommonChannelConfigKeys.sslContextFactory);\n        sslContext = Http2Configuration.configureSSL(sslContextFactory, metricId);\n    }\n\n    @Override\n    protected void initChannel(Channel ch) {\n        SslHandler sslHandler = sslContext.newHandler(ch.alloc());\n        sslHandler.engine().setEnabledProtocols(serverSslConfig.getProtocols());\n\n        //        SSLParameters sslParameters = new SSLParameters();\n        //        AlgorithmConstraints algoConstraints = new AlgorithmConstraints();\n        //        sslParameters.setAlgorithmConstraints(algoConstraints);\n        //        sslParameters.setUseCipherSuitesOrder(true);\n        //        sslHandler.engine().setSSLParameters(sslParameters);\n\n        if (LOG.isDebugEnabled()) {\n            LOG.debug(\n                    \"ssl protocols supported: {}\",\n                    String.join(\", \", sslHandler.engine().getSupportedProtocols()));\n            LOG.debug(\n                    \"ssl protocols enabled: {}\",\n                    String.join(\", \", sslHandler.engine().getEnabledProtocols()));\n\n            LOG.debug(\n                    \"ssl ciphers supported: {}\",\n                    String.join(\", \", sslHandler.engine().getSupportedCipherSuites()));\n            LOG.debug(\n                    \"ssl ciphers enabled: {}\",\n                    String.join(\", \", sslHandler.engine().getEnabledCipherSuites()));\n        }\n\n        // Configure our pipeline of ChannelHandlerS.\n        ChannelPipeline pipeline = ch.pipeline();\n\n        storeChannel(ch);\n        addTimeoutHandlers(pipeline);\n        addPassportHandler(pipeline);\n        addTcpRelatedHandlers(pipeline);\n        pipeline.addLast(new Http2FrameLoggingPerClientIpHandler());\n        pipeline.addLast(\"ssl\", sslHandler);\n        addSslInfoHandlers(pipeline, isSSlFromIntermediary);\n        addSslClientCertChecks(pipeline);\n\n        Http2MetricsChannelHandlers http2MetricsChannelHandlers =\n                new Http2MetricsChannelHandlers(registry, \"server\", \"http2-\" + http2SslMetricId);\n\n        Http2ConnectionCloseHandler connectionCloseHandler = new Http2ConnectionCloseHandler(registry);\n        Http2ConnectionExpiryHandler connectionExpiryHandler = new Http2ConnectionExpiryHandler(\n                maxRequestsPerConnection, maxRequestsPerConnectionInBrownout, connectionExpiry);\n\n        pipeline.addLast(\n                \"http2CodecSwapper\",\n                new Http2OrHttpHandler(\n                        new Http2StreamInitializer(\n                                ch,\n                                this::http1Handlers,\n                                http2MetricsChannelHandlers,\n                                connectionCloseHandler,\n                                connectionExpiryHandler),\n                        channelConfig,\n                        cp -> {\n                            http1Codec(cp);\n                            http1Handlers(cp);\n                        }));\n        pipeline.addLast(\"codec_placeholder\", DUMMY_HANDLER);\n\n        pipeline.addLast(swallowSomeHttp2ExceptionsHandler);\n    }\n\n    private void http1Handlers(ChannelPipeline pipeline) {\n        addHttpRelatedHandlers(pipeline);\n        addZuulHandlers(pipeline);\n    }\n\n    private void http1Codec(ChannelPipeline pipeline) {\n        pipeline.replace(\"codec_placeholder\", HTTP_CODEC_HANDLER_NAME, createHttpServerCodec());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamErrorHandler.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.http2;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.DecoderException;\nimport io.netty.handler.codec.http2.DefaultHttp2ResetFrame;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.handler.codec.http2.Http2Exception;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/7/2018\n */\n@ChannelHandler.Sharable\npublic class Http2StreamErrorHandler extends ChannelInboundHandlerAdapter {\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        if (cause instanceof Http2Exception.StreamException streamEx) {\n\n            ctx.writeAndFlush(new DefaultHttp2ResetFrame(streamEx.error()));\n        } else if (cause instanceof DecoderException) {\n            ctx.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.PROTOCOL_ERROR));\n        } else {\n            super.exceptionCaught(ctx, cause);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamHeaderCleaner.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.HttpRequest;\n\n/**\n * The Http2ServerDowngrader currently is always incorrectly setting the \"x-http2-stream-id\"\n * header to \"0\", which is confusing. And as we don't actually need it and the other \"x-http2-\" headers, we\n * strip them out here to avoid the confusion.\n *\n * Hopefully in a future netty release that header value will be correct and we can then\n * stop doing this. Although potentially we _never_ want to pass these downstream to origins .... ?\n */\n@ChannelHandler.Sharable\npublic class Http2StreamHeaderCleaner extends ChannelInboundHandlerAdapter {\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n        if (msg instanceof HttpRequest req) {\n\n            for (String name : req.headers().names()) {\n                if (name.startsWith(\"x-http2-\")) {\n                    req.headers().remove(name);\n                }\n            }\n        }\n\n        super.channelRead(ctx, msg);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/http2/Http2StreamInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport com.netflix.netty.common.Http2ConnectionCloseHandler;\nimport com.netflix.netty.common.Http2ConnectionExpiryHandler;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.metrics.Http2MetricsChannelHandlers;\nimport com.netflix.netty.common.proxyprotocol.HAProxyMessageChannelHandler;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport com.netflix.zuul.netty.server.Server;\nimport com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http2.Http2StreamFrameToHttpObjectCodec;\nimport io.netty.util.AttributeKey;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * TODO - can this be done when we create the Http2StreamChannelBootstrap instead now?\n */\n@ChannelHandler.Sharable\npublic class Http2StreamInitializer extends ChannelInboundHandlerAdapter {\n\n    private static final Set<AttributeKey<?>> ATTRIBUTES_TO_COPY = Set.of(\n            SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS,\n            SourceAddressChannelHandler.ATTR_LOCAL_INET_ADDR,\n            SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS,\n            SourceAddressChannelHandler.ATTR_REMOTE_ADDR,\n            SourceAddressChannelHandler.ATTR_SOURCE_INET_ADDR,\n            SourceAddressChannelHandler.ATTR_SERVER_LOCAL_ADDRESS,\n            SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT,\n            SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS,\n            Http2OrHttpHandler.PROTOCOL_NAME,\n            SslHandshakeInfoHandler.ATTR_SSL_INFO,\n            HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE,\n            HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION,\n            HAProxyMessageChannelHandler.ATTR_HAPROXY_CUSTOM_TLVS,\n            BaseZuulChannelInitializer.ATTR_CHANNEL_CONFIG,\n            Server.CONN_DIMENSIONS);\n\n    private static final Http2StreamHeaderCleaner http2StreamHeaderCleaner = new Http2StreamHeaderCleaner();\n    private static final Http2ResetFrameHandler http2ResetFrameHandler = new Http2ResetFrameHandler();\n    private static final Http2StreamErrorHandler http2StreamErrorHandler = new Http2StreamErrorHandler();\n\n    private final Channel parent;\n    private final Consumer<ChannelPipeline> addHttpHandlerFn;\n\n    private final Http2MetricsChannelHandlers http2MetricsChannelHandlers;\n    private final Http2ConnectionCloseHandler connectionCloseHandler;\n    private final Http2ConnectionExpiryHandler connectionExpiryHandler;\n\n    public Http2StreamInitializer(\n            Channel parent,\n            Consumer<ChannelPipeline> addHttpHandlerFn,\n            Http2MetricsChannelHandlers http2MetricsChannelHandlers,\n            Http2ConnectionCloseHandler connectionCloseHandler,\n            Http2ConnectionExpiryHandler connectionExpiryHandler) {\n        this.parent = parent;\n        this.addHttpHandlerFn = addHttpHandlerFn;\n\n        this.http2MetricsChannelHandlers = http2MetricsChannelHandlers;\n        this.connectionCloseHandler = connectionCloseHandler;\n        this.connectionExpiryHandler = connectionExpiryHandler;\n    }\n\n    @Override\n    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {\n        copyAttrsFromParentChannel(this.parent, ctx.channel());\n        addHttp2MetricsHandlers(ctx.pipeline());\n        addHttp2StreamSpecificHandlers(ctx.pipeline());\n        addHttpHandlerFn.accept(ctx.pipeline());\n\n        ctx.pipeline().remove(this);\n    }\n\n    protected void addHttp2StreamSpecificHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(\"h2_max_requests_per_conn\", connectionExpiryHandler);\n        pipeline.addLast(\"h2_conn_close\", connectionCloseHandler);\n\n        pipeline.addLast(http2ResetFrameHandler);\n        pipeline.addLast(\"h2_downgrader\", new Http2StreamFrameToHttpObjectCodec(true));\n        pipeline.addLast(http2StreamErrorHandler);\n        pipeline.addLast(http2StreamHeaderCleaner);\n        pipeline.addLast(new Http2ContentLengthEnforcingHandler());\n    }\n\n    protected void addHttp2MetricsHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(\"h2_metrics_inbound\", http2MetricsChannelHandlers.inbound());\n        pipeline.addLast(\"h2_metrics_outbound\", http2MetricsChannelHandlers.outbound());\n    }\n\n    protected void copyAttrsFromParentChannel(Channel parent, Channel child) {\n        for (AttributeKey<?> key : ATTRIBUTES_TO_COPY) {\n            copyAttributesFromParentChannel(parent, child, key);\n        }\n    }\n\n    protected <T> void copyAttributesFromParentChannel(Channel parent, Channel child, AttributeKey<T> key) {\n        child.attr(key).set(parent.attr(key).get());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ClientPSKIdentityInfo.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.netty.server.psk;\n\nimport java.util.List;\n\npublic record ClientPSKIdentityInfo(List<Byte> clientPSKIdentity) {}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ExternalTlsPskProvider.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.psk;\n\npublic interface ExternalTlsPskProvider {\n    byte[] provide(byte[] clientPskIdentity, byte[] clientRandom) throws PskCreationFailureException;\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/PskCreationFailureException.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.netty.server.psk;\n\npublic class PskCreationFailureException extends Exception {\n\n    public enum TlsAlertMessage {\n        /**\n         * The server does not recognize the (client) PSK identity\n         */\n        unknown_psk_identity,\n        /**\n         * The (client) PSK identity existed but the key was incorrect\n         */\n        decrypt_error,\n    }\n\n    private final TlsAlertMessage tlsAlertMessage;\n\n    public PskCreationFailureException(TlsAlertMessage tlsAlertMessage, String message) {\n        super(message);\n        this.tlsAlertMessage = tlsAlertMessage;\n    }\n\n    public PskCreationFailureException(TlsAlertMessage tlsAlertMessage, String message, Throwable cause) {\n        super(message, cause);\n        this.tlsAlertMessage = tlsAlertMessage;\n    }\n\n    public TlsAlertMessage getTlsAlertMessage() {\n        return tlsAlertMessage;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskDecoder.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.psk;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.ByteToMessageDecoder;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\nimport java.util.List;\nimport org.bouncycastle.tls.TlsFatalAlert;\n\npublic class TlsPskDecoder extends ByteToMessageDecoder {\n\n    private final TlsPskServerProtocol tlsPskServerProtocol;\n\n    public TlsPskDecoder(TlsPskServerProtocol tlsPskServerProtocol) {\n        this.tlsPskServerProtocol = tlsPskServerProtocol;\n    }\n\n    @Override\n    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {\n        byte[] bytesRead = in.hasArray() ? in.array() : TlsPskUtils.readDirect(in);\n        try {\n            tlsPskServerProtocol.offerInput(bytesRead);\n        } catch (TlsFatalAlert tlsFatalAlert) {\n            writeOutputIfAvailable(ctx);\n            ctx.fireUserEventTriggered(new SslHandshakeCompletionEvent(tlsFatalAlert));\n            ctx.close();\n            return;\n        }\n        writeOutputIfAvailable(ctx);\n        int appDataAvailable = tlsPskServerProtocol.getAvailableInputBytes();\n        if (appDataAvailable > 0) {\n            byte[] appData = new byte[appDataAvailable];\n            tlsPskServerProtocol.readInput(appData, 0, appDataAvailable);\n            out.add(Unpooled.wrappedBuffer(appData));\n        }\n    }\n\n    private void writeOutputIfAvailable(ChannelHandlerContext ctx) {\n        int availableOutputBytes = tlsPskServerProtocol.getAvailableOutputBytes();\n        // output is available immediately (handshake not complete), pipe that back to the client right away\n        if (availableOutputBytes != 0) {\n            byte[] outputBytes = new byte[availableOutputBytes];\n            tlsPskServerProtocol.readOutput(outputBytes, 0, availableOutputBytes);\n            ctx.writeAndFlush(Unpooled.wrappedBuffer(outputBytes))\n                    .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskHandler.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.psk;\n\nimport com.netflix.spectator.api.Registry;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.ReferenceCountUtil;\nimport java.security.SecureRandom;\nimport java.util.Map;\nimport java.util.Set;\nimport javax.net.ssl.SSLSession;\nimport org.bouncycastle.tls.CipherSuite;\nimport org.bouncycastle.tls.ProtocolName;\nimport org.bouncycastle.tls.crypto.impl.jcajce.JcaTlsCryptoProvider;\n\npublic class TlsPskHandler extends ChannelDuplexHandler {\n\n    public static final Map<Integer, String> SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP = Map.of(\n            CipherSuite.TLS_AES_128_GCM_SHA256,\n            \"TLS_AES_128_GCM_SHA256\",\n            CipherSuite.TLS_AES_256_GCM_SHA384,\n            \"TLS_AES_256_GCM_SHA384\");\n    public static final AttributeKey<ClientPSKIdentityInfo> CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY =\n            AttributeKey.newInstance(\"_client_psk_identity_info\");\n    public static final SecureRandom secureRandom = new SecureRandom();\n\n    private final Registry registry;\n    private final ExternalTlsPskProvider externalTlsPskProvider;\n    private final Set<ProtocolName> supportedApplicationProtocols;\n    private final TlsPskServerProtocol tlsPskServerProtocol;\n\n    private ZuulPskServer tlsPskServer;\n\n    public TlsPskHandler(\n            Registry registry,\n            ExternalTlsPskProvider externalTlsPskProvider,\n            Set<ProtocolName> supportedApplicationProtocols) {\n        super();\n        this.registry = registry;\n        this.externalTlsPskProvider = externalTlsPskProvider;\n        this.supportedApplicationProtocols = supportedApplicationProtocols;\n        this.tlsPskServerProtocol = new TlsPskServerProtocol();\n    }\n\n    @Override\n    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n        if (!(msg instanceof ByteBuf byteBufMsg)) {\n            ReferenceCountUtil.safeRelease(msg);\n            promise.setFailure(\n                    new IllegalStateException(\"Failed to write message on the channel. Message is not a ByteBuf\"));\n            return;\n        }\n        byte[] appDataBytes = TlsPskUtils.getAppDataBytesAndRelease(byteBufMsg);\n        tlsPskServerProtocol.writeApplicationData(appDataBytes, 0, appDataBytes.length);\n        int availableOutputBytes = tlsPskServerProtocol.getAvailableOutputBytes();\n        if (availableOutputBytes != 0) {\n            byte[] outputBytes = new byte[availableOutputBytes];\n            tlsPskServerProtocol.readOutput(outputBytes, 0, availableOutputBytes);\n            ctx.writeAndFlush(Unpooled.wrappedBuffer(outputBytes), promise)\n                    .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);\n        }\n    }\n\n    @Override\n    public void handlerAdded(ChannelHandlerContext ctx) {\n        ctx.pipeline().addBefore(ctx.name(), \"tls_psk_handler\", new TlsPskDecoder(tlsPskServerProtocol));\n    }\n\n    @Override\n    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {\n        tlsPskServer = new ZuulPskServer(\n                new JcaTlsCryptoProvider().create(secureRandom),\n                registry,\n                externalTlsPskProvider,\n                ctx,\n                supportedApplicationProtocols);\n        tlsPskServerProtocol.accept(tlsPskServer);\n        super.channelRegistered(ctx);\n    }\n\n    /**\n     * Returns the name of the current application-level protocol.\n     * Returns:\n     * the protocol name or null if application-level protocol has not been negotiated\n     */\n    public String getApplicationProtocol() {\n        return tlsPskServer != null ? tlsPskServer.getApplicationProtocol() : null;\n    }\n\n    public SSLSession getSession() {\n        return tlsPskServerProtocol != null ? tlsPskServerProtocol.getSSLSession() : null;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskServerProtocol.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.psk;\n\nimport java.security.Principal;\nimport java.security.cert.Certificate;\nimport javax.net.ssl.SSLSession;\nimport javax.net.ssl.SSLSessionContext;\nimport javax.security.cert.X509Certificate;\nimport org.bouncycastle.tls.TlsServerProtocol;\n\npublic class TlsPskServerProtocol extends TlsServerProtocol {\n\n    public SSLSession getSSLSession() {\n        return new SSLSession() {\n            @Override\n            public byte[] getId() {\n                return tlsSession.getSessionID();\n            }\n\n            @Override\n            public SSLSessionContext getSessionContext() {\n                return null;\n            }\n\n            @Override\n            public long getCreationTime() {\n                return 0;\n            }\n\n            @Override\n            public long getLastAccessedTime() {\n                return 0;\n            }\n\n            @Override\n            public void invalidate() {}\n\n            @Override\n            public boolean isValid() {\n                return !isClosed();\n            }\n\n            @Override\n            public void putValue(String name, Object value) {}\n\n            @Override\n            public Object getValue(String name) {\n                return null;\n            }\n\n            @Override\n            public void removeValue(String name) {}\n\n            @Override\n            public String[] getValueNames() {\n                return new String[0];\n            }\n\n            @Override\n            public Certificate[] getPeerCertificates() {\n                return new Certificate[0];\n            }\n\n            @Override\n            public Certificate[] getLocalCertificates() {\n                return new Certificate[0];\n            }\n\n            @Override\n            @SuppressWarnings(\"removal\")\n            public X509Certificate[] getPeerCertificateChain() {\n                return new X509Certificate[0];\n            }\n\n            @Override\n            public Principal getPeerPrincipal() {\n                return null;\n            }\n\n            @Override\n            public Principal getLocalPrincipal() {\n                return null;\n            }\n\n            @Override\n            public String getCipherSuite() {\n                return TlsPskHandler.SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.get(\n                        getContext().getSecurityParameters().getCipherSuite());\n            }\n\n            @Override\n            public String getProtocol() {\n                return getContext().getServerVersion().getName();\n            }\n\n            @Override\n            public String getPeerHost() {\n                return null;\n            }\n\n            @Override\n            public int getPeerPort() {\n                return 0;\n            }\n\n            @Override\n            public int getPacketBufferSize() {\n                return 0;\n            }\n\n            @Override\n            public int getApplicationBufferSize() {\n                return 0;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/TlsPskUtils.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.psk;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.util.ReferenceCountUtil;\n\nclass TlsPskUtils {\n    protected static byte[] readDirect(ByteBuf byteBufMsg) {\n        int length = byteBufMsg.readableBytes();\n        byte[] dest = new byte[length];\n        byteBufMsg.readBytes(dest);\n        return dest;\n    }\n\n    protected static byte[] getAppDataBytesAndRelease(ByteBuf byteBufMsg) {\n        byte[] appDataBytes = byteBufMsg.hasArray() ? byteBufMsg.array() : TlsPskUtils.readDirect(byteBufMsg);\n        ReferenceCountUtil.safeRelease(byteBufMsg);\n        return appDataBytes;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/psk/ZuulPskServer.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.psk;\n\nimport com.google.common.primitives.Bytes;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Timer;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.ssl.SslCloseCompletionEvent;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\nimport io.netty.util.AttributeKey;\nimport java.io.IOException;\nimport java.util.Hashtable;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.Vector;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\nimport lombok.SneakyThrows;\nimport org.bouncycastle.tls.AbstractTlsServer;\nimport org.bouncycastle.tls.AlertDescription;\nimport org.bouncycastle.tls.AlertLevel;\nimport org.bouncycastle.tls.BasicTlsPSKExternal;\nimport org.bouncycastle.tls.CipherSuite;\nimport org.bouncycastle.tls.PRFAlgorithm;\nimport org.bouncycastle.tls.ProtocolName;\nimport org.bouncycastle.tls.ProtocolVersion;\nimport org.bouncycastle.tls.PskIdentity;\nimport org.bouncycastle.tls.TlsCredentials;\nimport org.bouncycastle.tls.TlsFatalAlert;\nimport org.bouncycastle.tls.TlsPSKExternal;\nimport org.bouncycastle.tls.TlsUtils;\nimport org.bouncycastle.tls.crypto.TlsCrypto;\nimport org.bouncycastle.tls.crypto.TlsSecret;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class ZuulPskServer extends AbstractTlsServer {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ZuulPskServer.class);\n\n    public static final AttributeKey<Boolean> TLS_HANDSHAKE_USING_EXTERNAL_PSK =\n            AttributeKey.newInstance(\"_tls_handshake_using_external_psk\");\n\n    private static class PSKTimings {\n        private final Timer handshakeCompleteTimer;\n\n        private Long handshakeStartTime;\n\n        PSKTimings(Registry registry) {\n            handshakeCompleteTimer = registry.timer(\"zuul.psk.handshake.complete.time\");\n        }\n\n        public void recordHandshakeStarting() {\n            handshakeStartTime = System.nanoTime();\n        }\n\n        public void recordHandshakeComplete() {\n            handshakeCompleteTimer.record(System.nanoTime() - handshakeStartTime, TimeUnit.NANOSECONDS);\n        }\n    }\n\n    private final PSKTimings pskTimings;\n\n    private final ExternalTlsPskProvider externalTlsPskProvider;\n\n    private final ChannelHandlerContext ctx;\n\n    private final Set<ProtocolName> supportedApplicationProtocols;\n\n    public ZuulPskServer(\n            TlsCrypto crypto,\n            Registry registry,\n            ExternalTlsPskProvider externalTlsPskProvider,\n            ChannelHandlerContext ctx,\n            Set<ProtocolName> supportedApplicationProtocols) {\n        super(crypto);\n        this.pskTimings = new PSKTimings(registry);\n        this.externalTlsPskProvider = externalTlsPskProvider;\n        this.ctx = ctx;\n        this.supportedApplicationProtocols = supportedApplicationProtocols;\n    }\n\n    @Override\n    public TlsCredentials getCredentials() {\n        return null;\n    }\n\n    @Override\n    protected Vector getProtocolNames() {\n        Vector protocolNames = new Vector();\n        if (supportedApplicationProtocols != null) {\n            supportedApplicationProtocols.forEach(protocolNames::addElement);\n        }\n        return protocolNames;\n    }\n\n    @Override\n    public void notifyHandshakeBeginning() throws IOException {\n        pskTimings.recordHandshakeStarting();\n        this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(false);\n        // TODO: sunnys - handshake timeouts\n        super.notifyHandshakeBeginning();\n    }\n\n    @Override\n    public void notifyHandshakeComplete() throws IOException {\n        pskTimings.recordHandshakeComplete();\n        this.ctx.channel().attr(TLS_HANDSHAKE_USING_EXTERNAL_PSK).set(true);\n        super.notifyHandshakeComplete();\n        ctx.fireUserEventTriggered(SslHandshakeCompletionEvent.SUCCESS);\n    }\n\n    @Override\n    protected ProtocolVersion[] getSupportedVersions() {\n        return ProtocolVersion.TLSv13.only();\n    }\n\n    @Override\n    protected int[] getSupportedCipherSuites() {\n        return TlsUtils.getSupportedCipherSuites(\n                getCrypto(),\n                TlsPskHandler.SUPPORTED_TLS_PSK_CIPHER_SUITE_MAP.keySet().stream()\n                        .mapToInt(Number::intValue)\n                        .toArray());\n    }\n\n    @Override\n    public ProtocolVersion getServerVersion() throws IOException {\n        return super.getServerVersion();\n    }\n\n    /**\n     * TODO: Ask BC folks to see if getExternalPSK can throw a checked exception\n     * https://github.com/bcgit/bc-java/issues/1673\n     * We are using SneakyThrows here because getExternalPSK is an override and we can't have throws in the method signature\n     * and we dont want to catch and wrap in RuntimeException.\n     * SneakyThrows allows up to compile and it will throw the exception at runtime.\n     */\n    @Override\n    @SneakyThrows\n    public TlsPSKExternal getExternalPSK(Vector clientPskIdentities) {\n        byte[] clientPskIdentity = ((PskIdentity) clientPskIdentities.get(0)).getIdentity();\n        byte[] psk;\n        try {\n            this.ctx\n                    .channel()\n                    .attr(TlsPskHandler.CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY)\n                    .set(new ClientPSKIdentityInfo(List.copyOf(Bytes.asList(clientPskIdentity))));\n            psk = externalTlsPskProvider.provide(\n                    clientPskIdentity,\n                    this.context.getSecurityParametersHandshake().getClientRandom());\n        } catch (PskCreationFailureException e) {\n            throw switch (e.getTlsAlertMessage()) {\n                case unknown_psk_identity ->\n                    new TlsFatalAlert(AlertDescription.unknown_psk_identity, \"Unknown or null client PSk identity\");\n                case decrypt_error ->\n                    new TlsFatalAlert(AlertDescription.decrypt_error, \"Invalid or expired client PSk identity\");\n            };\n        }\n        TlsSecret pskTlsSecret = getCrypto().createSecret(psk);\n        int prfAlgorithm = getPRFAlgorithm13(getSelectedCipherSuite());\n        return new BasicTlsPSKExternal(clientPskIdentity, pskTlsSecret, prfAlgorithm);\n    }\n\n    @Override\n    public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Throwable cause) {\n        super.notifyAlertRaised(alertLevel, alertDescription, message, cause);\n        Consumer<String> loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug;\n        loggerFunc.accept(\"TLS/PSK server raised alert: \" + AlertLevel.getText(alertLevel) + \", \"\n                + AlertDescription.getText(alertDescription));\n        if (message != null) {\n            loggerFunc.accept(\"> \" + message);\n        }\n        if (cause != null) {\n            LOGGER.error(\"TLS/PSK alert stacktrace\", cause);\n        }\n\n        if (alertDescription == AlertDescription.close_notify) {\n            ctx.fireUserEventTriggered(SslCloseCompletionEvent.SUCCESS);\n        }\n    }\n\n    @Override\n    public void notifyAlertReceived(short alertLevel, short alertDescription) {\n        Consumer<String> loggerFunc = (alertLevel == AlertLevel.fatal) ? LOGGER::error : LOGGER::debug;\n        loggerFunc.accept(\"TLS 1.3 PSK server received alert: \" + AlertLevel.getText(alertLevel) + \", \"\n                + AlertDescription.getText(alertDescription));\n    }\n\n    @Override\n    public void processClientExtensions(Hashtable clientExtensions) throws IOException {\n        if (context.getSecurityParametersHandshake().getClientRandom() == null) {\n            throw new TlsFatalAlert(AlertDescription.internal_error);\n        }\n        super.processClientExtensions(clientExtensions);\n    }\n\n    @Override\n    public Hashtable getServerExtensions() throws IOException {\n        if (context.getSecurityParametersHandshake().getServerRandom() == null) {\n            throw new TlsFatalAlert(AlertDescription.internal_error);\n        }\n        return super.getServerExtensions();\n    }\n\n    @Override\n    public void getServerExtensionsForConnection(Hashtable serverExtensions) throws IOException {\n        if (context.getSecurityParametersHandshake().getServerRandom() == null) {\n            throw new TlsFatalAlert(AlertDescription.internal_error);\n        }\n        super.getServerExtensionsForConnection(serverExtensions);\n    }\n\n    public String getApplicationProtocol() {\n        ProtocolName protocolName = context.getSecurityParametersConnection().getApplicationProtocol();\n        if (protocolName != null) {\n            return protocolName.getUtf8Decoding();\n        }\n        return null;\n    }\n\n    private static int getPRFAlgorithm13(int cipherSuite) {\n        return switch (cipherSuite) {\n            case CipherSuite.TLS_AES_128_CCM_SHA256,\n                    CipherSuite.TLS_AES_128_CCM_8_SHA256,\n                    CipherSuite.TLS_AES_128_GCM_SHA256,\n                    CipherSuite.TLS_CHACHA20_POLY1305_SHA256 -> PRFAlgorithm.tls13_hkdf_sha256;\n            case CipherSuite.TLS_AES_256_GCM_SHA384 -> PRFAlgorithm.tls13_hkdf_sha384;\n            case CipherSuite.TLS_SM4_CCM_SM3, CipherSuite.TLS_SM4_GCM_SM3 -> PRFAlgorithm.tls13_hkdf_sm3;\n            default -> -1;\n        };\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushAuthHandler.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport com.google.common.base.Strings;\nimport com.netflix.zuul.message.http.Cookies;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.FullHttpResponse;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpUtil;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.handler.codec.http.cookie.ServerCookieDecoder;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Objects;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/11/18\n */\n@ChannelHandler.Sharable\npublic abstract class PushAuthHandler extends SimpleChannelInboundHandler<FullHttpRequest> {\n\n    private final String pushConnectionPath;\n    private final String originDomain;\n\n    public static final String NAME = \"push_auth_handler\";\n    private static final Logger logger = LoggerFactory.getLogger(PushAuthHandler.class);\n\n    public PushAuthHandler(String pushConnectionPath, String originDomain) {\n        this.pushConnectionPath = pushConnectionPath;\n        this.originDomain = originDomain;\n    }\n\n    public final void sendHttpResponse(HttpRequest req, ChannelHandlerContext ctx, HttpResponseStatus status) {\n        FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);\n        resp.headers().add(\"Content-Length\", \"0\");\n        boolean closeConn = (!Objects.equals(status, HttpResponseStatus.OK) || !HttpUtil.isKeepAlive(req));\n        if (closeConn) {\n            resp.headers().add(HttpHeaderNames.CONNECTION, \"Close\");\n        }\n        ChannelFuture cf = ctx.channel().writeAndFlush(resp);\n        if (closeConn) {\n            cf.addListener(ChannelFutureListener.CLOSE);\n        }\n    }\n\n    @Override\n    protected final void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) {\n        if (!Objects.equals(req.method(), HttpMethod.GET)) {\n            sendHttpResponse(req, ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);\n            return;\n        }\n\n        String path = req.uri();\n        if (Objects.equals(path, \"/healthcheck\")) {\n            sendHttpResponse(req, ctx, HttpResponseStatus.OK);\n        } else if (pushConnectionPath.equals(path)) {\n            // CSRF protection\n            if (isInvalidOrigin(req)) {\n                sendHttpResponse(req, ctx, HttpResponseStatus.BAD_REQUEST);\n            } else if (isDelayedAuth(req, ctx)) {\n                // client auth will happen later, continue with WebSocket upgrade handshake\n                ctx.fireChannelRead(req.retain());\n            } else {\n                PushUserAuth authEvent = doAuth(req, ctx);\n                if (authEvent.isSuccess()) {\n                    ctx.fireChannelRead(req.retain()); // continue with WebSocket upgrade handshake\n                    ctx.fireUserEventTriggered(authEvent);\n                } else {\n                    logger.warn(\"Auth failed: {}\", authEvent.statusCode());\n                    sendHttpResponse(req, ctx, HttpResponseStatus.valueOf(authEvent.statusCode()));\n                }\n            }\n        } else {\n            sendHttpResponse(req, ctx, HttpResponseStatus.NOT_FOUND);\n        }\n    }\n\n    protected boolean isInvalidOrigin(FullHttpRequest req) {\n        String origin = req.headers().get(HttpHeaderNames.ORIGIN);\n        if (origin == null || !origin.toLowerCase(Locale.ROOT).endsWith(originDomain)) {\n            logger.error(\"Invalid Origin header {} in WebSocket upgrade request\", origin);\n            return true;\n        }\n        return false;\n    }\n\n    protected final Cookies parseCookies(FullHttpRequest req) {\n        Cookies cookies = new Cookies();\n        String cookieStr = req.headers().get(HttpHeaderNames.COOKIE);\n        if (!Strings.isNullOrEmpty(cookieStr)) {\n            List<Cookie> decoded = ServerCookieDecoder.LAX.decodeAll(cookieStr);\n            decoded.forEach(cookies::add);\n        }\n        return cookies;\n    }\n\n    /**\n     * @return true if Auth credentials will be provided later, for example in first WebSocket frame sent\n     */\n    protected abstract boolean isDelayedAuth(FullHttpRequest req, ChannelHandlerContext ctx);\n\n    protected abstract PushUserAuth doAuth(FullHttpRequest req, ChannelHandlerContext ctx);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushChannelInitializer.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/15/18\n */\npublic abstract class PushChannelInitializer extends BaseZuulChannelInitializer {\n\n    /**\n     * Use {@link #PushChannelInitializer(String, ChannelConfig, ChannelConfig, ChannelGroup)} instead.\n     */\n    @Deprecated\n    public PushChannelInitializer(\n            int port, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        this(String.valueOf(port), channelConfig, channelDependencies, channels);\n    }\n\n    protected PushChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        super(metricId, channelConfig, channelDependencies, channels);\n    }\n\n    @Override\n    protected void addHttp1Handlers(ChannelPipeline pipeline) {\n        pipeline.addLast(\n                HTTP_CODEC_HANDLER_NAME,\n                new HttpServerCodec(MAX_INITIAL_LINE_LENGTH.get(), MAX_HEADER_SIZE.get(), MAX_CHUNK_SIZE.get(), false));\n        pipeline.addLast(new HttpObjectAggregator(8192));\n    }\n\n    @Override\n    protected void addHttpRelatedHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(stripInboundProxyHeadersHandler);\n    }\n\n    @Override\n    protected void initChannel(Channel ch) throws Exception {\n        ChannelPipeline pipeline = ch.pipeline();\n        storeChannel(ch);\n        addTcpRelatedHandlers(pipeline);\n        addHttp1Handlers(pipeline);\n        addHttpRelatedHandlers(pipeline);\n        addPushHandlers(pipeline);\n    }\n\n    protected abstract void addPushHandlers(ChannelPipeline pipeline);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushClientProtocolHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.push;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\n\n/**\n * Author: Susheel Aroskar\n * Date: 11/2/2018\n */\npublic class PushClientProtocolHandler extends ChannelInboundHandlerAdapter {\n\n    protected PushUserAuth authEvent;\n\n    protected boolean isAuthenticated() {\n        return (authEvent != null && authEvent.isSuccess());\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof PushUserAuth) {\n            authEvent = (PushUserAuth) evt;\n        }\n        super.userEventTriggered(ctx, evt);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushConnection.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.push;\n\nimport com.google.common.base.Charsets;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.WebSocketCloseStatus;\n\n/**\n * Author: Susheel Aroskar\n * Date:\n */\npublic class PushConnection {\n\n    private final PushProtocol pushProtocol;\n    private final ChannelHandlerContext ctx;\n    private String secureToken;\n\n    // Token bucket implementation state.\n    private double tkBktAllowance;\n    private long tkBktLastCheckTime;\n    public static final CachedDynamicIntProperty TOKEN_BUCKET_RATE =\n            new CachedDynamicIntProperty(\"zuul.push.tokenBucket.rate\", 3);\n    public static final CachedDynamicIntProperty TOKEN_BUCKET_WINDOW =\n            new CachedDynamicIntProperty(\"zuul.push.tokenBucket.window.millis\", 2000);\n\n    public PushConnection(PushProtocol pushProtocol, ChannelHandlerContext ctx) {\n        this.pushProtocol = pushProtocol;\n        this.ctx = ctx;\n        tkBktAllowance = TOKEN_BUCKET_RATE.get();\n        tkBktLastCheckTime = System.currentTimeMillis();\n    }\n\n    public String getSecureToken() {\n        return secureToken;\n    }\n\n    public void setSecureToken(String secureToken) {\n        this.secureToken = secureToken;\n    }\n\n    /**\n     * Implementation of TokenBucket algorithm to do rate limiting: http://stackoverflow.com/a/668327\n     * @return true if should be rate limited, false if it is OK to send the message\n     */\n    public synchronized boolean isRateLimited() {\n        double rate = TOKEN_BUCKET_RATE.get();\n        double window = TOKEN_BUCKET_WINDOW.get();\n        long current = System.currentTimeMillis();\n        double timePassed = current - tkBktLastCheckTime;\n\n        tkBktLastCheckTime = current;\n        tkBktAllowance = tkBktAllowance + timePassed * (rate / window);\n\n        if (tkBktAllowance > rate) {\n            tkBktAllowance = rate; // cap max to rate\n        }\n\n        if (tkBktAllowance < 1.0) {\n            return true;\n        }\n\n        tkBktAllowance = tkBktAllowance - 1.0;\n        return false;\n    }\n\n    public ChannelFuture sendPushMessage(ByteBuf mesg) {\n        return pushProtocol.sendPushMessage(ctx, mesg);\n    }\n\n    public ChannelFuture sendPushMessage(String mesg) {\n        return sendPushMessage(Unpooled.copiedBuffer(mesg, Charsets.UTF_8));\n    }\n\n    public ChannelFuture sendPing() {\n        return pushProtocol.sendPing(ctx);\n    }\n\n    public void closeConnection(WebSocketCloseStatus status, String message) {\n        ctx.writeAndFlush(new CloseWebSocketFrame(status, message)).addListener(ChannelFutureListener.CLOSE);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushConnectionRegistry.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport java.security.SecureRandom;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport javax.annotation.Nullable;\n\n/**\n * Maintains client identity to web socket or SSE channel mapping.\n *\n * Created by saroskar on 9/26/16.\n */\n@Singleton\npublic class PushConnectionRegistry {\n\n    private final ConcurrentMap<String, PushConnection> clientPushConnectionMap;\n    private final SecureRandom secureTokenGenerator;\n\n    @Inject\n    public PushConnectionRegistry() {\n        clientPushConnectionMap = new ConcurrentHashMap<>(1024 * 32);\n        secureTokenGenerator = new SecureRandom();\n    }\n\n    @Nullable\n    public PushConnection get(String clientId) {\n        return clientPushConnectionMap.get(clientId);\n    }\n\n    public List<PushConnection> getAll() {\n        return new ArrayList<>(clientPushConnectionMap.values());\n    }\n\n    public Map<String, PushConnection> getAllEntries() {\n        return Collections.unmodifiableMap(clientPushConnectionMap);\n    }\n\n    public String mintNewSecureToken() {\n        byte[] tokenBuffer = new byte[15];\n        secureTokenGenerator.nextBytes(tokenBuffer);\n        return Base64.getUrlEncoder().encodeToString(tokenBuffer);\n    }\n\n    public void put(String clientId, PushConnection pushConnection) {\n        pushConnection.setSecureToken(mintNewSecureToken());\n        clientPushConnectionMap.put(clientId, pushConnection);\n    }\n\n    public PushConnection remove(String clientId) {\n        PushConnection pc = clientPushConnectionMap.remove(clientId);\n        return pc;\n    }\n\n    public int size() {\n        return clientPushConnectionMap.size();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.push;\n\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\n\n/**\n * Author: Susheel Aroskar\n * Date: 11/2/2018\n */\npublic abstract class PushMessageFactory {\n\n    public final void sendErrorAndClose(ChannelHandlerContext ctx, int statusCode, String reasonText) {\n        ctx.writeAndFlush(serverClosingConnectionMessage(statusCode, reasonText))\n                .addListener(ChannelFutureListener.CLOSE);\n    }\n\n    /**\n     * Application level protocol for asking client to close connection\n     * @return WebSocketFrame which when sent to client will cause it to close the WebSocket\n     */\n    protected abstract Object goAwayMessage();\n\n    /**\n     * Message server sends to the client just before it force closes connection from its side\n     */\n    protected abstract Object serverClosingConnectionMessage(int statusCode, String reasonText);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageSender.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport com.google.common.base.Strings;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.FullHttpResponse;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpUtil;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.util.ReferenceCountUtil;\nimport jakarta.inject.Inject;\nimport java.util.Objects;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Serves \"/push\" URL that is used by the backend to POST push messages to a given Zuul instance. This URL handler\n * MUST BE accessible ONLY from RFC 1918 private internal network space (10.0.0.0 or 172.16.0.0) to guarantee that\n * external applications/agents cannot push messages to your client. In AWS this can typically be achieved using\n * correctly configured security groups.\n *\n * Author: Susheel Aroskar\n * Date: 5/14/18\n */\n@ChannelHandler.Sharable\npublic abstract class PushMessageSender extends SimpleChannelInboundHandler<FullHttpRequest> {\n\n    private final PushConnectionRegistry pushConnectionRegistry;\n\n    public static final String SECURE_TOKEN_HEADER_NAME = \"X-Zuul.push.secure.token\";\n    private static final Logger logger = LoggerFactory.getLogger(PushMessageSender.class);\n\n    @Inject\n    public PushMessageSender(PushConnectionRegistry pushConnectionRegistry) {\n        this.pushConnectionRegistry = pushConnectionRegistry;\n    }\n\n    protected void sendHttpResponse(\n            ChannelHandlerContext ctx, FullHttpRequest request, HttpResponseStatus status, PushUserAuth userAuth) {\n        FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);\n        resp.headers().add(\"Content-Length\", \"0\");\n        ChannelFuture cf = ctx.channel().writeAndFlush(resp);\n        if (!HttpUtil.isKeepAlive(request)) {\n            cf.addListener(ChannelFutureListener.CLOSE);\n        }\n        logPushEvent(request, status, userAuth);\n    }\n\n    protected boolean verifySecureToken(FullHttpRequest request, PushConnection conn) {\n        String secureToken = request.headers().get(SECURE_TOKEN_HEADER_NAME);\n        if (Strings.isNullOrEmpty(secureToken)) {\n            // caller is not asking to verify secure token\n            return true;\n        }\n        return secureToken.equals(conn.getSecureToken());\n    }\n\n    @Override\n    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {\n        if (!request.decoderResult().isSuccess()) {\n            sendHttpResponse(ctx, request, HttpResponseStatus.BAD_REQUEST, null);\n            return;\n        }\n\n        String path = request.uri();\n        if (path == null) {\n            sendHttpResponse(ctx, request, HttpResponseStatus.BAD_REQUEST, null);\n            return;\n        }\n\n        if (path.endsWith(\"/push\")) {\n            logPushAttempt();\n\n            HttpMethod method = request.method();\n            if (!Objects.equals(method, HttpMethod.POST) && !Objects.equals(method, HttpMethod.GET)) {\n                sendHttpResponse(ctx, request, HttpResponseStatus.METHOD_NOT_ALLOWED, null);\n                return;\n            }\n\n            PushUserAuth userAuth = getPushUserAuth(request);\n            if (!userAuth.isSuccess()) {\n                sendHttpResponse(ctx, request, HttpResponseStatus.UNAUTHORIZED, userAuth);\n                logNoIdentity();\n                return;\n            }\n\n            PushConnection pushConn = pushConnectionRegistry.get(userAuth.getClientIdentity());\n            if (pushConn == null) {\n                sendHttpResponse(ctx, request, HttpResponseStatus.NOT_FOUND, userAuth);\n                logClientNotConnected();\n                return;\n            }\n\n            if (!verifySecureToken(request, pushConn)) {\n                sendHttpResponse(ctx, request, HttpResponseStatus.FORBIDDEN, userAuth);\n                logSecurityTokenVerificationFail();\n                return;\n            }\n\n            if (Objects.equals(method, HttpMethod.GET)) {\n                // client only checking if particular CID + ESN is connected to this instance\n                sendHttpResponse(ctx, request, HttpResponseStatus.OK, userAuth);\n                return;\n            }\n\n            if (pushConn.isRateLimited()) {\n                sendHttpResponse(ctx, request, HttpResponseStatus.SERVICE_UNAVAILABLE, userAuth);\n                logRateLimited();\n                return;\n            }\n\n            ByteBuf body = request.content().retain();\n            if (body.readableBytes() <= 0) {\n                sendHttpResponse(ctx, request, HttpResponseStatus.NO_CONTENT, userAuth);\n                // Because we are not passing the body to the pushConn (who would normally handle destroying),\n                // we need to release it here.\n                ReferenceCountUtil.release(body);\n                return;\n            }\n\n            logPushEventBody(request, body);\n            ChannelFuture clientFuture = pushConn.sendPushMessage(body);\n            clientFuture.addListener(cf -> {\n                HttpResponseStatus status;\n                if (cf.isSuccess()) {\n                    logPushSuccess();\n                    status = HttpResponseStatus.OK;\n                } else {\n                    logPushError(cf.cause());\n                    status = HttpResponseStatus.INTERNAL_SERVER_ERROR;\n                }\n                sendHttpResponse(ctx, request, status, userAuth);\n            });\n        } else {\n            // Last handler in the chain\n            sendHttpResponse(ctx, request, HttpResponseStatus.BAD_REQUEST, null);\n        }\n    }\n\n    protected void logPushAttempt() {\n        logger.debug(\"pushing notification\");\n    }\n\n    protected void logNoIdentity() {\n        logger.debug(\"push notification missing identity\");\n    }\n\n    protected void logClientNotConnected() {\n        logger.debug(\"push notification, client not connected\");\n    }\n\n    protected void logPushSuccess() {\n        logger.debug(\"push notification success\");\n    }\n\n    protected void logPushError(Throwable t) {\n        logger.debug(\"pushing notification error\", t);\n    }\n\n    protected void logRateLimited() {\n        logger.warn(\"Push message was rejected because of the rate limiting\");\n    }\n\n    protected void logSecurityTokenVerificationFail() {\n        logger.warn(\"Push secure token verification failed\");\n    }\n\n    protected void logPushEvent(FullHttpRequest request, HttpResponseStatus status, PushUserAuth userAuth) {\n        logger.debug(\"Push notification status: {}, auth: {}\", status.code(), userAuth != null ? userAuth : \"-\");\n    }\n\n    protected void logPushEventBody(FullHttpRequest request, ByteBuf body) {\n        logger.debug(\"push event body\");\n    }\n\n    protected abstract PushUserAuth getPushUserAuth(FullHttpRequest request);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushMessageSenderInitializer.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\npublic abstract class PushMessageSenderInitializer extends ChannelInitializer<Channel> {\n    @Override\n    protected void initChannel(Channel ch) throws Exception {\n        ChannelPipeline pipeline = ch.pipeline();\n        pipeline.addLast(new HttpServerCodec());\n        pipeline.addLast(new HttpObjectAggregator(65536));\n        addPushMessageHandlers(pipeline);\n    }\n\n    protected abstract void addPushMessageHandlers(ChannelPipeline pipeline);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushProtocol.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport com.google.common.base.Charsets;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.PingWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;\n\n/**\n * Created by saroskar on 10/10/16.\n */\npublic enum PushProtocol {\n    WEBSOCKET {\n        @Override\n        // The alternative object for HANDSHAKE_COMPLETE is not publicly visible, so disable deprecation warnings.  In\n        // the future, it may be possible to not fire this even and remove the suppression.\n        @SuppressWarnings(\"deprecation\")\n        public Object getHandshakeCompleteEvent() {\n            return WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE;\n        }\n\n        @Override\n        public String getPath() {\n            return \"/ws\";\n        }\n\n        @Override\n        public ChannelFuture sendPushMessage(ChannelHandlerContext ctx, ByteBuf mesg) {\n            TextWebSocketFrame wsf = new TextWebSocketFrame(mesg);\n            return ctx.channel().writeAndFlush(wsf);\n        }\n\n        @Override\n        public ChannelFuture sendPing(ChannelHandlerContext ctx) {\n            return ctx.channel().writeAndFlush(new PingWebSocketFrame());\n        }\n\n        @Override\n        public Object goAwayMessage() {\n            return new TextWebSocketFrame(\"_CLOSE_\");\n        }\n\n        @Override\n        public Object serverClosingConnectionMessage(int statusCode, String reasonText) {\n            return new CloseWebSocketFrame(statusCode, reasonText);\n        }\n    },\n\n    SSE {\n        private static final String SSE_HANDSHAKE_COMPLETE_EVENT = \"sse_handshake_complete\";\n\n        @Override\n        public Object getHandshakeCompleteEvent() {\n            return SSE_HANDSHAKE_COMPLETE_EVENT;\n        }\n\n        @Override\n        public String getPath() {\n            return \"/sse\";\n        }\n\n        private static final String SSE_PREAMBLE = \"event: push\\r\\ndata: \";\n        private static final String SSE_TERMINATION = \"\\r\\n\\r\\n\";\n\n        @Override\n        public ChannelFuture sendPushMessage(ChannelHandlerContext ctx, ByteBuf mesg) {\n            ByteBuf newBuff = ctx.alloc().buffer();\n            newBuff.ensureWritable(SSE_PREAMBLE.length());\n            newBuff.writeCharSequence(SSE_PREAMBLE, Charsets.UTF_8);\n            newBuff.ensureWritable(mesg.writableBytes());\n            newBuff.writeBytes(mesg);\n            newBuff.ensureWritable(SSE_TERMINATION.length());\n            newBuff.writeCharSequence(SSE_TERMINATION, Charsets.UTF_8);\n            mesg.release();\n            return ctx.channel().writeAndFlush(newBuff);\n        }\n\n        private static final String SSE_PING = \"event: ping\\r\\ndata: ping\\r\\n\\r\\n\";\n\n        @Override\n        public ChannelFuture sendPing(ChannelHandlerContext ctx) {\n            ByteBuf newBuff = ctx.alloc().buffer();\n            newBuff.ensureWritable(SSE_PING.length());\n            newBuff.writeCharSequence(SSE_PING, Charsets.UTF_8);\n            return ctx.channel().writeAndFlush(newBuff);\n        }\n\n        @Override\n        public Object goAwayMessage() {\n            return \"event: goaway\\r\\ndata: _CLOSE_\\r\\n\\r\\n\";\n        }\n\n        @Override\n        public Object serverClosingConnectionMessage(int statusCode, String reasonText) {\n            return \"event: close\\r\\ndata: \" + statusCode + \" \" + reasonText + \"\\r\\n\\r\\n\";\n        }\n    };\n\n    public final void sendErrorAndClose(ChannelHandlerContext ctx, int statusCode, String reasonText) {\n        Object mesg = serverClosingConnectionMessage(statusCode, reasonText);\n        ctx.writeAndFlush(mesg).addListener(ChannelFutureListener.CLOSE);\n    }\n\n    public abstract Object getHandshakeCompleteEvent();\n\n    public abstract String getPath();\n\n    public abstract ChannelFuture sendPushMessage(ChannelHandlerContext ctx, ByteBuf mesg);\n\n    public abstract ChannelFuture sendPing(ChannelHandlerContext ctx);\n    /**\n     * Application level protocol for asking client to close connection\n     * @return WebSocketFrame which when sent to client will cause it to close the WebSocket\n     */\n    public abstract Object goAwayMessage();\n    /**\n     * Message server sends to the client just before it force closes connection from its side\n     */\n    public abstract Object serverClosingConnectionMessage(int statusCode, String reasonText);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushRegistrationHandler.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.websocketx.PingWebSocketFrame;\nimport io.netty.util.concurrent.ScheduledFuture;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.ThreadLocalRandom;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/14/18\n */\npublic class PushRegistrationHandler extends ChannelInboundHandlerAdapter {\n\n    protected final PushConnectionRegistry pushConnectionRegistry;\n    protected final PushProtocol pushProtocol;\n\n    /* Identity */\n    protected volatile PushUserAuth authEvent;\n\n    /* state */\n    protected final AtomicBoolean destroyed;\n    private ChannelHandlerContext ctx;\n    private volatile PushConnection pushConnection;\n    private final List<ScheduledFuture<?>> scheduledFutures;\n\n    public static final CachedDynamicIntProperty PUSH_REGISTRY_TTL =\n            new CachedDynamicIntProperty(\"zuul.push.registry.ttl.seconds\", 30 * 60);\n    public static final CachedDynamicIntProperty RECONNECT_DITHER =\n            new CachedDynamicIntProperty(\"zuul.push.reconnect.dither.seconds\", 3 * 60);\n    public static final CachedDynamicIntProperty UNAUTHENTICATED_CONN_TTL =\n            new CachedDynamicIntProperty(\"zuul.push.noauth.ttl.seconds\", 8);\n    public static final CachedDynamicIntProperty CLIENT_CLOSE_GRACE_PERIOD =\n            new CachedDynamicIntProperty(\"zuul.push.client.close.grace.period\", 4);\n    public static final CachedDynamicBooleanProperty KEEP_ALIVE_ENABLED =\n            new CachedDynamicBooleanProperty(\"zuul.push.keepalive.enabled\", true);\n    public static final CachedDynamicIntProperty KEEP_ALIVE_INTERVAL =\n            new CachedDynamicIntProperty(\"zuul.push.keepalive.interval.seconds\", 3 * 60);\n\n    private static final Logger logger = LoggerFactory.getLogger(PushRegistrationHandler.class);\n\n    public PushRegistrationHandler(PushConnectionRegistry pushConnectionRegistry, PushProtocol pushProtocol) {\n        this.pushConnectionRegistry = pushConnectionRegistry;\n        this.pushProtocol = pushProtocol;\n        this.destroyed = new AtomicBoolean();\n        this.scheduledFutures = Collections.synchronizedList(new ArrayList<>());\n    }\n\n    protected final boolean isAuthenticated() {\n        return (authEvent != null && authEvent.isSuccess());\n    }\n\n    protected void tearDown() {\n        if (!destroyed.getAndSet(true)) {\n            if (authEvent != null) {\n                // We should only remove the PushConnection entry from the registry if it's still this pushConnection.\n                String clientID = authEvent.getClientIdentity();\n                PushConnection savedPushConnection = pushConnectionRegistry.get(clientID);\n                if (savedPushConnection != null && savedPushConnection == pushConnection) {\n                    pushConnectionRegistry.remove(authEvent.getClientIdentity());\n                    logger.debug(\"Removed connection from registry for {}\", authEvent);\n                }\n                logger.debug(\"Closing connection for {}\", authEvent);\n            }\n        }\n        scheduledFutures.forEach(f -> f.cancel(false));\n        scheduledFutures.clear();\n    }\n\n    @Override\n    public final void channelInactive(ChannelHandlerContext ctx) throws Exception {\n        tearDown();\n        super.channelInactive(ctx);\n        ctx.close();\n    }\n\n    @Override\n    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n        logger.error(\"Exception caught, closing push channel for {}\", authEvent, cause);\n        ctx.close();\n    }\n\n    protected final void forceCloseConnectionFromServerSide() {\n        if (!destroyed.get()) {\n            logger.debug(\"server forcing close connection\");\n            pushProtocol.sendErrorAndClose(ctx, 1000, \"Server closed connection\");\n        }\n    }\n\n    private void closeIfNotAuthenticated() {\n        if (!isAuthenticated()) {\n            logger.debug(\n                    \"Closing connection because it is still unauthenticated after {} seconds.\",\n                    UNAUTHENTICATED_CONN_TTL.get());\n            forceCloseConnectionFromServerSide();\n        }\n    }\n\n    private void requestClientToCloseConnection() {\n        if (ctx.channel().isActive()) {\n            // Application level protocol for asking client to close connection\n            ctx.writeAndFlush(pushProtocol.goAwayMessage());\n            // Force close connection if client doesn't close in reasonable time after we made request\n            scheduledFutures.add(ctx.executor()\n                    .schedule(\n                            this::forceCloseConnectionFromServerSide,\n                            CLIENT_CLOSE_GRACE_PERIOD.get(),\n                            TimeUnit.SECONDS));\n        } else {\n            forceCloseConnectionFromServerSide();\n        }\n    }\n\n    protected void keepAlive() {\n        if (KEEP_ALIVE_ENABLED.get()) {\n            ctx.writeAndFlush(new PingWebSocketFrame());\n        }\n    }\n\n    private int ditheredReconnectDeadline() {\n        int dither = ThreadLocalRandom.current().nextInt(RECONNECT_DITHER.get());\n        return PUSH_REGISTRY_TTL.get() - dither - CLIENT_CLOSE_GRACE_PERIOD.get();\n    }\n\n    @Override\n    public final void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        this.ctx = ctx;\n        if (!destroyed.get()) {\n            if (evt == pushProtocol.getHandshakeCompleteEvent()) {\n                pushConnection = new PushConnection(pushProtocol, ctx);\n                // Unauthenticated connection, wait for small amount of time for a client to send auth token in\n                // a first web socket frame, otherwise close connection\n                ctx.executor()\n                        .schedule(this::closeIfNotAuthenticated, UNAUTHENTICATED_CONN_TTL.get(), TimeUnit.SECONDS);\n                logger.debug(\"WebSocket handshake complete.\");\n            } else if (evt instanceof PushUserAuth) {\n                authEvent = (PushUserAuth) evt;\n                if (authEvent.isSuccess() && (pushConnection != null)) {\n                    logger.debug(\"registering client {}\", authEvent);\n                    ctx.pipeline().remove(PushAuthHandler.NAME);\n                    registerClient(ctx, authEvent, pushConnection, pushConnectionRegistry);\n                    logger.debug(\"Authentication complete {}\", authEvent);\n                } else {\n                    logger.error(\n                            \"Push registration failed: Auth success={}, WS handshake success={}\",\n                            authEvent.isSuccess(),\n                            pushConnection != null);\n                    if (pushConnection != null) {\n                        pushProtocol.sendErrorAndClose(ctx, 1008, \"Auth failed\");\n                    }\n                }\n            }\n        }\n        super.userEventTriggered(ctx, evt);\n    }\n\n    protected int getKeepAliveInterval() {\n        return KEEP_ALIVE_INTERVAL.get();\n    }\n\n    /**\n     * Register authenticated client  - represented by PushAuthEvent - with PushConnectionRegistry of this instance.\n     *\n     * For all but really simplistic case - basically anything other than a single node push cluster, You'd most likely\n     * need some sort of off-box, partitioned, global registration registry that keeps track of which client is connected\n     * to which push server instance. You should override this default implementation for such cases and register your\n     * client with your global registry in addition to local push connection registry that is limited to this JVM instance\n     * Make sure such a registration is done in strictly non-blocking fashion lest you will block Netty event loop\n     * decimating your throughput.\n     *\n     * A typical arrangement is to use something like Memcached or redis cluster sharded by client connection key and\n     * to use blocking Memcached/redis driver in a background thread-pool to do the actual registration so that Netty\n     * event loop doesn't block\n     */\n    protected void registerClient(\n            ChannelHandlerContext ctx, PushUserAuth authEvent, PushConnection conn, PushConnectionRegistry registry) {\n        registry.put(authEvent.getClientIdentity(), conn);\n        // Make client reconnect after ttl seconds by closing this connection to limit stickiness of the client\n        scheduledFutures.add(ctx.executor()\n                .schedule(this::requestClientToCloseConnection, ditheredReconnectDeadline(), TimeUnit.SECONDS));\n        if (KEEP_ALIVE_ENABLED.get()) {\n            scheduledFutures.add(ctx.executor()\n                    .scheduleWithFixedDelay(\n                            this::keepAlive, getKeepAliveInterval(), getKeepAliveInterval(), TimeUnit.SECONDS));\n        }\n    }\n\n    @VisibleForTesting\n    PushConnection getPushConnection() {\n        return pushConnection;\n    }\n\n    @VisibleForTesting\n    List<ScheduledFuture<?>> getScheduledFutures() {\n        return scheduledFutures;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/push/PushUserAuth.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\npublic interface PushUserAuth {\n\n    boolean isSuccess();\n\n    int statusCode();\n\n    String getClientIdentity();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.ssl;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.ssl.SslHandshakeInfo;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Tag;\nimport com.netflix.zuul.netty.ChannelUtils;\nimport com.netflix.zuul.netty.server.psk.ClientPSKIdentityInfo;\nimport com.netflix.zuul.netty.server.psk.TlsPskHandler;\nimport com.netflix.zuul.netty.server.psk.ZuulPskServer;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.ssl.ClientAuth;\nimport io.netty.handler.ssl.SniCompletionEvent;\nimport io.netty.handler.ssl.SslCloseCompletionEvent;\nimport io.netty.handler.ssl.SslHandler;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\nimport io.netty.util.AttributeKey;\nimport java.nio.channels.ClosedChannelException;\nimport java.security.cert.Certificate;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport javax.net.ssl.ExtendedSSLSession;\nimport javax.net.ssl.SNIHostName;\nimport javax.net.ssl.SNIServerName;\nimport javax.net.ssl.SSLException;\nimport javax.net.ssl.SSLSession;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Stores info about the client and server's SSL certificates in the context, after a successful handshake.\n * <p>\n * User: michaels@netflix.com Date: 3/29/16 Time: 10:48 AM\n */\npublic class SslHandshakeInfoHandler extends ChannelInboundHandlerAdapter {\n\n    public static final AttributeKey<SslHandshakeInfo> ATTR_SSL_INFO = AttributeKey.newInstance(\"_ssl_handshake_info\");\n\n    // optionally set by an outbound handler that parses the TLS ServerHello key_share extension\n    public static final AttributeKey<String> ATTR_SSL_NAMED_GROUP = AttributeKey.newInstance(\"_ssl_named_group\");\n\n    private static final Logger logger = LoggerFactory.getLogger(SslHandshakeInfoHandler.class);\n\n    // extracts reason string from SSL errors formatted in the open ssl style\n    // error:[error code]:[library name]:OPENSSL_internal:[reason string]\n    // see https://github.com/google/boringssl/blob/d206f3db6ac2b74e8949ddd9947b94a5424d6a1d/include/openssl/err.h#L231\n    private static final Pattern OPEN_SSL_PATTERN = Pattern.compile(\"OPENSSL_internal:(.+)\");\n\n    static DynamicBooleanProperty SNI_LOGGING_ENABLED =\n            new DynamicBooleanProperty(\"zuul.ssl.handshake.snilogging.enabled\", false);\n\n    private final Registry spectatorRegistry;\n    private final boolean isSSlFromIntermediary;\n\n    public SslHandshakeInfoHandler(Registry spectatorRegistry, boolean isSSlFromIntermediary) {\n        this.spectatorRegistry = Preconditions.checkNotNull(spectatorRegistry);\n        this.isSSlFromIntermediary = isSSlFromIntermediary;\n    }\n\n    @VisibleForTesting\n    SslHandshakeInfoHandler() {\n        spectatorRegistry = new NoopRegistry();\n        isSSlFromIntermediary = false;\n    }\n\n    @Override\n    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {\n        if (evt instanceof SslHandshakeCompletionEvent) {\n            try {\n                SslHandshakeCompletionEvent sslEvent = (SslHandshakeCompletionEvent) evt;\n                if (sslEvent.isSuccess()) {\n\n                    CurrentPassport.fromChannel(ctx.channel()).add(PassportState.SERVER_CH_SSL_HANDSHAKE_COMPLETE);\n\n                    SSLSession session = getSSLSession(ctx);\n                    if (session == null) {\n                        logger.warn(\"Error getting the SSL handshake info. SSLSession is null\");\n                        return;\n                    }\n\n                    ClientAuth clientAuth = whichClientAuthEnum(ctx);\n\n                    Certificate serverCert = null;\n                    X509Certificate peerCert = null;\n\n                    if ((clientAuth == ClientAuth.REQUIRE || clientAuth == ClientAuth.OPTIONAL)\n                            && session.getPeerCertificates() != null\n                            && session.getPeerCertificates().length > 0) {\n                        peerCert = (X509Certificate) session.getPeerCertificates()[0];\n                    }\n                    if (session.getLocalCertificates() != null && session.getLocalCertificates().length > 0) {\n                        serverCert = session.getLocalCertificates()[0];\n                    }\n\n                    // if attribute is true, then true. If null or false then false\n                    boolean tlsHandshakeUsingExternalPSK = Objects.equals(\n                            ctx.channel()\n                                    .attr(ZuulPskServer.TLS_HANDSHAKE_USING_EXTERNAL_PSK)\n                                    .get(),\n                            Boolean.TRUE);\n\n                    ClientPSKIdentityInfo clientPSKIdentityInfo = ctx.channel()\n                            .attr(TlsPskHandler.CLIENT_PSK_IDENTITY_ATTRIBUTE_KEY)\n                            .get();\n\n                    String requestedSni = \"none\";\n                    try {\n                        List<SNIServerName> serverNames = ((ExtendedSSLSession) session).getRequestedServerNames();\n                        if (serverNames != null) {\n                            requestedSni = serverNames.stream()\n                                    .filter(sni -> sni instanceof SNIHostName)\n                                    .findFirst()\n                                    .map(sni -> ((SNIHostName) sni).getAsciiName())\n                                    .orElse(\"none\");\n                        }\n                    } catch (Exception e) {\n                        logger.warn(\"Error getting the request server names.\", e);\n                    }\n\n                    SslHandshakeInfo info = SslHandshakeInfo.builder()\n                            .requestedSni(requestedSni)\n                            .isOfIntermediary(isSSlFromIntermediary)\n                            .protocol(session.getProtocol())\n                            .cipherSuite(session.getCipherSuite())\n                            .namedGroup(ctx.channel().attr(ATTR_SSL_NAMED_GROUP).get())\n                            .clientAuthRequirement(clientAuth)\n                            .serverCertificate(serverCert)\n                            .clientCertificate(peerCert)\n                            .usingExternalPSK(tlsHandshakeUsingExternalPSK)\n                            .clientPSKIdentityInfo(clientPSKIdentityInfo)\n                            .build();\n                    ctx.channel().attr(ATTR_SSL_INFO).set(info);\n\n                    // Metrics.\n                    incrementCounters(sslEvent, info);\n\n                    logger.debug(\"Successful SSL Handshake: {}\", info);\n                } else {\n                    String clientIP = ctx.channel()\n                            .attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS)\n                            .get();\n                    Throwable cause = sslEvent.cause();\n\n                    PassportState passportState =\n                            CurrentPassport.fromChannel(ctx.channel()).getState();\n                    if (cause instanceof ClosedChannelException\n                            && (passportState == PassportState.SERVER_CH_INACTIVE\n                                    || passportState == PassportState.SERVER_CH_IDLE_TIMEOUT)) {\n                        // Either client closed the connection without/before having completed a handshake, or\n                        // the connection idle timed-out before handshake.\n                        // NOTE: we were seeing a lot of these in prod and can repro by just telnetting to port and then\n                        // closing terminal\n                        // without sending anything.\n                        // So don't treat these as SSL handshake failures.\n                        logger.debug(\n                                \"Client closed connection or it idle timed-out without doing an ssl handshake. ,\"\n                                        + \" client_ip = {}, channel_info = {}\",\n                                clientIP,\n                                ChannelUtils.channelInfoForLogging(ctx.channel()));\n                    } else if (cause instanceof SSLException\n                            && cause.getMessage().contains(\"handshake timed out\")) {\n                        logger.debug(\n                                \"Client timed-out doing the ssl handshake. , client_ip = {}, channel_info = {}\",\n                                clientIP,\n                                ChannelUtils.channelInfoForLogging(ctx.channel()));\n                    } else if (cause instanceof SSLException\n                            && cause.getMessage().contains(\"failure when writing TLS control frames\")) {\n                        // This can happen if the ClientHello is sent followed by an RST packet, before we can respond.\n                        logger.debug(\n                                \"Client terminated handshake early., client_ip = {}, channel_info = {}\",\n                                clientIP,\n                                ChannelUtils.channelInfoForLogging(ctx.channel()));\n                    } else {\n                        if (logger.isDebugEnabled()) {\n                            String msg = \"Unsuccessful SSL Handshake: \" + sslEvent\n                                    + \", client_ip = \" + clientIP\n                                    + \", channel_info = \" + ChannelUtils.channelInfoForLogging(ctx.channel())\n                                    + \", error = \" + cause;\n                            if (cause instanceof ClosedChannelException) {\n                                logger.debug(msg);\n                            } else {\n                                logger.debug(msg, cause);\n                            }\n                        }\n\n                        SslHandshakeInfo info = null;\n\n                        SSLSession session = getSSLSession(ctx);\n                        if (session != null) {\n                            List<SNIServerName> serverNames = ((ExtendedSSLSession) session).getRequestedServerNames();\n                            String requestedSni = serverNames.stream()\n                                    .filter(sni -> sni instanceof SNIHostName)\n                                    .findFirst()\n                                    .map(sni -> ((SNIHostName) sni).getAsciiName())\n                                    .orElse(\"none\");\n\n                            info = SslHandshakeInfo.builder()\n                                    .requestedSni(requestedSni)\n                                    .isOfIntermediary(isSSlFromIntermediary)\n                                    .build();\n                        }\n                        incrementCounters(sslEvent, info);\n                    }\n                }\n            } catch (Throwable e) {\n                logger.warn(\"Error getting the SSL handshake info.\", e);\n            } finally {\n                // Now remove this handler from the pipeline as no longer needed once the ssl handshake has completed.\n                ctx.pipeline().remove(this);\n            }\n        } else if (evt instanceof SslCloseCompletionEvent) {\n            // TODO - increment a separate metric for this event?\n        } else if (evt instanceof SniCompletionEvent) {\n            logger.debug(\"SNI Parsing Complete: {}\", evt);\n\n            SniCompletionEvent sniCompletionEvent = (SniCompletionEvent) evt;\n            if (sniCompletionEvent.isSuccess()) {\n                spectatorRegistry.counter(\"zuul.sni.parse.success\").increment();\n            } else {\n                Throwable cause = sniCompletionEvent.cause();\n                spectatorRegistry\n                        .counter(\"zuul.sni.parse.failure\", \"cause\", cause != null ? cause.getMessage() : \"UNKNOWN\")\n                        .increment();\n            }\n        }\n        super.userEventTriggered(ctx, evt);\n    }\n\n    private SSLSession getSSLSession(ChannelHandlerContext ctx) {\n        SslHandler sslhandler = ctx.channel().pipeline().get(SslHandler.class);\n        if (sslhandler != null) {\n            return sslhandler.engine().getSession();\n        }\n        TlsPskHandler tlsPskHandler = ctx.channel().pipeline().get(TlsPskHandler.class);\n        if (tlsPskHandler != null) {\n            return tlsPskHandler.getSession();\n        }\n        return null;\n    }\n\n    private ClientAuth whichClientAuthEnum(ChannelHandlerContext ctx) {\n        SslHandler sslhandler = ctx.channel().pipeline().get(SslHandler.class);\n        if (sslhandler == null) {\n            return ClientAuth.NONE;\n        }\n\n        ClientAuth clientAuth;\n        if (sslhandler.engine().getNeedClientAuth()) {\n            clientAuth = ClientAuth.REQUIRE;\n        } else if (sslhandler.engine().getWantClientAuth()) {\n            clientAuth = ClientAuth.OPTIONAL;\n        } else {\n            clientAuth = ClientAuth.NONE;\n        }\n        return clientAuth;\n    }\n\n    private void incrementCounters(\n            SslHandshakeCompletionEvent sslHandshakeCompletionEvent, SslHandshakeInfo handshakeInfo) {\n        try {\n            List<Tag> tagList = new ArrayList<>();\n            if (sslHandshakeCompletionEvent.isSuccess()) {\n                tagList.add(Tag.of(\n                        \"protocol\", handshakeInfo.getProtocol().isEmpty() ? \"unknown\" : handshakeInfo.getProtocol()));\n                tagList.add(Tag.of(\n                        \"ciphersuite\",\n                        handshakeInfo.getCipherSuite().isEmpty() ? \"unknown\" : handshakeInfo.getCipherSuite()));\n                tagList.add(Tag.of(\"clientauth\", String.valueOf(handshakeInfo.getClientAuthRequirement())));\n                tagList.add(Tag.of(\"namedgroup\", Objects.requireNonNullElse(handshakeInfo.getNamedGroup(), \"unknown\")));\n\n            } else {\n                tagList.add(Tag.of(\"failure_cause\", getFailureCause(sslHandshakeCompletionEvent.cause())));\n            }\n\n            tagList.add(Tag.of(\"success\", String.valueOf(sslHandshakeCompletionEvent.isSuccess())));\n            if (SNI_LOGGING_ENABLED.get()) {\n                tagList.add(Tag.of(\"sni\", handshakeInfo.getRequestedSni()));\n            }\n            spectatorRegistry.counter(\"server.ssl.handshake\", tagList).increment();\n        } catch (Exception e) {\n            logger.error(\"Error incrementing counters for SSL handshake!\", e);\n        }\n    }\n\n    @VisibleForTesting\n    String getFailureCause(Throwable throwable) {\n        String message = throwable.getMessage();\n        if (message == null) {\n            return throwable.toString();\n        }\n\n        Matcher matcher = OPEN_SSL_PATTERN.matcher(message);\n        return matcher.find() ? matcher.group(1) : message;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/ssl/BaseSslContextFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ssl;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\n\nimport com.google.errorprone.annotations.ForOverride;\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport io.netty.handler.ssl.CipherSuiteFilter;\nimport io.netty.handler.ssl.ClientAuth;\nimport io.netty.handler.ssl.OpenSsl;\nimport io.netty.handler.ssl.OpenSslContextOption;\nimport io.netty.handler.ssl.OpenSslSessionStats;\nimport io.netty.handler.ssl.ReferenceCountedOpenSslContext;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport io.netty.handler.ssl.SslProvider;\nimport io.netty.handler.ssl.SupportedCipherSuiteFilter;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.cert.CertificateException;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Base64;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.function.ToDoubleFunction;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 3/4/16\n * Time: 4:00 PM\n */\npublic class BaseSslContextFactory implements SslContextFactory {\n    private static final Logger LOG = LoggerFactory.getLogger(BaseSslContextFactory.class);\n\n    private static final DynamicBooleanProperty ALLOW_USE_OPENSSL =\n            new DynamicBooleanProperty(\"zuul.ssl.openssl.allow\", true);\n\n    // matches Netty's OpenSSL defaults (@see io.netty.handler.ssl.OpenSsl)\n    private static final String[] DEFAULT_NAMED_GROUPS = {\"x25519\", \"secp256r1\", \"secp384r1\", \"secp521r1\"};\n\n    static {\n        // Install BouncyCastle provider.\n        java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());\n    }\n\n    protected final Registry spectatorRegistry;\n    protected final ServerSslConfig serverSslConfig;\n\n    public BaseSslContextFactory(Registry spectatorRegistry, ServerSslConfig serverSslConfig) {\n        this.spectatorRegistry = Objects.requireNonNull(spectatorRegistry);\n        this.serverSslConfig = Objects.requireNonNull(serverSslConfig);\n    }\n\n    @Override\n    public SslContextBuilder createBuilderForServer() {\n        try {\n            List<X509Certificate> trustedCerts = getTrustedX509Certificates();\n            SslProvider sslProvider = chooseSslProvider();\n\n            LOG.debug(\"Using SslProvider of type {}\", sslProvider.name());\n\n            SslContextBuilder builder = newBuilderForServer()\n                    .ciphers(getCiphers(), getCiphersFilter())\n                    .sessionTimeout(serverSslConfig.getSessionTimeout())\n                    .sslProvider(sslProvider)\n                    .option(OpenSslContextOption.GROUPS, getNamedGroups());\n\n            if (serverSslConfig.getClientAuth() != null && trustedCerts != null && !trustedCerts.isEmpty()) {\n                builder = builder.trustManager(trustedCerts.toArray(new X509Certificate[0]))\n                        .clientAuth(serverSslConfig.getClientAuth());\n            }\n\n            return builder;\n        } catch (Exception e) {\n            throw new RuntimeException(\"Error configuring SslContext!\", e);\n        }\n    }\n\n    /**\n     * This function is meant to call the correct overload of {@code SslContextBuilder.forServer()}.  It should not\n     * apply any other customization.\n     */\n    @ForOverride\n    protected SslContextBuilder newBuilderForServer() throws IOException {\n        LOG.debug(\"Using certChainFile {}\", serverSslConfig.getCertChainFile());\n        try (InputStream keyInput = getKeyInputStream();\n                InputStream certChainInput = new FileInputStream(serverSslConfig.getCertChainFile())) {\n            return SslContextBuilder.forServer(certChainInput, keyInput);\n        }\n    }\n\n    @Override\n    public void enableSessionTickets(SslContext sslContext) {\n        // TODO\n    }\n\n    @Override\n    public void configureOpenSslStatsMetrics(SslContext sslContext, String sslContextId) {\n        // Setup metrics tracking the OpenSSL stats.\n        if (sslContext instanceof ReferenceCountedOpenSslContext) {\n            OpenSslSessionStats stats = ((ReferenceCountedOpenSslContext) sslContext)\n                    .sessionContext()\n                    .stats();\n\n            openSslStatGauge(stats, sslContextId, \"accept\", OpenSslSessionStats::accept);\n            openSslStatGauge(stats, sslContextId, \"accept_good\", OpenSslSessionStats::acceptGood);\n            openSslStatGauge(stats, sslContextId, \"accept_renegotiate\", OpenSslSessionStats::acceptRenegotiate);\n            openSslStatGauge(stats, sslContextId, \"number\", OpenSslSessionStats::number);\n            openSslStatGauge(stats, sslContextId, \"connect\", OpenSslSessionStats::connect);\n            openSslStatGauge(stats, sslContextId, \"connect_good\", OpenSslSessionStats::connectGood);\n            openSslStatGauge(stats, sslContextId, \"connect_renegotiate\", OpenSslSessionStats::connectRenegotiate);\n            openSslStatGauge(stats, sslContextId, \"hits\", OpenSslSessionStats::hits);\n            openSslStatGauge(stats, sslContextId, \"cb_hits\", OpenSslSessionStats::cbHits);\n            openSslStatGauge(stats, sslContextId, \"misses\", OpenSslSessionStats::misses);\n            openSslStatGauge(stats, sslContextId, \"timeouts\", OpenSslSessionStats::timeouts);\n            openSslStatGauge(stats, sslContextId, \"cache_full\", OpenSslSessionStats::cacheFull);\n            openSslStatGauge(stats, sslContextId, \"ticket_key_fail\", OpenSslSessionStats::ticketKeyFail);\n            openSslStatGauge(stats, sslContextId, \"ticket_key_new\", OpenSslSessionStats::ticketKeyNew);\n            openSslStatGauge(stats, sslContextId, \"ticket_key_renew\", OpenSslSessionStats::ticketKeyRenew);\n            openSslStatGauge(stats, sslContextId, \"ticket_key_resume\", OpenSslSessionStats::ticketKeyResume);\n        }\n    }\n\n    private void openSslStatGauge(\n            OpenSslSessionStats stats,\n            String sslContextId,\n            String statName,\n            ToDoubleFunction<OpenSslSessionStats> value) {\n        Id id = spectatorRegistry.createId(\"server.ssl.stats\", \"id\", sslContextId, \"stat\", statName);\n        PolledMeter.using(spectatorRegistry).withId(id).monitorValue(stats, value);\n        LOG.debug(\"Registered spectator gauge - {}\", id.name());\n    }\n\n    public static SslProvider chooseSslProvider() {\n        // Use openssl only if available and has ALPN support (ie. version > 1.0.2).\n        SslProvider sslProvider;\n        if (ALLOW_USE_OPENSSL.get() && OpenSsl.isAvailable() && SslProvider.isAlpnSupported(SslProvider.OPENSSL)) {\n            sslProvider = SslProvider.OPENSSL;\n        } else {\n            sslProvider = SslProvider.JDK;\n        }\n        return sslProvider;\n    }\n\n    public ServerSslConfig getServerSslConfig() {\n        return serverSslConfig;\n    }\n\n    @Override\n    public String[] getProtocols() {\n        return serverSslConfig.getProtocols();\n    }\n\n    @Override\n    public List<String> getCiphers() throws NoSuchAlgorithmException {\n        return serverSslConfig.getCiphers();\n    }\n\n    protected CipherSuiteFilter getCiphersFilter() {\n        return SupportedCipherSuiteFilter.INSTANCE;\n    }\n\n    protected String[] getNamedGroups() {\n        return DEFAULT_NAMED_GROUPS;\n    }\n\n    protected List<X509Certificate> getTrustedX509Certificates()\n            throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException {\n        ArrayList<X509Certificate> trustedCerts = new ArrayList<>();\n\n        // Add the certificates from the JKS truststore - ie. the CA's of the client cert that peer Zuul's will use.\n        if (serverSslConfig.getClientAuth() == ClientAuth.REQUIRE\n                || serverSslConfig.getClientAuth() == ClientAuth.OPTIONAL) {\n            // Get the encrypted bytes of the truststore password.\n            byte[] trustStorePwdBytes;\n            if (serverSslConfig.getClientAuthTrustStorePassword() != null) {\n                trustStorePwdBytes = Base64.getDecoder().decode(serverSslConfig.getClientAuthTrustStorePassword());\n            } else if (serverSslConfig.getClientAuthTrustStorePasswordFile() != null) {\n                trustStorePwdBytes = Files.readAllBytes(\n                        serverSslConfig.getClientAuthTrustStorePasswordFile().toPath());\n            } else {\n                throw new IllegalArgumentException(\n                        \"Must specify either ClientAuthTrustStorePassword or ClientAuthTrustStorePasswordFile!\");\n            }\n\n            // Decrypt the truststore password.\n            String trustStorePassword = getTruststorePassword(trustStorePwdBytes);\n\n            boolean dumpDecryptedTrustStorePassword = false;\n            if (dumpDecryptedTrustStorePassword) {\n                LOG.debug(\"X509Cert Trust Store Password {}\", trustStorePassword);\n            }\n\n            KeyStore trustStore = KeyStore.getInstance(\"JKS\");\n            trustStore.load(\n                    new FileInputStream(serverSslConfig.getClientAuthTrustStoreFile()),\n                    trustStorePassword.toCharArray());\n\n            Enumeration<String> aliases = trustStore.aliases();\n            while (aliases.hasMoreElements()) {\n                X509Certificate cert = (X509Certificate) trustStore.getCertificate(aliases.nextElement());\n                trustedCerts.add(cert);\n            }\n        }\n\n        return trustedCerts;\n    }\n\n    /**\n     * Can be overridden to implement your own decryption scheme.\n     *\n     */\n    protected String getTruststorePassword(byte[] trustStorePwdBytes) {\n        return new String(trustStorePwdBytes, UTF_8).trim();\n    }\n\n    /**\n     * Can be overridden to implement your own decryption scheme.\n     */\n    protected InputStream getKeyInputStream() throws IOException {\n        return new FileInputStream(serverSslConfig.getKeyFile());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/ssl/ClientSslContextFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ssl;\n\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.spectator.api.Registry;\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Client Ssl Context Factory\n *\n * Author: Arthur Gonigberg\n * Date: May 14, 2018\n */\npublic final class ClientSslContextFactory extends BaseSslContextFactory {\n\n    private static final DynamicBooleanProperty ENABLE_CLIENT_TLS13 =\n            new DynamicBooleanProperty(\"com.netflix.zuul.netty.ssl.enable_tls13\", false);\n\n    private static final Logger log = LoggerFactory.getLogger(ClientSslContextFactory.class);\n\n    private static final ServerSslConfig DEFAULT_CONFIG = ServerSslConfig.builder()\n            .protocols(maybeAddTls13(ENABLE_CLIENT_TLS13.get(), \"TLSv1.2\"))\n            .ciphers(ServerSslConfig.getDefaultCiphers())\n            .build();\n\n    public ClientSslContextFactory(Registry spectatorRegistry) {\n        super(spectatorRegistry, DEFAULT_CONFIG);\n    }\n\n    public ClientSslContextFactory(Registry spectatorRegistry, ServerSslConfig serverSslConfig) {\n        super(spectatorRegistry, serverSslConfig);\n    }\n\n    public SslContext getClientSslContext() {\n        try {\n            return SslContextBuilder.forClient()\n                    .sslProvider(chooseSslProvider())\n                    .ciphers(getCiphers(), getCiphersFilter())\n                    .protocols(getProtocols())\n                    .build();\n        } catch (Exception e) {\n            log.error(\"Error loading SslContext client request.\", e);\n            throw new RuntimeException(\"Error configuring SslContext for client request!\", e);\n        }\n    }\n\n    static String[] maybeAddTls13(boolean enableTls13, String... defaultProtocols) {\n        if (enableTls13) {\n            String[] protocols = new String[defaultProtocols.length + 1];\n            System.arraycopy(defaultProtocols, 0, protocols, 1, defaultProtocols.length);\n            protocols[0] = \"TLSv1.3\";\n            return protocols;\n        } else {\n            return defaultProtocols;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/ssl/SslContextFactory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ssl;\n\nimport io.netty.handler.ssl.SslContext;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.List;\n\n/**\n * User: michaels@netflix.com\n * Date: 11/8/16\n * Time: 1:01 PM\n */\npublic interface SslContextFactory {\n    SslContextBuilder createBuilderForServer();\n\n    String[] getProtocols();\n\n    List<String> getCiphers() throws NoSuchAlgorithmException;\n\n    void enableSessionTickets(SslContext sslContext);\n\n    void configureOpenSslStatsMetrics(SslContext sslContext, String sslContextId);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/timeouts/HttpHeadersTimeoutHandler.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.timeouts;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.handler.codec.http.HttpMessage;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.BooleanSupplier;\nimport java.util.function.IntSupplier;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class HttpHeadersTimeoutHandler {\n    private static final Logger LOG = LoggerFactory.getLogger(HttpHeadersTimeoutHandler.class);\n\n    @VisibleForTesting\n    static final AttributeKey<ScheduledFuture<Void>> HTTP_HEADERS_READ_TIMEOUT_FUTURE =\n            AttributeKey.newInstance(\"httpHeadersReadTimeoutFuture\");\n\n    @VisibleForTesting\n    static final AttributeKey<Long> HTTP_HEADERS_READ_START_TIME = AttributeKey.newInstance(\"httpHeadersReadStartTime\");\n\n    public static class InboundHandler extends ChannelInboundHandlerAdapter {\n        private final BooleanSupplier httpHeadersReadTimeoutEnabledSupplier;\n        private final IntSupplier httpHeadersReadTimeoutSupplier;\n\n        private final Counter httpHeadersReadTimeoutCounter;\n        private final PercentileTimer httpHeadersReadTimer;\n\n        private boolean closed = false;\n\n        public InboundHandler(\n                BooleanSupplier httpHeadersReadTimeoutEnabledSupplier,\n                IntSupplier httpHeadersReadTimeoutSupplier,\n                Counter httpHeadersReadTimeoutCounter,\n                PercentileTimer httpHeadersReadTimer) {\n            this.httpHeadersReadTimeoutEnabledSupplier = httpHeadersReadTimeoutEnabledSupplier;\n            this.httpHeadersReadTimeoutSupplier = httpHeadersReadTimeoutSupplier;\n            this.httpHeadersReadTimeoutCounter = httpHeadersReadTimeoutCounter;\n            this.httpHeadersReadTimer = httpHeadersReadTimer;\n        }\n\n        @Override\n        public void channelActive(ChannelHandlerContext ctx) throws Exception {\n            try {\n                ctx.channel().attr(HTTP_HEADERS_READ_START_TIME).set(System.nanoTime());\n                if (!httpHeadersReadTimeoutEnabledSupplier.getAsBoolean()) return;\n                int timeout = httpHeadersReadTimeoutSupplier.getAsInt();\n                ctx.channel()\n                        .attr(HTTP_HEADERS_READ_TIMEOUT_FUTURE)\n                        .set(ctx.executor()\n                                .schedule(\n                                        () -> {\n                                            if (!closed) {\n                                                ctx.close(); // triggers channelInactive -> destroy\n                                                closed = true;\n                                                if (httpHeadersReadTimeoutCounter != null)\n                                                    httpHeadersReadTimeoutCounter.increment();\n                                                LOG.debug(\n                                                        \"[{}] HTTP headers read timeout handler timed out\",\n                                                        ctx.channel().id());\n                                            }\n                                            return null;\n                                        },\n                                        timeout,\n                                        TimeUnit.MILLISECONDS));\n                LOG.debug(\n                        \"[{}] Adding HTTP headers read timeout handler: {}\",\n                        ctx.channel().id(),\n                        timeout);\n            } finally {\n                super.channelActive(ctx);\n            }\n        }\n\n        @Override\n        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n            try {\n                if (msg instanceof HttpMessage) {\n                    Long readStartTime =\n                            ctx.channel().attr(HTTP_HEADERS_READ_START_TIME).get();\n                    if (httpHeadersReadTimer != null && readStartTime != null)\n                        httpHeadersReadTimer.record(System.nanoTime() - readStartTime, TimeUnit.NANOSECONDS);\n                    ctx.pipeline().remove(this); // triggers handlerRemoved -> destroy\n                }\n            } finally {\n                super.channelRead(ctx, msg);\n            }\n        }\n\n        @Override\n        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {\n            destroy(ctx);\n        }\n\n        @Override\n        public void channelInactive(ChannelHandlerContext ctx) throws Exception {\n            destroy(ctx);\n            super.channelInactive(ctx);\n        }\n\n        private void destroy(ChannelHandlerContext ctx) {\n            ScheduledFuture<Void> future =\n                    ctx.channel().attr(HTTP_HEADERS_READ_TIMEOUT_FUTURE).get();\n            if (future != null) {\n                future.cancel(false);\n                ctx.channel().attr(HTTP_HEADERS_READ_TIMEOUT_FUTURE).set(null);\n                ctx.channel().attr(HTTP_HEADERS_READ_START_TIME).set(null);\n                LOG.debug(\n                        \"[{}] Removing HTTP headers read timeout handler\",\n                        ctx.channel().id());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManager.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.timeouts;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.client.config.CommonClientConfigKey;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.config.DynamicLongProperty;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.origins.NettyOrigin;\nimport java.time.Duration;\nimport java.util.Objects;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * Origin Timeout Manager\n *\n * @author Arthur Gonigberg\n * @since February 24, 2021\n */\npublic class OriginTimeoutManager {\n\n    private final NettyOrigin origin;\n\n    public OriginTimeoutManager(NettyOrigin origin) {\n        this.origin = Objects.requireNonNull(origin);\n    }\n\n    @VisibleForTesting\n    static final DynamicLongProperty MAX_OUTBOUND_READ_TIMEOUT_MS = new DynamicLongProperty(\n            \"zuul.origin.readtimeout.max\", Duration.ofSeconds(90).toMillis());\n\n    /**\n     * Derives the read timeout from the configuration.  This implementation prefers the longer of either the origin\n     * timeout or the request timeout.\n     * <p>\n     * This method can also be used to validate timeout and deadline boundaries and throw exceptions as needed. If\n     * extending this method to do validation, you should extend {@link com.netflix.zuul.exception.OutboundException}\n     * and set the appropriate {@link com.netflix.zuul.exception.ErrorType}.\n     *\n     * @param request    the request.\n     * @param attemptNum the attempt number, starting at 1.\n     */\n    public Duration computeReadTimeout(HttpRequestMessage request, int attemptNum) {\n        IClientConfig clientConfig = getRequestClientConfig(request);\n        Long originTimeout = getOriginReadTimeout();\n        Long requestTimeout = getRequestReadTimeout(clientConfig);\n\n        long computedTimeout;\n        if (originTimeout == null && requestTimeout == null) {\n            computedTimeout = MAX_OUTBOUND_READ_TIMEOUT_MS.get();\n        } else if (originTimeout == null || requestTimeout == null) {\n            computedTimeout = originTimeout == null ? requestTimeout : originTimeout;\n        } else {\n            // return the stricter (i.e. lower) of the two timeouts\n            computedTimeout = Math.min(originTimeout, requestTimeout);\n        }\n\n        // enforce max timeout upperbound\n        return Duration.ofMillis(Math.min(computedTimeout, MAX_OUTBOUND_READ_TIMEOUT_MS.get()));\n    }\n\n    /**\n     * This method will create a new client config or retrieve the existing one from the current request.\n     *\n     * @param zuulRequest - the request\n     * @return the config\n     */\n    protected IClientConfig getRequestClientConfig(HttpRequestMessage zuulRequest) {\n        IClientConfig overriddenClientConfig = zuulRequest.getContext().get(CommonContextKeys.REST_CLIENT_CONFIG);\n        if (overriddenClientConfig == null) {\n            overriddenClientConfig = new DefaultClientConfigImpl();\n            zuulRequest.getContext().put(CommonContextKeys.REST_CLIENT_CONFIG, overriddenClientConfig);\n        }\n\n        return overriddenClientConfig;\n    }\n\n    /**\n     * This method makes the assumption that the timeout is a numeric value\n     */\n    @Nullable\n    private Long getRequestReadTimeout(IClientConfig clientConfig) {\n        return Optional.ofNullable(clientConfig.get(CommonClientConfigKey.ReadTimeout))\n                .map(Long::valueOf)\n                .orElse(null);\n    }\n\n    /**\n     * This method makes the assumption that the timeout is a numeric value\n     */\n    @Nullable\n    private Long getOriginReadTimeout() {\n        return Optional.ofNullable(origin.getClientConfig().get(CommonClientConfigKey.ReadTimeout))\n                .map(Long::valueOf)\n                .orElse(null);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempt.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.niws;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.netflix.appinfo.AmazonInfo;\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.client.config.IClientConfigKey;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.discovery.SimpleMetaInfo;\nimport com.netflix.zuul.exception.OutboundException;\nimport com.netflix.zuul.netty.connectionpool.OriginConnectException;\nimport io.netty.handler.timeout.ReadTimeoutException;\nimport java.net.InetAddress;\nimport java.util.Locale;\nimport javax.net.ssl.SSLHandshakeException;\n\n/**\n * User: michaels@netflix.com\n * Date: 9/2/14\n * Time: 2:52 PM\n */\npublic class RequestAttempt {\n    private static final ObjectMapper JACKSON_MAPPER = new ObjectMapper();\n\n    private int attempt;\n    private int status;\n    private long duration;\n    private String cause;\n    private String error;\n    private String exceptionType;\n    private String app;\n    private String asg;\n    private String instanceId;\n    private String host;\n    private int port;\n    private String ipAddress;\n    private String vip;\n    private String region;\n    private String availabilityZone;\n    private long readTimeout;\n    private int connectTimeout;\n    private int maxRetries;\n\n    public RequestAttempt(\n            int attemptNumber,\n            InstanceInfo server,\n            InetAddress serverAddr,\n            String targetVip,\n            String chosenWarmupLB,\n            int status,\n            String error,\n            String exceptionType,\n            int readTimeout,\n            int connectTimeout,\n            int maxRetries) {\n        if (attemptNumber < 1) {\n            throw new IllegalArgumentException(\"Attempt number must be greater than 0! - \" + attemptNumber);\n        }\n        this.attempt = attemptNumber;\n        this.vip = targetVip;\n\n        if (server != null) {\n            this.app = server.getAppName().toLowerCase(Locale.ROOT);\n            this.asg = server.getASGName();\n            this.instanceId = server.getInstanceId();\n            this.host = server.getHostName();\n            this.port = server.getPort();\n\n            // If targetVip is null, then try to use the actual server's vip.\n            if (targetVip == null) {\n                this.vip = server.getVIPAddress();\n            }\n\n            if (server.getDataCenterInfo() instanceof AmazonInfo) {\n                this.availabilityZone =\n                        ((AmazonInfo) server.getDataCenterInfo()).getMetadata().get(\"availability-zone\");\n\n                // HACK - get region by just removing the last char from zone.\n                String az = getAvailabilityZone();\n                if (az != null && az.length() > 0) {\n                    this.region = az.substring(0, az.length() - 1);\n                }\n            }\n        }\n\n        if (serverAddr != null) {\n            ipAddress = serverAddr.getHostAddress();\n        }\n\n        this.status = status;\n        this.error = error;\n        this.exceptionType = exceptionType;\n        this.readTimeout = readTimeout;\n        this.connectTimeout = connectTimeout;\n        this.maxRetries = maxRetries;\n    }\n\n    public RequestAttempt(\n            DiscoveryResult server,\n            InetAddress serverAddr,\n            IClientConfig clientConfig,\n            int attemptNumber,\n            int readTimeout) {\n        this.status = -1;\n        this.attempt = attemptNumber;\n        this.readTimeout = readTimeout;\n\n        if (server != null && !server.equals(DiscoveryResult.EMPTY)) {\n            this.host = server.getHost();\n            this.port = server.getPort();\n            this.availabilityZone = server.getZone();\n\n            if (server.isDiscoveryEnabled()) {\n                this.app = server.getAppName().toLowerCase(Locale.ROOT);\n                this.asg = server.getASGName();\n                this.instanceId = server.getServerId();\n                this.host = server.getHost();\n                this.port = server.getPort();\n                this.vip = server.getTarget();\n                this.availabilityZone = server.getAvailabilityZone();\n\n            } else {\n                SimpleMetaInfo metaInfo = server.getMetaInfo();\n                if (metaInfo != null) {\n                    this.asg = metaInfo.getServerGroup();\n                    this.vip = metaInfo.getServiceIdForDiscovery();\n                    this.instanceId = metaInfo.getInstanceId();\n                }\n            }\n            // HACK - get region by just removing the last char from zone.\n            if (availabilityZone != null && availabilityZone.length() > 0) {\n                region = availabilityZone.substring(0, availabilityZone.length() - 1);\n            }\n        }\n\n        if (serverAddr != null) {\n            ipAddress = serverAddr.getHostAddress();\n        }\n\n        if (clientConfig != null) {\n            this.connectTimeout = clientConfig.get(IClientConfigKey.Keys.ConnectTimeout);\n        }\n    }\n\n    private RequestAttempt() {}\n\n    public void complete(int responseStatus, long durationMs, Throwable exception) {\n        if (responseStatus > -1) {\n            setStatus(responseStatus);\n        }\n\n        this.duration = durationMs;\n\n        if (exception != null) {\n            setException(exception);\n        }\n    }\n\n    public int getAttempt() {\n        return attempt;\n    }\n\n    public String getVip() {\n        return vip;\n    }\n\n    public int getStatus() {\n        return this.status;\n    }\n\n    public long getDuration() {\n        return this.duration;\n    }\n\n    public String getCause() {\n        return cause;\n    }\n\n    public String getError() {\n        return error;\n    }\n\n    public String getApp() {\n        return app;\n    }\n\n    public String getAsg() {\n        return asg;\n    }\n\n    public String getInstanceId() {\n        return instanceId;\n    }\n\n    public String getHost() {\n        return host;\n    }\n\n    public int getPort() {\n        return port;\n    }\n\n    public String getIpAddress() {\n        return ipAddress;\n    }\n\n    public String getRegion() {\n        return region;\n    }\n\n    public String getAvailabilityZone() {\n        return availabilityZone;\n    }\n\n    public String getExceptionType() {\n        return exceptionType;\n    }\n\n    public long getReadTimeout() {\n        return readTimeout;\n    }\n\n    public int getConnectTimeout() {\n        return connectTimeout;\n    }\n\n    public int getMaxRetries() {\n        return maxRetries;\n    }\n\n    public void setStatus(int status) {\n        this.status = status;\n    }\n\n    public void setError(String error) {\n        this.error = error;\n    }\n\n    public void setExceptionType(String exceptionType) {\n        this.exceptionType = exceptionType;\n    }\n\n    public void setApp(String app) {\n        this.app = app;\n    }\n\n    public void setAsg(String asg) {\n        this.asg = asg;\n    }\n\n    public void setInstanceId(String instanceId) {\n        this.instanceId = instanceId;\n    }\n\n    public void setHost(String host) {\n        this.host = host;\n    }\n\n    public void setPort(int port) {\n        this.port = port;\n    }\n\n    public void setIpAddress(String ipAddress) {\n        this.ipAddress = ipAddress;\n    }\n\n    public void setVip(String vip) {\n        this.vip = vip;\n    }\n\n    public void setRegion(String region) {\n        this.region = region;\n    }\n\n    public void setAvailabilityZone(String availabilityZone) {\n        this.availabilityZone = availabilityZone;\n    }\n\n    public void setReadTimeout(long readTimeout) {\n        this.readTimeout = readTimeout;\n    }\n\n    public void setConnectTimeout(int connectTimeout) {\n        this.connectTimeout = connectTimeout;\n    }\n\n    public void setException(Throwable t) {\n        if (t != null) {\n            if (t instanceof ReadTimeoutException) {\n                error = \"READ_TIMEOUT\";\n                exceptionType = t.getClass().getSimpleName();\n            } else if (t instanceof OriginConnectException oce) {\n                if (oce.getErrorType() != null) {\n                    error = oce.getErrorType().toString();\n                } else {\n                    error = \"ORIGIN_CONNECT_ERROR\";\n                }\n\n                Throwable oceCause = oce.getCause();\n\n                // unwrap ssl handshake exceptions to emit the underlying handshake failure causes\n                if (oceCause instanceof SSLHandshakeException sslHandshakeException\n                        && sslHandshakeException.getCause() != null) {\n                    oceCause = sslHandshakeException.getCause();\n                }\n\n                if (oceCause != null) {\n                    exceptionType = oce.getCause().getClass().getSimpleName();\n                    cause = oceCause.getMessage();\n                } else {\n                    exceptionType = oce.getClass().getSimpleName();\n                }\n\n            } else if (t instanceof OutboundException obe) {\n                error = obe.getOutboundErrorType().toString();\n                exceptionType = OutboundException.class.getSimpleName();\n            } else if (t instanceof SSLHandshakeException) {\n                error = t.getMessage();\n                exceptionType = t.getClass().getSimpleName();\n                cause = t.getCause().getMessage();\n            } else {\n                error = t.getMessage();\n                exceptionType = t.getClass().getSimpleName();\n\n                // for unexpected exceptions, just capture the first line of the stacktrace\n                // otherwise we risk large stacktraces in memory and metrics systems\n                StackTraceElement[] stackTraceElements = t.getStackTrace();\n                if (stackTraceElements.length > 0) {\n                    cause = stackTraceElements[0].toString();\n                }\n            }\n        }\n    }\n\n    public void setMaxRetries(int maxRetries) {\n        this.maxRetries = maxRetries;\n    }\n\n    @Override\n    public String toString() {\n        try {\n            return JACKSON_MAPPER.writeValueAsString(toJsonNode());\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(\"Error serializing RequestAttempt!\", e);\n        }\n    }\n\n    public ObjectNode toJsonNode() {\n        ObjectNode root = JACKSON_MAPPER.createObjectNode();\n        root.put(\"status\", status);\n        root.put(\"duration\", duration);\n        root.put(\"attempt\", attempt);\n\n        putNullableAttribute(root, \"error\", error);\n        putNullableAttribute(root, \"cause\", cause);\n        putNullableAttribute(root, \"exceptionType\", exceptionType);\n        putNullableAttribute(root, \"region\", region);\n        putNullableAttribute(root, \"availabilityZone\", availabilityZone);\n        putNullableAttribute(root, \"asg\", asg);\n        putNullableAttribute(root, \"instanceId\", instanceId);\n        putNullableAttribute(root, \"vip\", vip);\n        putNullableAttribute(root, \"ipAddress\", ipAddress);\n\n        if (port > 0) {\n            root.put(\"port\", port);\n        }\n\n        if (status < 1) {\n            root.put(\"readTimeout\", readTimeout);\n            root.put(\"connectTimeout\", connectTimeout);\n        }\n\n        return root;\n    }\n\n    private static ObjectNode putNullableAttribute(ObjectNode node, String name, String value) {\n        if (value != null) {\n            node.put(name, value);\n        }\n        return node;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/niws/RequestAttempts.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.niws;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: michaels@netflix.com\n * Date: 6/25/15\n * Time: 1:03 PM\n */\npublic class RequestAttempts extends ArrayList<RequestAttempt> {\n    private static final Logger LOG = LoggerFactory.getLogger(RequestAttempts.class);\n    private static final ObjectMapper JACKSON_MAPPER = new ObjectMapper();\n\n    public RequestAttempts() {\n        super();\n    }\n\n    @Nullable\n    public RequestAttempt getFinalAttempt() {\n        if (size() > 0) {\n            return get(size() - 1);\n        } else {\n            return null;\n        }\n    }\n\n    public static RequestAttempts getFromSessionContext(SessionContext ctx) {\n        return ctx.get(CommonContextKeys.REQUEST_ATTEMPTS);\n    }\n\n    public static RequestAttempts parse(String attemptsJson) throws IOException {\n        return JACKSON_MAPPER.readValue(attemptsJson, RequestAttempts.class);\n    }\n\n    public String toJSON() {\n        ArrayNode array = JACKSON_MAPPER.createArrayNode();\n        for (RequestAttempt attempt : this) {\n            array.add(attempt.toJsonNode());\n        }\n\n        try {\n            return JACKSON_MAPPER.writeValueAsString(array);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(\"Error serializing RequestAttempts!\", e);\n        }\n    }\n\n    @Override\n    public String toString() {\n        try {\n            return toJSON();\n        } catch (Throwable e) {\n            LOG.error(e.getMessage(), e);\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOrigin.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.client.config.CommonClientConfigKey;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.exception.ErrorType;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.NettyRequestAttemptFactory;\nimport com.netflix.zuul.netty.SpectatorUtils;\nimport com.netflix.zuul.netty.connectionpool.ClientChannelManager;\nimport com.netflix.zuul.netty.connectionpool.DefaultClientChannelManager;\nimport com.netflix.zuul.netty.connectionpool.PooledConnection;\nimport com.netflix.zuul.niws.RequestAttempt;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport io.netty.channel.EventLoop;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetAddress;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Netty Origin basic implementation that can be used for most apps, with the more complex methods having no-op\n * implementations.\n *\n * Author: Arthur Gonigberg\n * Date: December 01, 2017\n */\npublic class BasicNettyOrigin implements NettyOrigin {\n\n    private final OriginName originName;\n    private final Registry registry;\n    private final IClientConfig config;\n    private final ClientChannelManager clientChannelManager;\n    private final NettyRequestAttemptFactory requestAttemptFactory;\n\n    private final AtomicInteger concurrentRequests;\n    private final Counter rejectedRequests;\n    private final CachedDynamicIntProperty concurrencyMax;\n    private final CachedDynamicBooleanProperty concurrencyProtectionEnabled;\n\n    public BasicNettyOrigin(OriginName originName, Registry registry) {\n        this.originName = Objects.requireNonNull(originName, \"originName\");\n        this.registry = registry;\n        this.config = setupClientConfig(originName);\n        this.clientChannelManager = createClientChannelManager(originName, config, registry);\n        this.clientChannelManager.init();\n        this.requestAttemptFactory = new NettyRequestAttemptFactory();\n\n        String niwsClientName = getName().getNiwsClientName();\n        this.concurrentRequests =\n                SpectatorUtils.newGauge(\"zuul.origin.concurrent.requests\", niwsClientName, new AtomicInteger(0));\n        this.rejectedRequests = SpectatorUtils.newCounter(\"zuul.origin.rejected.requests\", niwsClientName);\n        this.concurrencyMax =\n                new CachedDynamicIntProperty(\"zuul.origin.\" + niwsClientName + \".concurrency.max.requests\", 200);\n        this.concurrencyProtectionEnabled = new CachedDynamicBooleanProperty(\n                \"zuul.origin.\" + niwsClientName + \".concurrency.protect.enabled\", true);\n    }\n\n    protected IClientConfig setupClientConfig(OriginName originName) {\n        // Get the NIWS properties for this Origin.\n        IClientConfig niwsClientConfig =\n                DefaultClientConfigImpl.getClientConfigWithDefaultValues(originName.getNiwsClientName());\n        niwsClientConfig.set(CommonClientConfigKey.ClientClassName, originName.getNiwsClientName());\n        niwsClientConfig.loadProperties(originName.getNiwsClientName());\n        return niwsClientConfig;\n    }\n\n    /**\n     * Factory method to create the ClientChannelManager.\n     * Override this method in subclasses to provide a custom ClientChannelManager implementation.\n     *\n     * @param originName the origin name\n     * @param config the client configuration\n     * @param registry the Spectator registry\n     * @return a ClientChannelManager instance\n     */\n    protected ClientChannelManager createClientChannelManager(\n            OriginName originName, IClientConfig config, Registry registry) {\n        return new DefaultClientChannelManager(originName, config, registry);\n    }\n\n    @Override\n    public OriginName getName() {\n        return originName;\n    }\n\n    @Override\n    public boolean isAvailable() {\n        return clientChannelManager.isAvailable();\n    }\n\n    @Override\n    public boolean isCold() {\n        return clientChannelManager.isCold();\n    }\n\n    @Override\n    public Promise<PooledConnection> connectToOrigin(\n            HttpRequestMessage zuulReq,\n            EventLoop eventLoop,\n            int attemptNumber,\n            CurrentPassport passport,\n            AtomicReference<DiscoveryResult> chosenServer,\n            AtomicReference<? super InetAddress> chosenHostAddr) {\n        return clientChannelManager.acquire(eventLoop, null, passport, chosenServer, chosenHostAddr);\n    }\n\n    @Override\n    public int getMaxRetriesForRequest(SessionContext context) {\n        return config.get(CommonClientConfigKey.MaxAutoRetriesNextServer, 0);\n    }\n\n    @Override\n    public RequestAttempt newRequestAttempt(\n            DiscoveryResult server, InetAddress serverAddr, SessionContext zuulCtx, int attemptNum) {\n        return new RequestAttempt(\n                server, serverAddr, config, attemptNum, config.get(CommonClientConfigKey.ReadTimeout));\n    }\n\n    @Override\n    public String getIpAddrFromServer(DiscoveryResult discoveryResult) {\n        Optional<String> ipAddr = discoveryResult.getIPAddr();\n        return ipAddr.isPresent() ? ipAddr.get() : null;\n    }\n\n    @Override\n    public IClientConfig getClientConfig() {\n        return config;\n    }\n\n    @Override\n    public Registry getSpectatorRegistry() {\n        return registry;\n    }\n\n    @Override\n    public void recordFinalError(HttpRequestMessage requestMsg, Throwable throwable) {\n        if (throwable == null) {\n            return;\n        }\n\n        SessionContext zuulCtx = requestMsg.getContext();\n\n        // Choose StatusCategory based on the ErrorType.\n        ErrorType et = requestAttemptFactory.mapNettyToOutboundErrorType(throwable);\n        StatusCategory nfs = et.getStatusCategory();\n        StatusCategoryUtils.setStatusCategory(zuulCtx, nfs);\n        StatusCategoryUtils.setOriginStatusCategory(zuulCtx, nfs);\n\n        zuulCtx.setError(throwable);\n    }\n\n    @Override\n    public void recordFinalResponse(HttpResponseMessage resp) {\n        if (resp != null) {\n            SessionContext zuulCtx = resp.getContext();\n\n            // Store the status code of final attempt response.\n            int originStatusCode = resp.getStatus();\n            zuulCtx.put(CommonContextKeys.ORIGIN_STATUS, originStatusCode);\n\n            // Mark origin StatusCategory based on http status code.\n            StatusCategory originNfs = ZuulStatusCategory.SUCCESS;\n            if (originStatusCode == 503) {\n                originNfs = ZuulStatusCategory.FAILURE_ORIGIN_THROTTLED;\n            } else if (StatusCategoryUtils.isResponseHttpErrorStatus(originStatusCode)) {\n                originNfs = ZuulStatusCategory.FAILURE_ORIGIN;\n            }\n            StatusCategoryUtils.setOriginStatusCategory(zuulCtx, originNfs);\n            // Choose the zuul StatusCategory based on the origin one...\n            // ... but only if existing one has not already been set to a non-success value.\n            StatusCategoryUtils.storeStatusCategoryIfNotAlreadyFailure(zuulCtx, originNfs);\n        }\n    }\n\n    @Override\n    public void preRequestChecks(HttpRequestMessage zuulRequest) {\n        if (concurrencyProtectionEnabled.get() && concurrentRequests.get() > concurrencyMax.get()) {\n            rejectedRequests.increment();\n            throw new OriginConcurrencyExceededException(getName());\n        }\n\n        concurrentRequests.incrementAndGet();\n    }\n\n    @Override\n    public void recordProxyRequestEnd() {\n        concurrentRequests.decrementAndGet();\n    }\n\n    /* Not required for basic operation */\n\n    @Override\n    public double getErrorPercentage() {\n        return 0;\n    }\n\n    @Override\n    public double getErrorAllPercentage() {\n        return 0;\n    }\n\n    @Override\n    public void onRequestExecutionStart(HttpRequestMessage zuulReq) {}\n\n    @Override\n    public void onRequestStartWithServer(HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum) {}\n\n    @Override\n    public void onRequestExceptionWithServer(\n            HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum, Throwable t) {}\n\n    @Override\n    public void onRequestExecutionSuccess(\n            HttpRequestMessage zuulReq,\n            HttpResponseMessage zuulResp,\n            DiscoveryResult discoveryResult,\n            int attemptNum) {}\n\n    @Override\n    public void onRequestExecutionFailed(\n            HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum, Throwable t) {}\n\n    @Override\n    public void adjustRetryPolicyIfNeeded(HttpRequestMessage zuulRequest) {}\n\n    @Override\n    public void recordSuccessResponse() {}\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/BasicNettyOriginManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.context.SessionContext;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Basic Netty Origin Manager that most apps can use. This can also serve as a useful template for creating more\n * complex origin managers.\n *\n * Author: Arthur Gonigberg\n * Date: November 30, 2017\n */\n@Singleton\npublic class BasicNettyOriginManager implements OriginManager<BasicNettyOrigin> {\n\n    private final Registry registry;\n    private final ConcurrentHashMap<OriginName, BasicNettyOrigin> originMappings;\n\n    @Inject\n    public BasicNettyOriginManager(Registry registry) {\n        this.registry = registry;\n        this.originMappings = new ConcurrentHashMap<>();\n    }\n\n    @Override\n    public BasicNettyOrigin getOrigin(OriginName originName, String uri, SessionContext ctx) {\n        return originMappings.computeIfAbsent(originName, n -> createOrigin(originName, uri, ctx));\n    }\n\n    @Override\n    public BasicNettyOrigin createOrigin(OriginName originName, String uri, SessionContext ctx) {\n        return new BasicNettyOrigin(originName, registry);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/InstrumentedOrigin.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\n/**\n * User: michaels@netflix.com\n * Date: 10/8/14\n * Time: 6:15 PM\n */\npublic interface InstrumentedOrigin extends Origin {\n\n    double getErrorPercentage();\n\n    double getErrorAllPercentage();\n\n    void adjustRetryPolicyIfNeeded(HttpRequestMessage zuulRequest);\n\n    void preRequestChecks(HttpRequestMessage zuulRequest);\n\n    void recordSuccessResponse();\n\n    void recordProxyRequestEnd();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/NettyOrigin.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.connectionpool.PooledConnection;\nimport com.netflix.zuul.niws.RequestAttempt;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.EventLoop;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetAddress;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Netty Origin interface for integrating cleanly with the ProxyEndpoint state management class.\n *\n * Author: Arthur Gonigberg\n * Date: November 29, 2017\n */\npublic interface NettyOrigin extends InstrumentedOrigin {\n\n    Promise<PooledConnection> connectToOrigin(\n            HttpRequestMessage zuulReq,\n            EventLoop eventLoop,\n            int attemptNumber,\n            CurrentPassport passport,\n            AtomicReference<DiscoveryResult> chosenServer,\n            AtomicReference<? super InetAddress> chosenHostAddr);\n\n    int getMaxRetriesForRequest(SessionContext context);\n\n    void onRequestExecutionStart(HttpRequestMessage zuulReq);\n\n    void onRequestStartWithServer(HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum);\n\n    void onRequestExceptionWithServer(\n            HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum, Throwable t);\n\n    void onRequestExecutionSuccess(\n            HttpRequestMessage zuulReq, HttpResponseMessage zuulResp, DiscoveryResult discoveryResult, int attemptNum);\n\n    void onRequestExecutionFailed(\n            HttpRequestMessage zuulReq, DiscoveryResult discoveryResult, int attemptNum, Throwable t);\n\n    void recordFinalError(HttpRequestMessage requestMsg, Throwable throwable);\n\n    void recordFinalResponse(HttpResponseMessage resp);\n\n    RequestAttempt newRequestAttempt(\n            DiscoveryResult server, InetAddress serverAddr, SessionContext zuulCtx, int attemptNum);\n\n    String getIpAddrFromServer(DiscoveryResult server);\n\n    IClientConfig getClientConfig();\n\n    Registry getSpectatorRegistry();\n\n    default void originRetryPolicyAdjustmentIfNeeded(HttpRequestMessage zuulReq, HttpResponse nettyResponse) {}\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/Origin.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.origins;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/11/15\n * Time: 3:14 PM\n */\npublic interface Origin {\n    OriginName getName();\n\n    boolean isAvailable();\n\n    boolean isCold();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/OriginConcurrencyExceededException.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\n\npublic class OriginConcurrencyExceededException extends OriginThrottledException {\n    public OriginConcurrencyExceededException(OriginName originName) {\n        super(\n                originName,\n                \"Max concurrent requests on origin exceeded\",\n                ZuulStatusCategory.FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/OriginManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.origins;\n\nimport com.netflix.zuul.context.SessionContext;\n\n/**\n * User: michaels@netflix.com\n * Date: 5/11/15\n * Time: 3:15 PM\n */\npublic interface OriginManager<T extends Origin> {\n\n    T getOrigin(OriginName originName, String uri, SessionContext ctx);\n\n    T createOrigin(OriginName originName, String uri, SessionContext ctx);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/OriginName.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.zuul.util.VipUtils;\nimport java.util.Locale;\nimport java.util.Objects;\n\n/**\n * An Origin Name is a tuple of a target to connect to, an authority to use for connecting, and an NIWS client name\n * used for configuration of an Origin.   These fields are semi independent, but are usually used in proximity to each\n * other, and thus makes sense to group them together.\n *\n * <p>The {@code target} represents the string used to look up the origin during name resolution.   Currently, this is\n * a {@code VIP}, which is passed to a Eureka name resolved.   In the future, other targets will be supported, such as\n * DNS.\n *\n * <p>The {@code authority} represents who we plan to connect to.   In the case of TLS / SSL connections, this can be\n * used to verify the remote endpoint.  It is not specified what the format is, but it may not be null.  In the case\n * of VIPs, which are frequently used with AWS clusters, this is the Application Name.\n *\n * <p>The {@code NIWS Client Name} is a legacy construct, which is used to configure an origin.   When the origin is\n * created, the NIWS client name can be used as a key into a configuration mapping to decide how the Origin should\n * behave.  By default, the VIP is the same for the NIWS client name, but it can be different in scenarios where\n * multiple connections to the same origin are needed.  Additionally, the NIWS client name also functions as a key\n * in metrics.\n */\npublic final class OriginName {\n    /**\n     * The NIWS client name of the origin.  This is typically used in metrics and for configuration of NIWS\n     * {@link com.netflix.client.config.IClientConfig} objects.\n     */\n    private final String niwsClientName;\n\n    /**\n     * This should not be used in {@link #equals} or {@link #hashCode} as it is already covered by\n     * {@link #niwsClientName}.\n     */\n    private final String metricId;\n\n    /**\n     * The target to connect to, used for name resolution.  This is typically the VIP.\n     */\n    private final String target;\n    /**\n     * The authority of this origin.  Usually this is the Application name of origin.  It is primarily\n     * used for establishing a secure connection, as well as logging.\n     */\n    private final String authority;\n\n    /**\n     * @deprecated use {@link #fromVipAndApp(String, String)}\n     */\n    @Deprecated\n    public static OriginName fromVip(String vip) {\n        return fromVipAndApp(vip, VipUtils.extractUntrustedAppNameFromVIP(vip));\n    }\n\n    /**\n     * @deprecated use {@link #fromVipAndApp(String, String, String)}\n     */\n    @Deprecated\n    public static OriginName fromVip(String vip, String niwsClientName) {\n        return fromVipAndApp(vip, VipUtils.extractUntrustedAppNameFromVIP(vip), niwsClientName);\n    }\n\n    /**\n     * Constructs an OriginName with a target and authority from the vip and app name.   The vip is used as the NIWS\n     * client name, which is frequently used for configuration.\n     */\n    public static OriginName fromVipAndApp(String vip, String appName) {\n        return fromVipAndApp(vip, appName, vip);\n    }\n\n    /**\n     * Constructs an OriginName with a target, authority, and NIWS client name.   The NIWS client name can be different\n     * from the vip in cases where custom configuration for an Origin is needed.\n     */\n    public static OriginName fromVipAndApp(String vip, String appName, String niwsClientName) {\n        return new OriginName(vip, appName, niwsClientName);\n    }\n\n    private OriginName(String target, String authority, String niwsClientName) {\n        this.target = Objects.requireNonNull(target, \"target\");\n        this.authority = Objects.requireNonNull(authority, \"authority\");\n        this.niwsClientName = Objects.requireNonNull(niwsClientName, \"niwsClientName\");\n        this.metricId = niwsClientName.toLowerCase(Locale.ROOT);\n    }\n\n    /**\n     * This is typically the VIP for the given Origin.\n     */\n    public String getTarget() {\n        return target;\n    }\n\n    /**\n     * Returns the niwsClientName.   This is normally used for interaction with NIWS, and should be used without prior\n     * knowledge that the value will be used in NIWS libraries.\n     */\n    public String getNiwsClientName() {\n        return niwsClientName;\n    }\n\n    /**\n     * Returns the identifier for this this metric name.  This may be different than any of the other\n     * fields; currently it is equivalent to the lowercased {@link #getNiwsClientName()}.\n     */\n    public String getMetricId() {\n        return metricId;\n    }\n\n    /**\n     * Returns the Authority of this origin.   This is used for establishing secure connections.  May be absent\n     * if the authority is not trusted.\n     */\n    public String getAuthority() {\n        return authority;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof OriginName)) {\n            return false;\n        }\n        OriginName that = (OriginName) o;\n        return Objects.equals(niwsClientName, that.niwsClientName)\n                && Objects.equals(target, that.target)\n                && Objects.equals(authority, that.authority);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(niwsClientName, target, authority);\n    }\n\n    @Override\n    public String toString() {\n        return \"OriginName{\" + \"niwsClientName='\"\n                + niwsClientName + '\\'' + \", target='\"\n                + target + '\\'' + \", authority='\"\n                + authority + '\\'' + '}';\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/origins/OriginThrottledException.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport java.util.Objects;\n\npublic abstract class OriginThrottledException extends ZuulException {\n    private final OriginName originName;\n    private final StatusCategory statusCategory;\n\n    public OriginThrottledException(OriginName originName, String msg, StatusCategory statusCategory) {\n        // Ensure this exception does not fill its stacktrace as causes too much load.\n        super(msg + \", origin=\" + originName, true);\n        this.originName = Objects.requireNonNull(originName, \"originName\");\n        this.statusCategory = statusCategory;\n        this.setStatusCode(503);\n    }\n\n    public OriginName getOriginName() {\n        return originName;\n    }\n\n    public StatusCategory getStatusCategory() {\n        return statusCategory;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/passport/CurrentPassport.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.passport;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Ticker;\nimport com.google.common.collect.Sets;\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport io.netty.channel.Channel;\nimport io.netty.util.AttributeKey;\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.ConcurrentModificationException;\nimport java.util.Deque;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class CurrentPassport {\n    protected static final Logger logger = LoggerFactory.getLogger(CurrentPassport.class);\n\n    private static final CachedDynamicBooleanProperty COUNT_STATES =\n            new CachedDynamicBooleanProperty(\"zuul.passport.count.enabled\", false);\n\n    public static final AttributeKey<CurrentPassport> CHANNEL_ATTR = AttributeKey.newInstance(\"_current_passport\");\n    private static final Ticker SYSTEM_TICKER = Ticker.systemTicker();\n    private static final Set<PassportState> CONTENT_STATES = Sets.newHashSet(\n            PassportState.IN_REQ_CONTENT_RECEIVED,\n            PassportState.IN_RESP_CONTENT_RECEIVED,\n            PassportState.OUT_REQ_CONTENT_SENDING,\n            PassportState.OUT_REQ_CONTENT_SENT,\n            PassportState.OUT_RESP_CONTENT_SENDING,\n            PassportState.OUT_RESP_CONTENT_SENT);\n    private static final CachedDynamicBooleanProperty CONTENT_STATE_ENABLED =\n            new CachedDynamicBooleanProperty(\"zuul.passport.state.content.enabled\", false);\n\n    private final Ticker ticker;\n    private final ArrayDeque<PassportItem> history;\n    private final HashSet<PassportState> statesAdded;\n    private final long creationTimeSinceEpochMs;\n\n    private final IntrospectiveReentrantLock historyLock = new IntrospectiveReentrantLock();\n    private final Unlocker unlocker = new Unlocker();\n\n    private final class Unlocker implements AutoCloseable {\n\n        @Override\n        public void close() {\n            historyLock.unlock();\n        }\n    }\n\n    private static final class IntrospectiveReentrantLock extends ReentrantLock {\n\n        @Override\n        protected Thread getOwner() {\n            return super.getOwner();\n        }\n    }\n\n    private Unlocker lock() {\n        boolean locked = false;\n        if ((historyLock.isLocked() && !historyLock.isHeldByCurrentThread()) || !(locked = historyLock.tryLock())) {\n            Thread owner = historyLock.getOwner();\n            String ownerStack = String.valueOf(owner != null ? Arrays.asList(owner.getStackTrace()) : historyLock);\n            logger.warn(\n                    \"CurrentPassport already locked!, other={}, self={}\",\n                    ownerStack,\n                    Thread.currentThread(),\n                    new ConcurrentModificationException());\n        }\n        if (!locked) {\n            historyLock.lock();\n        }\n        return unlocker;\n    }\n\n    CurrentPassport() {\n        this(SYSTEM_TICKER);\n    }\n\n    @VisibleForTesting\n    public CurrentPassport(Ticker ticker) {\n        this.ticker = ticker;\n        this.history = new ArrayDeque<>();\n        this.statesAdded = new HashSet<>();\n        this.creationTimeSinceEpochMs = System.currentTimeMillis();\n    }\n\n    public static CurrentPassport create() {\n        if (COUNT_STATES.get()) {\n            return new CountingCurrentPassport();\n        }\n        return new CurrentPassport();\n    }\n\n    public static CurrentPassport fromSessionContext(SessionContext ctx) {\n        return ctx.get(CommonContextKeys.PASSPORT);\n    }\n\n    public static CurrentPassport createForChannel(Channel ch) {\n        CurrentPassport passport = create();\n        passport.setOnChannel(ch);\n        return passport;\n    }\n\n    public static CurrentPassport fromChannel(Channel ch) {\n        CurrentPassport passport = fromChannelOrNull(ch);\n        if (passport == null) {\n            passport = create();\n            ch.attr(CHANNEL_ATTR).set(passport);\n        }\n        return passport;\n    }\n\n    public static CurrentPassport fromChannelOrNull(Channel ch) {\n        return ch.attr(CHANNEL_ATTR).get();\n    }\n\n    public void setOnChannel(Channel ch) {\n        ch.attr(CHANNEL_ATTR).set(this);\n    }\n\n    public static void clearFromChannel(Channel ch) {\n        ch.attr(CHANNEL_ATTR).set(null);\n    }\n\n    public PassportState getState() {\n        try (Unlocker ignored = lock()) {\n            PassportItem passportItem = history.peekLast();\n            return passportItem != null ? passportItem.getState() : null;\n        }\n    }\n\n    @VisibleForTesting\n    public Deque<PassportItem> getHistory() {\n        try (Unlocker ignored = lock()) {\n            // best effort, but doesn't actually protect anything\n            return history;\n        }\n    }\n\n    public void add(PassportState state) {\n        if (!CONTENT_STATE_ENABLED.get()) {\n            if (CONTENT_STATES.contains(state)) {\n                // Discard.\n                return;\n            }\n        }\n\n        try (Unlocker ignored = lock()) {\n            history.addLast(new PassportItem(state, now()));\n        }\n        statesAdded.add(state);\n    }\n\n    public void addIfNotAlready(PassportState state) {\n        if (!statesAdded.contains(state)) {\n            add(state);\n        }\n    }\n\n    public long calculateTimeBetweenFirstAnd(PassportState endState) {\n        long startTime = firstTime();\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (item.getState() == endState) {\n                    return item.getTime() - startTime;\n                }\n            }\n        }\n        return now() - startTime;\n    }\n\n    /**\n     * NOTE: This is NOT nanos since epoch. It's just since an arbitrary point in time. So only use relatively.\n     */\n    public long firstTime() {\n        try (Unlocker ignored = lock()) {\n            return history.getFirst().getTime();\n        }\n    }\n\n    public long creationTimeSinceEpochMs() {\n        return creationTimeSinceEpochMs;\n    }\n\n    public long calculateTimeBetween(StartAndEnd sae) {\n        if (sae.startNotFound() || sae.endNotFound()) {\n            return 0;\n        }\n        return sae.endTime - sae.startTime;\n    }\n\n    public long calculateTimeBetweenButIfNoEndThenUseNow(StartAndEnd sae) {\n        if (sae.startNotFound()) {\n            return 0;\n        }\n\n        // If no end state found, then default to now.\n        if (sae.endNotFound()) {\n            sae.endTime = now();\n        }\n\n        return sae.endTime - sae.startTime;\n    }\n\n    public StartAndEnd findStartAndEndStates(PassportState startState, PassportState endState) {\n        StartAndEnd sae = new StartAndEnd();\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (item.getState() == startState) {\n                    sae.startTime = item.getTime();\n                } else if (item.getState() == endState) {\n                    sae.endTime = item.getTime();\n                }\n            }\n        }\n\n        return sae;\n    }\n\n    public StartAndEnd findFirstStartAndLastEndStates(PassportState startState, PassportState endState) {\n        StartAndEnd sae = new StartAndEnd();\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (sae.startNotFound() && item.getState() == startState) {\n                    sae.startTime = item.getTime();\n                } else if (item.getState() == endState) {\n                    sae.endTime = item.getTime();\n                }\n            }\n        }\n        return sae;\n    }\n\n    public StartAndEnd findLastStartAndFirstEndStates(PassportState startState, PassportState endState) {\n        StartAndEnd sae = new StartAndEnd();\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (item.getState() == startState) {\n                    sae.startTime = item.getTime();\n                } else if (sae.endNotFound() && item.getState() == endState) {\n                    sae.endTime = item.getTime();\n                }\n            }\n        }\n        return sae;\n    }\n\n    public List<StartAndEnd> findEachPairOf(PassportState startState, PassportState endState) {\n        ArrayList<StartAndEnd> items = new ArrayList<>();\n\n        StartAndEnd currentPair = null;\n\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n\n                if (item.getState() == startState) {\n                    if (currentPair == null) {\n                        currentPair = new StartAndEnd();\n                        currentPair.startTime = item.getTime();\n                    }\n                } else if (item.getState() == endState) {\n                    if (currentPair != null) {\n                        currentPair.endTime = item.getTime();\n                        items.add(currentPair);\n                        currentPair = null;\n                    }\n                }\n            }\n        }\n\n        return items;\n    }\n\n    public PassportItem findState(PassportState state) {\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (item.getState() == state) {\n                    return item;\n                }\n            }\n        }\n        return null;\n    }\n\n    public PassportItem findStateBackwards(PassportState state) {\n        try (Unlocker ignored = lock()) {\n            Iterator itr = history.descendingIterator();\n            while (itr.hasNext()) {\n                PassportItem item = (PassportItem) itr.next();\n                if (item.getState() == state) {\n                    return item;\n                }\n            }\n        }\n        return null;\n    }\n\n    public List<PassportItem> findStates(PassportState state) {\n        ArrayList<PassportItem> items = new ArrayList<>();\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (item.getState() == state) {\n                    items.add(item);\n                }\n            }\n        }\n        return items;\n    }\n\n    public List<Long> findTimes(PassportState state) {\n        long startTick = firstTime();\n        ArrayList<Long> items = new ArrayList<>();\n        try (Unlocker ignored = lock()) {\n            for (PassportItem item : history) {\n                if (item.getState() == state) {\n                    items.add(item.getTime() - startTick);\n                }\n            }\n        }\n        return items;\n    }\n\n    public boolean wasProxyAttempt() {\n        // If an attempt was made to send outbound request headers on this session, then assume it was an\n        // attempt to proxy.\n        return findState(PassportState.OUT_REQ_HEADERS_SENDING) != null;\n    }\n\n    private long now() {\n        return ticker.read();\n    }\n\n    @Override\n    public String toString() {\n        try (Unlocker ignored = lock()) {\n            long startTime = history.size() > 0 ? firstTime() : 0;\n            long now = now();\n\n            StringBuilder sb = new StringBuilder();\n            sb.append(\"CurrentPassport {\");\n            sb.append(\"start_ms=\").append(creationTimeSinceEpochMs()).append(\", \");\n\n            sb.append('[');\n            for (PassportItem item : history) {\n                sb.append('+')\n                        .append(item.getTime() - startTime)\n                        .append('=')\n                        .append(item.getState().name())\n                        .append(\", \");\n            }\n            sb.append('+').append(now - startTime).append('=').append(\"NOW\");\n            sb.append(']');\n\n            sb.append('}');\n\n            return sb.toString();\n        }\n    }\n\n    @VisibleForTesting\n    public static CurrentPassport parseFromToString(String text) {\n        CurrentPassport passport = null;\n        Pattern ptn = Pattern.compile(\"CurrentPassport \\\\{start_ms=\\\\d+, \\\\[(.*)\\\\]\\\\}\");\n        Pattern ptnState = Pattern.compile(\"^\\\\+(\\\\d+)=(.+)$\");\n        Matcher m = ptn.matcher(text);\n        if (m.matches()) {\n            String[] stateStrs = m.group(1).split(\", \", -1);\n            MockTicker ticker = new MockTicker();\n            passport = new CurrentPassport(ticker);\n            try (Unlocker ignored = passport.lock()) {\n                for (String stateStr : stateStrs) {\n                    Matcher stateMatch = ptnState.matcher(stateStr);\n                    if (stateMatch.matches()) {\n                        String stateName = stateMatch.group(2);\n                        if (stateName.equals(\"NOW\")) {\n                            long startTime = passport.history.size() > 0 ? passport.firstTime() : 0;\n                            long now = Long.parseLong(stateMatch.group(1)) + startTime;\n                            ticker.setNow(now);\n                        } else {\n                            PassportState state = PassportState.valueOf(stateName);\n                            PassportItem item = new PassportItem(state, Long.parseLong(stateMatch.group(1)));\n                            passport.history.add(item);\n                        }\n                    }\n                }\n            }\n        }\n        return passport;\n    }\n\n    private static class MockTicker extends Ticker {\n        private long now = -1;\n\n        @Override\n        public long read() {\n            if (now == -1) {\n                throw new IllegalStateException();\n            }\n            return now;\n        }\n\n        public void setNow(long now) {\n            this.now = now;\n        }\n    }\n}\n\nclass CountingCurrentPassport extends CurrentPassport {\n    private static final Counter IN_REQ_HEADERS_RECEIVED_CNT = createCounter(\"in_req_hdrs_rec\");\n    private static final Counter IN_REQ_LAST_CONTENT_RECEIVED_CNT = createCounter(\"in_req_last_cont_rec\");\n\n    private static final Counter IN_RESP_HEADERS_RECEIVED_CNT = createCounter(\"in_resp_hdrs_rec\");\n    private static final Counter IN_RESP_LAST_CONTENT_RECEIVED_CNT = createCounter(\"in_resp_last_cont_rec\");\n\n    private static final Counter OUT_REQ_HEADERS_SENT_CNT = createCounter(\"out_req_hdrs_sent\");\n    private static final Counter OUT_REQ_LAST_CONTENT_SENT_CNT = createCounter(\"out_req_last_cont_sent\");\n\n    private static final Counter OUT_RESP_HEADERS_SENT_CNT = createCounter(\"out_resp_hdrs_sent\");\n    private static final Counter OUT_RESP_LAST_CONTENT_SENT_CNT = createCounter(\"out_resp_last_cont_sent\");\n\n    private static Counter createCounter(String name) {\n        return Spectator.globalRegistry().counter(\"zuul.passport.\" + name);\n    }\n\n    public CountingCurrentPassport() {\n        super();\n        incrementStateCounter(getState());\n    }\n\n    @Override\n    public void add(PassportState state) {\n        super.add(state);\n        incrementStateCounter(state);\n    }\n\n    private void incrementStateCounter(PassportState state) {\n        switch (state) {\n            case IN_REQ_HEADERS_RECEIVED:\n                IN_REQ_HEADERS_RECEIVED_CNT.increment();\n                break;\n            case IN_REQ_LAST_CONTENT_RECEIVED:\n                IN_REQ_LAST_CONTENT_RECEIVED_CNT.increment();\n                break;\n            case OUT_REQ_HEADERS_SENT:\n                OUT_REQ_HEADERS_SENT_CNT.increment();\n                break;\n            case OUT_REQ_LAST_CONTENT_SENT:\n                OUT_REQ_LAST_CONTENT_SENT_CNT.increment();\n                break;\n            case IN_RESP_HEADERS_RECEIVED:\n                IN_RESP_HEADERS_RECEIVED_CNT.increment();\n                break;\n            case IN_RESP_LAST_CONTENT_RECEIVED:\n                IN_RESP_LAST_CONTENT_RECEIVED_CNT.increment();\n                break;\n            case OUT_RESP_HEADERS_SENT:\n                OUT_RESP_HEADERS_SENT_CNT.increment();\n                break;\n            case OUT_RESP_LAST_CONTENT_SENT:\n                OUT_RESP_LAST_CONTENT_SENT_CNT.increment();\n                break;\n            default:\n                logger.debug(\"Not incrementing any state counter for state {}\", state);\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/passport/PassportItem.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.passport;\n\npublic class PassportItem {\n    private final long time;\n    private final PassportState state;\n\n    public PassportItem(PassportState state, long time) {\n        this.time = time;\n        this.state = state;\n    }\n\n    public long getTime() {\n        return time;\n    }\n\n    public PassportState getState() {\n        return state;\n    }\n\n    @Override\n    public String toString() {\n        return time + \"=\" + state;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/passport/PassportState.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.passport;\n\npublic enum PassportState {\n    IN_REQ_HEADERS_RECEIVED,\n    IN_REQ_CONTENT_RECEIVED,\n    IN_REQ_LAST_CONTENT_RECEIVED,\n    IN_REQ_REJECTED,\n    IN_REQ_READ_TIMEOUT,\n    IN_REQ_CANCELLED,\n\n    OUT_REQ_HEADERS_SENDING,\n    OUT_REQ_HEADERS_SENT,\n    OUT_REQ_HEADERS_ERROR_SENDING,\n    OUT_REQ_CONTENT_SENDING,\n    OUT_REQ_CONTENT_SENT,\n    OUT_REQ_CONTENT_ERROR_SENDING,\n    OUT_REQ_LAST_CONTENT_SENDING,\n    OUT_REQ_LAST_CONTENT_SENT,\n    OUT_REQ_LAST_CONTENT_ERROR_SENDING,\n\n    IN_RESP_HEADERS_RECEIVED,\n    IN_RESP_CONTENT_RECEIVED,\n    IN_RESP_LAST_CONTENT_RECEIVED,\n\n    OUT_RESP_HEADERS_SENDING,\n    OUT_RESP_HEADERS_SENT,\n    OUT_RESP_HEADERS_ERROR_SENDING,\n    OUT_RESP_CONTENT_SENDING,\n    OUT_RESP_CONTENT_SENT,\n    OUT_RESP_CONTENT_ERROR_SENDING,\n    OUT_RESP_LAST_CONTENT_SENDING,\n    OUT_RESP_LAST_CONTENT_SENT,\n    OUT_RESP_LAST_CONTENT_ERROR_SENDING,\n\n    FILTERS_INBOUND_START,\n    FILTERS_INBOUND_END,\n    FILTERS_OUTBOUND_START,\n    FILTERS_OUTBOUND_END,\n\n    FILTERS_INBOUND_BUF_START,\n    FILTERS_INBOUND_BUF_END,\n    FILTERS_OUTBOUND_BUF_START,\n    FILTERS_OUTBOUND_BUF_END,\n\n    ORIGIN_CONN_ACQUIRE_START,\n    ORIGIN_CONN_ACQUIRE_END,\n    ORIGIN_CONN_ACQUIRE_FAILED,\n\n    MISC_IO_START,\n    MISC_IO_STOP,\n\n    SERVER_CH_DISCONNECT,\n    SERVER_CH_CLOSE,\n    SERVER_CH_EXCEPTION,\n    SERVER_CH_IDLE_TIMEOUT,\n    SERVER_CH_ACTIVE,\n    SERVER_CH_INACTIVE,\n    SERVER_CH_THROTTLING,\n    SERVER_CH_REJECTING,\n    SERVER_CH_SSL_HANDSHAKE_COMPLETE,\n\n    ORIGIN_CH_CONNECTING,\n    ORIGIN_CH_CONNECTED,\n    ORIGIN_CH_DISCONNECT,\n    ORIGIN_CH_CLOSE,\n    ORIGIN_CH_EXCEPTION,\n    ORIGIN_CH_ACTIVE,\n    ORIGIN_CH_INACTIVE,\n    ORIGIN_CH_IDLE_TIMEOUT,\n    ORIGIN_CH_POOL_RETURNED,\n    ORIGIN_CH_READ_TIMEOUT,\n    ORIGIN_CH_IO_EX,\n    ORIGIN_RETRY_START,\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/passport/StartAndEnd.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.passport;\n\npublic class StartAndEnd {\n    long startTime = -1;\n    long endTime = -1;\n\n    public long getStart() {\n        return startTime;\n    }\n\n    public long getEnd() {\n        return endTime;\n    }\n\n    boolean startNotFound() {\n        return startTime == -1;\n    }\n\n    boolean endNotFound() {\n        return endTime == -1;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/plugins/Tracer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.plugins;\n\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.zuul.monitoring.TracerFactory;\nimport java.net.InetAddress;\nimport java.net.UnknownHostException;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Plugin to hook up Servo Tracers\n *\n * @author Mikey Cohen\n *         Date: 4/10/13\n *         Time: 4:51 PM\n */\npublic class Tracer extends TracerFactory {\n\n    @Override\n    public com.netflix.zuul.monitoring.Tracer startMicroTracer(String name) {\n        return new SpectatorTracer(name);\n    }\n\n    class SpectatorTracer implements com.netflix.zuul.monitoring.Tracer {\n\n        private String name;\n        private final long start;\n\n        private SpectatorTracer(String name) {\n            this.name = name;\n            start = System.nanoTime();\n        }\n\n        @Override\n        public void stopAndLog() {\n            Spectator.globalRegistry()\n                    .timer(name, \"hostname\", getHostName(), \"ip\", getIp())\n                    .record(System.nanoTime() - start, TimeUnit.NANOSECONDS);\n        }\n\n        @Override\n        public void setName(String name) {\n            this.name = name;\n        }\n    }\n\n    private static String getHostName() {\n        return (loadAddress() != null) ? loadAddress().getHostName() : \"unkownHost\";\n    }\n\n    private static String getIp() {\n        return (loadAddress() != null) ? loadAddress().getHostAddress() : \"unknownHost\";\n    }\n\n    private static InetAddress loadAddress() {\n        try {\n            return InetAddress.getLocalHost();\n        } catch (UnknownHostException e) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/AmazonInfoHolder.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.netflix.appinfo.AmazonInfo;\n\n/**\n * Builds and caches an <code>AmazonInfo</code> instance in memory.\n *\n * @author mhawthorne\n */\npublic class AmazonInfoHolder {\n\n    private static final AmazonInfo INFO = AmazonInfo.Builder.newBuilder().autoBuild(\"eureka\");\n\n    public static final AmazonInfo getInfo() {\n        return INFO;\n    }\n\n    private AmazonInfoHolder() {}\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/BasicRequestMetricsPublisher.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats;\n\nimport com.netflix.zuul.context.SessionContext;\n\n/**\n * User: michaels@netflix.com\n * Date: 6/4/15\n * Time: 4:22 PM\n */\npublic class BasicRequestMetricsPublisher implements RequestMetricsPublisher {\n    @Override\n    public void collectAndPublish(SessionContext context) {\n        // Record metrics here.\n\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/ErrorStatsData.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport com.netflix.zuul.stats.monitoring.NamedCount;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * Implementation of a Named counter to monitor and count error causes by route. Route is a defined zuul concept to\n * categorize requests into buckets. By default this is the first segment of the uri\n * @author Mikey Cohen\n * Date: 2/23/12\n * Time: 4:16 PM\n */\npublic class ErrorStatsData implements NamedCount {\n    private final String id;\n\n    private final String errorCause;\n    private final AtomicLong count = new AtomicLong();\n\n    /**\n     * create a counter by route and cause of error\n     * @param route\n     * @param cause\n     */\n    public ErrorStatsData(String route, String cause) {\n        if (route == null || route.equals(\"\")) {\n            route = \"UNKNOWN\";\n        }\n        id = route + \"_\" + cause;\n\n        this.errorCause = cause;\n        Registry registry = Spectator.globalRegistry();\n        PolledMeter.using(registry)\n                .withId(registry.createId(\"zuul.ErrorStatsData\", \"ID\", id))\n                .monitorValue(this, ErrorStatsData::getCount);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || !(o instanceof ErrorStatsData)) {\n            return false;\n        }\n\n        ErrorStatsData that = (ErrorStatsData) o;\n\n        return !(errorCause != null ? !errorCause.equals(that.errorCause) : that.errorCause != null);\n    }\n\n    @Override\n    public int hashCode() {\n        return errorCause != null ? errorCause.hashCode() : 0;\n    }\n\n    /**\n     * increments the counter\n     */\n    public void update() {\n        count.incrementAndGet();\n    }\n\n    @Override\n    public String getName() {\n        return id;\n    }\n\n    @Override\n    public long getCount() {\n        return count.get();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/ErrorStatsManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.netflix.zuul.stats.monitoring.MonitorRegistry;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * Manager to handle Error Statistics\n * @author Mikey Cohen\n * Date: 2/23/12\n * Time: 4:16 PM\n */\npublic class ErrorStatsManager {\n    ConcurrentHashMap<String, ConcurrentHashMap<String, ErrorStatsData>> routeMap =\n            new ConcurrentHashMap<String, ConcurrentHashMap<String, ErrorStatsData>>();\n    static final ErrorStatsManager INSTANCE = new ErrorStatsManager();\n\n    /**\n     *\n     * @return Singleton\n     */\n    public static ErrorStatsManager getManager() {\n        return INSTANCE;\n    }\n\n    /**\n     *\n     * @param route\n     * @param cause\n     * @return data structure for holding count information for a route and cause\n     */\n    public ErrorStatsData getStats(String route, String cause) {\n        Map<String, ErrorStatsData> map = routeMap.get(route);\n        if (map == null) {\n            return null;\n        }\n        return map.get(cause);\n    }\n\n    /**\n     * updates count for the given route and error cause\n     * @param route\n     * @param cause\n     */\n    public void putStats(String route, String cause) {\n        if (route == null) {\n            route = \"UNKNOWN_ROUTE\";\n        }\n        route = route.replace(\"/\", \"_\");\n        ConcurrentHashMap<String, ErrorStatsData> statsMap = routeMap.get(route);\n        if (statsMap == null) {\n            statsMap = new ConcurrentHashMap<String, ErrorStatsData>();\n            routeMap.putIfAbsent(route, statsMap);\n        }\n        ErrorStatsData sd = statsMap.get(cause);\n        if (sd == null) {\n            sd = new ErrorStatsData(route, cause);\n            ErrorStatsData sd1 = statsMap.putIfAbsent(cause, sd);\n            if (sd1 != null) {\n                sd = sd1;\n            } else {\n                MonitorRegistry.getInstance().registerObject(sd);\n            }\n        }\n        sd.update();\n    }\n\n    public static class UnitTest {}\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/NamedCountingMonitor.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport com.netflix.zuul.stats.monitoring.MonitorRegistry;\nimport com.netflix.zuul.stats.monitoring.NamedCount;\nimport java.util.concurrent.atomic.AtomicLong;\n\n/**\n * Simple Epic counter with a name and a count.\n *\n * @author mhawthorne\n */\npublic class NamedCountingMonitor implements NamedCount {\n\n    private final String name;\n\n    private final AtomicLong count = new AtomicLong();\n\n    public NamedCountingMonitor(String name) {\n        this.name = name;\n        Registry registry = Spectator.globalRegistry();\n        PolledMeter.using(registry)\n                .withId(registry.createId(\"zuul.ErrorStatsData\", \"ID\", name))\n                .monitorValue(this, NamedCountingMonitor::getCount);\n    }\n\n    /**\n     * registers this objects\n     */\n    public NamedCountingMonitor register() {\n        MonitorRegistry.getInstance().registerObject(this);\n        return this;\n    }\n\n    /**\n     * increments the counter\n     */\n    public long increment() {\n        return this.count.incrementAndGet();\n    }\n\n    @Override\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * @return the current count\n     */\n    @Override\n    public long getCount() {\n        return this.count.get();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/RequestMetricsPublisher.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.netflix.zuul.context.SessionContext;\n\n/**\n * User: michaels@netflix.com\n * Date: 3/9/15\n * Time: 5:56 PM\n */\npublic interface RequestMetricsPublisher {\n    void collectAndPublish(SessionContext context);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/RouteStatusCodeMonitor.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.spectator.api.patterns.PolledMeter;\nimport com.netflix.zuul.stats.monitoring.NamedCount;\nimport java.util.Objects;\nimport java.util.concurrent.atomic.AtomicLong;\nimport javax.annotation.Nullable;\n\n/**\n * counter for per route/status code counting\n * @author Mikey Cohen\n * Date: 2/3/12\n * Time: 3:04 PM\n */\npublic class RouteStatusCodeMonitor implements NamedCount {\n    private final String routeCode;\n\n    @VisibleForTesting\n    final String route;\n\n    private final int statusCode;\n\n    private final AtomicLong count = new AtomicLong();\n\n    public RouteStatusCodeMonitor(@Nullable String route, int statusCode) {\n        if (route == null) {\n            route = \"\";\n        }\n        this.route = route;\n        this.statusCode = statusCode;\n        this.routeCode = route + \"_\" + statusCode;\n        Registry registry = Spectator.globalRegistry();\n        PolledMeter.using(registry)\n                .withId(registry.createId(\"zuul.RouteStatusCodeMonitor\", \"ID\", routeCode))\n                .monitorValue(this, RouteStatusCodeMonitor::getCount);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || !(o instanceof RouteStatusCodeMonitor)) {\n            return false;\n        }\n\n        RouteStatusCodeMonitor statsData = (RouteStatusCodeMonitor) o;\n\n        if (statusCode != statsData.statusCode) {\n            return false;\n        }\n        if (!Objects.equals(route, statsData.route)) {\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    public int hashCode() {\n        int result = route != null ? route.hashCode() : 0;\n        result = 31 * result + statusCode;\n        return result;\n    }\n\n    @Override\n    public String getName() {\n        return routeCode;\n    }\n\n    @Override\n    public long getCount() {\n        return count.get();\n    }\n\n    /**\n     * increment the count\n     */\n    public void update() {\n        count.incrementAndGet();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/StatsManager.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport com.netflix.zuul.stats.monitoring.MonitorRegistry;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * High level statistics counter manager to count stats on various aspects of  requests\n *\n * @author Mikey Cohen\n *         Date: 2/3/12\n *         Time: 3:25 PM\n */\npublic class StatsManager {\n\n    private static final Logger LOG = LoggerFactory.getLogger(StatsManager.class);\n\n    protected static final Pattern HEX_PATTERN = Pattern.compile(\"[0-9a-fA-F]+\");\n\n    // should match *.amazonaws.com, *.nflxvideo.net, or raw IP addresses.\n    private static final Pattern HOST_PATTERN =\n            Pattern.compile(\"(?:(.+)\\\\.amazonaws\\\\.com)|((?:\\\\d{1,3}\\\\.?){4})|(ip-\\\\d+-\\\\d+-\\\\d+-\\\\d+)|\"\n                    + \"(?:(.+)\\\\.nflxvideo\\\\.net)|(?:(.+)\\\\.llnwd\\\\.net)|(?:(.+)\\\\.nflximg\\\\.com)\");\n\n    @VisibleForTesting\n    static final String HOST_HEADER = \"host\";\n\n    private static final String X_FORWARDED_FOR_HEADER = \"x-forwarded-for\";\n\n    @VisibleForTesting\n    static final String X_FORWARDED_PROTO_HEADER = \"x-forwarded-proto\";\n\n    @VisibleForTesting\n    final ConcurrentMap<String, ConcurrentHashMap<Integer, RouteStatusCodeMonitor>> routeStatusMap =\n            new ConcurrentHashMap<String, ConcurrentHashMap<Integer, RouteStatusCodeMonitor>>();\n\n    private final ConcurrentMap<String, NamedCountingMonitor> namedStatusMap =\n            new ConcurrentHashMap<String, NamedCountingMonitor>();\n\n    private final ConcurrentMap<String, NamedCountingMonitor> hostCounterMap =\n            new ConcurrentHashMap<String, NamedCountingMonitor>();\n\n    private final ConcurrentMap<String, NamedCountingMonitor> protocolCounterMap =\n            new ConcurrentHashMap<String, NamedCountingMonitor>();\n\n    private final ConcurrentMap<String, NamedCountingMonitor> ipVersionCounterMap =\n            new ConcurrentHashMap<String, NamedCountingMonitor>();\n\n    protected static StatsManager INSTANCE = new StatsManager();\n\n    public static StatsManager getManager() {\n        return INSTANCE;\n    }\n\n    /**\n     * @param route\n     * @param statusCode\n     * @return the RouteStatusCodeMonitor for the given route and status code\n     */\n    public RouteStatusCodeMonitor getRouteStatusCodeMonitor(String route, int statusCode) {\n        Map<Integer, RouteStatusCodeMonitor> map = routeStatusMap.get(route);\n        if (map == null) {\n            return null;\n        }\n        return map.get(statusCode);\n    }\n\n    @VisibleForTesting\n    NamedCountingMonitor getHostMonitor(String host) {\n        return this.hostCounterMap.get(hostKey(host));\n    }\n\n    @VisibleForTesting\n    NamedCountingMonitor getProtocolMonitor(String proto) {\n        return this.protocolCounterMap.get(protocolKey(proto));\n    }\n\n    @VisibleForTesting\n    static final String hostKey(String host) {\n        try {\n            Matcher m = HOST_PATTERN.matcher(host);\n\n            // I know which type of host matched by the number of the group that is non-null\n            // I use a different replacement string per host type to make the Epic stats more clear\n            if (m.matches()) {\n                if (m.group(1) != null) {\n                    host = host.replace(m.group(1), \"EC2\");\n                } else if (m.group(2) != null) {\n                    host = host.replace(m.group(2), \"IP\");\n                } else if (m.group(3) != null) {\n                    host = host.replace(m.group(3), \"IP\");\n                } else if (m.group(4) != null) {\n                    host = host.replace(m.group(4), \"CDN\");\n                } else if (m.group(5) != null) {\n                    host = host.replace(m.group(5), \"CDN\");\n                } else if (m.group(6) != null) {\n                    host = host.replace(m.group(6), \"CDN\");\n                }\n            }\n        } catch (Exception e) {\n            LOG.error(e.getMessage(), e);\n        }\n        return String.format(\"host_%s\", host);\n    }\n\n    private static final String protocolKey(String proto) {\n        return String.format(\"protocol_%s\", proto);\n    }\n\n    /**\n     * Collects counts statistics about the request: client ip address from the x-forwarded-for header;\n     * ipv4 or ipv6 and  host name from the host header;\n     *\n     * @param req\n     */\n    public void collectRequestStats(HttpRequestInfo req) {\n        // ipv4/ipv6 tracking\n        String clientIp;\n        String xForwardedFor = req.getHeaders().getFirst(X_FORWARDED_FOR_HEADER);\n        if (xForwardedFor == null) {\n            clientIp = req.getClientIp();\n        } else {\n            clientIp = extractClientIpFromXForwardedFor(xForwardedFor);\n        }\n\n        boolean isIPv6 = (clientIp != null) ? isIPv6(clientIp) : false;\n\n        String ipVersionKey = isIPv6 ? \"ipv6\" : \"ipv4\";\n        incrementNamedCountingMonitor(ipVersionKey, ipVersionCounterMap);\n\n        // host header\n        String host = req.getHeaders().getFirst(HOST_HEADER);\n        if (host != null) {\n            int colonIdx;\n            if (isIPv6) {\n                // an ipv6 host might be a raw IP with 7+ colons\n                colonIdx = host.lastIndexOf(\":\");\n            } else {\n                // strips port from host\n                colonIdx = host.indexOf(\":\");\n            }\n            if (colonIdx > -1) {\n                host = host.substring(0, colonIdx);\n            }\n            incrementNamedCountingMonitor(hostKey(host), this.hostCounterMap);\n        }\n\n        // http vs. https\n        String protocol = req.getHeaders().getFirst(X_FORWARDED_PROTO_HEADER);\n        if (protocol == null) {\n            protocol = req.getScheme();\n        }\n        incrementNamedCountingMonitor(protocolKey(protocol), this.protocolCounterMap);\n    }\n\n    @VisibleForTesting\n    static final boolean isIPv6(String ip) {\n        return ip.split(\":\", -1).length == 8;\n    }\n\n    @VisibleForTesting\n    static final String extractClientIpFromXForwardedFor(String xForwardedFor) {\n        return xForwardedFor.split(\",\", -1)[0];\n    }\n\n    /**\n     * helper method to create new monitor, place into map, and register with Epic, if necessary\n     */\n    protected void incrementNamedCountingMonitor(String name, ConcurrentMap<String, NamedCountingMonitor> map) {\n        NamedCountingMonitor monitor = map.get(name);\n        if (monitor == null) {\n            monitor = new NamedCountingMonitor(name);\n            NamedCountingMonitor conflict = map.putIfAbsent(name, monitor);\n            if (conflict != null) {\n                monitor = conflict;\n            } else {\n                MonitorRegistry.getInstance().registerObject(monitor);\n            }\n        }\n        monitor.increment();\n    }\n\n    /**\n     * collects and increments counts of status code, route/status code and statuc_code bucket, eg 2xx 3xx 4xx 5xx\n     *\n     * @param route\n     * @param statusCode\n     */\n    public void collectRouteStats(String route, int statusCode) {\n\n        // increments 200, 301, 401, 503, etc. status counters\n        String preciseStatusString = String.format(\"status_%d\", statusCode);\n        NamedCountingMonitor preciseStatus = namedStatusMap.get(preciseStatusString);\n        if (preciseStatus == null) {\n            preciseStatus = new NamedCountingMonitor(preciseStatusString);\n            NamedCountingMonitor found = namedStatusMap.putIfAbsent(preciseStatusString, preciseStatus);\n            if (found != null) {\n                preciseStatus = found;\n            } else {\n                MonitorRegistry.getInstance().registerObject(preciseStatus);\n            }\n        }\n        preciseStatus.increment();\n\n        // increments 2xx, 3xx, 4xx, 5xx status counters\n        String summaryStatusString = String.format(\"status_%dxx\", statusCode / 100);\n        NamedCountingMonitor summaryStatus = namedStatusMap.get(summaryStatusString);\n        if (summaryStatus == null) {\n            summaryStatus = new NamedCountingMonitor(summaryStatusString);\n            NamedCountingMonitor found = namedStatusMap.putIfAbsent(summaryStatusString, summaryStatus);\n            if (found != null) {\n                summaryStatus = found;\n            } else {\n                MonitorRegistry.getInstance().registerObject(summaryStatus);\n            }\n        }\n        summaryStatus.increment();\n\n        // increments route and status counter\n        if (route == null) {\n            route = \"ROUTE_NOT_FOUND\";\n        }\n        route = route.replace(\"/\", \"_\");\n        ConcurrentHashMap<Integer, RouteStatusCodeMonitor> statsMap = routeStatusMap.get(route);\n        if (statsMap == null) {\n            statsMap = new ConcurrentHashMap<Integer, RouteStatusCodeMonitor>();\n            routeStatusMap.putIfAbsent(route, statsMap);\n        }\n        RouteStatusCodeMonitor sd = statsMap.get(statusCode);\n        if (sd == null) {\n            // don't register only 404 status codes (these are garbage endpoints)\n            if (statusCode == 404) {\n                if (statsMap.size() == 0) {\n                    return;\n                }\n            }\n\n            sd = new RouteStatusCodeMonitor(route, statusCode);\n            RouteStatusCodeMonitor sd1 = statsMap.putIfAbsent(statusCode, sd);\n            if (sd1 != null) {\n                sd = sd1;\n            } else {\n                MonitorRegistry.getInstance().registerObject(sd);\n            }\n        }\n        sd.update();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/Monitor.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats.monitoring;\n\n/**\n * Interface to register a counter to monitor\n * @author Mikey Cohen\n * Date: 3/18/13\n * Time: 4:33 PM\n */\npublic interface Monitor {\n    /**\n     * Implement this to add this Counter to a Registry\n     * @param monitorObj\n     */\n    void register(NamedCount monitorObj);\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/MonitorRegistry.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats.monitoring;\n\n/**\n * Registry to register a Counter. a Monitor publisher should  be set to get counter information.\n * If it isn't set, registration will be ignored.\n * @author Mikey Cohen\n * Date: 3/18/13\n * Time: 4:24 PM\n */\npublic class MonitorRegistry {\n\n    private static final MonitorRegistry instance = new MonitorRegistry();\n    private Monitor publisher;\n\n    /**\n     * A Monitor implementation should be set here\n     * @param publisher\n     */\n    public void setPublisher(Monitor publisher) {\n        this.publisher = publisher;\n    }\n\n    public static MonitorRegistry getInstance() {\n        return instance;\n    }\n\n    public void registerObject(NamedCount monitorObj) {\n        if (publisher != null) {\n            publisher.register(monitorObj);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/monitoring/NamedCount.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.stats.monitoring;\n\n/**\n * Interface for a named counter\n * @author Mikey Cohen\n * Date: 3/18/13\n * Time: 4:33 PM\n */\npublic interface NamedCount {\n    String getName();\n\n    long getCount();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/status/StatusCategory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats.status;\n\nimport com.google.errorprone.annotations.Immutable;\n\n/**\n * Status Category\n *\n * Author: Arthur Gonigberg\n * Date: December 20, 2017\n */\n@Immutable\npublic interface StatusCategory {\n    String getId();\n\n    StatusCategoryGroup getGroup();\n\n    String getReason();\n\n    String name();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/status/StatusCategoryGroup.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats.status;\n\nimport com.google.errorprone.annotations.Immutable;\n\n/**\n * Status Category Group\n *\n * Author: Arthur Gonigberg\n * Date: December 20, 2017\n */\n@Immutable\npublic interface StatusCategoryGroup {\n    int getId();\n\n    String name();\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/status/StatusCategoryUtils.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats.status;\n\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport javax.annotation.Nullable;\n\n/**\n * User: michaels@netflix.com\n * Date: 6/9/15\n * Time: 2:48 PM\n */\npublic class StatusCategoryUtils {\n\n    public static StatusCategory getStatusCategory(ZuulMessage msg) {\n        return getStatusCategory(msg.getContext());\n    }\n\n    @Nullable\n    public static StatusCategory getStatusCategory(SessionContext ctx) {\n        return ctx.get(CommonContextKeys.STATUS_CATEGORY);\n    }\n\n    @Nullable\n    public static String getStatusCategoryReason(SessionContext ctx) {\n        return ctx.get(CommonContextKeys.STATUS_CATEGORY_REASON);\n    }\n\n    public static void setStatusCategory(SessionContext ctx, StatusCategory statusCategory) {\n        setStatusCategory(ctx, statusCategory, statusCategory.getReason());\n    }\n\n    public static void setStatusCategory(SessionContext ctx, StatusCategory statusCategory, String reason) {\n        ctx.put(CommonContextKeys.STATUS_CATEGORY, statusCategory);\n        ctx.put(CommonContextKeys.STATUS_CATEGORY_REASON, reason);\n    }\n\n    public static void clearStatusCategory(SessionContext ctx) {\n        ctx.remove(CommonContextKeys.STATUS_CATEGORY);\n        ctx.remove(CommonContextKeys.STATUS_CATEGORY_REASON);\n    }\n\n    @Nullable\n    public static StatusCategory getOriginStatusCategory(SessionContext ctx) {\n        return ctx.get(CommonContextKeys.ORIGIN_STATUS_CATEGORY);\n    }\n\n    @Nullable\n    public static String getOriginStatusCategoryReason(SessionContext ctx) {\n        return ctx.get(CommonContextKeys.ORIGIN_STATUS_CATEGORY_REASON);\n    }\n\n    public static void setOriginStatusCategory(SessionContext ctx, StatusCategory statusCategory) {\n        setOriginStatusCategory(ctx, statusCategory, statusCategory.getReason());\n    }\n\n    public static void setOriginStatusCategory(SessionContext ctx, StatusCategory statusCategory, String reason) {\n        ctx.put(CommonContextKeys.ORIGIN_STATUS_CATEGORY, statusCategory);\n        ctx.put(CommonContextKeys.ORIGIN_STATUS_CATEGORY_REASON, reason);\n    }\n\n    public static void clearOriginStatusCategory(SessionContext ctx) {\n        ctx.remove(CommonContextKeys.ORIGIN_STATUS_CATEGORY);\n        ctx.remove(CommonContextKeys.ORIGIN_STATUS_CATEGORY_REASON);\n    }\n\n    public static boolean isResponseHttpErrorStatus(HttpResponseMessage response) {\n        boolean isHttpError = false;\n        if (response != null) {\n            int status = response.getStatus();\n            isHttpError = isResponseHttpErrorStatus(status);\n        }\n        return isHttpError;\n    }\n\n    public static boolean isResponseHttpErrorStatus(int status) {\n        return (status < 100 || status >= 500);\n    }\n\n    public static void storeStatusCategoryIfNotAlreadyFailure(SessionContext context, StatusCategory statusCategory) {\n        if (statusCategory != null) {\n            StatusCategory nfs = getStatusCategory(context);\n            if (nfs == null || nfs.getGroup().getId() == ZuulStatusCategoryGroup.SUCCESS.getId()) {\n                setStatusCategory(context, statusCategory);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/status/ZuulStatusCategory.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats.status;\n\nimport com.google.errorprone.annotations.Immutable;\n\n/**\n * Zuul Status Category\n *\n * As some of the origin servers won't/can't return correct HTTP status codes in responses, we use set an\n * StatusCategory attribute to distinguish the main statuses that we care about from Zuul's perspective.\n *\n * These status categories are split into 2 groups:\n *\n *    SUCCESS | FAILURE\n *\n * each of which can have a narrower definition, eg:\n *\n *    FAILURE_THROTTLED\n *    FAILURE_ORIGIN\n *    etc...\n *\n *  which _should_ also be subdivided with one of:\n *\n *    ORIGIN\n *    CLIENT\n *    LOCAL\n */\n@Immutable\npublic enum ZuulStatusCategory implements StatusCategory {\n    SUCCESS(ZuulStatusCategoryGroup.SUCCESS, 1, \"Successfully proxied\"),\n    SUCCESS_NOT_FOUND(\n            ZuulStatusCategoryGroup.SUCCESS,\n            3,\n            \"Successfully proxied, origin responded with no resource found\"), // This is set on for all 404 responses\n\n    SUCCESS_LOCAL_NOTSET(\n            ZuulStatusCategoryGroup.SUCCESS,\n            4,\n            \"Default status\"), // This is set on the SessionContext as the default value.\n    SUCCESS_LOCAL_NO_ROUTE(ZuulStatusCategoryGroup.SUCCESS, 5, \"Unable to determine an origin to handle request\"),\n\n    FAILURE_LOCAL(ZuulStatusCategoryGroup.FAILURE, 1, \"Failed internally\"),\n    FAILURE_LOCAL_THROTTLED_ORIGIN_SERVER_MAXCONN(\n            ZuulStatusCategoryGroup.FAILURE, 7, \"Throttled due to reaching max number of connections to origin\"),\n    FAILURE_LOCAL_THROTTLED_ORIGIN_CONCURRENCY(\n            ZuulStatusCategoryGroup.FAILURE, 8, \"Throttled due to reaching concurrency limit to origin\"),\n    FAILURE_LOCAL_IDLE_TIMEOUT(ZuulStatusCategoryGroup.FAILURE, 9, \"Idle timeout due to channel inactivity\"),\n    FAILURE_LOCAL_HEADER_FIELDS_TOO_LARGE(ZuulStatusCategoryGroup.FAILURE, 5, \"Header Fields Too Large\"),\n    FAILURE_CLIENT_BAD_REQUEST(ZuulStatusCategoryGroup.FAILURE, 12, \"Invalid request provided\"),\n    FAILURE_CLIENT_CANCELLED(ZuulStatusCategoryGroup.FAILURE, 13, \"Client abandoned/closed the connection\"),\n    FAILURE_CLIENT_PIPELINE_REJECT(ZuulStatusCategoryGroup.FAILURE, 17, \"Client rejected due to HTTP Pipelining\"),\n    FAILURE_CLIENT_TIMEOUT(ZuulStatusCategoryGroup.FAILURE, 18, \"Timeout reading the client request\"),\n\n    FAILURE_ORIGIN(ZuulStatusCategoryGroup.FAILURE, 2, \"Origin returned an error status\"),\n    FAILURE_ORIGIN_READ_TIMEOUT(ZuulStatusCategoryGroup.FAILURE, 3, \"Timeout reading the response from origin\"),\n    FAILURE_ORIGIN_CONNECTIVITY(ZuulStatusCategoryGroup.FAILURE, 4, \"Connection to origin failed\"),\n    FAILURE_ORIGIN_THROTTLED(ZuulStatusCategoryGroup.FAILURE, 6, \"Throttled by origin returning 503 status\"),\n    FAILURE_ORIGIN_NO_SERVERS(ZuulStatusCategoryGroup.FAILURE, 14, \"No UP origin servers available in Discovery\"),\n    FAILURE_ORIGIN_RESET_CONNECTION(\n            ZuulStatusCategoryGroup.FAILURE, 15, \"Connection reset on an established origin connection\"),\n    FAILURE_ORIGIN_CLOSE_NOTIFY_CONNECTION(ZuulStatusCategoryGroup.FAILURE, 16, \"Connection TLS session shutdown\");\n\n    private final StatusCategoryGroup group;\n    private final String id;\n    private final String reason;\n\n    ZuulStatusCategory(StatusCategoryGroup group, int index, String reason) {\n        this.group = group;\n        this.id = (group.getId() + \"_\" + index).intern();\n        this.reason = reason;\n    }\n\n    @Override\n    public String getId() {\n        return id;\n    }\n\n    @Override\n    public StatusCategoryGroup getGroup() {\n        return group;\n    }\n\n    @Override\n    public String getReason() {\n        return reason;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/stats/status/ZuulStatusCategoryGroup.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats.status;\n\nimport com.google.errorprone.annotations.Immutable;\n\n/**\n * Zuul Status Category Group\n *\n * Author: Arthur Gonigberg\n * Date: December 20, 2017\n */\n@Immutable\npublic enum ZuulStatusCategoryGroup implements StatusCategoryGroup {\n    SUCCESS(1),\n    FAILURE(2);\n\n    private final int id;\n\n    ZuulStatusCategoryGroup(int id) {\n        this.id = id;\n    }\n\n    @Override\n    public int getId() {\n        return id;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/util/Gzipper.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.util;\n\nimport com.netflix.zuul.exception.ZuulException;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.HttpContent;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.zip.GZIPOutputStream;\n\n/**\n * Refactored this out of our GZipResponseFilter\n *\n * User: michaels@netflix.com\n * Date: 5/10/16\n * Time: 12:31 PM\n */\npublic class Gzipper {\n    private final ByteArrayOutputStream baos;\n    private final GZIPOutputStream gzos;\n\n    public Gzipper() throws RuntimeException {\n        try {\n            baos = new ByteArrayOutputStream(256);\n            gzos = new GZIPOutputStream(baos, true);\n        } catch (IOException e) {\n            throw new RuntimeException(\"Error finalizing the GzipOutputstream\", e);\n        }\n    }\n\n    private void write(ByteBuf bb) throws IOException {\n        byte[] bytes;\n        int offset;\n        int length = bb.readableBytes();\n        if (bb.hasArray()) {\n            /* avoid memory copy if possible */\n            bytes = bb.array();\n            offset = bb.arrayOffset();\n        } else {\n            bytes = new byte[length];\n            bb.getBytes(bb.readerIndex(), bytes);\n            offset = 0;\n        }\n        gzos.write(bytes, offset, length);\n    }\n\n    public void write(HttpContent chunk) {\n        try {\n            write(chunk.content());\n            gzos.flush();\n        } catch (IOException ioEx) {\n            throw new ZuulException(ioEx, \"Error Gzipping response content chunk\", true);\n        } finally {\n            chunk.release();\n        }\n    }\n\n    public void finish() throws RuntimeException {\n        try {\n            gzos.finish();\n            gzos.flush();\n            gzos.close();\n        } catch (IOException ioEx) {\n            throw new ZuulException(ioEx, \"Error finalizing the GzipOutputStream\", true);\n        }\n    }\n\n    public ByteBuf getByteBuf() {\n        ByteBuf copy = Unpooled.copiedBuffer(baos.toByteArray());\n        baos.reset();\n        return copy;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/util/HttpUtils.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.util;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpHeaderNames;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.HttpHeaderValues;\nimport io.netty.handler.codec.http2.Http2StreamChannel;\nimport java.util.Locale;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * User: Mike Smith\n * Date: 4/28/15\n * Time: 11:05 PM\n */\npublic class HttpUtils {\n    private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);\n    private static final char[] MALICIOUS_HEADER_CHARS = {'\\r', '\\n'};\n\n    /**\n     * Get the IP address of client making the request.\n     *\n     * Uses the \"x-forwarded-for\" HTTP header if available, otherwise uses the remote\n     * IP of requester.\n     *\n     * @param request <code>HttpRequestMessage</code>\n     * @return <code>String</code> IP address\n     */\n    public static String getClientIP(HttpRequestInfo request) {\n        String xForwardedFor = request.getHeaders().getFirst(HttpHeaderNames.X_FORWARDED_FOR);\n        String clientIP;\n        if (xForwardedFor == null) {\n            clientIP = request.getClientIp();\n        } else {\n            clientIP = extractClientIpFromXForwardedFor(xForwardedFor);\n        }\n        return clientIP;\n    }\n\n    /**\n     * Extract the client IP address from an x-forwarded-for header. Returns null if there is no x-forwarded-for header\n     *\n     * @param xForwardedFor a <code>String</code> value\n     * @return a <code>String</code> value\n     */\n    public static String extractClientIpFromXForwardedFor(String xForwardedFor) {\n        if (xForwardedFor == null) {\n            return null;\n        }\n        xForwardedFor = xForwardedFor.trim();\n        String tokenized[] = xForwardedFor.split(\",\", -1);\n        if (tokenized.length == 0) {\n            return null;\n        } else {\n            return tokenized[0].trim();\n        }\n    }\n\n    @VisibleForTesting\n    static boolean isCompressed(String contentEncoding) {\n        return contentEncoding.contains(HttpHeaderValues.GZIP.toString())\n                || contentEncoding.contains(HttpHeaderValues.DEFLATE.toString())\n                || contentEncoding.contains(HttpHeaderValues.BR.toString())\n                || contentEncoding.contains(HttpHeaderValues.COMPRESS.toString());\n    }\n\n    public static boolean isCompressed(Headers headers) {\n        String ce = headers.getFirst(HttpHeaderNames.CONTENT_ENCODING);\n        return ce != null && isCompressed(ce);\n    }\n\n    public static boolean acceptsGzip(Headers headers) {\n        String ae = headers.getFirst(HttpHeaderNames.ACCEPT_ENCODING);\n        return ae != null && ae.contains(HttpHeaderValues.GZIP.toString());\n    }\n\n    /**\n     * Ensure decoded new lines are not propagated in headers, in order to prevent XSS\n     *\n     * @param input - decoded header string\n     * @return - clean header string\n     */\n    public static String stripMaliciousHeaderChars(@Nullable String input) {\n        if (input == null) {\n            return null;\n        }\n        // TODO(carl-mastrangelo): implement this more efficiently.\n        for (char c : MALICIOUS_HEADER_CHARS) {\n            if (input.indexOf(c) != -1) {\n                input = input.replace(Character.toString(c), \"\");\n            }\n        }\n        return input;\n    }\n\n    public static boolean hasNonZeroContentLengthHeader(ZuulMessage msg) {\n        Integer contentLengthVal = getContentLengthIfPresent(msg);\n        return (contentLengthVal != null) && (contentLengthVal > 0);\n    }\n\n    public static Integer getContentLengthIfPresent(ZuulMessage msg) {\n        String contentLengthValue =\n                msg.getHeaders().getFirst(com.netflix.zuul.message.http.HttpHeaderNames.CONTENT_LENGTH);\n        if (!Strings.isNullOrEmpty(contentLengthValue)) {\n            try {\n                return Integer.valueOf(contentLengthValue);\n            } catch (NumberFormatException e) {\n                LOG.info(\"Invalid Content-Length header value on request. \" + \"value = {}\", contentLengthValue, e);\n            }\n        }\n        return null;\n    }\n\n    public static Integer getBodySizeIfKnown(ZuulMessage msg) {\n        Integer bodySize = getContentLengthIfPresent(msg);\n        if (bodySize != null) {\n            return bodySize;\n        }\n        if (msg.hasCompleteBody()) {\n            return msg.getBodyLength();\n        }\n        return null;\n    }\n\n    public static boolean hasChunkedTransferEncodingHeader(ZuulMessage msg) {\n        boolean isChunked = false;\n        String teValue = msg.getHeaders().getFirst(com.netflix.zuul.message.http.HttpHeaderNames.TRANSFER_ENCODING);\n        if (!Strings.isNullOrEmpty(teValue)) {\n            isChunked = teValue.toLowerCase(Locale.ROOT).equals(\"chunked\");\n        }\n        return isChunked;\n    }\n\n    /**\n     * If http/1 then will always want to just use ChannelHandlerContext.channel(), but for http/2\n     * will want the parent channel (as the child channel is different for each h2 stream).\n     */\n    public static Channel getMainChannel(ChannelHandlerContext ctx) {\n        return getMainChannel(ctx.channel());\n    }\n\n    public static Channel getMainChannel(Channel channel) {\n        if (channel instanceof Http2StreamChannel) {\n            return channel.parent();\n        }\n        return channel;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/util/JsonUtility.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.util;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Map;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Utility for generating JSON from Maps/Lists\n */\npublic class JsonUtility {\n\n    private static final Logger logger = LoggerFactory.getLogger(JsonUtility.class);\n\n    /**\n     * Pass in a Map and this method will return a JSON string.\n     *\n     * <p>The map can contain Objects, int[], Object[] and Collections and they will be converted\n     * into string representations.\n     *\n     * <p>Nested maps can be included as values and the JSON will have nested object notation.\n     *\n     * <p>Arrays/Collections can have Maps in them as well.\n     *\n     * <p>See the unit tests for examples.\n     *\n     * @param jsonData\n     */\n    public static String jsonFromMap(Map<String, Object> jsonData) {\n        try {\n            JsonDocument json = new JsonDocument();\n            json.startGroup();\n\n            for (String key : jsonData.keySet()) {\n                Object data = jsonData.get(key);\n                if (data instanceof Map) {\n                    /* it's a nested map, so we'll recursively add the JSON of this map to the current JSON */\n                    json.addValue(key, jsonFromMap((Map<String, Object>) data));\n                } else if (data instanceof Object[]) {\n                    /* it's an object array, so we'll iterate the elements and put them all in here */\n                    json.addValue(key, \"[\" + stringArrayFromObjectArray((Object[]) data) + \"]\");\n                } else if (data instanceof Collection) {\n                    /* it's a collection, so we'll iterate the elements and put them all in here */\n                    json.addValue(key, \"[\" + stringArrayFromObjectArray(((Collection) data).toArray()) + \"]\");\n                } else if (data instanceof int[]) {\n                    /* it's an int array, so we'll get the string representation */\n                    String intArray = Arrays.toString((int[]) data);\n                    /* remove whitespace */\n                    intArray = intArray.replaceAll(\" \", \"\");\n                    json.addValue(key, intArray);\n                } else if (data instanceof JsonCapableObject) {\n                    json.addValue(key, jsonFromMap(((JsonCapableObject) data).jsonMap()));\n                } else {\n                    /* all other objects we assume we are to just put the string value in */\n                    json.addValue(key, String.valueOf(data));\n                }\n            }\n\n            json.endGroup();\n\n            logger.debug(\"created json from map => {}\", json);\n            return json.toString();\n        } catch (Exception e) {\n            logger.error(\"Could not create JSON from Map. \", e);\n            return \"{}\";\n        }\n    }\n\n    /*\n     * return a string like: \"one\",\"two\",\"three\"\n     */\n    private static String stringArrayFromObjectArray(Object data[]) {\n        StringBuilder arrayAsString = new StringBuilder();\n        for (Object o : data) {\n            if (arrayAsString.length() > 0) {\n                arrayAsString.append(\",\");\n            }\n            if (o instanceof Map) {\n                arrayAsString.append(jsonFromMap((Map<String, Object>) o));\n            } else if (o instanceof JsonCapableObject) {\n                arrayAsString.append(jsonFromMap(((JsonCapableObject) o).jsonMap()));\n            } else {\n                arrayAsString.append(\"\\\"\").append(String.valueOf(o)).append(\"\\\"\");\n            }\n        }\n        return arrayAsString.toString();\n    }\n\n    private static class JsonDocument {\n        final StringBuilder json = new StringBuilder();\n\n        private boolean newGroup = false;\n\n        public JsonDocument startGroup() {\n            newGroup = true;\n            json.append(\"{\");\n            return this;\n        }\n\n        public JsonDocument endGroup() {\n            json.append(\"}\");\n            return this;\n        }\n\n        public JsonDocument addValue(String key, String value) {\n            if (!newGroup) {\n                // if this is not the first value in a group, put a comma\n                json.append(\",\");\n            }\n            /* once we're here, the group is no longer \"new\" */\n            newGroup = false;\n            /* append the key/value */\n            json.append(\"\\\"\").append(key).append(\"\\\"\");\n            json.append(\":\");\n            if (value.trim().startsWith(\"{\") || value.trim().startsWith(\"[\")) {\n                // the value is either JSON or an array, so we won't aggregate with quotes\n                json.append(value);\n            } else {\n                json.append(\"\\\"\").append(value).append(\"\\\"\");\n            }\n            return this;\n        }\n\n        @Override\n        public String toString() {\n            return json.toString();\n        }\n    }\n\n    public static interface JsonCapableObject {\n\n        public Map<String, Object> jsonMap();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/util/ProxyUtils.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.util;\n\nimport com.netflix.config.CachedDynamicBooleanProperty;\nimport com.netflix.zuul.message.HeaderName;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpHeaderNames;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * User: michaels@netflix.com\n * Date: 6/8/15\n * Time: 11:50 AM\n */\npublic class ProxyUtils {\n    private static final CachedDynamicBooleanProperty OVERWRITE_XF_HEADERS =\n            new CachedDynamicBooleanProperty(\"zuul.headers.xforwarded.overwrite\", false);\n\n    private static final Set<HeaderName> RESP_HEADERS_TO_STRIP = new HashSet<>();\n\n    static {\n        RESP_HEADERS_TO_STRIP.add(HttpHeaderNames.CONNECTION);\n        RESP_HEADERS_TO_STRIP.add(HttpHeaderNames.TRANSFER_ENCODING);\n        RESP_HEADERS_TO_STRIP.add(HttpHeaderNames.KEEP_ALIVE);\n    }\n\n    private static final Set<HeaderName> REQ_HEADERS_TO_STRIP = new HashSet<>();\n\n    static {\n        REQ_HEADERS_TO_STRIP.add(\n                HttpHeaderNames\n                        .CONTENT_LENGTH); // Because the httpclient library sets this itself, and doesn't like it if set\n        REQ_HEADERS_TO_STRIP.add(HttpHeaderNames.CONNECTION);\n        REQ_HEADERS_TO_STRIP.add(HttpHeaderNames.TRANSFER_ENCODING);\n        REQ_HEADERS_TO_STRIP.add(HttpHeaderNames.KEEP_ALIVE);\n    }\n\n    public static boolean isValidRequestHeader(HeaderName headerName) {\n        return !REQ_HEADERS_TO_STRIP.contains(headerName);\n    }\n\n    public static boolean isValidResponseHeader(HeaderName headerName) {\n        return !RESP_HEADERS_TO_STRIP.contains(headerName);\n    }\n\n    public static void addXForwardedHeaders(HttpRequestMessage request) {\n        // Add standard Proxy request headers.\n        Headers headers = request.getHeaders();\n        addXForwardedHeader(headers, HttpHeaderNames.X_FORWARDED_HOST, request.getOriginalHost());\n        addXForwardedHeader(headers, HttpHeaderNames.X_FORWARDED_PORT, Integer.toString(request.getPort()));\n        addXForwardedHeader(headers, HttpHeaderNames.X_FORWARDED_PROTO, request.getScheme());\n        addXForwardedHeader(headers, HttpHeaderNames.X_FORWARDED_FOR, request.getClientIp());\n    }\n\n    public static void addXForwardedHeader(Headers headers, HeaderName name, String latestValue) {\n        if (OVERWRITE_XF_HEADERS.get()) {\n            headers.set(name, latestValue);\n        } else {\n            // If this proxy header already exists (possibly due to an upstream ELB or reverse proxy\n            // setting it) then keep that value.\n            String existingValue = headers.getFirst(name);\n            if (existingValue == null) {\n                // Otherwise set new value.\n                if (latestValue != null) {\n                    headers.set(name, latestValue);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/main/java/com/netflix/zuul/util/VipUtils.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.util;\n\npublic final class VipUtils {\n    public static String getVIPPrefix(String vipAddress) {\n        for (int i = 0; i < vipAddress.length(); i++) {\n            char c = vipAddress.charAt(i);\n            if (c == '.' || c == ':') {\n                return vipAddress.substring(0, i);\n            }\n        }\n        return vipAddress;\n    }\n\n    /**\n     * Use {@link #extractUntrustedAppNameFromVIP} instead.\n     */\n    @Deprecated\n    public static String extractAppNameFromVIP(String vipAddress) {\n        String vipPrefix = getVIPPrefix(vipAddress);\n        return vipPrefix.split(\"-\", -1)[0];\n    }\n\n    /**\n     * Attempts to derive an app name from the VIP.   Because the VIP is an arbitrary collection of characters, the\n     * value is just a best guess and not suitable for security purposes.\n     */\n    public static String extractUntrustedAppNameFromVIP(String vipAddress) {\n        for (int i = 0; i < vipAddress.length(); i++) {\n            char c = vipAddress.charAt(i);\n            if (c == '-' || c == '.' || c == ':') {\n                return vipAddress.substring(0, i);\n            }\n        }\n        return vipAddress;\n    }\n\n    private VipUtils() {}\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/CloseOnIdleStateHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.netty.server.http2.DummyChannelHandler;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.timeout.IdleStateEvent;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass CloseOnIdleStateHandlerTest {\n\n    private final Registry registry = new DefaultRegistry();\n    private Id counterId;\n    private final String listener = \"test-idle-state\";\n\n    @BeforeEach\n    void setup() {\n        counterId = registry.createId(\"server.connections.idle.timeout\").withTags(\"id\", listener);\n    }\n\n    @Test\n    void incrementCounterOnIdleStateEvent() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addLast(new DummyChannelHandler());\n        channel.pipeline().addLast(new CloseOnIdleStateHandler(registry, listener));\n\n        channel.pipeline()\n                .context(DummyChannelHandler.class)\n                .fireUserEventTriggered(IdleStateEvent.ALL_IDLE_STATE_EVENT);\n\n        Counter idleTimeouts = (Counter) registry.get(counterId);\n        assertThat(idleTimeouts.count()).isEqualTo(1);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/HttpServerLifecycleChannelHandlerTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.State;\nimport com.netflix.netty.common.HttpServerLifecycleChannelHandler.HttpServerLifecycleInboundChannelHandler;\nimport com.netflix.netty.common.HttpServerLifecycleChannelHandler.HttpServerLifecycleOutboundChannelHandler;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.UnpooledByteBufAllocator;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultFullHttpRequest;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.util.ReferenceCountUtil;\nimport org.junit.jupiter.api.Test;\n\nclass HttpServerLifecycleChannelHandlerTest {\n\n    final class AssertReasonHandler extends ChannelInboundHandlerAdapter {\n\n        CompleteEvent completeEvent;\n\n        @Override\n        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {\n            assertThat(evt).isInstanceOf(CompleteEvent.class);\n            this.completeEvent = (CompleteEvent) evt;\n        }\n\n        public CompleteEvent getCompleteEvent() {\n            return completeEvent;\n        }\n    }\n\n    @Test\n    void completionEventReasonIsUpdatedOnPipelineReject() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpServerLifecycleOutboundChannelHandler());\n        AssertReasonHandler reasonHandler = new AssertReasonHandler();\n        channel.pipeline().addLast(reasonHandler);\n\n        channel.attr(HttpLifecycleChannelHandler.ATTR_STATE).set(State.STARTED);\n        // emulate pipeline rejection\n        channel.attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT).set(Boolean.TRUE);\n        // Fire close\n        channel.pipeline().close();\n\n        assertThat(reasonHandler.getCompleteEvent().getReason()).isEqualTo(CompleteReason.PIPELINE_REJECT);\n    }\n\n    @Test\n    void completionEventReasonIsCloseByDefault() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpServerLifecycleOutboundChannelHandler());\n        AssertReasonHandler reasonHandler = new AssertReasonHandler();\n        channel.pipeline().addLast(reasonHandler);\n\n        channel.attr(HttpLifecycleChannelHandler.ATTR_STATE).set(State.STARTED);\n        // Fire close\n        channel.pipeline().close();\n\n        assertThat(reasonHandler.getCompleteEvent().getReason()).isEqualTo(CompleteReason.CLOSE);\n    }\n\n    @Test\n    void pipelineRejectReleasesIfNeeded() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpServerLifecycleInboundChannelHandler());\n\n        ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();\n        try {\n            assertThat(buffer.refCnt()).isEqualTo(1);\n            FullHttpRequest httpRequest =\n                    new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/whatever\", buffer);\n            channel.attr(HttpLifecycleChannelHandler.ATTR_STATE).set(State.STARTED);\n            channel.writeInbound(httpRequest);\n\n            assertThat(channel.attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT)\n                            .get())\n                    .isEqualTo(Boolean.TRUE);\n            assertThat(buffer.refCnt()).isEqualTo(0);\n        } finally {\n            if (buffer.refCnt() != 0) {\n                ReferenceCountUtil.release(buffer);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/SourceAddressChannelHandlerTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetAddress;\nimport java.net.InetSocketAddress;\nimport java.net.NetworkInterface;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.Test;\nimport org.opentest4j.TestAbortedException;\n\n/**\n * Unit tests for {@link SourceAddressChannelHandler}.\n */\nclass SourceAddressChannelHandlerTest {\n\n    @Test\n    void ipv6AddressScopeIdRemoved() throws Exception {\n        Inet6Address address =\n                Inet6Address.getByAddress(\"localhost\", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 2);\n        assertThat(address.getScopeId()).isEqualTo(2);\n\n        String addressString = SourceAddressChannelHandler.getHostAddress(new InetSocketAddress(address, 8080));\n\n        assertThat(addressString).isEqualTo(\"0:0:0:0:0:0:0:1\");\n    }\n\n    @Test\n    void ipv4AddressString() throws Exception {\n        InetAddress address = Inet4Address.getByAddress(\"localhost\", new byte[] {127, 0, 0, 1});\n\n        String addressString = SourceAddressChannelHandler.getHostAddress(new InetSocketAddress(address, 8080));\n\n        assertThat(addressString).isEqualTo(\"127.0.0.1\");\n    }\n\n    @Test\n    void failsOnUnresolved() {\n        InetSocketAddress address = InetSocketAddress.createUnresolved(\"localhost\", 8080);\n\n        String addressString = SourceAddressChannelHandler.getHostAddress(address);\n\n        assertThat(addressString).isNull();\n    }\n\n    @Test\n    void mapsIpv4AddressFromIpv6Address() throws Exception {\n        // Can't think of a reason why this would ever come up, but testing it just in case.\n        // ::ffff:127.0.0.1\n        Inet6Address address = Inet6Address.getByAddress(\n                \"localhost\", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xFF, (byte) 0xFF, 127, 0, 0, 1}, -1);\n        assertThat(address.getScopeId()).isEqualTo(0);\n\n        String addressString = SourceAddressChannelHandler.getHostAddress(new InetSocketAddress(address, 8080));\n\n        assertThat(addressString).isEqualTo(\"127.0.0.1\");\n    }\n\n    @Test\n    void ipv6AddressScopeNameRemoved() throws Exception {\n        List<NetworkInterface> nics = Collections.list(NetworkInterface.getNetworkInterfaces());\n        Assumptions.assumeTrue(!nics.isEmpty(), \"No network interfaces\");\n\n        List<Throwable> failures = new ArrayList<>();\n        for (NetworkInterface nic : nics) {\n            Inet6Address address;\n            try {\n                address = Inet6Address.getByAddress(\n                        \"localhost\", new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, nic);\n            } catch (UnknownHostException e) {\n                // skip, the nic doesn't match\n                failures.add(e);\n                continue;\n            }\n\n            assertThat(address.toString().contains(\"%\")).as(address.toString()).isTrue();\n\n            String addressString = SourceAddressChannelHandler.getHostAddress(new InetSocketAddress(address, 8080));\n\n            assertThat(addressString).isEqualTo(\"0:0:0:0:0:0:0:1\");\n            return;\n        }\n\n        TestAbortedException failure = new TestAbortedException(\"No Compatible Nics were found\");\n        failures.forEach(failure::addSuppressed);\n        throw failure;\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/metrics/InstrumentedResourceLeakDetectorTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.metrics;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.buffer.ByteBuf;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass InstrumentedResourceLeakDetectorTest {\n\n    InstrumentedResourceLeakDetector<Object> leakDetector;\n\n    @BeforeEach\n    void setup() {\n        leakDetector = new InstrumentedResourceLeakDetector<>(ByteBuf.class, 1);\n    }\n\n    @Test\n    void test() {\n        leakDetector.reportTracedLeak(\"test\", \"test\");\n        assertThat(leakDetector.leakCounter.get()).isEqualTo(1);\n\n        leakDetector.reportTracedLeak(\"test\", \"test\");\n        assertThat(leakDetector.leakCounter.get()).isEqualTo(2);\n\n        leakDetector.reportTracedLeak(\"test\", \"test\");\n        assertThat(leakDetector.leakCounter.get()).isEqualTo(3);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/ElbProxyProtocolChannelHandlerTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.proxyprotocol;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.google.common.net.InetAddresses;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.haproxy.HAProxyMessage;\nimport io.netty.handler.codec.haproxy.HAProxyProtocolVersion;\nimport java.net.InetSocketAddress;\nimport java.nio.charset.StandardCharsets;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass ElbProxyProtocolChannelHandlerTest {\n\n    private Registry registry;\n\n    @BeforeEach\n    void setup() {\n        registry = new DefaultRegistry();\n    }\n\n    @Test\n    void noProxy() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, false));\n        ByteBuf buf = Unpooled.wrappedBuffer(\n                \"PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object dropped = channel.readInbound();\n        assertThat(buf).isEqualTo(dropped);\n        buf.release();\n\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNull();\n        assertThat(channel.pipeline().context(\"HAProxyMessageChannelHandler\")).isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)\n                        .get())\n                .isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE)\n                        .get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get())\n                .isNull();\n    }\n\n    @Test\n    void extraDataForwarded() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        ByteBuf buf = Unpooled.wrappedBuffer(\n                \"PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\\r\\nPOTATO\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object msg = channel.readInbound();\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNull();\n\n        ByteBuf readBuf = (ByteBuf) msg;\n        assertThat(new String(ByteBufUtil.getBytes(readBuf), StandardCharsets.US_ASCII))\n                .isEqualTo(\"POTATO\");\n        readBuf.release();\n    }\n\n    @Test\n    void passThrough_ProxyProtocolEnabled_nonProxyBytes() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        // Note that the bytes aren't prefixed by PROXY, as required by the spec\n        ByteBuf buf = Unpooled.wrappedBuffer(\n                \"TCP4 192.168.0.1 124.123.111.111 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object dropped = channel.readInbound();\n        assertThat(buf).isEqualTo(dropped);\n        buf.release();\n\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNull();\n        assertThat(channel.pipeline().context(\"HAProxyMessageChannelHandler\")).isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)\n                        .get())\n                .isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE)\n                        .get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get())\n                .isNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get())\n                .isNull();\n    }\n\n    @Test\n    void incrementCounterWhenPPEnabledButNonHAPMMessage() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        int port = 7007;\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(port);\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        // Note that the bytes aren't prefixed by PROXY, as required by the spec\n        ByteBuf buf = Unpooled.wrappedBuffer(\n                \"TCP4 192.168.0.1 124.123.111.111 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object dropped = channel.readInbound();\n        assertThat(buf).isEqualTo(dropped);\n        buf.release();\n\n        Counter counter = registry.counter(\n                \"zuul.hapm.decode\", \"success\", \"false\", \"port\", String.valueOf(port), \"needs_more_data\", \"false\");\n        assertThat(counter.count()).isEqualTo(1);\n    }\n\n    @Disabled\n    @Test\n    void detectsSplitPpv1Message() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        ByteBuf buf1 = Unpooled.wrappedBuffer(\"PROXY TCP4\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf1);\n        ByteBuf buf2 =\n                Unpooled.wrappedBuffer(\"192.168.0.1 124.123.111.111 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf2);\n\n        Object msg = channel.readInbound();\n        assertThat(msg instanceof HAProxyMessage).isTrue();\n        buf1.release();\n        buf2.release();\n        ((HAProxyMessage) msg).release();\n\n        // The handler should remove itself.\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.class))\n                .isNull();\n    }\n\n    @Test\n    void tracksSplitMessage() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        int port = 7007;\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(port);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        ByteBuf buf1 = Unpooled.wrappedBuffer(\"PROXY TCP4\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf1);\n\n        Object msg = channel.readInbound();\n        assertThat(msg).isEqualTo(buf1);\n        buf1.release();\n\n        // The handler should remove itself.\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.class))\n                .isNull();\n\n        Counter counter = registry.counter(\n                \"zuul.hapm.decode\", \"success\", \"false\", \"port\", String.valueOf(port), \"needs_more_data\", \"true\");\n        assertThat(counter.count()).isEqualTo(1);\n    }\n\n    @Test\n    void negotiateProxy_ppv1_ipv4() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        ByteBuf buf = Unpooled.wrappedBuffer(\n                \"PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object dropped = channel.readInbound();\n        assertThat(dropped).isNull();\n\n        // The handler should remove itself.\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNull();\n        assertThat(channel.pipeline().context(HAProxyMessageChannelHandler.class))\n                .isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)\n                        .get())\n                .isEqualTo(HAProxyProtocolVersion.V1);\n        // TODO(carl-mastrangelo): this check is in place, but it should be removed.  The message is not properly GC'd\n        // in later versions of netty.\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE)\n                        .get())\n                .isNotNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get())\n                .isEqualTo(\"124.123.111.111\");\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get())\n                .isEqualTo(new InetSocketAddress(InetAddresses.forString(\"124.123.111.111\"), 443));\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get())\n                .isEqualTo(\"192.168.0.1\");\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get())\n                .isEqualTo(new InetSocketAddress(InetAddresses.forString(\"192.168.0.1\"), 10008));\n    }\n\n    @Test\n    void negotiateProxy_ppv1_ipv6() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        ByteBuf buf = Unpooled.wrappedBuffer(\"PROXY TCP6 ::1 ::2 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object dropped = channel.readInbound();\n        assertThat(dropped).isNull();\n\n        // The handler should remove itself.\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)\n                        .get())\n                .isEqualTo(HAProxyProtocolVersion.V1);\n        // TODO(carl-mastrangelo): this check is in place, but it should be removed.  The message is not properly GC'd\n        // in later versions of netty.\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE)\n                        .get())\n                .isNotNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get())\n                .isEqualTo(\"::2\");\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get())\n                .isEqualTo(new InetSocketAddress(InetAddresses.forString(\"::2\"), 443));\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get())\n                .isEqualTo(\"::1\");\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get())\n                .isEqualTo(new InetSocketAddress(InetAddresses.forString(\"::1\"), 10008));\n    }\n\n    @Test\n    void negotiateProxy_ppv2_ipv4() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(7007);\n\n        channel.pipeline()\n                .addLast(ElbProxyProtocolChannelHandler.NAME, new ElbProxyProtocolChannelHandler(registry, true));\n        ByteBuf buf = Unpooled.wrappedBuffer(new byte[] {\n            0x0D,\n            0x0A,\n            0x0D,\n            0x0A,\n            0x00,\n            0x0D,\n            0x0A,\n            0x51,\n            0x55,\n            0x49,\n            0x54,\n            0x0A,\n            0x21,\n            0x11,\n            0x00,\n            0x0C,\n            (byte) 0xC0,\n            (byte) 0xA8,\n            0x00,\n            0x01,\n            0x7C,\n            0x7B,\n            0x6F,\n            0x6F,\n            0x27,\n            0x18,\n            0x01,\n            (byte) 0xbb\n        });\n        channel.writeInbound(buf);\n\n        Object dropped = channel.readInbound();\n        assertThat(dropped).isNull();\n\n        // The handler should remove itself.\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNull();\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_VERSION)\n                        .get())\n                .isEqualTo(HAProxyProtocolVersion.V2);\n        // TODO(carl-mastrangelo): this check is in place, but it should be removed.  The message is not properly GC'd\n        // in later versions of netty.\n        assertThat(channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE)\n                        .get())\n                .isNotNull();\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDRESS).get())\n                .isEqualTo(\"124.123.111.111\");\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).get())\n                .isEqualTo(new InetSocketAddress(InetAddresses.forString(\"124.123.111.111\"), 443));\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).get())\n                .isEqualTo(\"192.168.0.1\");\n        assertThat(channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get())\n                .isEqualTo(new InetSocketAddress(InetAddresses.forString(\"192.168.0.1\"), 10008));\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/HAProxyMessageChannelHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.proxyprotocol;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.haproxy.HAProxyMessage;\nimport io.netty.handler.codec.haproxy.HAProxyMessageDecoder;\nimport io.netty.handler.codec.haproxy.HAProxyTLV;\nimport java.net.InetSocketAddress;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\n\nclass HAProxyMessageChannelHandlerTest {\n\n    @Test\n    void setClientDestPortForHAPM() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        // This is normally done by Server.\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        // This is to emulate `ElbProxyProtocolChannelHandler`\n        channel.pipeline()\n                .addLast(HAProxyMessageDecoder.class.getSimpleName(), new HAProxyMessageDecoder())\n                .addLast(HAProxyMessageChannelHandler.class.getSimpleName(), new HAProxyMessageChannelHandler());\n\n        ByteBuf buf = Unpooled.wrappedBuffer(\n                \"PROXY TCP4 192.168.0.1 124.123.111.111 10008 443\\r\\n\".getBytes(StandardCharsets.US_ASCII));\n        channel.writeInbound(buf);\n\n        Object result = channel.readInbound();\n        assertThat(result).isNull();\n\n        InetSocketAddress destAddress = channel.attr(\n                        SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS)\n                .get();\n\n        InetSocketAddress srcAddress = (InetSocketAddress)\n                channel.attr(SourceAddressChannelHandler.ATTR_REMOTE_ADDR).get();\n\n        assertThat(destAddress.getHostString()).isEqualTo(\"124.123.111.111\");\n        assertThat(destAddress.getPort()).isEqualTo(443);\n\n        assertThat(srcAddress.getHostString()).isEqualTo(\"192.168.0.1\");\n        assertThat(srcAddress.getPort()).isEqualTo(10008);\n\n        Attrs attrs = channel.attr(Server.CONN_DIMENSIONS).get();\n        Integer port = HAProxyMessageChannelHandler.HAPM_DEST_PORT.get(attrs);\n        assertThat(port.intValue()).isEqualTo(443);\n        String sourceIpVersion = HAProxyMessageChannelHandler.HAPM_SRC_IP_VERSION.get(attrs);\n        assertThat(sourceIpVersion).isEqualTo(\"v4\");\n        String destIpVersion = HAProxyMessageChannelHandler.HAPM_DEST_IP_VERSION.get(attrs);\n        assertThat(destIpVersion).isEqualTo(\"v4\");\n    }\n\n    @Test\n    void v2parseCustomTLVs() {\n        byte[] header = new byte[46];\n\n        // Build \\r\\n\\r\\n\\0\\r\\nQUIT\\n\n        header[0] = 0x0D; //\n        header[1] = 0x0A; // -----\n        header[2] = 0x0D; // -----\n        header[3] = 0x0A; // -----\n        header[4] = 0x00; // -----\n        header[5] = 0x0D; // -----\n        header[6] = 0x0A; // -----\n        header[7] = 0x51; // -----\n        header[8] = 0x55; // -----\n        header[9] = 0x49; // -----\n        header[10] = 0x54; // -----\n        header[11] = 0x0A; // -----\n\n        header[12] = 0x21; // v2 PROXY\n        header[13] = 0x11; // TCP over IPv4\n\n        header[14] = 0x00; // Addl. bytes\n        header[15] = (byte) 0x1E; // -----\n\n        header[16] = (byte) 0xc0; // Src Addr 192.168.0.1\n        header[17] = (byte) 0xa8; // -----\n        header[18] = 0x00; // -----\n        header[19] = 0x01; // -----\n\n        header[20] = (byte) 0x7c; // Dst Addr 124.123.111.111\n        header[21] = (byte) 0x7b; // -----\n        header[22] = (byte) 0x6f; // -----\n        header[23] = (byte) 0x6f; // -----\n\n        header[24] = (byte) 0x27; // Source Port 10006\n        header[25] = 0x16; // -----\n\n        header[26] = 0x01; // Destination Port 443\n        header[27] = (byte) 0xbb; // -----\n\n        header[28] = (byte) (byte) 0xe2; // custom TLV type per spec\n        header[29] = (byte) 0x00; // Remaining bytes\n        header[30] = (byte) 0x0F; // -----\n\n        header[31] = (byte) 0x6e; // n\n        header[32] = (byte) 0x66; // f\n        header[33] = (byte) 0x6c; // l\n        header[34] = (byte) 0x78; // x\n        header[35] = (byte) 0x2e; // .\n        header[36] = (byte) 0x63; // c\n        header[37] = (byte) 0x75; // u\n        header[38] = (byte) 0x73; // s\n        header[39] = (byte) 0x74; // t\n        header[40] = (byte) 0x6f; // o\n        header[41] = (byte) 0x6d; // m\n        header[42] = (byte) 0x2e; // .\n        header[43] = (byte) 0x74; // t\n        header[44] = (byte) 0x6c; // l\n        header[45] = (byte) 0x76; // v\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.pipeline()\n                .addLast(HAProxyMessageDecoder.class.getSimpleName(), new HAProxyMessageDecoder())\n                .addLast(HAProxyMessageChannelHandler.class.getSimpleName(), new HAProxyMessageChannelHandler());\n\n        channel.writeInbound(Unpooled.wrappedBuffer(header));\n\n        Object result = channel.readInbound();\n        assertThat(result).isNull();\n\n        HAProxyMessage hapm =\n                channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get();\n\n        assertThat(hapm.sourceAddress()).isEqualTo(\"192.168.0.1\");\n        assertThat(hapm.destinationAddress()).isEqualTo(\"124.123.111.111\");\n        assertThat(hapm.sourcePort()).isEqualTo(10006);\n        assertThat(hapm.destinationPort()).isEqualTo(443);\n\n        List<HAProxyTLV> nflxTLV = channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_CUSTOM_TLVS)\n                .get();\n        assertThat(nflxTLV.size()).isEqualTo(1);\n        String payload = nflxTLV.get(0).content().toString(StandardCharsets.UTF_8);\n        assertThat(payload).isEqualTo(\"nflx.custom.tlv\");\n    }\n\n    @Test\n    void validatev2TCPV4NoTLVs() {\n\n        byte[] header = new byte[28];\n\n        // Build \\r\\n\\r\\n\\0\\r\\nQUIT\\n\n        header[0] = 0x0D; //\n        header[1] = 0x0A; // -----\n        header[2] = 0x0D; // -----\n        header[3] = 0x0A; // -----\n        header[4] = 0x00; // -----\n        header[5] = 0x0D; // -----\n        header[6] = 0x0A; // -----\n        header[7] = 0x51; // -----\n        header[8] = 0x55; // -----\n        header[9] = 0x49; // -----\n        header[10] = 0x54; // -----\n        header[11] = 0x0A; // -----\n\n        header[12] = 0x21; // v2 PROXY\n        header[13] = 0x11; // TCP over IPv4\n\n        header[14] = 0x00; // Addl. bytes\n        header[15] = (byte) 0x0C; // -----\n\n        header[16] = (byte) 0xc0; // Src Addr 192.168.0.1\n        header[17] = (byte) 0xa8; // -----\n        header[18] = 0x00; // -----\n        header[19] = 0x01; // -----\n\n        header[20] = (byte) 0x7c; // Dst Addr 124.123.111.111\n        header[21] = (byte) 0x7b; // -----\n        header[22] = (byte) 0x6f; // -----\n        header[23] = (byte) 0x6f; // -----\n\n        header[24] = (byte) 0x27; // Source Port 10006\n        header[25] = 0x16; // -----\n\n        header[26] = 0x01; // Destination Port 443\n        header[27] = (byte) 0xbb; // -----\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.pipeline()\n                .addLast(HAProxyMessageDecoder.class.getSimpleName(), new HAProxyMessageDecoder())\n                .addLast(HAProxyMessageChannelHandler.class.getSimpleName(), new HAProxyMessageChannelHandler());\n\n        channel.writeInbound(Unpooled.wrappedBuffer(header));\n\n        Object result = channel.readInbound();\n        assertThat(result).isNull();\n\n        HAProxyMessage hapm =\n                channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get();\n\n        assertThat(hapm.sourceAddress()).isEqualTo(\"192.168.0.1\");\n        assertThat(hapm.destinationAddress()).isEqualTo(\"124.123.111.111\");\n        assertThat(hapm.sourcePort()).isEqualTo(10006);\n        assertThat(hapm.destinationPort()).isEqualTo(443);\n\n        List<HAProxyTLV> customTLV = channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_CUSTOM_TLVS)\n                .get();\n        assertThat(customTLV.isEmpty()).isEqualTo(true);\n    }\n\n    @Test\n    void validateV2TCPV6NoTLVS() {\n        byte[] header = new byte[52];\n\n        // Build \\r\\n\\r\\n\\0\\r\\nQUIT\\n\n        header[0] = 0x0D; //\n        header[1] = 0x0A; // -----\n        header[2] = 0x0D; // -----\n        header[3] = 0x0A; // -----\n        header[4] = 0x00; // -----\n        header[5] = 0x0D; // -----\n        header[6] = 0x0A; // -----\n        header[7] = 0x51; // -----\n        header[8] = 0x55; // -----\n        header[9] = 0x49; // -----\n        header[10] = 0x54; // -----\n        header[11] = 0x0A; // -----\n\n        header[12] = 0x21; // v2 PROXY\n        header[13] = 0x21; // TCP over IPv6\n\n        header[14] = 0x00; // Addl. bytes\n        header[15] = 0x24; // -----\n\n        header[16] = 0x20; // Source Address\n        header[17] = 0x01; // -----\n        header[18] = 0x0d; // -----\n        header[19] = (byte) 0xb8; // -----\n        header[20] = (byte) 0x85; // -----\n        header[21] = (byte) 0xa3; // -----\n        header[22] = 0x00; // -----\n        header[23] = 0x00; // -----\n        header[24] = 0x00; // -----\n        header[25] = 0x00; // -----\n        header[26] = (byte) 0x8a; // -----\n        header[27] = 0x2e; // -----\n        header[28] = 0x03; // -----\n        header[29] = 0x70; // -----\n        header[30] = 0x73; // -----\n        header[31] = 0x34; // -----\n\n        header[32] = 0x10; // Destination Address\n        header[33] = 0x50; // -----\n        header[34] = 0x00; // -----\n        header[35] = 0x00; // -----\n        header[36] = 0x00; // -----\n        header[37] = 0x00; // -----\n        header[38] = 0x00; // -----\n        header[39] = 0x00; // -----\n        header[40] = 0x00; // -----\n        header[41] = 0x05; // -----\n        header[42] = 0x06; // -----\n        header[43] = 0x00; // -----\n        header[44] = 0x30; // -----\n        header[45] = 0x0c; // -----\n        header[46] = 0x32; // -----\n        header[47] = 0x6b; // -----\n\n        header[48] = (byte) 0x27; // Source Port 10006\n        header[49] = 0x16; // -----\n\n        header[50] = 0x01; // Destination Port 443\n        header[51] = (byte) 0xbb; // -----\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.attr(Server.CONN_DIMENSIONS).set(Attrs.newInstance());\n        channel.pipeline()\n                .addLast(HAProxyMessageDecoder.class.getSimpleName(), new HAProxyMessageDecoder())\n                .addLast(HAProxyMessageChannelHandler.class.getSimpleName(), new HAProxyMessageChannelHandler());\n\n        channel.writeInbound(Unpooled.wrappedBuffer(header));\n\n        Object result = channel.readInbound();\n        assertThat(result).isNull();\n\n        HAProxyMessage hapm =\n                channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_MESSAGE).get();\n\n        assertThat(hapm.sourceAddress()).isEqualTo(\"2001:db8:85a3:0:0:8a2e:370:7334\");\n        assertThat(hapm.destinationAddress()).isEqualTo(\"1050:0:0:0:5:600:300c:326b\");\n        assertThat(hapm.sourcePort()).isEqualTo(10006);\n        assertThat(hapm.destinationPort()).isEqualTo(443);\n\n        List<HAProxyTLV> customTLV = channel.attr(HAProxyMessageChannelHandler.ATTR_HAPROXY_CUSTOM_TLVS)\n                .get();\n        assertThat(customTLV.isEmpty()).isEqualTo(true);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/proxyprotocol/StripUntrustedProxyHeadersHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.proxyprotocol;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.common.collect.ImmutableList;\nimport com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler.AllowWhen;\nimport com.netflix.netty.common.ssl.SslHandshakeInfo;\nimport com.netflix.zuul.netty.server.ssl.SslHandshakeInfoHandler;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.DefaultHttpHeaders;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.ssl.ClientAuth;\nimport io.netty.util.AttributeKey;\nimport io.netty.util.DefaultAttributeMap;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.junit.jupiter.MockitoSettings;\nimport org.mockito.quality.Strictness;\n\n/**\n * Strip Untrusted Proxy Headers Handler Test\n *\n * @author Arthur Gonigberg\n * @since May 27, 2020\n */\n@ExtendWith(MockitoExtension.class)\n@MockitoSettings(strictness = Strictness.LENIENT)\nclass StripUntrustedProxyHeadersHandlerTest {\n\n    @Mock\n    private ChannelHandlerContext channelHandlerContext;\n\n    @Mock\n    private HttpRequest msg;\n\n    private HttpHeaders headers;\n\n    @Mock\n    private Channel channel;\n\n    @Mock\n    private SslHandshakeInfo sslHandshakeInfo;\n\n    @BeforeEach\n    void before() {\n        when(channelHandlerContext.channel()).thenReturn(channel);\n\n        DefaultAttributeMap attributeMap = new DefaultAttributeMap();\n        attributeMap.attr(SslHandshakeInfoHandler.ATTR_SSL_INFO).set(sslHandshakeInfo);\n        when(channel.attr(any())).thenAnswer(arg -> attributeMap.attr((AttributeKey) arg.getArguments()[0]));\n\n        headers = new DefaultHttpHeaders();\n        when(msg.headers()).thenReturn(headers);\n        headers.add(HttpHeaderNames.HOST, \"netflix.com\");\n    }\n\n    @Test\n    void allow_never() throws Exception {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.NEVER);\n\n        stripHandler.channelRead(channelHandlerContext, msg);\n\n        verify(stripHandler).stripXFFHeaders(any());\n    }\n\n    @Test\n    void allow_always() throws Exception {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.ALWAYS);\n\n        stripHandler.channelRead(channelHandlerContext, msg);\n\n        verify(stripHandler, never()).stripXFFHeaders(any());\n        verify(stripHandler).checkBlacklist(any(), any());\n    }\n\n    @Test\n    void allow_mtls_noCert() throws Exception {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH);\n\n        stripHandler.channelRead(channelHandlerContext, msg);\n\n        verify(stripHandler).stripXFFHeaders(any());\n    }\n\n    @Test\n    void allow_mtls_cert() throws Exception {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH);\n        when(sslHandshakeInfo.getClientAuthRequirement()).thenReturn(ClientAuth.REQUIRE);\n\n        stripHandler.channelRead(channelHandlerContext, msg);\n\n        verify(stripHandler, never()).stripXFFHeaders(any());\n        verify(stripHandler).checkBlacklist(any(), any());\n    }\n\n    @Test\n    void blacklist_noMatch() {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH);\n\n        stripHandler.checkBlacklist(msg, ImmutableList.of(\"netflix.net\"));\n\n        verify(stripHandler, never()).stripXFFHeaders(any());\n    }\n\n    @Test\n    void blacklist_match() {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH);\n\n        stripHandler.checkBlacklist(msg, ImmutableList.of(\"netflix.com\"));\n\n        verify(stripHandler).stripXFFHeaders(any());\n    }\n\n    @Test\n    void blacklist_match_casing() {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH);\n\n        stripHandler.checkBlacklist(msg, ImmutableList.of(\"NeTfLiX.cOm\"));\n\n        verify(stripHandler).stripXFFHeaders(any());\n    }\n\n    @Test\n    void strip_match() {\n        StripUntrustedProxyHeadersHandler stripHandler = getHandler(AllowWhen.MUTUAL_SSL_AUTH);\n\n        headers.add(\"x-forwarded-for\", \"abcd\");\n        stripHandler.stripXFFHeaders(msg);\n\n        assertThat(headers.contains(\"x-forwarded-for\")).isFalse();\n    }\n\n    private StripUntrustedProxyHeadersHandler getHandler(AllowWhen allowWhen) {\n        return spy(new StripUntrustedProxyHeadersHandler(allowWhen));\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/ssl/ServerSslConfigTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.ssl;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.handler.ssl.ClientAuth;\nimport java.io.File;\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\n\nclass ServerSslConfigTest {\n\n    @Test\n    void builderSetsDefaults() {\n        ServerSslConfig config = ServerSslConfig.builder().build();\n\n        assertThat(config.getClientAuth()).isEqualTo(ClientAuth.NONE);\n        assertThat(config.getSessionTimeout()).isGreaterThan(0);\n        assertThat(config.isSessionTicketsEnabled()).isFalse();\n        assertThat(config.getProtocols()).isNull();\n        assertThat(config.getCiphers()).isNull();\n        assertThat(config.getCertChainFile()).isNull();\n        assertThat(config.getKeyFile()).isNull();\n        assertThat(config.getClientAuthTrustStoreFile()).isNull();\n        assertThat(config.getClientAuthTrustStorePassword()).isNull();\n        assertThat(config.getClientAuthTrustStorePasswordFile()).isNull();\n    }\n\n    @Test\n    void builderSetsAllFields() {\n        File certFile = new File(\"cert.pem\");\n        File keyFile = new File(\"key.pem\");\n        File trustStoreFile = new File(\"truststore.jks\");\n        List<String> ciphers = List.of(\"TLS_AES_128_GCM_SHA256\");\n\n        ServerSslConfig config = ServerSslConfig.builder()\n                .protocols(new String[] {\"TLSv1.3\", \"TLSv1.2\"})\n                .ciphers(ciphers)\n                .certChainFile(certFile)\n                .keyFile(keyFile)\n                .clientAuth(ClientAuth.REQUIRE)\n                .clientAuthTrustStoreFile(trustStoreFile)\n                .clientAuthTrustStorePassword(\"secret\")\n                .sessionTimeout(3600)\n                .sessionTicketsEnabled(true)\n                .build();\n\n        assertThat(config.getProtocols()).containsExactly(\"TLSv1.3\", \"TLSv1.2\");\n        assertThat(config.getCiphers()).containsExactly(\"TLS_AES_128_GCM_SHA256\");\n        assertThat(config.getCertChainFile()).isEqualTo(certFile);\n        assertThat(config.getKeyFile()).isEqualTo(keyFile);\n        assertThat(config.getClientAuth()).isEqualTo(ClientAuth.REQUIRE);\n        assertThat(config.getClientAuthTrustStoreFile()).isEqualTo(trustStoreFile);\n        assertThat(config.getClientAuthTrustStorePassword()).isEqualTo(\"secret\");\n        assertThat(config.getSessionTimeout()).isEqualTo(3600);\n        assertThat(config.isSessionTicketsEnabled()).isTrue();\n    }\n\n    @Test\n    void getDefaultCiphersReturnsNonEmptyList() {\n        assertThat(ServerSslConfig.getDefaultCiphers()).isNotEmpty();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/netty/common/throttle/MaxInboundConnectionsHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.netty.common.throttle;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.netty.server.http2.DummyChannelHandler;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass MaxInboundConnectionsHandlerTest {\n\n    private final Registry registry = new DefaultRegistry();\n    private final String listener = \"test-throttled\";\n    private Id counterId;\n\n    @BeforeEach\n    void setup() {\n        counterId = registry.createId(\"server.connections.throttled\").withTags(\"id\", listener);\n    }\n\n    @Test\n    void verifyPassportStateAndAttrs() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addLast(new DummyChannelHandler());\n        channel.pipeline().addLast(new MaxInboundConnectionsHandler(registry, listener, 1));\n\n        // Fire twice to increment current conns. count\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelActive();\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelActive();\n\n        Counter throttledCount = (Counter) registry.get(counterId);\n\n        assertThat(throttledCount.count()).isEqualTo(1);\n        assertThat(CurrentPassport.fromChannel(channel).getState()).isEqualTo(PassportState.SERVER_CH_THROTTLING);\n        assertThat(channel.attr(MaxInboundConnectionsHandler.ATTR_CH_THROTTLED).get())\n                .isTrue();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/AttrsTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nimport org.junit.jupiter.api.Test;\n\nclass AttrsTest {\n    @Test\n    void keysAreUnique() {\n        Attrs attrs = Attrs.newInstance();\n        Attrs.Key<String> key1 = Attrs.newKey(\"foo\");\n        key1.put(attrs, \"bar\");\n        Attrs.Key<String> key2 = Attrs.newKey(\"foo\");\n        key2.put(attrs, \"baz\");\n\n        assertThat(attrs.keySet()).containsExactlyInAnyOrder(key1, key2);\n    }\n\n    @Test\n    void newKeyFailsOnNull() {\n        assertThatThrownBy(() -> Attrs.newKey(null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void attrsPutFailsOnNull() {\n        Attrs attrs = Attrs.newInstance();\n        Attrs.Key<String> key = Attrs.newKey(\"foo\");\n\n        assertThatThrownBy(() -> key.put(attrs, null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void attrsPutReplacesOld() {\n        Attrs attrs = Attrs.newInstance();\n        Attrs.Key<String> key = Attrs.newKey(\"foo\");\n        key.put(attrs, \"bar\");\n        key.put(attrs, \"baz\");\n\n        assertThat(key.get(attrs)).isEqualTo(\"baz\");\n        assertThat(attrs.keySet()).containsExactly(key);\n    }\n\n    @Test\n    void getReturnsNull() {\n        Attrs attrs = Attrs.newInstance();\n        Attrs.Key<String> key = Attrs.newKey(\"foo\");\n\n        assertThat(key.get(attrs)).isNull();\n    }\n\n    @Test\n    void getOrDefault_picksDefault() {\n        Attrs attrs = Attrs.newInstance();\n        Attrs.Key<String> key = Attrs.newKey(\"foo\");\n\n        assertThat(key.getOrDefault(attrs, \"bar\")).isEqualTo(\"bar\");\n    }\n\n    @Test\n    void getOrDefault_failsOnNullDefault() {\n        Attrs attrs = Attrs.newInstance();\n        Attrs.Key<String> key = Attrs.newKey(\"foo\");\n        key.put(attrs, \"bar\");\n\n        assertThatThrownBy(() -> key.getOrDefault(attrs, null)).isInstanceOf(NullPointerException.class);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/DynamicFilterLoaderTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.filters.BaseSyncFilter;\nimport com.netflix.zuul.filters.FilterRegistry;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.MutableFilterRegistry;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport java.util.Collection;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockitoAnnotations;\n\nclass DynamicFilterLoaderTest {\n\n    private final FilterRegistry registry = new MutableFilterRegistry();\n\n    private final FilterFactory filterFactory = new DefaultFilterFactory();\n\n    private DynamicFilterLoader loader;\n\n    @BeforeEach\n    void before() throws Exception {\n        MockitoAnnotations.initMocks(this);\n\n        loader = new DynamicFilterLoader(registry, filterFactory);\n    }\n\n    @Test\n    void testPutFiltersForClasses() throws Exception {\n        loader.putFiltersForClasses(new String[] {TestZuulFilter.class.getName()});\n\n        Collection<ZuulFilter<?, ?>> filters = registry.getAllFilters();\n        assertThat(filters.size()).isEqualTo(1);\n    }\n\n    @Test\n    void testPutFiltersForClassesException() throws Exception {\n        Exception caught = null;\n        try {\n            loader.putFiltersForClasses(new String[] {\"asdf\"});\n        } catch (ClassNotFoundException e) {\n            caught = e;\n        }\n        assertThat(caught != null).isTrue();\n        Collection<ZuulFilter<?, ?>> filters = registry.getAllFilters();\n        assertThat(filters.size()).isEqualTo(0);\n    }\n\n    @Test\n    void testGetFiltersByType() throws Exception {\n        loader.putFiltersForClasses(new String[] {TestZuulFilter.class.getName()});\n\n        Collection<ZuulFilter<?, ?>> filters = registry.getAllFilters();\n        assertThat(filters.size()).isEqualTo(1);\n\n        Collection<ZuulFilter<?, ?>> list = loader.getFiltersByType(FilterType.INBOUND);\n        assertThat(list != null).isTrue();\n        assertThat(list.size()).isEqualTo(1);\n        ZuulFilter<?, ?> filter = list.iterator().next();\n        assertThat(filter != null).isTrue();\n        assertThat(filter.filterType()).isEqualTo(FilterType.INBOUND);\n    }\n\n    private static final class TestZuulFilter extends BaseSyncFilter {\n\n        TestZuulFilter() {\n            super();\n        }\n\n        @Override\n        public FilterType filterType() {\n            return FilterType.INBOUND;\n        }\n\n        @Override\n        public int filterOrder() {\n            return 0;\n        }\n\n        @Override\n        public boolean shouldFilter(ZuulMessage msg) {\n            return false;\n        }\n\n        @Override\n        public ZuulMessage apply(ZuulMessage msg) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/StaticFilterLoaderTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.google.common.collect.ImmutableSet;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.filters.http.HttpInboundSyncFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.SortedSet;\nimport org.junit.jupiter.api.Test;\n\nclass StaticFilterLoaderTest {\n\n    private static final FilterFactory factory = new DefaultFilterFactory();\n\n    @Test\n    void getFiltersByType() {\n        StaticFilterLoader filterLoader = new StaticFilterLoader(\n                factory, ImmutableSet.of(DummyFilter2.class, DummyFilter1.class, DummyFilter22.class));\n\n        SortedSet<ZuulFilter<?, ?>> filters = filterLoader.getFiltersByType(FilterType.INBOUND);\n        assertThat(filters).hasSize(3);\n        List<ZuulFilter<?, ?>> filterList = new ArrayList<>(filters);\n\n        assertThat(filterList.get(0)).isInstanceOf(DummyFilter1.class);\n        assertThat(filterList.get(1)).isInstanceOf(DummyFilter2.class);\n        assertThat(filterList.get(2)).isInstanceOf(DummyFilter22.class);\n    }\n\n    @Test\n    void getFilterByNameAndType() {\n        StaticFilterLoader filterLoader =\n                new StaticFilterLoader(factory, ImmutableSet.of(DummyFilter2.class, DummyFilter1.class));\n\n        ZuulFilter<?, ?> filter = filterLoader.getFilterByNameAndType(\"Robin\", FilterType.INBOUND);\n\n        assertThat(filter).isInstanceOf(DummyFilter2.class);\n    }\n\n    @Filter(order = 0, type = FilterType.INBOUND)\n    static class DummyFilter1 extends HttpInboundSyncFilter {\n\n        @Override\n        public String filterName() {\n            return \"Batman\";\n        }\n\n        @Override\n        public int filterOrder() {\n            return 0;\n        }\n\n        @Override\n        public boolean shouldFilter(HttpRequestMessage msg) {\n            return true;\n        }\n\n        @Override\n        public HttpRequestMessage apply(HttpRequestMessage input) {\n            return input;\n        }\n    }\n\n    @Filter(order = 1, type = FilterType.INBOUND)\n    static class DummyFilter2 extends HttpInboundSyncFilter {\n\n        @Override\n        public String filterName() {\n            return \"Robin\";\n        }\n\n        @Override\n        public int filterOrder() {\n            return 1;\n        }\n\n        @Override\n        public boolean shouldFilter(HttpRequestMessage msg) {\n            return true;\n        }\n\n        @Override\n        public HttpRequestMessage apply(HttpRequestMessage input) {\n            return input;\n        }\n    }\n\n    @Filter(order = 1, type = FilterType.INBOUND)\n    static class DummyFilter22 extends HttpInboundSyncFilter {\n\n        @Override\n        public String filterName() {\n            return \"Williams\";\n        }\n\n        @Override\n        public int filterOrder() {\n            return 1;\n        }\n\n        @Override\n        public boolean shouldFilter(HttpRequestMessage msg) {\n            return true;\n        }\n\n        @Override\n        public HttpRequestMessage apply(HttpRequestMessage input) {\n            return input;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/com/netflix/zuul/netty/server/push/PushConnectionTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.com.netflix.zuul.netty.server.push;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.netty.server.push.PushConnection;\nimport com.netflix.zuul.netty.server.push.PushProtocol;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Author: Susheel Aroskar\n * Date: 10/18/2018\n */\nclass PushConnectionTest {\n\n    @Test\n    void testOneMessagePerSecond() throws InterruptedException {\n        PushConnection conn = new PushConnection(PushProtocol.WEBSOCKET, null);\n        for (int i = 0; i < 5; i++) {\n            assertThat(conn.isRateLimited()).isFalse();\n            Thread.sleep(1000);\n        }\n    }\n\n    @Test\n    void testThreeMessagesInSuccession() {\n        PushConnection conn = new PushConnection(PushProtocol.WEBSOCKET, null);\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n    }\n\n    @Test\n    void testFourMessagesInSuccession() {\n        PushConnection conn = new PushConnection(PushProtocol.WEBSOCKET, null);\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isTrue();\n    }\n\n    @Test\n    void testFirstThreeMessagesSuccess() {\n        PushConnection conn = new PushConnection(PushProtocol.WEBSOCKET, null);\n        for (int i = 0; i < 10; i++) {\n            if (i < 3) {\n                assertThat(conn.isRateLimited()).isFalse();\n            } else {\n                assertThat(conn.isRateLimited()).isTrue();\n            }\n        }\n    }\n\n    @Test\n    void testMessagesInBatches() throws InterruptedException {\n        PushConnection conn = new PushConnection(PushProtocol.WEBSOCKET, null);\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isTrue();\n        Thread.sleep(2000);\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isFalse();\n        assertThat(conn.isRateLimited()).isTrue();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/context/DebugTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.context;\n\nimport static com.netflix.zuul.context.Debug.addRequestDebug;\nimport static com.netflix.zuul.context.Debug.addRoutingDebug;\nimport static com.netflix.zuul.context.Debug.debugRequest;\nimport static com.netflix.zuul.context.Debug.debugRouting;\nimport static com.netflix.zuul.context.Debug.getRequestDebug;\nimport static com.netflix.zuul.context.Debug.getRoutingDebug;\nimport static com.netflix.zuul.context.Debug.setDebugRequest;\nimport static com.netflix.zuul.context.Debug.setDebugRouting;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport com.netflix.zuul.message.util.HttpRequestBuilder;\nimport io.netty.handler.codec.http.HttpMethod;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass DebugTest {\n\n    private SessionContext ctx;\n    private Headers headers;\n    private HttpQueryParams params;\n    private HttpRequestMessage request;\n    private HttpResponseMessage response;\n\n    @BeforeEach\n    void setup() {\n        ctx = new SessionContext();\n\n        headers = new Headers();\n        headers.add(\"lah\", \"deda\");\n\n        params = new HttpQueryParams();\n        params.add(\"k1\", \"v1\");\n\n        request = new HttpRequestBuilder(ctx)\n                .withMethod(HttpMethod.POST)\n                .withUri(\"/some/where\")\n                .withHeaders(headers)\n                .withQueryParams(params)\n                .build();\n        request.setBodyAsText(\"some text\");\n        request.storeInboundRequest();\n\n        response = new HttpResponseMessageImpl(ctx, headers, request, 200);\n        response.setBodyAsText(\"response text\");\n    }\n\n    @Test\n    void testRequestDebug() {\n        assertThat(debugRouting(ctx)).isFalse();\n        assertThat(debugRequest(ctx)).isFalse();\n        setDebugRouting(ctx, true);\n        setDebugRequest(ctx, true);\n        assertThat(debugRouting(ctx)).isTrue();\n        assertThat(debugRequest(ctx)).isTrue();\n\n        addRoutingDebug(ctx, \"test1\");\n        assertThat(getRoutingDebug(ctx).contains(\"test1\")).isTrue();\n\n        addRequestDebug(ctx, \"test2\");\n        assertThat(getRequestDebug(ctx).contains(\"test2\")).isTrue();\n    }\n\n    @Test\n    void testWriteInboundRequestDebug() {\n        ctx.setDebugRequest(true);\n        ctx.setDebugRequestHeadersOnly(true);\n        Debug.writeDebugRequest(ctx, request, true).toBlocking().single();\n\n        List<String> debugLines = getRequestDebug(ctx);\n        assertThat(debugLines)\n                .containsExactlyInAnyOrder(\n                        \"REQUEST_INBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1\",\n                        \"REQUEST_INBOUND:: > HDR: Content-Length:13\",\n                        \"REQUEST_INBOUND:: > HDR: lah:deda\");\n    }\n\n    @Test\n    void testWriteOutboundRequestDebug() {\n        ctx.setDebugRequest(true);\n        ctx.setDebugRequestHeadersOnly(true);\n        Debug.writeDebugRequest(ctx, request, false).toBlocking().single();\n\n        List<String> debugLines = getRequestDebug(ctx);\n        assertThat(debugLines)\n                .containsExactlyInAnyOrder(\n                        \"REQUEST_OUTBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1\",\n                        \"REQUEST_OUTBOUND:: > HDR: Content-Length:13\",\n                        \"REQUEST_OUTBOUND:: > HDR: lah:deda\");\n    }\n\n    @Test\n    void testWriteRequestDebug_WithBody() {\n        ctx.setDebugRequest(true);\n        ctx.setDebugRequestHeadersOnly(false);\n        Debug.writeDebugRequest(ctx, request, true).toBlocking().single();\n\n        List<String> debugLines = getRequestDebug(ctx);\n        assertThat(debugLines)\n                .containsExactlyInAnyOrder(\n                        \"REQUEST_INBOUND:: > LINE: POST /some/where?k1=v1 HTTP/1.1\",\n                        \"REQUEST_INBOUND:: > HDR: Content-Length:13\",\n                        \"REQUEST_INBOUND:: > HDR: lah:deda\",\n                        \"REQUEST_INBOUND:: > BODY: some text\");\n    }\n\n    @Test\n    void testWriteInboundResponseDebug() {\n        ctx.setDebugRequest(true);\n        ctx.setDebugRequestHeadersOnly(true);\n        Debug.writeDebugResponse(ctx, response, true).toBlocking().single();\n\n        List<String> debugLines = getRequestDebug(ctx);\n        assertThat(debugLines)\n                .containsExactlyInAnyOrder(\n                        \"RESPONSE_INBOUND:: < STATUS: 200\",\n                        \"RESPONSE_INBOUND:: < HDR: Content-Length:13\",\n                        \"RESPONSE_INBOUND:: < HDR: lah:deda\");\n    }\n\n    @Test\n    void testWriteOutboundResponseDebug() {\n        ctx.setDebugRequest(true);\n        ctx.setDebugRequestHeadersOnly(true);\n        Debug.writeDebugResponse(ctx, response, false).toBlocking().single();\n\n        List<String> debugLines = getRequestDebug(ctx);\n        assertThat(debugLines)\n                .containsExactlyInAnyOrder(\n                        \"RESPONSE_OUTBOUND:: < STATUS: 200\",\n                        \"RESPONSE_OUTBOUND:: < HDR: Content-Length:13\",\n                        \"RESPONSE_OUTBOUND:: < HDR: lah:deda\");\n    }\n\n    @Test\n    void testWriteResponseDebug_WithBody() {\n        ctx.setDebugRequest(true);\n        ctx.setDebugRequestHeadersOnly(false);\n        Debug.writeDebugResponse(ctx, response, true).toBlocking().single();\n\n        List<String> debugLines = getRequestDebug(ctx);\n        assertThat(debugLines)\n                .containsExactlyInAnyOrder(\n                        \"RESPONSE_INBOUND:: < STATUS: 200\",\n                        \"RESPONSE_INBOUND:: < HDR: Content-Length:13\",\n                        \"RESPONSE_INBOUND:: < HDR: lah:deda\",\n                        \"RESPONSE_INBOUND:: < BODY: response text\");\n    }\n\n    @Test\n    void testNoCMEWhenComparingContexts() {\n        SessionContext context = new SessionContext();\n        SessionContext copy = new SessionContext();\n\n        context.set(\"foo\", \"bar\");\n\n        Debug.compareContextState(\"testfilter\", context, copy);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/context/SessionContextTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.context;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass SessionContextTest {\n\n    @Test\n    void testBoolean() {\n        SessionContext context = new SessionContext();\n        assertThat(context.getBoolean(\"boolean_test\")).isEqualTo(Boolean.FALSE);\n        assertThat(context.getBoolean(\"boolean_test\", true)).isEqualTo(true);\n    }\n\n    @Test\n    void keysAreUnique() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key1 = SessionContext.newKey(\"foo\");\n        context.put(key1, \"bar\");\n        SessionContext.Key<String> key2 = SessionContext.newKey(\"foo\");\n        context.put(key2, \"baz\");\n\n        assertThat(context.keys()).containsExactlyInAnyOrder(key1, key2);\n    }\n\n    @Test\n    void newKeyFailsOnNull() {\n        assertThatThrownBy(() -> SessionContext.newKey(null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void putFailsOnNull() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n\n        assertThatThrownBy(() -> context.put(key, null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void putReplacesOld() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n        context.put(key, \"bar\");\n        context.put(key, \"baz\");\n\n        assertThat(context.get(key)).isEqualTo(\"baz\");\n        assertThat(context.keys()).containsExactly(key);\n    }\n\n    @Test\n    void getReturnsNull() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n\n        assertThat(context.get(key)).isNull();\n    }\n\n    @Test\n    void getOrDefault_picksDefault() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n\n        assertThat(context.getOrDefault(key, \"bar\")).isEqualTo(\"bar\");\n    }\n\n    @Test\n    void getOrDefault_failsOnNullDefault() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n        context.put(key, \"bar\");\n\n        assertThatThrownBy(() -> context.getOrDefault(key, null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void getUsesDefaultValueSupplier() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\", () -> \"bar\");\n        assertThat(context.get(key)).isEqualTo(\"bar\");\n    }\n\n    @Test\n    void getOrDefaultUsesDefaultValueSupplier() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\", () -> \"bar\");\n        assertThat(context.getOrDefault(key)).isEqualTo(\"bar\");\n    }\n\n    @Test\n    void getOrDefaultUsesDefaultValueSupplierFailsWithout() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n        assertThatThrownBy(() -> context.getOrDefault(key)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void remove() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n        context.put(key, \"bar\");\n\n        assertThat(context.get(key)).isEqualTo(\"bar\");\n\n        String val = context.remove(key);\n        assertThat(context.get(key)).isNull();\n        assertThat(val).isEqualTo(\"bar\");\n    }\n\n    @Test\n    void containsKey() {\n        SessionContext context = new SessionContext();\n        SessionContext.Key<String> key = SessionContext.newKey(\"foo\");\n        context.put(key, \"bar\");\n\n        assertThat(context.containsKey(key)).isTrue();\n\n        String val = context.remove(key);\n        assertThat(val).isEqualTo(\"bar\");\n\n        assertThat(context.containsKey(key)).isFalse();\n    }\n\n    @Test\n    void setInBrownoutModeWithReason() {\n        SessionContext context = new SessionContext();\n        assertThat(context.getBrownoutReason()).isNull();\n        context.setInBrownoutMode(\"High CPU usage\");\n\n        assertThat(context.isInBrownoutMode()).isTrue();\n        assertThat(context.getBrownoutReason()).isEqualTo(\"High CPU usage\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/filters/BaseFilterTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.filters;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.ZuulMessageImpl;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport rx.Observable;\n\n/**\n * Tests for {@link BaseFilter}\n */\n@ExtendWith(MockitoExtension.class)\nclass BaseFilterTest {\n\n    private final AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n\n    @BeforeEach\n    public void setUpTest() {\n        config.clear();\n    }\n\n    @Test\n    void validateDefaultConcurrencyLimit() throws InterruptedException {\n        int[] limit = {0};\n        class ConcInboundFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n            @Override\n            public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n                limit[0] = calculateConcurency();\n                return Observable.just(new ZuulMessageImpl(new SessionContext()));\n            }\n\n            @Override\n            public FilterType filterType() {\n                return FilterType.INBOUND;\n            }\n\n            @Override\n            public boolean shouldFilter(ZuulMessage msg) {\n                return true;\n            }\n        }\n        new ConcInboundFilter()\n                .applyAsync(new ZuulMessageImpl(new SessionContext(), new Headers()))\n                .toBlocking()\n                .single();\n        assertThat(limit[0]).isEqualTo(4000);\n    }\n\n    @Test\n    void validateFilterGlobalConcurrencyLimitOverride() throws InterruptedException {\n        config.setProperty(\"zuul.filter.concurrency.limit.default\", 7000);\n        config.setProperty(\"zuul.ConcInboundFilter.in.concurrency.limit\", 4000);\n        int[] limit = {0};\n\n        class ConcInboundFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n            @Override\n            public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n                limit[0] = calculateConcurency();\n                return Observable.just(new ZuulMessageImpl(new SessionContext()));\n            }\n\n            @Override\n            public FilterType filterType() {\n                return FilterType.INBOUND;\n            }\n\n            @Override\n            public boolean shouldFilter(ZuulMessage msg) {\n                return true;\n            }\n        }\n        new ConcInboundFilter()\n                .applyAsync(new ZuulMessageImpl(new SessionContext(), new Headers()))\n                .toBlocking()\n                .single();\n        assertThat(limit[0]).isEqualTo(7000);\n    }\n\n    @Test\n    void validateFilterSpecificConcurrencyLimitOverride() throws InterruptedException {\n        config.setProperty(\"zuul.filter.concurrency.limit.default\", 7000);\n        config.setProperty(\"zuul.ConcInboundFilter.in.concurrency.limit\", 4300);\n        int[] limit = {0};\n\n        class ConcInboundFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n            @Override\n            public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n                limit[0] = calculateConcurency();\n                return Observable.just(new ZuulMessageImpl(new SessionContext()));\n            }\n\n            @Override\n            public FilterType filterType() {\n                return FilterType.INBOUND;\n            }\n\n            @Override\n            public boolean shouldFilter(ZuulMessage msg) {\n                return true;\n            }\n        }\n        new ConcInboundFilter()\n                .applyAsync(new ZuulMessageImpl(new SessionContext(), new Headers()))\n                .toBlocking()\n                .single();\n        assertThat(limit[0]).isEqualTo(4300);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/filters/common/GZipResponseFilterTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.common;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpHeaderNames;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.util.zip.GZIPInputStream;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass GZipResponseFilterTest {\n    private final SessionContext context = new SessionContext();\n    private final Headers originalRequestHeaders = new Headers();\n\n    @Mock\n    private HttpRequestMessage request;\n\n    @Mock\n    private HttpRequestMessage originalRequest;\n\n    GZipResponseFilter filter;\n    HttpResponseMessage response;\n\n    @BeforeEach\n    void setup() {\n        // when(request.getContext()).thenReturn(context);\n        when(originalRequest.getHeaders()).thenReturn(originalRequestHeaders);\n\n        filter = Mockito.spy(new GZipResponseFilter());\n        response = new HttpResponseMessageImpl(context, request, 99);\n        response.getHeaders().set(HttpHeaderNames.CONTENT_TYPE, \"text/html\");\n        when(response.getInboundRequest()).thenReturn(originalRequest);\n    }\n\n    @Test\n    void prepareResponseBody_NeedsGZipping() throws Exception {\n        originalRequestHeaders.set(\"Accept-Encoding\", \"gzip\");\n\n        byte[] originBody = \"blah\".getBytes(UTF_8);\n        response.getHeaders().set(\"Content-Length\", Integer.toString(originBody.length));\n        Mockito.when(filter.isRightSizeForGzip(response)).thenReturn(true); // Force GZip for small response\n        response.setHasBody(true);\n        assertThat(filter.shouldFilter(response)).isTrue();\n\n        HttpResponseMessage result = filter.apply(response);\n        HttpContent hc1 = filter.processContentChunk(\n                response, new DefaultHttpContent(Unpooled.wrappedBuffer(originBody)).retain());\n        HttpContent hc2 = filter.processContentChunk(response, new DefaultLastHttpContent());\n        byte[] body = new byte[hc1.content().readableBytes() + hc2.content().readableBytes()];\n        int hc1Len = hc1.content().readableBytes();\n        int hc2Len = hc2.content().readableBytes();\n        hc1.content().readBytes(body, 0, hc1Len);\n        hc2.content().readBytes(body, hc1Len, hc2Len);\n\n        String bodyStr;\n        // Check body is a gzipped version of the origin body.\n        try (ByteArrayInputStream bais = new ByteArrayInputStream(body);\n                GZIPInputStream gzis = new GZIPInputStream(bais);\n                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {\n            int b;\n            while ((b = gzis.read()) != -1) {\n                baos.write(b);\n            }\n            bodyStr = baos.toString(\"UTF-8\");\n        }\n        assertThat(bodyStr).isEqualTo(\"blah\");\n        assertThat(result.getHeaders().getFirst(\"Content-Encoding\")).isEqualTo(\"gzip\");\n\n        // Check Content-Length header has been removed\n        assertThat(result.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(0);\n    }\n\n    @Test\n    void prepareResponseBody_NeedsGZipping_gzipDeflate() throws Exception {\n        originalRequestHeaders.set(\"Accept-Encoding\", \"gzip,deflate\");\n\n        byte[] originBody = \"blah\".getBytes(UTF_8);\n        response.getHeaders().set(\"Content-Length\", Integer.toString(originBody.length));\n        Mockito.when(filter.isRightSizeForGzip(response)).thenReturn(true); // Force GZip for small response\n        response.setHasBody(true);\n        assertThat(filter.shouldFilter(response)).isTrue();\n\n        HttpResponseMessage result = filter.apply(response);\n        HttpContent hc1 = filter.processContentChunk(\n                response, new DefaultHttpContent(Unpooled.wrappedBuffer(originBody)).retain());\n        HttpContent hc2 = filter.processContentChunk(response, new DefaultLastHttpContent());\n        byte[] body = new byte[hc1.content().readableBytes() + hc2.content().readableBytes()];\n        int hc1Len = hc1.content().readableBytes();\n        int hc2Len = hc2.content().readableBytes();\n        hc1.content().readBytes(body, 0, hc1Len);\n        hc2.content().readBytes(body, hc1Len, hc2Len);\n\n        String bodyStr;\n        // Check body is a gzipped version of the origin body.\n        try (ByteArrayInputStream bais = new ByteArrayInputStream(body);\n                GZIPInputStream gzis = new GZIPInputStream(bais);\n                ByteArrayOutputStream baos = new ByteArrayOutputStream()) {\n            int b;\n            while ((b = gzis.read()) != -1) {\n                baos.write(b);\n            }\n            bodyStr = baos.toString(\"UTF-8\");\n        }\n        assertThat(bodyStr).isEqualTo(\"blah\");\n        assertThat(result.getHeaders().getFirst(\"Content-Encoding\")).isEqualTo(\"gzip\");\n\n        // Check Content-Length header has been removed\n        assertThat(result.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(0);\n    }\n\n    @Test\n    void prepareResponseBody_alreadyZipped() throws Exception {\n        originalRequestHeaders.set(\"Accept-Encoding\", \"gzip,deflate\");\n\n        byte[] originBody = \"blah\".getBytes(UTF_8);\n        response.getHeaders().set(\"Content-Length\", Integer.toString(originBody.length));\n        response.getHeaders().set(\"Content-Type\", \"application/json\");\n        response.getHeaders().set(\"Content-Encoding\", \"gzip\");\n        response.setHasBody(true);\n        assertThat(filter.shouldFilter(response)).isFalse();\n    }\n\n    @Test\n    void prepareResponseBody_alreadyDeflated() throws Exception {\n        originalRequestHeaders.set(\"Accept-Encoding\", \"gzip,deflate\");\n\n        byte[] originBody = \"blah\".getBytes(UTF_8);\n        response.getHeaders().set(\"Content-Length\", Integer.toString(originBody.length));\n        response.getHeaders().set(\"Content-Type\", \"application/json\");\n        response.getHeaders().set(\"Content-Encoding\", \"deflate\");\n        response.setHasBody(true);\n        assertThat(filter.shouldFilter(response)).isFalse();\n    }\n\n    @Test\n    void prepareResponseBody_NeedsGZipping_butTooSmall() throws Exception {\n        originalRequestHeaders.set(\"Accept-Encoding\", \"gzip\");\n        byte[] originBody = \"blah\".getBytes(UTF_8);\n        response.getHeaders().set(\"Content-Length\", Integer.toString(originBody.length));\n        response.setHasBody(true);\n        assertThat(filter.shouldFilter(response)).isFalse();\n    }\n\n    @Test\n    void prepareChunkedEncodedResponseBody_NeedsGZipping() throws Exception {\n        originalRequestHeaders.set(\"Accept-Encoding\", \"gzip\");\n        response.getHeaders().set(\"Transfer-Encoding\", \"chunked\");\n        response.setHasBody(true);\n        assertThat(filter.shouldFilter(response)).isTrue();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/filters/endpoint/ProxyEndpointTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.endpoint;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doAnswer;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.spectator.api.Spectator;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.netty.NettyRequestAttemptFactory;\nimport com.netflix.zuul.netty.connectionpool.DefaultOriginChannelInitializer;\nimport com.netflix.zuul.netty.connectionpool.PooledConnection;\nimport com.netflix.zuul.netty.server.MethodBinding;\nimport com.netflix.zuul.netty.timeouts.OriginTimeoutManager;\nimport com.netflix.zuul.niws.RequestAttempt;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.origins.BasicNettyOriginManager;\nimport com.netflix.zuul.origins.NettyOrigin;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportItem;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.channel.local.LocalAddress;\nimport io.netty.handler.codec.http.DefaultHttpResponse;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http.LastHttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport io.netty.util.concurrent.Promise;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.junit.jupiter.MockitoSettings;\nimport org.mockito.quality.Strictness;\n\n@ExtendWith(MockitoExtension.class)\n@MockitoSettings(strictness = Strictness.LENIENT)\nclass ProxyEndpointTest {\n\n    @Mock\n    private ChannelHandlerContext chc;\n\n    @Mock\n    private NettyOrigin nettyOrigin;\n\n    @Mock\n    private OriginTimeoutManager timeoutManager;\n\n    @Mock\n    private NettyRequestAttemptFactory attemptFactory;\n\n    private ProxyEndpoint proxyEndpoint;\n    private SessionContext context;\n    private HttpRequestMessage request;\n    private HttpResponse response;\n    private CurrentPassport passport;\n    private EmbeddedChannel channel;\n\n    @BeforeEach\n    void setup() {\n        channel = new EmbeddedChannel();\n        doReturn(channel).when(chc).channel();\n\n        context = new SessionContext();\n        request = createRequest(context, \"POST\", \"/some/where\");\n        request.storeInboundRequest();\n\n        request.setBody(\"Hello There\".getBytes(UTF_8));\n        BasicNettyOriginManager originManager = new BasicNettyOriginManager(Spectator.globalRegistry());\n\n        context.set(CommonContextKeys.ORIGIN_MANAGER, originManager);\n        context.setRouteVIP(\"some-vip\");\n        passport = CurrentPassport.create();\n        context.put(CommonContextKeys.PASSPORT, passport);\n        context.put(CommonContextKeys.REQUEST_ATTEMPTS, new RequestAttempts());\n\n        Promise<PooledConnection> promise = channel.eventLoop().newPromise();\n        doReturn(promise).when(nettyOrigin).connectToOrigin(any(), any(), anyInt(), any(), any(), any());\n\n        proxyEndpoint = spy(new ProxyEndpoint(request, chc, null, MethodBinding.NO_OP_BINDING, attemptFactory) {\n            @Override\n            public NettyOrigin getOrigin(HttpRequestMessage request) {\n                return nettyOrigin;\n            }\n\n            @Override\n            protected OriginTimeoutManager getTimeoutManager(NettyOrigin origin) {\n                return timeoutManager;\n            }\n        });\n\n        doNothing().when(proxyEndpoint).operationComplete(any());\n        doNothing().when(proxyEndpoint).invokeNext((HttpResponseMessage) any());\n    }\n\n    @Test\n    void testRecordProxyRequestEndIsCalledOnce() {\n        proxyEndpoint.apply(request);\n        proxyEndpoint.finish(false);\n\n        verify(nettyOrigin, times(1)).recordProxyRequestEnd();\n    }\n\n    @Test\n    void testRetryWillResetBodyReader() {\n\n        assertThat(new String(request.getBody(), UTF_8)).isEqualTo(\"Hello There\");\n\n        // move the body readerIndex to the end to mimic nettys behavior after writing to the origin channel\n        request.getBodyContents()\n                .forEach((b) -> b.content().readerIndex(b.content().capacity()));\n\n        createResponse(HttpResponseStatus.SERVICE_UNAVAILABLE);\n\n        DiscoveryResult discoveryResult = createDiscoveryResult();\n\n        // when retrying a response, the request body reader should have it's indexes reset\n        proxyEndpoint.handleOriginNonSuccessResponse(response, discoveryResult);\n        assertThat(new String(request.getBody(), UTF_8)).isEqualTo(\"Hello There\");\n    }\n\n    @Test\n    void retryWhenNoAdjustment() {\n        createResponse(HttpResponseStatus.SERVICE_UNAVAILABLE);\n\n        proxyEndpoint.handleOriginNonSuccessResponse(response, createDiscoveryResult());\n        verify(nettyOrigin).adjustRetryPolicyIfNeeded(eq(request));\n        verify(nettyOrigin).originRetryPolicyAdjustmentIfNeeded(request, response);\n        verify(nettyOrigin).connectToOrigin(any(), any(), anyInt(), any(), any(), any());\n    }\n\n    @Test\n    void testRetryAdjustsLimit() {\n        createResponse(HttpResponseStatus.SERVICE_UNAVAILABLE);\n        disableRetriesOnAdjustment();\n\n        proxyEndpoint.handleOriginNonSuccessResponse(response, createDiscoveryResult());\n        validateNoRetry();\n    }\n\n    @Test\n    void noRetryAdjustmentOnNonRetriableStatusCode() {\n        createResponse(HttpResponseStatus.BAD_REQUEST);\n        proxyEndpoint.handleOriginNonSuccessResponse(response, createDiscoveryResult());\n        verify(nettyOrigin, never()).adjustRetryPolicyIfNeeded(request);\n        verify(nettyOrigin, never()).originRetryPolicyAdjustmentIfNeeded(request, response);\n        validateNoRetry();\n    }\n\n    @Test\n    public void onErrorFromOriginNoRetryAdjustment() {\n        doReturn(OutboundErrorType.RESET_CONNECTION).when(attemptFactory).mapNettyToOutboundErrorType(any());\n        proxyEndpoint.errorFromOrigin(new RuntimeException());\n\n        verify(nettyOrigin).adjustRetryPolicyIfNeeded(request);\n        verify(nettyOrigin).connectToOrigin(any(), any(), anyInt(), any(), any(), any());\n    }\n\n    @Test\n    void onErrorFromOriginWithRetryAdjustment() {\n        doReturn(OutboundErrorType.RESET_CONNECTION).when(attemptFactory).mapNettyToOutboundErrorType(any());\n        disableRetriesOnAdjustment();\n\n        proxyEndpoint.errorFromOrigin(new RuntimeException());\n        validateNoRetry();\n    }\n\n    @Test\n    public void onErrorFromOriginNoRetryOnNonRetriableError() {\n        doReturn(OutboundErrorType.OTHER).when(attemptFactory).mapNettyToOutboundErrorType(any());\n        disableRetriesOnAdjustment();\n\n        proxyEndpoint.errorFromOrigin(new RuntimeException());\n        verify(nettyOrigin, never()).adjustRetryPolicyIfNeeded(request);\n        verify(nettyOrigin, never()).originRetryPolicyAdjustmentIfNeeded(request, response);\n        validateNoRetry();\n    }\n\n    @Test\n    public void lastContentAfterProxyStartedIsConsideredReplayable() {\n        Promise<PooledConnection> promise = channel.eventLoop().newPromise();\n\n        PooledConnection pooledConnection = Mockito.mock(PooledConnection.class);\n        promise.setSuccess(pooledConnection);\n\n        doReturn(channel).when(pooledConnection).getChannel();\n        doReturn(promise).when(nettyOrigin).connectToOrigin(any(), any(), anyInt(), any(), any(), any());\n\n        doReturn(Mockito.mock(RequestAttempt.class)).when(nettyOrigin).newRequestAttempt(any(), any(), any(), anyInt());\n\n        request = createRequest(context, \"POST\", \"/some/where\");\n        request.storeInboundRequest();\n\n        proxyEndpoint = spy(new ProxyEndpoint(request, chc, null, MethodBinding.NO_OP_BINDING, attemptFactory) {\n            @Override\n            public NettyOrigin getOrigin(HttpRequestMessage request) {\n                return nettyOrigin;\n            }\n\n            @Override\n            protected OriginTimeoutManager getTimeoutManager(NettyOrigin origin) {\n                return timeoutManager;\n            }\n        });\n\n        channel.pipeline()\n                .addLast(DefaultOriginChannelInitializer.CONNECTION_POOL_HANDLER, new ChannelInboundHandlerAdapter());\n\n        proxyEndpoint.apply(request);\n        LastHttpContent lastContent = new DefaultLastHttpContent();\n        assertThat(proxyEndpoint.isRequestReplayable()).isFalse();\n        proxyEndpoint.processContentChunk(request, lastContent);\n        assertThat(proxyEndpoint.isRequestReplayable()).isTrue();\n\n        channel.releaseOutbound();\n        assertThat(lastContent.refCnt())\n                .as(\"ref count should be 1 in case a retry is needed\")\n                .isEqualTo(1);\n        ReferenceCountUtil.safeRelease(lastContent);\n    }\n\n    @Test\n    void testMassageRequestURIWithEncodedAmpersand() {\n        // Test that encoded ampersands in query parameter values are handled correctly\n        // and do not create additional parameters\n        SessionContext context = new SessionContext();\n        context.set(\"overrideURI\", \"/path?param=123%26hidden%3Dvalue\");\n\n        HttpRequestMessage request = createRequest(context, \"GET\", \"/original\");\n        HttpRequestMessage result = ProxyEndpoint.massageRequestURI(request);\n\n        assertThat(result.getPath()).isEqualTo(\"/path\");\n        HttpQueryParams params = result.getQueryParams();\n        assertThat(params.getFirst(\"param\")).isEqualTo(\"123&hidden=value\");\n        assertThat(params.contains(\"hidden\")).isFalse();\n    }\n\n    @Test\n    void testMassageRequestURIWithMultipleEncodedParams() {\n        SessionContext context = new SessionContext();\n        context.set(\"overrideURI\", \"/path?foo=bar&param=a%26b&another=test%3Dvalue\");\n\n        HttpRequestMessage request = createRequest(context, \"GET\", \"/original\");\n        HttpRequestMessage result = ProxyEndpoint.massageRequestURI(request);\n\n        assertThat(result.getPath()).isEqualTo(\"/path\");\n        HttpQueryParams params = result.getQueryParams();\n        assertThat(params.getFirst(\"foo\")).isEqualTo(\"bar\");\n        assertThat(params.getFirst(\"param\")).isEqualTo(\"a&b\");\n        assertThat(params.getFirst(\"another\")).isEqualTo(\"test=value\");\n    }\n\n    @Test\n    void testMassageRequestURIWithNoQueryString() {\n        SessionContext context = new SessionContext();\n        context.set(\"overrideURI\", \"/path/to/resource\");\n\n        HttpRequestMessage request = createRequest(context, \"GET\", \"/original\");\n        HttpRequestMessage result = ProxyEndpoint.massageRequestURI(request);\n\n        assertThat(result.getPath()).isEqualTo(\"/path/to/resource\");\n        assertThat(result.getQueryParams().entries()).isEmpty();\n    }\n\n    @Test\n    void testMassageRequestURIWithRequestURIContext() {\n        SessionContext context = new SessionContext();\n        context.set(\"requestURI\", \"/contextpath?key=value%20with%20spaces\");\n\n        HttpRequestMessage request = createRequest(context, \"GET\", \"/original\");\n        HttpRequestMessage result = ProxyEndpoint.massageRequestURI(request);\n\n        assertThat(result.getPath()).isEqualTo(\"/contextpath\");\n        HttpQueryParams params = result.getQueryParams();\n        assertThat(params.getFirst(\"key\")).isEqualTo(\"value with spaces\");\n    }\n\n    @Test\n    void testMassageRequestURIOverrideURITakesPrecedence() {\n        // Test that overrideURI takes precedence over requestURI\n        SessionContext context = new SessionContext();\n        context.set(\"requestURI\", \"/first?key=first\");\n        context.set(\"overrideURI\", \"/second?key=second\");\n\n        HttpRequestMessage request = createRequest(context, \"GET\", \"/original\");\n        HttpRequestMessage result = ProxyEndpoint.massageRequestURI(request);\n\n        assertThat(result.getPath()).isEqualTo(\"/second\");\n        HttpQueryParams params = result.getQueryParams();\n        assertThat(params.getFirst(\"key\")).isEqualTo(\"second\");\n    }\n\n    @Test\n    void testMassageRequestURIWithNoContextOverride() {\n        // Test that when neither requestURI nor overrideURI are set, the request is returned unchanged\n        SessionContext context = new SessionContext();\n\n        HttpRequestMessage request = createRequest(context, \"GET\", \"/original\");\n        HttpRequestMessage result = ProxyEndpoint.massageRequestURI(request);\n\n        // Path and query params should remain as they were in the original request\n        assertThat(result).isSameAs(request);\n    }\n\n    private void validateNoRetry() {\n        verify(nettyOrigin, never()).connectToOrigin(any(), any(), anyInt(), any(), any(), any());\n        passport.getHistory().stream()\n                .map(PassportItem::getState)\n                .filter(s -> s == PassportState.ORIGIN_RETRY_START)\n                .findAny()\n                .ifPresent(s -> org.junit.jupiter.api.Assertions.fail());\n    }\n\n    private void disableRetriesOnAdjustment() {\n        doAnswer(invocation -> {\n                    doReturn(-1).when(nettyOrigin).getMaxRetriesForRequest(context);\n                    return null;\n                })\n                .when(nettyOrigin)\n                .adjustRetryPolicyIfNeeded(request);\n    }\n\n    private static DiscoveryResult createDiscoveryResult() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"app\")\n                .setHostName(\"localhost\")\n                .setPort(443)\n                .build();\n        return DiscoveryResult.from(instanceInfo, true);\n    }\n\n    private void createResponse(HttpResponseStatus status) {\n        response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);\n    }\n\n    private HttpRequestMessage createRequest(SessionContext context, String method, String path) {\n        return new HttpRequestMessageImpl(\n                context,\n                \"HTTP/1.1\",\n                method,\n                path,\n                null,\n                null,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new LocalAddress(\"777\"),\n                false);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/message/HeadersTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\nimport com.netflix.zuul.exception.ZuulException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests for {@link Headers}.\n */\nclass HeadersTest {\n\n    @Test\n    void copyOf() {\n        Headers headers = new Headers();\n        headers.set(\"Content-Length\", \"5\");\n        Headers headers2 = Headers.copyOf(headers);\n        headers2.add(\"Via\", \"duct\");\n\n        assertThat(headers.getAll(\"Via\")).isEmpty();\n        assertThat(headers2.size()).isEqualTo(2);\n        assertThat(headers2.getAll(\"Content-Length\")).containsExactly(\"5\");\n    }\n\n    @Test\n    void getFirst_normalizesName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getFirst(\"cOOkIE\")).isEqualTo(\"this=that\");\n    }\n\n    @Test\n    void getFirst_headerName_normalizesName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getFirst(new HeaderName(\"cOOkIE\"))).isEqualTo(\"this=that\");\n    }\n\n    @Test\n    void getFirst_returnsNull() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getFirst(\"Date\")).isNull();\n    }\n\n    @Test\n    void getFirst_headerName_returnsNull() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getFirst(new HeaderName(\"Date\"))).isNull();\n    }\n\n    @Test\n    void getFirst_returnsDefault() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getFirst(\"Date\", \"tuesday\")).isEqualTo(\"tuesday\");\n    }\n\n    @Test\n    void getFirst_headerName_returnsDefault() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getFirst(new HeaderName(\"Date\"), \"tuesday\")).isEqualTo(\"tuesday\");\n    }\n\n    @Test\n    void forEachNormalised() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=Frazzle\");\n        Map<String, List<String>> result = new LinkedHashMap<>();\n\n        headers.forEachNormalised((k, v) ->\n                result.computeIfAbsent(k, discard -> new ArrayList<>()).add(v));\n\n        assertThat(result)\n                .containsExactly(\n                        entry(\"via\", Collections.singletonList(\"duct\")),\n                        entry(\"cookie\", Arrays.asList(\"this=that\", \"frizzle=Frazzle\")));\n    }\n\n    @Test\n    void getAll() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getAll(\"CookiE\")).containsExactly(\"this=that\", \"frizzle=frazzle\");\n    }\n\n    @Test\n    void getAll_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThat(headers.getAll(new HeaderName(\"CookiE\"))).containsExactly(\"this=that\", \"frizzle=frazzle\");\n    }\n\n    @Test\n    void setClearsExisting() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.set(\"cookIe\", \"dilly=dally\");\n\n        assertThat(headers.getAll(\"CookiE\")).containsExactly(\"dilly=dally\");\n        assertThat(headers.size()).isEqualTo(2);\n    }\n\n    @Test\n    void setClearsExisting_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.set(new HeaderName(\"cookIe\"), \"dilly=dally\");\n\n        assertThat(headers.getAll(\"CookiE\")).containsExactly(\"dilly=dally\");\n        assertThat(headers.size()).isEqualTo(2);\n    }\n\n    @Test\n    void setNullIsEmtpy() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.set(\"cookIe\", null);\n\n        assertThat(headers.getAll(\"CookiE\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void setNullIsEmtpy_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.set(new HeaderName(\"cookIe\"), null);\n\n        assertThat(headers.getAll(\"CookiE\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void setIfValidNullIsEmtpy() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfValid(\"cookIe\", null);\n\n        assertThat(headers.getAll(\"CookiE\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void setIfValidNullIsEmtpy_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfValid(new HeaderName(\"cookIe\"), null);\n\n        assertThat(headers.getAll(\"CookiE\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void setIfValidIgnoresInvalidValues() {\n        Headers headers = new Headers();\n        headers.add(\"X-Valid-K1\", \"abc-xyz\");\n        headers.add(\"X-Valid-K2\", \"def-xyz\");\n        headers.add(\"X-Valid-K3\", \"xyz-xyz\");\n\n        headers.setIfValid(\"X-Valid-K1\", \"abc\\r\\n-xy\\r\\nz\");\n        headers.setIfValid(\"X-Valid-K2\", \"abc\\r-xy\\rz\");\n        headers.setIfValid(\"X-Valid-K3\", \"abc\\n-xy\\nz\");\n\n        assertThat(headers.getAll(\"X-Valid-K1\")).containsExactly(\"abc-xyz\");\n        assertThat(headers.getAll(\"X-Valid-K2\")).containsExactly(\"def-xyz\");\n        assertThat(headers.getAll(\"X-Valid-K3\")).containsExactly(\"xyz-xyz\");\n        assertThat(headers.size()).isEqualTo(3);\n    }\n\n    @Test\n    void setIfValidIgnoresInvalidValues_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"X-Valid-K1\", \"abc-xyz\");\n        headers.add(\"X-Valid-K2\", \"def-xyz\");\n        headers.add(\"X-Valid-K3\", \"xyz-xyz\");\n\n        headers.setIfValid(new HeaderName(\"X-Valid-K1\"), \"abc\\r\\n-xy\\r\\nz\");\n        headers.setIfValid(new HeaderName(\"X-Valid-K2\"), \"abc\\r-xy\\rz\");\n        headers.setIfValid(new HeaderName(\"X-Valid-K3\"), \"abc\\n-xy\\nz\");\n\n        assertThat(headers.getAll(\"X-Valid-K1\")).containsExactly(\"abc-xyz\");\n        assertThat(headers.getAll(\"X-Valid-K2\")).containsExactly(\"def-xyz\");\n        assertThat(headers.getAll(\"X-Valid-K3\")).containsExactly(\"xyz-xyz\");\n        assertThat(headers.size()).isEqualTo(3);\n    }\n\n    @Test\n    void setIfValidIgnoresInvalidKey() {\n        Headers headers = new Headers();\n        headers.add(\"X-Valid-K1\", \"abc-xyz\");\n\n        headers.setIfValid(\"X-K\\r\\ney-1\", \"abc-def\");\n        headers.setIfValid(\"X-K\\ney-2\", \"def-xyz\");\n        headers.setIfValid(\"X-K\\rey-3\", \"xyz-xyz\");\n\n        assertThat(headers.getAll(\"X-Valid-K1\")).containsExactly(\"abc-xyz\");\n        assertThat(headers.getAll(\"X-K\\r\\ney-1\")).isEmpty();\n        assertThat(headers.getAll(\"X-K\\ney-2\")).isEmpty();\n        assertThat(headers.getAll(\"X-K\\rey-3\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void setIfValidIgnoresInvalidKey_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"X-Valid-K1\", \"abc-xyz\");\n\n        headers.setIfValid(new HeaderName(\"X-K\\r\\ney-1\"), \"abc-def\");\n        headers.setIfValid(new HeaderName(\"X-K\\ney-2\"), \"def-xyz\");\n        headers.setIfValid(new HeaderName(\"X-K\\rey-3\"), \"xyz-xyz\");\n\n        assertThat(headers.getAll(\"X-Valid-K1\")).containsExactly(\"abc-xyz\");\n        assertThat(headers.getAll(\"X-K\\r\\ney-1\")).isEmpty();\n        assertThat(headers.getAll(\"X-K\\ney-2\")).isEmpty();\n        assertThat(headers.getAll(\"X-K\\rey-3\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void setIfAbsentKeepsExisting() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfAbsent(\"cookIe\", \"dilly=dally\");\n\n        assertThat(headers.getAll(\"CookiE\")).containsExactly(\"this=that\", \"frizzle=frazzle\");\n    }\n\n    @Test\n    void setIfAbsentKeepsExisting_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfAbsent(new HeaderName(\"cookIe\"), \"dilly=dally\");\n\n        assertThat(headers.getAll(\"CookiE\")).containsExactly(\"this=that\", \"frizzle=frazzle\");\n    }\n\n    @Test\n    void setIfAbsentFailsOnNull() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThatThrownBy(() -> headers.setIfAbsent(\"cookIe\", null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void setIfAbsentFailsOnNull_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        assertThatThrownBy(() -> headers.setIfAbsent(new HeaderName(\"cookIe\"), null))\n                .isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void setIfAbsent() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfAbsent(\"X-Netflix-Awesome\", \"true\");\n\n        assertThat(headers.getAll(\"X-netflix-Awesome\")).containsExactly(\"true\");\n    }\n\n    @Test\n    void setIfAbsent_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfAbsent(new HeaderName(\"X-Netflix-Awesome\"), \"true\");\n\n        assertThat(headers.getAll(\"X-netflix-Awesome\")).containsExactly(\"true\");\n    }\n\n    @Test\n    void setIfAbsentAndValid() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.setIfAbsentAndValid(\"X-Netflix-Awesome\", \"true\");\n        headers.setIfAbsentAndValid(\"X-Netflix-Awesome\", \"True\");\n\n        assertThat(headers.getAll(\"X-netflix-Awesome\")).containsExactly(\"true\");\n        assertThat(headers.size()).isEqualTo(4);\n    }\n\n    @Test\n    void setIfAbsentAndValidIgnoresInvalidValues() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n\n        headers.setIfAbsentAndValid(\"X-Invalid-K1\", \"abc\\r\\nxy\\r\\nz\");\n        headers.setIfAbsentAndValid(\"X-Invalid-K2\", \"abc\\rxy\\rz\");\n        headers.setIfAbsentAndValid(\"X-Invalid-K3\", \"abc\\nxy\\nz\");\n\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\");\n        assertThat(headers.getAll(\"X-Invalid-K1\")).isEmpty();\n        assertThat(headers.getAll(\"X-Invalid-K2\")).isEmpty();\n        assertThat(headers.getAll(\"X-Invalid-K3\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void add() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.add(\"via\", \"con Dios\");\n\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\", \"con Dios\");\n    }\n\n    @Test\n    void add_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        headers.add(new HeaderName(\"via\"), \"con Dios\");\n\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\", \"con Dios\");\n    }\n\n    @Test\n    void addIfValid() {\n        Headers headers = new Headers();\n        headers.addIfValid(\"Via\", \"duct\");\n        headers.addIfValid(\"Cookie\", \"abc=def\");\n        headers.addIfValid(\"cookie\", \"uvw=xyz\");\n\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\");\n        assertThat(headers.getAll(\"Cookie\")).containsExactly(\"abc=def\", \"uvw=xyz\");\n        assertThat(headers.size()).isEqualTo(3);\n    }\n\n    @Test\n    void addIfValid_headerName() {\n        Headers headers = new Headers();\n        headers.addIfValid(\"Via\", \"duct\");\n        headers.addIfValid(\"Cookie\", \"abc=def\");\n        headers.addIfValid(new HeaderName(\"cookie\"), \"uvw=xyz\");\n\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\");\n        assertThat(headers.getAll(\"Cookie\")).containsExactly(\"abc=def\", \"uvw=xyz\");\n        assertThat(headers.size()).isEqualTo(3);\n    }\n\n    @Test\n    void addIfValidIgnoresInvalidValues() {\n        Headers headers = new Headers();\n        headers.addIfValid(\"Via\", \"duct\");\n        headers.addIfValid(\"Cookie\", \"abc=def\");\n        headers.addIfValid(\"X-Invalid-K1\", \"abc\\r\\nxy\\r\\nz\");\n        headers.addIfValid(\"X-Invalid-K2\", \"abc\\rxy\\rz\");\n        headers.addIfValid(\"X-Invalid-K3\", \"abc\\nxy\\nz\");\n\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\");\n        assertThat(headers.getAll(\"Cookie\")).containsExactly(\"abc=def\");\n        assertThat(headers.getAll(\"X-Invalid-K1\")).isEmpty();\n        assertThat(headers.getAll(\"X-Invalid-K2\")).isEmpty();\n        assertThat(headers.getAll(\"X-Invalid-K3\")).isEmpty();\n        assertThat(headers.size()).isEqualTo(2);\n    }\n\n    @Test\n    void putAll() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n\n        Headers other = new Headers();\n        other.add(\"cookie\", \"a=b\");\n        other.add(\"via\", \"com\");\n\n        headers.putAll(other);\n\n        // Only check the order per field, not for the entire set.\n        assertThat(headers.getAll(\"Via\")).containsExactly(\"duct\", \"com\");\n        assertThat(headers.getAll(\"coOkiE\")).containsExactly(\"this=that\", \"frizzle=frazzle\", \"a=b\");\n        assertThat(headers.size()).isEqualTo(5);\n    }\n\n    @Test\n    void remove() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        List<String> removed = headers.remove(\"Cookie\");\n\n        assertThat(headers.getAll(\"Cookie\")).isEmpty();\n        assertThat(headers.getAll(\"Soup\")).containsExactly(\"salad\");\n        assertThat(headers.size()).isEqualTo(2);\n        assertThat(removed).containsExactly(\"this=that\", \"frizzle=frazzle\");\n    }\n\n    @Test\n    void remove_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        List<String> removed = headers.remove(new HeaderName(\"Cookie\"));\n\n        assertThat(headers.getAll(\"Cookie\")).isEmpty();\n        assertThat(headers.getAll(\"Soup\")).containsExactly(\"salad\");\n        assertThat(headers.size()).isEqualTo(2);\n        assertThat(removed).containsExactly(\"this=that\", \"frizzle=frazzle\");\n    }\n\n    @Test\n    void removeEmpty() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        List<String> removed = headers.remove(\"Monkey\");\n\n        assertThat(headers.getAll(\"Cookie\")).isNotEmpty();\n        assertThat(headers.getAll(\"Soup\")).containsExactly(\"salad\");\n        assertThat(headers.size()).isEqualTo(4);\n        assertThat(removed).isEmpty();\n    }\n\n    @Test\n    void removeEmpty_headerName() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"Cookie\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        List<String> removed = headers.remove(new HeaderName(\"Monkey\"));\n\n        assertThat(headers.getAll(\"Cookie\")).isNotEmpty();\n        assertThat(headers.getAll(\"Soup\")).containsExactly(\"salad\");\n        assertThat(headers.size()).isEqualTo(4);\n        assertThat(removed).isEmpty();\n    }\n\n    @Test\n    void removeIf() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"Cookie\", \"this=that\");\n        headers.add(\"COOkie\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        boolean removed = headers.removeIf(entry -> entry.getKey().getName().equals(\"Cookie\"));\n\n        assertThat(removed).isTrue();\n        assertThat(headers.getAll(\"cOoKie\")).containsExactly(\"frizzle=frazzle\");\n        assertThat(headers.size()).isEqualTo(3);\n    }\n\n    @Test\n    void keySet() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"COOKie\", \"this=that\");\n        headers.add(\"cookIE\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        Set<HeaderName> keySet = headers.keySet();\n\n        assertThat(keySet)\n                .containsExactlyInAnyOrder(new HeaderName(\"COOKie\"), new HeaderName(\"Soup\"), new HeaderName(\"Via\"));\n        for (HeaderName headerName : keySet) {\n            if (headerName.getName().equals(\"COOKie\")) {\n                return;\n            }\n        }\n        throw new AssertionError(\"didn't find right cookie in keys: \" + keySet);\n    }\n\n    @Test\n    void contains() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"COOKie\", \"this=that\");\n        headers.add(\"cookIE\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        assertThat(headers.contains(\"CoOkIe\")).isTrue();\n        assertThat(headers.contains(new HeaderName(\"CoOkIe\"))).isTrue();\n        assertThat(headers.contains(\"cookie\")).isTrue();\n        assertThat(headers.contains(new HeaderName(\"cookie\"))).isTrue();\n        assertThat(headers.contains(\"Cookie\")).isTrue();\n        assertThat(headers.contains(new HeaderName(\"Cookie\"))).isTrue();\n        assertThat(headers.contains(\"COOKIE\")).isTrue();\n        assertThat(headers.contains(new HeaderName(\"COOKIE\"))).isTrue();\n        assertThat(headers.contains(\"Monkey\")).isFalse();\n        assertThat(headers.contains(new HeaderName(\"Monkey\"))).isFalse();\n\n        headers.remove(\"cookie\");\n        assertThat(headers.contains(\"cookie\")).isFalse();\n        assertThat(headers.contains(new HeaderName(\"cookie\"))).isFalse();\n    }\n\n    @Test\n    void containsValue() {\n        Headers headers = new Headers();\n        headers.add(\"Via\", \"duct\");\n        headers.add(\"COOKie\", \"this=that\");\n        headers.add(\"cookIE\", \"frizzle=frazzle\");\n        headers.add(\"Soup\", \"salad\");\n\n        // note the swapping of the two cookie casings.\n        assertThat(headers.contains(\"CoOkIe\", \"frizzle=frazzle\")).isTrue();\n        assertThat(headers.contains(new HeaderName(\"CoOkIe\"), \"frizzle=frazzle\"))\n                .isTrue();\n        assertThat(headers.contains(\"Via\", \"lin\")).isFalse();\n        assertThat(headers.contains(new HeaderName(\"Soup\"), \"of the day\")).isFalse();\n    }\n\n    @Test\n    void testCaseInsensitiveKeys_Set() {\n        Headers headers = new Headers();\n        headers.set(\"Content-Length\", \"5\");\n        headers.set(\"content-length\", \"10\");\n\n        assertThat(headers.getFirst(\"Content-Length\")).isEqualTo(\"10\");\n        assertThat(headers.getFirst(\"content-length\")).isEqualTo(\"10\");\n        assertThat(headers.getAll(\"content-length\").size()).isEqualTo(1);\n    }\n\n    @Test\n    void testCaseInsensitiveKeys_Add() {\n        Headers headers = new Headers();\n        headers.add(\"Content-Length\", \"5\");\n        headers.add(\"content-length\", \"10\");\n\n        List<String> values = headers.getAll(\"content-length\");\n        assertThat(values.contains(\"10\")).isTrue();\n        assertThat(values.contains(\"5\")).isTrue();\n        assertThat(values.size()).isEqualTo(2);\n    }\n\n    @Test\n    void testCaseInsensitiveKeys_SetIfAbsent() {\n        Headers headers = new Headers();\n        headers.set(\"Content-Length\", \"5\");\n        headers.setIfAbsent(\"content-length\", \"10\");\n\n        List<String> values = headers.getAll(\"content-length\");\n        assertThat(values.size()).isEqualTo(1);\n        assertThat(values.get(0)).isEqualTo(\"5\");\n    }\n\n    @Test\n    void testCaseInsensitiveKeys_PutAll() {\n        Headers headers = new Headers();\n        headers.add(\"Content-Length\", \"5\");\n        headers.add(\"content-length\", \"10\");\n\n        Headers headers2 = new Headers();\n        headers2.putAll(headers);\n\n        List<String> values = headers2.getAll(\"content-length\");\n        assertThat(values.contains(\"10\")).isTrue();\n        assertThat(values.contains(\"5\")).isTrue();\n        assertThat(values.size()).isEqualTo(2);\n    }\n\n    @Test\n    void testSanitizeValues_CRLF() {\n        Headers headers = new Headers();\n\n        assertThatThrownBy(() -> headers.addAndValidate(\"x-test-break1\", \"a\\r\\nb\\r\\nc\"))\n                .isInstanceOf(ZuulException.class);\n        assertThatThrownBy(() -> headers.setAndValidate(\"x-test-break1\", \"a\\r\\nb\\r\\nc\"))\n                .isInstanceOf(ZuulException.class);\n    }\n\n    @Test\n    void testSanitizeValues_LF() {\n        Headers headers = new Headers();\n\n        assertThatThrownBy(() -> headers.addAndValidate(\"x-test-break1\", \"a\\nb\\nc\"))\n                .isInstanceOf(ZuulException.class);\n        assertThatThrownBy(() -> headers.setAndValidate(\"x-test-break1\", \"a\\nb\\nc\"))\n                .isInstanceOf(ZuulException.class);\n    }\n\n    @Test\n    void testSanitizeValues_ISO88591Value() {\n        Headers headers = new Headers();\n\n        headers.addAndValidate(\"x-test-ISO-8859-1\", \"P Venkmän\");\n        assertThat(headers.getAll(\"x-test-ISO-8859-1\")).containsExactly(\"P Venkmän\");\n        assertThat(headers.size()).isEqualTo(1);\n\n        headers.setAndValidate(\"x-test-ISO-8859-1\", \"Venkmän\");\n        assertThat(headers.getAll(\"x-test-ISO-8859-1\")).containsExactly(\"Venkmän\");\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void testSanitizeValues_UTF8Value() {\n        // Ideally Unicode characters should not appear in the Header values.\n        Headers headers = new Headers();\n\n        String rawHeaderValue = \"\\u017d\" + \"\\u0172\" + \"\\u016e\" + \"\\u013F\"; // ŽŲŮĽ\n        byte[] bytes = rawHeaderValue.getBytes(StandardCharsets.UTF_8);\n        String utf8HeaderValue = new String(bytes, StandardCharsets.UTF_8);\n        headers.addAndValidate(\"x-test-UTF8\", utf8HeaderValue);\n        assertThat(headers.getAll(\"x-test-UTF8\")).containsExactly(utf8HeaderValue);\n        assertThat(headers.size()).isEqualTo(1);\n\n        rawHeaderValue = \"\\u017d\" + \"\\u0172\" + \"uuu\" + \"\\u016e\" + \"\\u013F\"; // ŽŲuuuŮĽ\n        bytes = rawHeaderValue.getBytes(StandardCharsets.UTF_8);\n        utf8HeaderValue = new String(bytes, StandardCharsets.UTF_8);\n        headers.setAndValidate(\"x-test-UTF8\", utf8HeaderValue);\n        assertThat(headers.getAll(\"x-test-UTF8\")).containsExactly(utf8HeaderValue);\n        assertThat(headers.size()).isEqualTo(1);\n    }\n\n    @Test\n    void testSanitizeValues_addSetHeaderName() {\n        Headers headers = new Headers();\n\n        assertThatThrownBy(() -> headers.setAndValidate(new HeaderName(\"x-test-break1\"), \"a\\nb\\nc\"))\n                .isInstanceOf(ZuulException.class);\n        assertThatThrownBy(() -> headers.addAndValidate(new HeaderName(\"x-test-break2\"), \"a\\r\\nb\\r\\nc\"))\n                .isInstanceOf(ZuulException.class);\n    }\n\n    @Test\n    void testSanitizeValues_nameCRLF() {\n        Headers headers = new Headers();\n\n        assertThatThrownBy(() -> headers.addAndValidate(\"x-test-br\\r\\neak1\", \"a\\r\\nb\\r\\nc\"))\n                .isInstanceOf(ZuulException.class);\n        assertThatThrownBy(() -> headers.setAndValidate(\"x-test-br\\r\\neak2\", \"a\\r\\nb\\r\\nc\"))\n                .isInstanceOf(ZuulException.class);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/message/ZuulMessageImplTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.context.SessionContext;\nimport io.netty.buffer.Unpooled;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.HttpContent;\nimport java.nio.charset.StandardCharsets;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass ZuulMessageImplTest {\n    private static final String TEXT1 = \"Hello World!\";\n    private static final String TEXT2 = \"Goodbye World!\";\n\n    @Test\n    void testClone() {\n        SessionContext ctx1 = new SessionContext();\n        ctx1.set(\"k1\", \"v1\");\n        Headers headers1 = new Headers();\n        headers1.set(\"k1\", \"v1\");\n\n        ZuulMessage msg1 = new ZuulMessageImpl(ctx1, headers1);\n        ZuulMessage msg2 = msg1.clone();\n\n        assertThat(msg2.getBodyAsText()).isEqualTo(msg1.getBodyAsText());\n        assertThat(msg2.getHeaders()).isEqualTo(msg1.getHeaders());\n        assertThat(msg2.getContext()).isEqualTo(msg1.getContext());\n\n        // Verify that values of the 2 messages are decoupled.\n        msg1.getHeaders().set(\"k1\", \"v_new\");\n        msg1.getContext().set(\"k1\", \"v_new\");\n\n        assertThat(msg2.getHeaders().getFirst(\"k1\")).isEqualTo(\"v1\");\n        assertThat(msg2.getContext().get(\"k1\")).isEqualTo(\"v1\");\n    }\n\n    @Test\n    void testBufferBody2GetBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"Hello \".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultLastHttpContent(Unpooled.copiedBuffer(\"World!\".getBytes(UTF_8))));\n        String body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(0);\n    }\n\n    @Test\n    void testBufferBody3GetBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"Hello \".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"World!\".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultLastHttpContent());\n        String body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(0);\n    }\n\n    @Test\n    void testBufferBody3GetBodyAsText() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"Hello \".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"World!\".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultLastHttpContent());\n        String body = msg.getBodyAsText();\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(0);\n    }\n\n    @Test\n    void testSetBodyGetBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.setBody(TEXT1.getBytes(UTF_8));\n        String body = new String(msg.getBody(), UTF_8);\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT1.length()));\n    }\n\n    @Test\n    void testSetBodyAsTextGetBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.setBodyAsText(TEXT1);\n        String body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT1.length()));\n    }\n\n    @Test\n    void testSetBodyAsTextGetBodyAsText() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.setBodyAsText(TEXT1);\n        String body = msg.getBodyAsText();\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT1.length()));\n    }\n\n    @Test\n    void testMultiSetBodyAsTextGetBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.setBodyAsText(TEXT1);\n        String body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT1.length()));\n\n        msg.setBodyAsText(TEXT2);\n        body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT2);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT2.length()));\n    }\n\n    @Test\n    void testMultiSetBodyGetBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.setBody(TEXT1.getBytes(UTF_8));\n        String body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT1);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT1.length()));\n\n        msg.setBody(TEXT2.getBytes(UTF_8));\n        body = new String(msg.getBody(), UTF_8);\n        assertThat(msg.hasBody()).isTrue();\n        assertThat(msg.hasCompleteBody()).isTrue();\n        assertThat(body).isEqualTo(TEXT2);\n        assertThat(msg.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(1);\n        assertThat(msg.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT2.length()));\n    }\n\n    @Test\n    void testResettingBodyReaderIndex() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"Hello \".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultLastHttpContent(Unpooled.copiedBuffer(\"World!\".getBytes(UTF_8))));\n\n        // replicate what happens in nettys tls channel writer which moves the reader index on the contained ByteBuf\n        for (HttpContent c : msg.getBodyContents()) {\n            c.content().readerIndex(c.content().capacity());\n        }\n\n        for (HttpContent c : msg.getBodyContents()) {\n            assertThat(c.content().isReadable()).isFalse();\n            assertThat(c.content().readableBytes()).isEqualTo(0);\n        }\n\n        msg.resetBodyReader();\n\n        for (HttpContent c : msg.getBodyContents()) {\n            assertThat(c.content().isReadable()).isTrue();\n            assertThat(c.content().readableBytes() > 0).isTrue();\n        }\n    }\n\n    @Test\n    void testFetchingBodyReturnsEntireBuffer() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"Hello \".getBytes(UTF_8))));\n        msg.bufferBodyContents(new DefaultLastHttpContent(Unpooled.copiedBuffer(\"World!\".getBytes(UTF_8))));\n\n        // move the reader indexes to the end of the content buffers\n        for (HttpContent c : msg.getBodyContents()) {\n            c.content().readerIndex(c.content().capacity());\n        }\n\n        // ensure body returns entire chunk content irregardless of reader index movement above\n        assertThat(msg.getBodyLength()).isEqualTo(12);\n        assertThat(new String(msg.getBody(), StandardCharsets.UTF_8)).isEqualTo(TEXT1);\n\n        // buffer more content and ensure body returns entire chunk content\n        msg.bufferBodyContents(new DefaultLastHttpContent(Unpooled.copiedBuffer(\" Bye\".getBytes(UTF_8))));\n\n        assertThat(msg.getBodyLength()).isEqualTo(16);\n        assertThat(new String(msg.getBody(), StandardCharsets.UTF_8)).isEqualTo(\"Hello World! Bye\");\n    }\n\n    @Test\n    void testFetchingEmptyBody() {\n        ZuulMessage msg = new ZuulMessageImpl(new SessionContext(), new Headers());\n        assertThat(msg.getBodyLength()).isEqualTo(0);\n        assertThat(msg.getBody()).isNull();\n\n        msg.bufferBodyContents(new DefaultHttpContent(Unpooled.copiedBuffer(\"\".getBytes(UTF_8))));\n        assertThat(msg.getBodyLength()).isEqualTo(0);\n        assertThat(msg.getBody().length).isEqualTo(0);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/message/http/CookiesTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertTrue;\n\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport io.netty.handler.codec.http.cookie.DefaultCookie;\nimport java.util.List;\nimport java.util.Set;\nimport org.junit.jupiter.api.Test;\n\npublic class CookiesTest {\n\n    @Test\n    public void getNamesReturnsEmptySetWhenNoCookies() {\n        Cookies cookies = new Cookies();\n\n        Set<String> names = cookies.getNames();\n\n        assertNotNull(names);\n        assertTrue(names.isEmpty());\n    }\n\n    @Test\n    public void getNamesReturnsSingleCookieName() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"sessionId\", \"abc123\"));\n\n        Set<String> names = cookies.getNames();\n\n        assertEquals(1, names.size());\n        assertTrue(names.contains(\"sessionId\"));\n    }\n\n    @Test\n    public void getNamesReturnsMultipleCookieNames() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"NetflixId\", \"value1\"));\n        cookies.add(new DefaultCookie(\"SecureNetflixId\", \"value2\"));\n        cookies.add(new DefaultCookie(\"vdid\", \"value3\"));\n\n        Set<String> names = cookies.getNames();\n\n        assertEquals(3, names.size());\n        assertTrue(names.contains(\"NetflixId\"));\n        assertTrue(names.contains(\"SecureNetflixId\"));\n        assertTrue(names.contains(\"vdid\"));\n    }\n\n    @Test\n    public void getNamesReturnsAllUniqueNames() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"first\", \"1\"));\n        cookies.add(new DefaultCookie(\"second\", \"2\"));\n        cookies.add(new DefaultCookie(\"third\", \"3\"));\n\n        Set<String> names = cookies.getNames();\n\n        assertEquals(3, names.size());\n        assertTrue(names.contains(\"first\"));\n        assertTrue(names.contains(\"second\"));\n        assertTrue(names.contains(\"third\"));\n    }\n\n    @Test\n    public void getNamesReturnsUniqueNames() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"duplicate\", \"value1\"));\n        cookies.add(new DefaultCookie(\"duplicate\", \"value2\"));\n        cookies.add(new DefaultCookie(\"other\", \"value3\"));\n\n        Set<String> names = cookies.getNames();\n\n        assertEquals(2, names.size());\n        assertTrue(names.contains(\"duplicate\"));\n        assertTrue(names.contains(\"other\"));\n    }\n\n    @Test\n    public void getAllReturnsAllCookies() {\n        Cookies cookies = new Cookies();\n        Cookie c1 = new DefaultCookie(\"a\", \"1\");\n        Cookie c2 = new DefaultCookie(\"b\", \"2\");\n        cookies.add(c1);\n        cookies.add(c2);\n\n        List<Cookie> all = cookies.getAll();\n\n        assertEquals(2, all.size());\n        assertEquals(c1, all.get(0));\n        assertEquals(c2, all.get(1));\n    }\n\n    @Test\n    public void getReturnsMatchingCookies() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"session\", \"value1\"));\n        cookies.add(new DefaultCookie(\"session\", \"value2\"));\n        cookies.add(new DefaultCookie(\"other\", \"value3\"));\n\n        List<Cookie> sessionCookies = cookies.get(\"session\");\n\n        assertNotNull(sessionCookies);\n        assertEquals(2, sessionCookies.size());\n        assertEquals(\"session\", sessionCookies.get(0).name());\n        assertEquals(\"value1\", sessionCookies.get(0).value());\n        assertEquals(\"session\", sessionCookies.get(1).name());\n        assertEquals(\"value2\", sessionCookies.get(1).value());\n    }\n\n    @Test\n    public void getReturnsNullForNonExistentCookie() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"exists\", \"value\"));\n\n        List<Cookie> result = cookies.get(\"doesNotExist\");\n\n        assertNull(result);\n    }\n\n    @Test\n    public void getFirstReturnsFirstMatchingCookie() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"session\", \"first\"));\n        cookies.add(new DefaultCookie(\"session\", \"second\"));\n\n        Cookie first = cookies.getFirst(\"session\");\n\n        assertNotNull(first);\n        assertEquals(\"session\", first.name());\n        assertEquals(\"first\", first.value());\n    }\n\n    @Test\n    public void getFirstReturnsNullForNonExistentCookie() {\n        Cookies cookies = new Cookies();\n\n        Cookie result = cookies.getFirst(\"doesNotExist\");\n\n        assertNull(result);\n    }\n\n    @Test\n    public void getFirstValueReturnsValueOfFirstMatchingCookie() {\n        Cookies cookies = new Cookies();\n        cookies.add(new DefaultCookie(\"token\", \"abc123\"));\n        cookies.add(new DefaultCookie(\"token\", \"def456\"));\n\n        String value = cookies.getFirstValue(\"token\");\n\n        assertEquals(\"abc123\", value);\n    }\n\n    @Test\n    public void getFirstValueReturnsNullForNonExistentCookie() {\n        Cookies cookies = new Cookies();\n\n        String value = cookies.getFirstValue(\"doesNotExist\");\n\n        assertNull(value);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/message/http/HttpQueryParamsTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass HttpQueryParamsTest {\n\n    @Test\n    void testMultiples() {\n        HttpQueryParams qp = new HttpQueryParams();\n        qp.add(\"k1\", \"v1\");\n        qp.add(\"k1\", \"v2\");\n        qp.add(\"k2\", \"v3\");\n\n        assertThat(qp.toEncodedString()).isEqualTo(\"k1=v1&k1=v2&k2=v3\");\n    }\n\n    @Test\n    void testDuplicateKv() {\n        HttpQueryParams qp = new HttpQueryParams();\n        qp.add(\"k1\", \"v1\");\n        qp.add(\"k1\", \"v1\");\n        qp.add(\"k1\", \"v1\");\n\n        assertThat(qp.toEncodedString()).isEqualTo(\"k1=v1&k1=v1&k1=v1\");\n        assertThat(qp.get(\"k1\")).isEqualTo(List.of(\"v1\", \"v1\", \"v1\"));\n    }\n\n    @Test\n    void testToEncodedString() {\n        HttpQueryParams qp = new HttpQueryParams();\n        qp.add(\"k'1\", \"v1&\");\n        assertThat(qp.toEncodedString()).isEqualTo(\"k%271=v1%26\");\n\n        qp = new HttpQueryParams();\n        qp.add(\"k+\", \"\\n\");\n        assertThat(qp.toEncodedString()).isEqualTo(\"k%2B=%0A\");\n    }\n\n    @Test\n    void testToString() {\n        HttpQueryParams qp = new HttpQueryParams();\n        qp.add(\"k'1\", \"v1&\");\n        assertThat(qp.toString()).isEqualTo(\"k'1=v1&\");\n\n        qp = new HttpQueryParams();\n        qp.add(\"k+\", \"\\n\");\n        assertThat(qp.toString()).isEqualTo(\"k+=\\n\");\n    }\n\n    @Test\n    void testEquals() {\n        HttpQueryParams qp1 = new HttpQueryParams();\n        qp1.add(\"k1\", \"v1\");\n        qp1.add(\"k2\", \"v2\");\n        HttpQueryParams qp2 = new HttpQueryParams();\n        qp2.add(\"k1\", \"v1\");\n        qp2.add(\"k2\", \"v2\");\n\n        assertThat(qp2).isEqualTo(qp1);\n    }\n\n    @Test\n    void parseKeysWithoutValues() {\n        HttpQueryParams expected = new HttpQueryParams();\n        expected.add(\"k1\", \"\");\n        expected.add(\"k2\", \"v2\");\n        expected.add(\"k3\", \"\");\n\n        HttpQueryParams actual = HttpQueryParams.parse(\"k1=&k2=v2&k3=\");\n\n        assertThat(actual).isEqualTo(expected);\n\n        assertThat(actual.toEncodedString()).isEqualTo(\"k1=&k2=v2&k3=\");\n    }\n\n    @Test\n    void parseKeyWithoutValueEquals() {\n        HttpQueryParams expected = new HttpQueryParams();\n        expected.add(\"k1\", \"\");\n\n        HttpQueryParams actual = HttpQueryParams.parse(\"k1=\");\n\n        assertThat(actual).isEqualTo(expected);\n\n        assertThat(actual.toEncodedString()).isEqualTo(\"k1=\");\n    }\n\n    @Test\n    void parseKeyWithoutValue() {\n        HttpQueryParams expected = new HttpQueryParams();\n        expected.add(\"k1\", \"\");\n\n        HttpQueryParams actual = HttpQueryParams.parse(\"k1\");\n\n        assertThat(actual).isEqualTo(expected);\n\n        assertThat(actual.toEncodedString()).isEqualTo(\"k1\");\n    }\n\n    @Test\n    void parseKeyWithoutValueShort() {\n        HttpQueryParams expected = new HttpQueryParams();\n        expected.add(\"=\", \"\");\n\n        HttpQueryParams actual = HttpQueryParams.parse(\"=\");\n\n        assertThat(actual).isEqualTo(expected);\n\n        assertThat(actual.toEncodedString()).isEqualTo(\"%3D\");\n    }\n\n    @Test\n    void parseKeysWithoutValuesMixedTrailers() {\n        HttpQueryParams expected = new HttpQueryParams();\n        expected.add(\"k1\", \"\");\n        expected.add(\"k2\", \"v2\");\n        expected.add(\"k3\", \"\");\n        expected.add(\"k4\", \"v4\");\n\n        HttpQueryParams actual = HttpQueryParams.parse(\"k1=&k2=v2&k3&k4=v4\");\n\n        assertThat(actual).isEqualTo(expected);\n\n        assertThat(actual.toEncodedString()).isEqualTo(\"k1=&k2=v2&k3&k4=v4\");\n    }\n\n    @Test\n    void parseKeysIgnoreCase() {\n        String camelCaseKey = \"keyName\";\n        HttpQueryParams queryParams = new HttpQueryParams();\n        queryParams.add(\"foo\", \"bar\");\n        queryParams.add(camelCaseKey.toLowerCase(Locale.ROOT), \"value\");\n\n        assertThat(queryParams.containsIgnoreCase(camelCaseKey)).isTrue();\n    }\n\n    @Test\n    void maintainsOrderOnToString() {\n        String queryString =\n                IntStream.range(0, 100).mapToObj(i -> \"k%d=v%d\".formatted(i, i)).collect(Collectors.joining(\"&\"));\n        HttpQueryParams queryParams = HttpQueryParams.parse(queryString);\n        assertThat(queryParams.toEncodedString()).isEqualTo(queryString);\n        assertThat(queryParams.toString()).isEqualTo(queryString);\n        assertThat(queryParams.immutableCopy().toString()).isEqualTo(queryString);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/message/http/HttpRequestMessageImplTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.common.net.InetAddresses;\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport io.netty.channel.local.LocalAddress;\nimport io.netty.handler.codec.http.cookie.Cookie;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.net.URISyntaxException;\nimport java.util.List;\nimport java.util.Optional;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@SuppressWarnings(\"AddressSelection\")\n@ExtendWith(MockitoExtension.class)\nclass HttpRequestMessageImplTest {\n\n    HttpRequestMessageImpl request;\n    private final AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n\n    @AfterEach\n    void resetConfig() {\n        config.clearProperty(\"zuul.HttpRequestMessage.host.header.strict.validation\");\n    }\n\n    @Test\n    void testOriginalRequestInfo() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        queryParams.add(\"flag\", \"5\");\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new LocalAddress(\"777\"),\n                false);\n\n        request.storeInboundRequest();\n        HttpRequestInfo originalRequest = request.getInboundRequest();\n\n        assertThat(originalRequest.getPort()).isEqualTo(request.getPort());\n        assertThat(originalRequest.getPath()).isEqualTo(request.getPath());\n        assertThat(originalRequest.getQueryParams().getFirst(\"flag\"))\n                .isEqualTo(request.getQueryParams().getFirst(\"flag\"));\n        assertThat(originalRequest.getHeaders().getFirst(\"Host\"))\n                .isEqualTo(request.getHeaders().getFirst(\"Host\"));\n\n        request.setPort(8080);\n        request.setPath(\"/another/place\");\n        request.getQueryParams().set(\"flag\", \"20\");\n        request.getHeaders().set(\"Host\", \"wah.netflix.com\");\n\n        assertThat(originalRequest.getPort()).isEqualTo(7002);\n        assertThat(originalRequest.getPath()).isEqualTo(\"/some/where\");\n        assertThat(originalRequest.getQueryParams().getFirst(\"flag\")).isEqualTo(\"5\");\n        assertThat(originalRequest.getHeaders().getFirst(\"Host\")).isEqualTo(\"blah.netflix.com\");\n    }\n\n    @Test\n    void testReconstructURI() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        queryParams.add(\"flag\", \"5\");\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.reconstructURI()).isEqualTo(\"https://blah.netflix.com:7002/some/where?flag=5\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        headers.add(\"X-Forwarded-Host\", \"place.netflix.com\");\n        headers.add(\"X-Forwarded-Port\", \"80\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"http\",\n                7002,\n                \"localhost\");\n        assertThat(request.reconstructURI()).isEqualTo(\"http://place.netflix.com/some/where\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        headers.add(\"X-Forwarded-Host\", \"place.netflix.com\");\n        headers.add(\"X-Forwarded-Proto\", \"https\");\n        headers.add(\"X-Forwarded-Port\", \"443\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"http\",\n                7002,\n                \"localhost\");\n        assertThat(request.reconstructURI()).isEqualTo(\"https://place.netflix.com/some/where\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"http\",\n                7002,\n                \"localhost\");\n        assertThat(request.reconstructURI()).isEqualTo(\"http://localhost:7002/some/where\");\n\n        queryParams = new HttpQueryParams();\n        queryParams.add(\"flag\", \"5\");\n        queryParams.add(\"flag B\", \"9\");\n        headers = new Headers();\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some%20where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.reconstructURI()).isEqualTo(\"https://localhost:7002/some%20where?flag=5&flag+B=9\");\n    }\n\n    @Test\n    void testReconstructURI_immutable() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        queryParams.add(\"flag\", \"5\");\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new SocketAddress() {},\n                true);\n\n        // Check it's the same value 2nd time.\n        assertThat(request.reconstructURI()).isEqualTo(\"https://blah.netflix.com:7002/some/where?flag=5\");\n        assertThat(request.reconstructURI()).isEqualTo(\"https://blah.netflix.com:7002/some/where?flag=5\");\n\n        // Check that cached on 1st usage.\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new SocketAddress() {},\n                true);\n        request = spy(request);\n        when(request._reconstructURI()).thenReturn(\"http://testhost/blah\");\n        verify(request, times(1))._reconstructURI();\n        assertThat(request.reconstructURI()).isEqualTo(\"http://testhost/blah\");\n        assertThat(request.reconstructURI()).isEqualTo(\"http://testhost/blah\");\n\n        // Check that throws exception if we try to mutate it.\n        try {\n            request.setPath(\"/new-path\");\n            fail();\n        } catch (IllegalStateException e) {\n            assertThat(true).isTrue();\n        }\n    }\n\n    @Test\n    void testPathAndQuery() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        queryParams.add(\"flag\", \"5\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                new Headers(),\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n\n        // Check that value changes.\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/some/where?flag=5\");\n        request.getQueryParams().add(\"k\", \"v\");\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/some/where?flag=5&k=v\");\n        request.setPath(\"/other\");\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/other?flag=5&k=v\");\n    }\n\n    @Test\n    void testPathAndQuery_immutable() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        queryParams.add(\"flag\", \"5\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                new Headers(),\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new SocketAddress() {},\n                true);\n\n        // Check it's the same value 2nd time.\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/some/where?flag=5\");\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/some/where?flag=5\");\n\n        // Check that cached on 1st usage.\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                new Headers(),\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new SocketAddress() {},\n                true);\n        request = spy(request);\n        when(request.generatePathAndQuery()).thenReturn(\"/blah\");\n        verify(request, times(1)).generatePathAndQuery();\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/blah\");\n        assertThat(request.getPathAndQuery()).isEqualTo(\"/blah\");\n    }\n\n    @Test\n    void testGetOriginalHost_immutable() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new SocketAddress() {},\n                true);\n\n        // Check it's the same value 2nd time.\n        assertThat(request.getOriginalHost()).isEqualTo(\"blah.netflix.com\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"blah.netflix.com\");\n\n        // Update the Host header value and ensure the result didn't change.\n        headers.set(\"Host\", \"testOriginalHost2\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"blah.netflix.com\");\n    }\n\n    @Test\n    void testGetOriginalHost() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"blah.netflix.com\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        headers.add(\"Host\", \"0.0.0.1\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"0.0.0.1\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        headers.add(\"Host\", \"0.0.0.1:2\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"0.0.0.1\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        headers.add(\"Host\", \"[::2]\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"[::2]\");\n\n        queryParams = new HttpQueryParams();\n        headers = new Headers();\n        headers.add(\"Host\", \"[::2]:3\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"[::2]\");\n\n        headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        headers.add(\"X-Forwarded-Host\", \"foo.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"foo.netflix.com\");\n\n        headers = new Headers();\n        headers.add(\"X-Forwarded-Host\", \"foo.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"foo.netflix.com\");\n\n        headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com:8080\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"blah.netflix.com\");\n    }\n\n    @Test\n    void testGetOriginalHost_handlesNonRFC2396Hostnames() {\n        config.setProperty(\"zuul.HttpRequestMessage.host.header.strict.validation\", false);\n\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"my_underscore_endpoint.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"my_underscore_endpoint.netflix.com\");\n\n        headers = new Headers();\n        headers.add(\"Host\", \"my_underscore_endpoint.netflix.com:8080\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"my_underscore_endpoint.netflix.com\");\n\n        headers = new Headers();\n        headers.add(\"Host\", \"my_underscore_endpoint^including~more-chars.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"my_underscore_endpoint^including~more-chars.netflix.com\");\n\n        headers = new Headers();\n        headers.add(\"Host\", \"hostname%5Ewith-url-encoded.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalHost()).isEqualTo(\"hostname%5Ewith-url-encoded.netflix.com\");\n    }\n\n    @Test\n    void getOriginalHost_failsOnUnbracketedIpv6Address() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"ba::dd\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n\n        assertThatThrownBy(() -> HttpRequestMessageImpl.getOriginalHost(headers, \"server\"))\n                .isInstanceOf(URISyntaxException.class);\n    }\n\n    @Test\n    void getOriginalHost_fallsBackOnUnbracketedIpv6Address_WithNonStrictValidation() {\n        config.setProperty(\"zuul.HttpRequestMessage.host.header.strict.validation\", false);\n\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"ba::dd\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"server\");\n\n        assertThat(request.getOriginalHost()).isEqualTo(\"server\");\n    }\n\n    @Test\n    void testGetOriginalPort() {\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(7002);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com\");\n        headers.add(\"X-Forwarded-Port\", \"443\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(443);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com:443\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(443);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"127.0.0.2:443\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(443);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"127.0.0.2\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(7002);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"[::2]:443\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(443);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"[::2]\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(7002);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"blah.netflix.com:443\");\n        headers.add(\"X-Forwarded-Port\", \"7005\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(7005);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"host_with_underscores.netflix.com:8080\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort())\n                .as(\"should fallback to server port\")\n                .isEqualTo(7002);\n    }\n\n    @Test\n    void testGetOriginalPort_NonStrictValidation() {\n        config.setProperty(\"zuul.HttpRequestMessage.host.header.strict.validation\", false);\n\n        HttpQueryParams queryParams = new HttpQueryParams();\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"host_with_underscores.netflix.com:8080\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(8080);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"host-with-carrots^1.0.0.netflix.com:8080\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(8080);\n\n        headers = new Headers();\n        headers.add(\"Host\", \"host-with-carrots-no-port^1.0.0.netflix.com\");\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                queryParams,\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getOriginalPort()).isEqualTo(7002);\n    }\n\n    @Test\n    void getOriginalPort_fallsBackOnUnbracketedIpv6Address() throws URISyntaxException {\n        Headers headers = new Headers();\n        headers.add(\"Host\", \"ba::33\");\n\n        assertThat(HttpRequestMessageImpl.getOriginalPort(new SessionContext(), headers, 9999))\n                .isEqualTo(9999);\n    }\n\n    @Test\n    void getOriginalPort_EmptyXFFPort() throws URISyntaxException {\n        Headers headers = new Headers();\n        headers.add(HttpHeaderNames.X_FORWARDED_PORT, \"\");\n\n        // Default to using server port\n        assertThat(HttpRequestMessageImpl.getOriginalPort(new SessionContext(), headers, 9999))\n                .isEqualTo(9999);\n    }\n\n    @Test\n    void getOriginalPort_respectsProxyProtocol() throws URISyntaxException {\n        SessionContext context = new SessionContext();\n        context.set(\n                CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS,\n                new InetSocketAddress(InetAddresses.forString(\"1.1.1.1\"), 443));\n        Headers headers = new Headers();\n        headers.add(\"X-Forwarded-Port\", \"6000\");\n        assertThat(HttpRequestMessageImpl.getOriginalPort(context, headers, 9999))\n                .isEqualTo(443);\n    }\n\n    @Test\n    void testCleanCookieHeaders() {\n        assertThat(HttpRequestMessageImpl.cleanCookieHeader(\"BlahId=12345; Secure, something=67890;\"))\n                .isEqualTo(\"BlahId=12345; something=67890;\");\n        assertThat(HttpRequestMessageImpl.cleanCookieHeader(\"BlahId=12345; something=67890;\"))\n                .isEqualTo(\"BlahId=12345; something=67890;\");\n        assertThat(HttpRequestMessageImpl.cleanCookieHeader(\" Secure, BlahId=12345; Secure, something=67890;\"))\n                .isEqualTo(\" BlahId=12345; something=67890;\");\n        assertThat(HttpRequestMessageImpl.cleanCookieHeader(\"\")).isEqualTo(\"\");\n    }\n\n    @Test\n    void shouldPreferClientDestPortWhenInitialized() {\n        HttpRequestMessageImpl message = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                new HttpQueryParams(),\n                new Headers(),\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new InetSocketAddress(\"api.netflix.com\", 443),\n                true);\n\n        assertThat(Optional.of(443)).isEqualTo(message.getClientDestinationPort());\n    }\n\n    @Test\n    public void duplicateCookieNames() {\n        Headers headers = new Headers();\n        headers.add(\"cookie\", \"k=v1;k=v2\");\n        HttpRequestMessageImpl message = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/some/where\",\n                new HttpQueryParams(),\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\",\n                new InetSocketAddress(\"api.netflix.com\", 443),\n                true);\n        Cookies cookies = message.parseCookies();\n        assertThat(cookies.getAll().size()).isEqualTo(2);\n        List<Cookie> kCookies = cookies.get(\"k\");\n        assertThat(kCookies.size()).isEqualTo(2);\n        assertThat(kCookies.get(0).value()).isEqualTo(\"v1\");\n        assertThat(kCookies.get(1).value()).isEqualTo(\"v2\");\n    }\n\n    @Test\n    public void testGetDecodedPath() {\n        Headers headers = new Headers();\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/%C3%B1\",\n                new HttpQueryParams(),\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getPath()).isEqualTo(\"/%C3%B1\");\n        assertThat(request.getDecodedPath()).isEqualTo(\"/ñ\");\n\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/ñ\",\n                new HttpQueryParams(),\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getPath()).isEqualTo(\"/ñ\");\n        assertThat(request.getDecodedPath()).isEqualTo(\"/ñ\");\n\n        request = new HttpRequestMessageImpl(\n                new SessionContext(),\n                \"HTTP/1.1\",\n                \"POST\",\n                \"/path\",\n                new HttpQueryParams(),\n                headers,\n                \"192.168.0.2\",\n                \"https\",\n                7002,\n                \"localhost\");\n        assertThat(request.getPath()).isEqualTo(\"/path\");\n        assertThat(request.getDecodedPath()).isEqualTo(\"/path\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/message/http/HttpResponseMessageImplTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.message.http;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport java.nio.charset.StandardCharsets;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Unit tests for {@link HttpResponseMessageImpl}.\n */\n@ExtendWith(MockitoExtension.class)\nclass HttpResponseMessageImplTest {\n    private static final String TEXT1 = \"Hello World!\";\n    private static final String TEXT2 = \"Goodbye World!\";\n\n    @Mock\n    private HttpRequestMessage request;\n\n    private HttpResponseMessageImpl response;\n\n    @BeforeEach\n    void setup() {\n        response = new HttpResponseMessageImpl(new SessionContext(), new Headers(), request, 200);\n    }\n\n    @Test\n    void testHasSetCookieWithName() {\n        response.getHeaders()\n                .add(\n                        \"Set-Cookie\",\n                        \"c1=1234; Max-Age=-1; Expires=Tue, 01 Sep 2015 22:49:57 GMT; Path=/; Domain=.netflix.com\");\n        response.getHeaders()\n                .add(\n                        \"Set-Cookie\",\n                        \"c2=4567; Max-Age=-1; Expires=Tue, 01 Sep 2015 22:49:57 GMT; Path=/; Domain=.netflix.com\");\n\n        assertThat(response.hasSetCookieWithName(\"c1\")).isTrue();\n        assertThat(response.hasSetCookieWithName(\"c2\")).isTrue();\n        assertThat(response.hasSetCookieWithName(\"XX\")).isFalse();\n    }\n\n    @Test\n    void testRemoveExistingSetCookie() {\n        response.getHeaders()\n                .add(\n                        \"Set-Cookie\",\n                        \"c1=1234; Max-Age=-1; Expires=Tue, 01 Sep 2015 22:49:57 GMT; Path=/; Domain=.netflix.com\");\n        response.getHeaders()\n                .add(\n                        \"Set-Cookie\",\n                        \"c2=4567; Max-Age=-1; Expires=Tue, 01 Sep 2015 22:49:57 GMT; Path=/; Domain=.netflix.com\");\n\n        response.removeExistingSetCookie(\"c1\");\n\n        assertThat(response.getHeaders().size()).isEqualTo(1);\n        assertThat(response.hasSetCookieWithName(\"c1\")).isFalse();\n        assertThat(response.hasSetCookieWithName(\"c2\")).isTrue();\n    }\n\n    @Test\n    void testContentLengthHeaderHasCorrectValue() {\n        assertThat(response.getHeaders().getAll(\"Content-Length\").size()).isEqualTo(0);\n\n        response.setBodyAsText(TEXT1);\n        assertThat(response.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT1.length()));\n\n        response.setBody(TEXT2.getBytes(StandardCharsets.UTF_8));\n        assertThat(response.getHeaders().getFirst(\"Content-Length\")).isEqualTo(String.valueOf(TEXT2.length()));\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnCounterTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.monitoring;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Gauge;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport org.junit.jupiter.api.Test;\n\nclass ConnCounterTest {\n    @Test\n    void record() {\n        EmbeddedChannel chan = new EmbeddedChannel();\n        Attrs attrs = Attrs.newInstance();\n        chan.attr(Server.CONN_DIMENSIONS).set(attrs);\n        Registry registry = new DefaultRegistry();\n        ConnCounter counter = ConnCounter.install(chan, registry, registry.createId(\"foo\"));\n\n        counter.increment(\"start\");\n        counter.increment(\"middle\");\n        Attrs.newKey(\"bar\").put(attrs, \"baz\");\n        counter.increment(\"end\");\n\n        Gauge meter1 = registry.gauge(registry.createId(\"foo.start\", \"from\", \"nascent\"));\n        assertThat(meter1).isNotNull();\n        assertThat(meter1.value()).isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.0));\n\n        Gauge meter2 = registry.gauge(registry.createId(\"foo.middle\", \"from\", \"start\"));\n        assertThat(meter2).isNotNull();\n        assertThat(meter2.value()).isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.0));\n\n        Gauge meter3 = registry.gauge(registry.createId(\"foo.end\", \"from\", \"middle\", \"bar\", \"baz\"));\n        assertThat(meter3).isNotNull();\n        assertThat(meter3.value()).isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.0));\n    }\n\n    @Test\n    void activeConnsCount() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        Attrs attrs = Attrs.newInstance();\n        channel.attr(Server.CONN_DIMENSIONS).set(attrs);\n        Registry registry = new DefaultRegistry();\n\n        ConnCounter.install(channel, registry, registry.createId(\"foo\"));\n\n        // Dedup increments\n        ConnCounter.from(channel).increment(\"active\");\n        ConnCounter.from(channel).increment(\"active\");\n\n        assertThat(ConnCounter.from(channel).getCurrentActiveConns())\n                .isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.0));\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/monitoring/ConnTimerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.monitoring;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.histogram.PercentileTimer;\nimport com.netflix.zuul.Attrs;\nimport com.netflix.zuul.netty.server.Server;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport org.junit.jupiter.api.Test;\n\nclass ConnTimerTest {\n    @Test\n    void record() {\n        EmbeddedChannel chan = new EmbeddedChannel();\n        Attrs attrs = Attrs.newInstance();\n        chan.attr(Server.CONN_DIMENSIONS).set(attrs);\n        Registry registry = new DefaultRegistry();\n        ConnTimer timer = ConnTimer.install(chan, registry, registry.createId(\"foo\"));\n\n        timer.record(1000L, \"start\");\n        timer.record(2000L, \"middle\");\n        Attrs.newKey(\"bar\").put(attrs, \"baz\");\n        timer.record(4000L, \"end\");\n\n        PercentileTimer meter1 = PercentileTimer.get(registry, registry.createId(\"foo.start-middle\"));\n        assertThat(meter1).isNotNull();\n        assertThat(meter1.totalTime()).isEqualTo(1000L);\n\n        PercentileTimer meter2 = PercentileTimer.get(registry, registry.createId(\"foo.middle-end\", \"bar\", \"baz\"));\n        assertThat(meter2).isNotNull();\n        assertThat(meter2.totalTime()).isEqualTo(2000L);\n\n        PercentileTimer meter3 = PercentileTimer.get(registry, registry.createId(\"foo.start-end\", \"bar\", \"baz\"));\n        assertThat(meter3).isNotNull();\n        assertThat(meter3.totalTime()).isEqualTo(3000L);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/NettyRequestAttemptFactoryTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.exception.OutboundException;\nimport com.netflix.zuul.netty.connectionpool.OriginConnectException;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.origins.OriginConcurrencyExceededException;\nimport com.netflix.zuul.origins.OriginName;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport io.netty.handler.timeout.ReadTimeoutException;\nimport java.nio.channels.ClosedChannelException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\npublic class NettyRequestAttemptFactoryTest {\n    private NettyRequestAttemptFactory factory;\n\n    @BeforeEach\n    public void setup() {\n        factory = new NettyRequestAttemptFactory();\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToOutboundException() {\n        Exception e = new OutboundException(OutboundErrorType.OTHER, new RequestAttempts());\n        OutboundException mapException = factory.mapNettyToOutboundException(e, new SessionContext());\n\n        assertThat(mapException).isEqualTo(e);\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToReadTimeoutException() {\n        OutboundException mapException =\n                factory.mapNettyToOutboundException(new ReadTimeoutException(), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.READ_TIMEOUT);\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToOriginConcurrencyExceededException() {\n        OutboundException mapException = factory.mapNettyToOutboundException(\n                new OriginConcurrencyExceededException(OriginName.fromVipAndApp(\"vip\", \"app\")), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.ORIGIN_CONCURRENCY_EXCEEDED);\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToOriginConnectException() {\n        OutboundException mapException = factory.mapNettyToOutboundException(\n                new OriginConnectException(\"error\", OutboundErrorType.OTHER), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.OTHER);\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToClosedChannelException() {\n        OutboundException mapException =\n                factory.mapNettyToOutboundException(new ClosedChannelException(), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.RESET_CONNECTION);\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToHeaderListSizeException() {\n        OutboundException mapException = factory.mapNettyToOutboundException(\n                Http2Exception.headerListSizeError(1, Http2Error.CONNECT_ERROR, false, \"\"), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.HEADER_FIELDS_TOO_LARGE);\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToIllegalStateException() {\n        OutboundException mapException = factory.mapNettyToOutboundException(\n                new Exception(new IllegalStateException(new Throwable(\"No available server\"))), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.NO_AVAILABLE_SERVERS);\n    }\n\n    @Test\n    public void mapNettyToOutboundExceptionMapsToOtherExceptionType() {\n        OutboundException mapException = factory.mapNettyToOutboundException(new Exception(), new SessionContext());\n\n        // check that the type is OutboundException\n        assertThat(mapException).isNotNull();\n        assertThat(mapException.getOutboundErrorType()).isEqualTo(OutboundErrorType.OTHER);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/ClientTimeoutHandlerTest.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelDuplexHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.util.ReferenceCountUtil;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * @author Justin Guerra\n * @since 7/30/24\n */\n@ExtendWith(MockitoExtension.class)\nclass ClientTimeoutHandlerTest {\n\n    @Mock\n    private PooledConnection pooledConnection;\n\n    private EmbeddedChannel channel;\n    private WriteVerifyingHandler verifier;\n\n    @BeforeEach\n    public void setup() {\n        channel = new EmbeddedChannel();\n        channel.attr(PooledConnection.CHANNEL_ATTR).set(pooledConnection);\n        verifier = new WriteVerifyingHandler();\n        channel.pipeline().addLast(verifier);\n        channel.pipeline().addLast(new ClientTimeoutHandler.OutboundHandler());\n    }\n\n    @AfterEach\n    public void cleanup() {\n        channel.finishAndReleaseAll();\n    }\n\n    @Test\n    public void dontStartReadTimeoutHandlerIfNotLastContent() {\n        addTimeoutToChannel();\n        channel.writeOutbound(new DefaultHttpContent(Unpooled.wrappedBuffer(\"yo\".getBytes(UTF_8))));\n        verify(pooledConnection, never()).startReadTimeoutHandler(any());\n        verifyWrite();\n    }\n\n    @Test\n    public void dontStartReadTimeoutHandlerIfNoTimeout() {\n        channel.writeOutbound(new DefaultLastHttpContent());\n        verify(pooledConnection, never()).startReadTimeoutHandler(any());\n        verifyWrite();\n    }\n\n    @Test\n    public void dontStartReadTimeoutHandlerOnFailedPromise() {\n        addTimeoutToChannel();\n\n        channel.pipeline().addFirst(new ChannelDuplexHandler() {\n            @Override\n            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {\n                ReferenceCountUtil.safeRelease(msg);\n                promise.setFailure(new RuntimeException());\n            }\n        });\n        try {\n            channel.writeOutbound(new DefaultLastHttpContent());\n        } catch (RuntimeException e) {\n            // expected\n        }\n        verify(pooledConnection, never()).startReadTimeoutHandler(any());\n        verifyWrite();\n    }\n\n    @Test\n    public void startReadTimeoutHandlerOnSuccessfulPromise() {\n        Duration timeout = addTimeoutToChannel();\n        channel.writeOutbound(new DefaultLastHttpContent());\n        verify(pooledConnection).startReadTimeoutHandler(timeout);\n        verifyWrite();\n    }\n\n    private Duration addTimeoutToChannel() {\n        Duration timeout = Duration.of(5, ChronoUnit.SECONDS);\n        channel.attr(ClientTimeoutHandler.ORIGIN_RESPONSE_READ_TIMEOUT).set(timeout);\n        return timeout;\n    }\n\n    private void verifyWrite() {\n        assertThat(verifier.seenWrite).isTrue();\n    }\n\n    private static class WriteVerifyingHandler extends ChannelDuplexHandler {\n        boolean seenWrite;\n\n        @Override\n        public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n            seenWrite = true;\n            super.write(ctx, msg, promise);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolConfigImplTest.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static com.netflix.zuul.netty.connectionpool.ConnectionPoolConfigImpl.MAX_REQUESTS_PER_CONNECTION;\nimport static com.netflix.zuul.netty.connectionpool.ConnectionPoolConfigImpl.PER_SERVER_WATERLINE;\nimport static com.netflix.zuul.netty.connectionpool.ConnectionPoolConfigImpl.TCP_KEEP_ALIVE;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.client.config.IClientConfigKey;\nimport com.netflix.zuul.origins.OriginName;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Justin Guerra\n * @since 6/20/24\n */\nclass ConnectionPoolConfigImplTest {\n\n    private ConnectionPoolConfig connectionPoolConfig;\n    private DefaultClientConfigImpl clientConfig;\n\n    @BeforeEach\n    void setup() {\n        OriginName originName = OriginName.fromVip(\"whatever\");\n        clientConfig = DefaultClientConfigImpl.getEmptyConfig();\n        connectionPoolConfig = new ConnectionPoolConfigImpl(originName, clientConfig);\n    }\n\n    @Test\n    void testGetConnectTimeout() {\n        assertThat(connectionPoolConfig.getConnectTimeout())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_CONNECT_TIMEOUT);\n    }\n\n    @Test\n    void testGetConnectTimeoutOverride() {\n        clientConfig.set(IClientConfigKey.Keys.ConnectTimeout, 1000);\n        assertThat(connectionPoolConfig.getConnectTimeout()).isEqualTo(1000);\n    }\n\n    @Test\n    void testGetMaxRequestsPerConnection() {\n        assertThat(connectionPoolConfig.getMaxRequestsPerConnection())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_MAX_REQUESTS_PER_CONNECTION);\n    }\n\n    @Test\n    void testGetMaxRequestsPerConnectionOverride() {\n        clientConfig.set(MAX_REQUESTS_PER_CONNECTION, 2000);\n        assertThat(connectionPoolConfig.getMaxRequestsPerConnection()).isEqualTo(2000);\n    }\n\n    @Test\n    void testMaxConnectionsPerHost() {\n        assertThat(connectionPoolConfig.maxConnectionsPerHost())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_MAX_CONNS_PER_HOST);\n    }\n\n    @Test\n    void testMaxConnectionsPerHostOverride() {\n        clientConfig.set(IClientConfigKey.Keys.MaxConnectionsPerHost, 60);\n        assertThat(connectionPoolConfig.maxConnectionsPerHost()).isEqualTo(60);\n    }\n\n    @Test\n    void testPerServerWaterline() {\n        assertThat(connectionPoolConfig.perServerWaterline())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_PER_SERVER_WATERLINE);\n    }\n\n    @Test\n    void testPerServerWaterlineOverride() {\n        clientConfig.set(PER_SERVER_WATERLINE, 5);\n        assertThat(connectionPoolConfig.perServerWaterline()).isEqualTo(5);\n    }\n\n    @Test\n    void testGetIdleTimeout() {\n        assertThat(connectionPoolConfig.getIdleTimeout()).isEqualTo(ConnectionPoolConfigImpl.DEFAULT_IDLE_TIMEOUT);\n    }\n\n    @Test\n    void testGetIdleTimeoutOverride() {\n        clientConfig.set(IClientConfigKey.Keys.ConnIdleEvictTimeMilliSeconds, 70000);\n        assertThat(connectionPoolConfig.getIdleTimeout()).isEqualTo(70000);\n    }\n\n    @Test\n    void testGetTcpKeepAlive() {\n        assertThat(connectionPoolConfig.getTcpKeepAlive()).isFalse();\n    }\n\n    @Test\n    void testGetTcpKeepAliveOverride() {\n        clientConfig.set(TCP_KEEP_ALIVE, true);\n        assertThat(connectionPoolConfig.getTcpKeepAlive()).isTrue();\n    }\n\n    @Test\n    void testGetTcpNoDelay() {\n        assertThat(connectionPoolConfig.getTcpNoDelay()).isTrue();\n    }\n\n    @Test\n    void testGetTcpNoDelayOverride() {\n        clientConfig.set(ConnectionPoolConfigImpl.TCP_NO_DELAY, true);\n        assertThat(connectionPoolConfig.getTcpNoDelay()).isTrue();\n    }\n\n    @Test\n    void testGetTcpReceiveBufferSize() {\n        assertThat(connectionPoolConfig.getTcpReceiveBufferSize())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_BUFFER_SIZE);\n    }\n\n    @Test\n    void testGetTcpReceiveBufferSizeOverride() {\n        clientConfig.set(IClientConfigKey.Keys.ReceiveBufferSize, 40000);\n        assertThat(connectionPoolConfig.getTcpReceiveBufferSize()).isEqualTo(40000);\n    }\n\n    @Test\n    void testGetTcpSendBufferSize() {\n        assertThat(connectionPoolConfig.getTcpSendBufferSize()).isEqualTo(ConnectionPoolConfigImpl.DEFAULT_BUFFER_SIZE);\n    }\n\n    @Test\n    void testGetTcpSendBufferSizeOverride() {\n        clientConfig.set(IClientConfigKey.Keys.SendBufferSize, 40000);\n        assertThat(connectionPoolConfig.getTcpSendBufferSize()).isEqualTo(40000);\n    }\n\n    @Test\n    void testGetNettyWriteBufferHighWaterMark() {\n        assertThat(connectionPoolConfig.getNettyWriteBufferHighWaterMark())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_WRITE_BUFFER_HIGH_WATER_MARK);\n    }\n\n    @Test\n    void testGetNettyWriteBufferHighWaterMarkOverride() {\n        clientConfig.set(ConnectionPoolConfigImpl.WRITE_BUFFER_HIGH_WATER_MARK, 40000);\n        assertThat(connectionPoolConfig.getNettyWriteBufferHighWaterMark()).isEqualTo(40000);\n    }\n\n    @Test\n    void testGetNettyWriteBufferLowWaterMark() {\n        assertThat(connectionPoolConfig.getNettyWriteBufferLowWaterMark())\n                .isEqualTo(ConnectionPoolConfigImpl.DEFAULT_WRITE_BUFFER_LOW_WATER_MARK);\n    }\n\n    @Test\n    void testGetNettyWriteBufferLowWaterMarkOverride() {\n        clientConfig.set(ConnectionPoolConfigImpl.WRITE_BUFFER_LOW_WATER_MARK, 10000);\n        assertThat(connectionPoolConfig.getNettyWriteBufferLowWaterMark()).isEqualTo(10000);\n    }\n\n    @Test\n    void testGetNettyAutoRead() {\n        assertThat(connectionPoolConfig.getNettyAutoRead()).isFalse();\n    }\n\n    @Test\n    void testGetNettyAutoReadOverride() {\n        clientConfig.set(ConnectionPoolConfigImpl.AUTO_READ, true);\n        assertThat(connectionPoolConfig.getNettyAutoRead()).isTrue();\n    }\n\n    @Test\n    void testIsSecure() {\n        assertThat(connectionPoolConfig.isSecure()).isFalse();\n    }\n\n    @Test\n    void testIsSecureOverride() {\n        clientConfig.set(IClientConfigKey.Keys.IsSecure, true);\n        assertThat(connectionPoolConfig.isSecure()).isTrue();\n    }\n\n    @Test\n    void testUseIPAddrForServer() {\n        assertThat(connectionPoolConfig.useIPAddrForServer()).isTrue();\n    }\n\n    @Test\n    void testUseIPAddrForServerOverride() {\n        clientConfig.set(IClientConfigKey.Keys.UseIPAddrForServer, false);\n        assertThat(connectionPoolConfig.useIPAddrForServer()).isFalse();\n    }\n\n    @Test\n    void testIsCloseOnCircuitBreakerEnabled() {\n        assertThat(connectionPoolConfig.isCloseOnCircuitBreakerEnabled()).isTrue();\n    }\n\n    @Test\n    void testIsCloseOnCircuitBreakerEnabledOverride() {\n        clientConfig.set(ConnectionPoolConfigImpl.CLOSE_ON_CIRCUIT_BREAKER, false);\n        assertThat(connectionPoolConfig.isCloseOnCircuitBreakerEnabled()).isFalse();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/ConnectionPoolMetricsTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.google.common.collect.Lists;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Tag;\nimport com.netflix.zuul.origins.OriginName;\nimport java.util.Map;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Justin Guerra\n * @since 2/28/25\n */\nclass ConnectionPoolMetricsTest {\n\n    @Test\n    public void validateMetricNames() {\n        DefaultRegistry registry = new DefaultRegistry();\n        OriginName originName = OriginName.fromVipAndApp(\"whatever\", \"whatever\");\n        ConnectionPoolMetrics metrics = ConnectionPoolMetrics.create(originName, registry);\n\n        validateCounter(\"connectionpool_create\", metrics.createNewConnCounter());\n        validateCounter(\"connectionpool_create_success\", metrics.createConnSucceededCounter());\n        validateCounter(\"connectionpool_create_fail\", metrics.createConnFailedCounter());\n\n        validateCounter(\"connectionpool_close\", metrics.closeConnCounter());\n        validateCounter(\"connectionpool_closeAbovePoolHighWaterMark\", metrics.closeAbovePoolHighWaterMarkCounter());\n        validateCounter(\"connectionpool_closeExpiredConnLifetime\", metrics.closeExpiredConnLifetimeCounter());\n        validateCounter(\"connectionpool_request\", metrics.requestConnCounter());\n        validateCounter(\"connectionpool_reuse\", metrics.reuseConnCounter());\n        validateCounter(\"connectionpool_release\", metrics.releaseConnCounter());\n        validateCounter(\"connectionpool_alreadyClosed\", metrics.alreadyClosedCounter());\n        validateCounter(\"connectionpool_fromPoolIsClosed\", metrics.connTakenFromPoolIsNotOpen());\n        validateCounter(\"connectionpool_maxConnsPerHostExceeded\", metrics.maxConnsPerHostExceededCounter());\n        validateCounter(\"connectionpool_closeWrtBusyConnCounter\", metrics.closeWrtBusyConnCounter());\n        validateCounter(\"connectionpool_closeCircuitBreaker\", metrics.circuitBreakerClose());\n\n        validateCounter(\"connectionpool_idle\", metrics.idleCounter());\n        validateCounter(\"connectionpool_inactive\", metrics.inactiveCounter());\n        validateCounter(\"connectionpool_error\", metrics.errorCounter());\n        validateCounter(\"connectionpool_headerClose\", metrics.headerCloseCounter());\n        validateCounter(\"connectionpool_sslClose\", metrics.sslCloseCompletionCounter());\n    }\n\n    private void validateCounter(String name, Counter counter) {\n        assertThat(counter.id().name()).isEqualTo(name);\n        Map<String, String> tags = Lists.newArrayList(counter.id().tags().iterator()).stream()\n                .collect(Collectors.toMap(Tag::key, Tag::value));\n        assertThat(tags.get(\"id\")).isEqualTo(\"whatever\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/DefaultClientChannelManagerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyBoolean;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.google.common.net.InetAddresses;\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.discovery.DynamicServerResolver;\nimport com.netflix.zuul.discovery.NonDiscoveryServer;\nimport com.netflix.zuul.netty.server.Server;\nimport com.netflix.zuul.origins.OriginName;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport io.netty.channel.DefaultEventLoop;\nimport io.netty.channel.EventLoop;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.channel.nio.NioEventLoopGroup;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.util.concurrent.Promise;\nimport java.net.InetSocketAddress;\nimport java.net.ServerSocket;\nimport java.net.SocketAddress;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\n\n/**\n * Tests for {@link DefaultClientChannelManager}.  These tests don't use IPv6 addresses because {@link InstanceInfo} is\n * not capable of expressing them.\n */\nclass DefaultClientChannelManagerTest {\n\n    @Test\n    void pickAddressInternal_discovery() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"app\")\n                .setHostName(\"192.168.0.1\")\n                .setPort(443)\n                .build();\n        DiscoveryResult s = DiscoveryResult.from(instanceInfo, true);\n\n        SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip(\"vip\"));\n\n        assertThat(addr).isInstanceOf(InetSocketAddress.class);\n        InetSocketAddress socketAddress = (InetSocketAddress) addr;\n        assertThat(socketAddress.getAddress()).isEqualTo(InetAddresses.forString(\"192.168.0.1\"));\n        assertThat(socketAddress.getPort()).isEqualTo(443);\n    }\n\n    @Test\n    void pickAddressInternal_discovery_unresolved() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"app\")\n                .setHostName(\"localhost\")\n                .setPort(443)\n                .build();\n        DiscoveryResult s = DiscoveryResult.from(instanceInfo, true);\n\n        SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip(\"vip\"));\n\n        assertThat(addr).isInstanceOf(InetSocketAddress.class);\n        InetSocketAddress socketAddress = (InetSocketAddress) addr;\n\n        assertThat(socketAddress.getAddress().isLoopbackAddress())\n                .as(socketAddress.toString())\n                .isTrue();\n        assertThat(socketAddress.getPort()).isEqualTo(443);\n    }\n\n    @Test\n    void pickAddressInternal_nonDiscovery() {\n        NonDiscoveryServer s = new NonDiscoveryServer(\"192.168.0.1\", 443);\n\n        SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip(\"vip\"));\n\n        assertThat(addr).isInstanceOf(InetSocketAddress.class);\n        InetSocketAddress socketAddress = (InetSocketAddress) addr;\n        assertThat(socketAddress.getAddress()).isEqualTo(InetAddresses.forString(\"192.168.0.1\"));\n        assertThat(socketAddress.getPort()).isEqualTo(443);\n    }\n\n    @Test\n    void pickAddressInternal_nonDiscovery_unresolved() {\n        NonDiscoveryServer s = new NonDiscoveryServer(\"localhost\", 443);\n\n        SocketAddress addr = DefaultClientChannelManager.pickAddressInternal(s, OriginName.fromVip(\"vip\"));\n\n        assertThat(addr).isInstanceOf(InetSocketAddress.class);\n        InetSocketAddress socketAddress = (InetSocketAddress) addr;\n\n        assertThat(socketAddress.getAddress().isLoopbackAddress())\n                .as(socketAddress.toString())\n                .isTrue();\n        assertThat(socketAddress.getPort()).isEqualTo(443);\n    }\n\n    @Test\n    void updateServerRefOnEmptyDiscoveryResult() {\n        OriginName originName = OriginName.fromVip(\"vip\", \"test\");\n        DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl();\n        DynamicServerResolver resolver = mock(DynamicServerResolver.class);\n\n        when(resolver.resolve(any())).thenReturn(DiscoveryResult.EMPTY);\n\n        DefaultClientChannelManager clientChannelManager =\n                new DefaultClientChannelManager(originName, clientConfig, resolver, new DefaultRegistry());\n\n        AtomicReference<DiscoveryResult> serverRef = new AtomicReference<>();\n\n        Promise<PooledConnection> promise = clientChannelManager.acquire(\n                new DefaultEventLoop(), null, CurrentPassport.create(), serverRef, new AtomicReference<>());\n\n        assertThat(promise.isSuccess()).isFalse();\n        assertThat(serverRef.get()).isSameAs(DiscoveryResult.EMPTY);\n    }\n\n    @Test\n    void updateServerRefOnValidDiscoveryResult() throws InterruptedException {\n        OriginName originName = OriginName.fromVip(\"vip\", \"test\");\n        DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl();\n\n        DynamicServerResolver resolver = mock(DynamicServerResolver.class);\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"server-equality\")\n                .setHostName(\"server-equality\")\n                .setPort(7777)\n                .build();\n        DiscoveryResult discoveryResult = DiscoveryResult.from(instanceInfo, false);\n\n        when(resolver.resolve(any())).thenReturn(discoveryResult);\n\n        DefaultClientChannelManager clientChannelManager =\n                new DefaultClientChannelManager(originName, clientConfig, resolver, new DefaultRegistry());\n\n        AtomicReference<DiscoveryResult> serverRef = new AtomicReference<>();\n\n        // TODO(argha-c) capture and assert on the promise once we have a dummy with ServerStats initialized\n        var unusedFuture = clientChannelManager.acquire(\n                new DefaultEventLoop(), null, CurrentPassport.create(), serverRef, new AtomicReference<>());\n\n        assertThat(serverRef.get()).isSameAs(discoveryResult);\n    }\n\n    @Test\n    void initializeAndShutdown() throws Exception {\n        String appName = \"app-\" + UUID.randomUUID();\n        ServerSocket serverSocket = new ServerSocket(0);\n        InetSocketAddress serverSocketAddress = (InetSocketAddress) serverSocket.getLocalSocketAddress();\n        String serverHostname = serverSocketAddress.getHostName();\n        int serverPort = serverSocketAddress.getPort();\n        OriginName originName = OriginName.fromVipAndApp(\"vip\", appName);\n        DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl();\n\n        Server.defaultOutboundChannelType.set(NioSocketChannel.class);\n\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(appName)\n                .setHostName(serverHostname)\n                .setPort(serverPort)\n                .build();\n        DiscoveryResult discoveryResult = DiscoveryResult.from(instanceInfo, true);\n\n        DynamicServerResolver resolver = mock(DynamicServerResolver.class);\n        when(resolver.resolve(any())).thenReturn(discoveryResult);\n        when(resolver.hasServers()).thenReturn(true);\n\n        Registry registry = new DefaultRegistry();\n        DefaultClientChannelManager clientChannelManager =\n                new DefaultClientChannelManager(originName, clientConfig, resolver, registry);\n\n        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(10);\n        EventLoop eventLoop = eventLoopGroup.next();\n\n        clientChannelManager.init();\n\n        assertThat(clientChannelManager.getConnsInUse()).isEqualTo(0);\n\n        Promise<PooledConnection> promiseConn = clientChannelManager.acquire(eventLoop);\n        promiseConn.await(200, TimeUnit.MILLISECONDS);\n        assertThat(promiseConn.isDone()).isTrue();\n        assertThat(promiseConn.isSuccess()).isTrue();\n\n        PooledConnection connection = promiseConn.get();\n        assertThat(connection.isActive()).isTrue();\n        assertThat(connection.isInPool()).isFalse();\n\n        assertThat(clientChannelManager.getConnsInUse()).isEqualTo(1);\n\n        boolean releaseResult = clientChannelManager.release(connection);\n        assertThat(releaseResult).isTrue();\n        assertThat(connection.isInPool()).isTrue();\n\n        assertThat(clientChannelManager.getConnsInUse()).isEqualTo(0);\n\n        clientChannelManager.shutdown();\n        serverSocket.close();\n    }\n\n    @Test\n    void closeOnCircuitBreaker() {\n        OriginName originName = OriginName.fromVipAndApp(\"whatever\", \"whatever\");\n        DefaultClientChannelManager manager =\n                new DefaultClientChannelManager(\n                        originName,\n                        new DefaultClientConfigImpl(),\n                        Mockito.mock(DynamicServerResolver.class),\n                        new NoopRegistry()) {\n                    @Override\n                    protected void updateServerStatsOnRelease(PooledConnection conn) {}\n                };\n\n        PooledConnection connection = mock(PooledConnection.class);\n        DiscoveryResult discoveryResult = mock(DiscoveryResult.class);\n        doReturn(discoveryResult).when(connection).getServer();\n        doReturn(true).when(discoveryResult).isCircuitBreakerTripped();\n        doReturn(new EmbeddedChannel()).when(connection).getChannel();\n\n        assertThat(manager.release(connection)).isFalse();\n        verify(connection).setInPool(false);\n        verify(connection).close();\n    }\n\n    @Test\n    void skipCloseOnCircuitBreaker() {\n        OriginName originName = OriginName.fromVipAndApp(\"whatever\", \"whatever\");\n        DefaultClientConfigImpl clientConfig = new DefaultClientConfigImpl();\n        DefaultClientChannelManager manager =\n                new DefaultClientChannelManager(\n                        originName, clientConfig, Mockito.mock(DynamicServerResolver.class), new NoopRegistry()) {\n                    @Override\n                    protected void updateServerStatsOnRelease(PooledConnection conn) {}\n\n                    @Override\n                    protected void releaseHandlers(PooledConnection conn) {}\n                };\n\n        PooledConnection connection = mock(PooledConnection.class);\n        DiscoveryResult discoveryResult = mock(DiscoveryResult.class);\n        doReturn(discoveryResult).when(connection).getServer();\n        doReturn(true).when(discoveryResult).isCircuitBreakerTripped();\n        doReturn(true).when(connection).isActive();\n        doReturn(new EmbeddedChannel()).when(connection).getChannel();\n\n        IConnectionPool connectionPool = mock(IConnectionPool.class);\n        doReturn(true).when(connectionPool).release(connection);\n        manager.getPerServerPools().put(discoveryResult, connectionPool);\n        clientConfig.set(ConnectionPoolConfigImpl.CLOSE_ON_CIRCUIT_BREAKER, false);\n\n        assertThat(manager.release(connection)).isTrue();\n        verify(connection, never()).setInPool(anyBoolean());\n        verify(connection, never()).close();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/PerServerConnectionPoolTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.Mockito.spy;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.client.config.IClientConfigKey.Keys;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Timer;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport com.netflix.zuul.netty.connectionpool.PooledConnection.ConnectionState;\nimport com.netflix.zuul.netty.server.Server;\nimport com.netflix.zuul.origins.OriginName;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.EventLoop;\nimport io.netty.channel.MultiThreadIoEventLoopGroup;\nimport io.netty.channel.MultithreadEventLoopGroup;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.channel.local.LocalAddress;\nimport io.netty.channel.local.LocalChannel;\nimport io.netty.channel.local.LocalIoHandler;\nimport io.netty.channel.local.LocalServerChannel;\nimport io.netty.handler.codec.DecoderException;\nimport io.netty.util.concurrent.Promise;\nimport java.util.Deque;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\nimport javax.net.ssl.SSLHandshakeException;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * @author Justin Guerra\n * @since 2/24/23\n */\nclass PerServerConnectionPoolTest {\n\n    private static LocalAddress LOCAL_ADDRESS;\n    private static MultithreadEventLoopGroup ORIGIN_EVENT_LOOP_GROUP;\n    private static MultithreadEventLoopGroup CLIENT_EVENT_LOOP_GROUP;\n    private static EventLoop CLIENT_EVENT_LOOP;\n    private static Class<? extends Channel> PREVIOUS_CHANNEL_TYPE;\n\n    @Mock\n    private ClientChannelManager channelManager;\n\n    private Registry registry;\n    private DiscoveryResult discoveryResult;\n    private DefaultClientConfigImpl clientConfig;\n    private ConnectionPoolConfig connectionPoolConfig;\n    private PerServerConnectionPool pool;\n\n    private Counter createNewConnCounter;\n    private Counter createConnSucceededCounter;\n    private Counter createConnFailedCounter;\n    private Counter requestConnCounter;\n    private Counter reuseConnCounter;\n    private Counter connTakenFromPoolIsNotOpen;\n    private Counter closeAboveHighWaterMarkCounter;\n    private Counter maxConnsPerHostExceededCounter;\n    private Timer connEstablishTimer;\n    private AtomicInteger connsInPool;\n    private AtomicInteger connsInUse;\n\n    @BeforeAll\n    @SuppressWarnings(\"deprecation\")\n    static void staticSetup() throws InterruptedException {\n        LOCAL_ADDRESS = new LocalAddress(UUID.randomUUID().toString());\n\n        CLIENT_EVENT_LOOP_GROUP = new MultiThreadIoEventLoopGroup(1, LocalIoHandler.newFactory());\n        CLIENT_EVENT_LOOP = CLIENT_EVENT_LOOP_GROUP.next();\n\n        ORIGIN_EVENT_LOOP_GROUP = new MultiThreadIoEventLoopGroup(1, LocalIoHandler.newFactory());\n        ServerBootstrap bootstrap = new ServerBootstrap()\n                .group(ORIGIN_EVENT_LOOP_GROUP)\n                .localAddress(LOCAL_ADDRESS)\n                .channel(LocalServerChannel.class)\n                .childHandler(new ChannelInitializer<LocalChannel>() {\n                    @Override\n                    protected void initChannel(LocalChannel ch) {}\n                });\n\n        bootstrap.bind().sync();\n        PREVIOUS_CHANNEL_TYPE = Server.defaultOutboundChannelType.getAndSet(LocalChannel.class);\n    }\n\n    @AfterAll\n    @SuppressWarnings(\"deprecation\")\n    static void staticCleanup() {\n        ORIGIN_EVENT_LOOP_GROUP.shutdownGracefully();\n        CLIENT_EVENT_LOOP_GROUP.shutdownGracefully();\n\n        if (PREVIOUS_CHANNEL_TYPE != null) {\n            Server.defaultOutboundChannelType.set(PREVIOUS_CHANNEL_TYPE);\n        }\n    }\n\n    @BeforeEach\n    void setup() {\n        MockitoAnnotations.openMocks(this);\n\n        registry = new DefaultRegistry();\n\n        int index = 0;\n        createNewConnCounter = registry.counter(\"fake_counter\" + index++);\n        createConnSucceededCounter = registry.counter(\"fake_counter\" + index++);\n        createConnFailedCounter = registry.counter(\"fake_counter\" + index++);\n        requestConnCounter = registry.counter(\"fake_counter\" + index++);\n        reuseConnCounter = registry.counter(\"fake_counter\" + index++);\n        connTakenFromPoolIsNotOpen = registry.counter(\"fake_counter\" + index++);\n        closeAboveHighWaterMarkCounter = registry.counter(\"fake_counter\" + index++);\n        maxConnsPerHostExceededCounter = registry.counter(\"fake_counter\" + index++);\n        connEstablishTimer = registry.timer(\"fake_timer\");\n        connsInPool = new AtomicInteger();\n        connsInUse = new AtomicInteger();\n\n        OriginName originName = OriginName.fromVipAndApp(\"whatever\", \"whatever-secure\");\n\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setIPAddr(\"175.45.176.0\")\n                .setPort(7001)\n                .setAppName(\"whatever\")\n                .build();\n        discoveryResult = DiscoveryResult.from(instanceInfo, true);\n\n        clientConfig = new DefaultClientConfigImpl();\n        connectionPoolConfig = spy(new ConnectionPoolConfigImpl(originName, clientConfig));\n\n        NettyClientConnectionFactory nettyConnectionFactory =\n                new NettyClientConnectionFactory(connectionPoolConfig, new ChannelInitializer<Channel>() {\n                    @Override\n                    protected void initChannel(Channel ch) {}\n                });\n\n        PooledConnectionFactory pooledConnectionFactory = this::newPooledConnection;\n\n        pool = new PerServerConnectionPool(\n                discoveryResult,\n                LOCAL_ADDRESS,\n                nettyConnectionFactory,\n                pooledConnectionFactory,\n                connectionPoolConfig,\n                clientConfig,\n                createNewConnCounter,\n                createConnSucceededCounter,\n                createConnFailedCounter,\n                requestConnCounter,\n                reuseConnCounter,\n                connTakenFromPoolIsNotOpen,\n                closeAboveHighWaterMarkCounter,\n                maxConnsPerHostExceededCounter,\n                connEstablishTimer,\n                connsInPool,\n                connsInUse);\n    }\n\n    @Test\n    void acquireNewConnectionHitsMaxConnections() {\n        CurrentPassport currentPassport = CurrentPassport.create();\n\n        clientConfig.set(Keys.MaxConnectionsPerHost, 1);\n        discoveryResult.incrementOpenConnectionsCount();\n\n        Promise<PooledConnection> promise = pool.acquire(CLIENT_EVENT_LOOP, currentPassport, new AtomicReference<>());\n\n        assertThat(promise.isSuccess()).isFalse();\n        assertThat(promise.cause() instanceof OriginConnectException).isTrue();\n        assertThat(maxConnsPerHostExceededCounter.count()).isEqualTo(1);\n    }\n\n    @Test\n    void acquireNewConnection() throws InterruptedException, ExecutionException {\n        CurrentPassport currentPassport = CurrentPassport.create();\n        Promise<PooledConnection> promise = pool.acquire(CLIENT_EVENT_LOOP, currentPassport, new AtomicReference<>());\n\n        PooledConnection connection = promise.sync().get();\n        assertThat(requestConnCounter.count()).isEqualTo(1);\n        assertThat(createNewConnCounter.count()).isEqualTo(1);\n        assertThat(currentPassport.findState(PassportState.ORIGIN_CH_CONNECTING))\n                .isNotNull();\n        assertThat(currentPassport.findState(PassportState.ORIGIN_CH_CONNECTED)).isNotNull();\n        assertThat(createConnSucceededCounter.count()).isEqualTo(1);\n        assertThat(connsInUse.get()).isEqualTo(1);\n\n        // check state on PooledConnection - not all thread safe\n        CLIENT_EVENT_LOOP\n                .submit(() -> {\n                    checkChannelState(connection, currentPassport, 1);\n                })\n                .sync();\n    }\n\n    @Test\n    void acquireConnectionFromPoolAndRelease() throws InterruptedException, ExecutionException {\n        CurrentPassport currentPassport = CurrentPassport.create();\n        Promise<PooledConnection> promise = pool.acquire(CLIENT_EVENT_LOOP, currentPassport, new AtomicReference<>());\n\n        PooledConnection connection = promise.sync().get();\n\n        CLIENT_EVENT_LOOP\n                .submit(() -> {\n                    pool.release(connection);\n                })\n                .sync();\n\n        assertThat(connsInPool.get()).isEqualTo(1);\n\n        CurrentPassport newPassport = CurrentPassport.create();\n        Promise<PooledConnection> secondPromise = pool.acquire(CLIENT_EVENT_LOOP, newPassport, new AtomicReference<>());\n\n        PooledConnection connection2 = secondPromise.sync().get();\n        assertThat(connection2).isEqualTo(connection);\n        assertThat(requestConnCounter.count()).isEqualTo(2);\n        assertThat(connsInPool.get()).isEqualTo(0);\n\n        CLIENT_EVENT_LOOP\n                .submit(() -> {\n                    checkChannelState(connection, newPassport, 2);\n                })\n                .sync();\n    }\n\n    @Test\n    void releaseFromPoolButAlreadyClosed() throws InterruptedException, ExecutionException {\n        CurrentPassport currentPassport = CurrentPassport.create();\n        Promise<PooledConnection> promise = pool.acquire(CLIENT_EVENT_LOOP, currentPassport, new AtomicReference<>());\n\n        PooledConnection connection = promise.sync().get();\n\n        CLIENT_EVENT_LOOP\n                .submit(() -> {\n                    pool.release(connection);\n                })\n                .sync();\n\n        // make the connection invalid\n        connection.getChannel().deregister().sync();\n\n        CurrentPassport newPassport = CurrentPassport.create();\n        Promise<PooledConnection> secondPromise = pool.acquire(CLIENT_EVENT_LOOP, newPassport, new AtomicReference<>());\n        PooledConnection connection2 = secondPromise.sync().get();\n\n        assertThat(connection).isNotEqualTo(connection2);\n        assertThat(connTakenFromPoolIsNotOpen.count()).isEqualTo(1);\n        assertThat(connsInPool.get()).isEqualTo(0);\n        assertThat(connection.getChannel().closeFuture().await(5, TimeUnit.SECONDS))\n                .as(\"Channel should have been closed by pool\")\n                .isTrue();\n    }\n\n    @Test\n    void releaseFromPoolAboveHighWaterMark() throws InterruptedException, ExecutionException {\n        CurrentPassport currentPassport = CurrentPassport.create();\n\n        clientConfig.set(ConnectionPoolConfigImpl.PER_SERVER_WATERLINE, 0);\n        Promise<PooledConnection> promise = pool.acquire(CLIENT_EVENT_LOOP, currentPassport, new AtomicReference<>());\n\n        PooledConnection connection = promise.sync().get();\n        CLIENT_EVENT_LOOP\n                .submit(() -> {\n                    assertThat(pool.release(connection)).isFalse();\n                    assertThat(closeAboveHighWaterMarkCounter.count()).isEqualTo(1);\n                    assertThat(connection.isInPool()).isFalse();\n                })\n                .sync();\n        assertThat(connection.getChannel().closeFuture().await(5, TimeUnit.SECONDS))\n                .as(\"connection should have been closed\")\n                .isTrue();\n    }\n\n    @Test\n    void releaseFromPoolWhileDraining() throws InterruptedException, ExecutionException {\n        Promise<PooledConnection> promise =\n                pool.acquire(CLIENT_EVENT_LOOP, CurrentPassport.create(), new AtomicReference<>());\n\n        PooledConnection connection = promise.sync().get();\n        pool.drain();\n\n        CLIENT_EVENT_LOOP\n                .submit(() -> {\n                    assertThat(connection.isInPool()).isFalse();\n                    assertThat(connection.getChannel().isActive())\n                            .as(\"connection was incorrectly closed during the drain\")\n                            .isTrue();\n                    pool.release(connection);\n                })\n                .sync();\n\n        assertThat(connection.getChannel().closeFuture().await(5, TimeUnit.SECONDS))\n                .as(\"connection should have been closed after release\")\n                .isTrue();\n    }\n\n    @Test\n    void acquireWhileDraining() {\n        pool.drain();\n        assertThat(pool.isAvailable()).isFalse();\n        assertThatThrownBy(() -> pool.acquire(CLIENT_EVENT_LOOP, CurrentPassport.create(), new AtomicReference<>()))\n                .isInstanceOf(IllegalStateException.class);\n    }\n\n    @Test\n    void gracefulDrain() {\n        EmbeddedChannel channel1 = new EmbeddedChannel();\n        EmbeddedChannel channel2 = new EmbeddedChannel();\n\n        PooledConnection connection1 = newPooledConnection(channel1);\n        PooledConnection connection2 = newPooledConnection(channel2);\n\n        Deque<PooledConnection> connections = pool.getPoolForEventLoop(channel1.eventLoop());\n        connections.add(connection1);\n        connections.add(connection2);\n        connsInPool.set(2);\n\n        assertThat(connsInPool.get()).isEqualTo(2);\n        pool.drainIdleConnectionsOnEventLoop(channel1.eventLoop());\n        channel1.runPendingTasks();\n\n        assertThat(connsInPool.get()).isEqualTo(0);\n        assertThat(connection1.getChannel().closeFuture().isSuccess()).isTrue();\n        assertThat(connection2.getChannel().closeFuture().isSuccess()).isTrue();\n    }\n\n    @Test\n    void handleConnectCompletionWithException() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        Promise<PooledConnection> promise = CLIENT_EVENT_LOOP.newPromise();\n        pool.handleConnectCompletion(\n                channel.newFailedFuture(new RuntimeException(\"runtime failure\")), promise, CurrentPassport.create());\n\n        assertThat(promise.isSuccess()).isFalse();\n        assertThat(promise.cause()).isNotNull();\n        assertThat(promise.cause()).isInstanceOf(OriginConnectException.class);\n        assertThat(promise.cause().getCause()).as(\"expect cause remains\").isInstanceOf(RuntimeException.class);\n    }\n\n    @Test\n    void handleConnectCompletionWithDecoderExceptionIsUnwrapped() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        Promise<PooledConnection> promise = CLIENT_EVENT_LOOP.newPromise();\n        pool.handleConnectCompletion(\n                channel.newFailedFuture(new DecoderException(new SSLHandshakeException(\"Invalid tls cert\"))),\n                promise,\n                CurrentPassport.create());\n\n        assertThat(promise.isSuccess()).isFalse();\n        assertThat(promise.cause()).isNotNull();\n        assertThat(promise.cause()).isInstanceOf(OriginConnectException.class);\n        assertThat(promise.cause().getCause())\n                .as(\"expect decoder exception is unwrapped\")\n                .isInstanceOf(SSLHandshakeException.class);\n    }\n\n    private void checkChannelState(PooledConnection connection, CurrentPassport passport, int expectedUsage) {\n        Channel channel = connection.getChannel();\n        assertThat(connection.getUsageCount()).isEqualTo(expectedUsage);\n        assertThat(CurrentPassport.fromChannelOrNull(channel)).isEqualTo(passport);\n        assertThat(connection.isReleased()).isFalse();\n        assertThat(connection.getConnectionState()).isEqualTo(ConnectionState.WRITE_BUSY);\n        assertThat(channel.pipeline().get(DefaultClientChannelManager.IDLE_STATE_HANDLER_NAME))\n                .isNull();\n    }\n\n    private PooledConnection newPooledConnection(Channel ch) {\n        return new PooledConnection(\n                ch,\n                discoveryResult,\n                channelManager,\n                registry.counter(\"fake_close_counter\"),\n                registry.counter(\"fake_close_wrt_counter\"));\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/connectionpool/PooledConnectionTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.connectionpool;\n\nimport static com.netflix.zuul.netty.connectionpool.PooledConnection.READ_TIMEOUT_HANDLER_NAME;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.zuul.discovery.DiscoveryResult;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport java.time.Duration;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * @author Justin Guerra\n * @since 10/3/25\n */\n@ExtendWith(MockitoExtension.class)\nclass PooledConnectionTest {\n\n    @Mock\n    private ClientChannelManager manager;\n\n    private EmbeddedChannel channel;\n    private PooledConnection connection;\n\n    @BeforeEach\n    public void setup() {\n        channel = new EmbeddedChannel();\n        NoopRegistry noopRegistry = new NoopRegistry();\n        connection = new PooledConnection(\n                channel,\n                DiscoveryResult.EMPTY,\n                manager,\n                noopRegistry.counter(\"close\"),\n                noopRegistry.counter(\"closeBusy\"));\n    }\n\n    @Test\n    void startReadTimeoutHandler() {\n        channel.pipeline()\n                .addLast(DefaultOriginChannelInitializer.ORIGIN_NETTY_LOGGER, new ChannelInboundHandlerAdapter());\n        connection.startReadTimeoutHandler(Duration.ofSeconds(1));\n        List<String> names = channel.pipeline().names();\n        assertThat(names.get(0)).isEqualTo(READ_TIMEOUT_HANDLER_NAME);\n        assertThat(names.get(1)).isEqualTo(DefaultOriginChannelInitializer.ORIGIN_NETTY_LOGGER);\n    }\n\n    @Test\n    void startReadTimeoutHandlerInactive() {\n        channel.close();\n        connection.startReadTimeoutHandler(Duration.ofSeconds(1));\n        assertThat(channel.pipeline().get(READ_TIMEOUT_HANDLER_NAME)).isNull();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/filter/BaseZuulFilterRunnerTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.doThrow;\n\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.FilterConstraint;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.BaseFilter;\nimport com.netflix.zuul.filters.FilterSyncType;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.util.HttpRequestBuilder;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.MultiThreadIoEventLoopGroup;\nimport io.netty.channel.MultithreadEventLoopGroup;\nimport io.netty.channel.local.LocalChannel;\nimport io.netty.channel.local.LocalIoHandler;\nimport io.netty.handler.codec.http.HttpContent;\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Function;\nimport lombok.NonNull;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport rx.Observable;\n\n/**\n * @author Justin Guerra\n * @since 5/20/25\n */\n@ExtendWith(MockitoExtension.class)\npublic class BaseZuulFilterRunnerTest {\n\n    @Mock\n    private FilterUsageNotifier notifier;\n\n    @Mock\n    private FilterRunner<ZuulMessage, ZuulMessage> nextStage;\n\n    private ZuulMessage message;\n    private TestBaseZuulFilterRunner runner;\n    private MultithreadEventLoopGroup group;\n    private TestResumer resumer;\n    private ErrorCapturingHandler errorCapturingHandler;\n    private TestConstraint constraint;\n\n    @BeforeEach\n    public void setup() {\n        constraint = new TestConstraint();\n        runner = new TestBaseZuulFilterRunner(FilterType.INBOUND, notifier, nextStage, new NoopRegistry());\n        SessionContext sessionContext = new SessionContext();\n        message = new HttpRequestBuilder(sessionContext).build();\n\n        group = new MultiThreadIoEventLoopGroup(1, LocalIoHandler.newFactory());\n        LocalChannel localChannel = new LocalChannel();\n        errorCapturingHandler = new ErrorCapturingHandler();\n        localChannel.pipeline().addLast(new ChannelInboundHandlerAdapter());\n        localChannel.pipeline().addLast(errorCapturingHandler);\n        group.register(localChannel);\n        ChannelHandlerContext context = localChannel.pipeline().context(ChannelInboundHandlerAdapter.class);\n        sessionContext.set(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, context);\n        resumer = new TestResumer(Function.identity());\n    }\n\n    @AfterEach\n    public void teardown() {\n        group.shutdownGracefully();\n    }\n\n    @Test\n    public void onCompleteCalledBeforeResume() throws Exception {\n        AsyncFilter asyncFilter = new AsyncFilter();\n        asyncFilter.output.set(message.clone());\n\n        resumer.validator = m -> {\n            assertThat(asyncFilter.getConcurrency())\n                    .as(\"concurrency should have been decremented before the filter chain was resumed\")\n                    .isEqualTo(0);\n            return m;\n        };\n\n        runner.executeFilter(asyncFilter, message);\n        ZuulMessage filteredMessage = resumer.future.get(5, TimeUnit.SECONDS);\n        // sanity check to verify the message was transformed by AsyncFilter\n        assertThat(filteredMessage).isNotSameAs(message);\n    }\n\n    @Test\n    public void onCompleteCalledWithNullMessage() throws Exception {\n        AsyncFilter asyncFilter = new AsyncFilter();\n        asyncFilter.output.set(null);\n\n        resumer.validator = m -> {\n            assertThat(asyncFilter.getConcurrency())\n                    .as(\"concurrency should have been decremented before the filter chain was resumed\")\n                    .isEqualTo(0);\n            assertThat(m).isNotNull();\n            return m;\n        };\n\n        runner.executeFilter(asyncFilter, message);\n        ZuulMessage filteredMessage = resumer.future.get(5, TimeUnit.SECONDS);\n        // sanity check to verify the message was transformed by AsyncFilter\n        assertThat(filteredMessage).isSameAs(message);\n    }\n\n    @Test\n    public void onCompleteThrows() {\n        doThrow(new RuntimeException()).when(notifier).notify(any(), any());\n        AsyncFilter asyncFilter = new AsyncFilter();\n        asyncFilter.output.set(message);\n\n        runner.executeFilter(asyncFilter, message);\n        Awaitility.await(\"request should have failed with an exception\")\n                .atMost(5, TimeUnit.SECONDS)\n                .until(() -> errorCapturingHandler.error.get() != null);\n    }\n\n    @Test\n    public void endpointFilterNeverSkipped() {\n        EndpointFilter filter = new EndpointFilter(false);\n        message.getContext().stopFilterProcessing();\n        message.getContext().cancel();\n        assertThat(runner.shouldSkipFilter(message, filter)).isFalse();\n    }\n\n    @Test\n    public void stopFilterProcessingSkipsFilter() {\n        InboundFilter filter = new InboundFilter(true);\n        message.getContext().stopFilterProcessing();\n        assertThat(runner.shouldSkipFilter(message, filter)).isTrue();\n    }\n\n    @Test\n    public void stopFilterProcessingDoesNotSkipWhenOverridden() {\n        OverrideStopFilter filter = new OverrideStopFilter(true);\n        message.getContext().stopFilterProcessing();\n        assertThat(runner.shouldSkipFilter(message, filter)).isFalse();\n    }\n\n    @Test\n    public void cancelledRequestSkipsFilter() {\n        InboundFilter filter = new InboundFilter(true);\n        message.getContext().cancel();\n        assertThat(runner.shouldSkipFilter(message, filter)).isTrue();\n    }\n\n    @Test\n    public void constrainedFilterSkipped() {\n        InboundFilter filter = new InboundFilter(true);\n        constraint.constrained = true;\n        assertThat(runner.shouldSkipFilter(message, filter)).isTrue();\n    }\n\n    @Test\n    public void shouldFilterFalseSkipsFilter() {\n        InboundFilter filter = new InboundFilter(false);\n        assertThat(runner.shouldSkipFilter(message, filter)).isTrue();\n    }\n\n    @Test\n    public void shouldFilterTrueDoesNotSkip() {\n        InboundFilter filter = new InboundFilter(true);\n        assertThat(runner.shouldSkipFilter(message, filter)).isFalse();\n    }\n\n    @Filter(type = FilterType.INBOUND, sync = FilterSyncType.ASYNC, order = 1)\n    private static class AsyncFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n\n        private AtomicReference<ZuulMessage> output = new AtomicReference<>();\n\n        @Override\n        public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n            return Observable.just(output.get());\n        }\n\n        @Override\n        public boolean shouldFilter(ZuulMessage msg) {\n            return true;\n        }\n    }\n\n    private static class TestResumer {\n\n        private final CompletableFuture<ZuulMessage> future;\n        private volatile Function<ZuulMessage, ZuulMessage> validator;\n\n        public TestResumer(Function<ZuulMessage, ZuulMessage> validator) {\n            this.future = new CompletableFuture<>();\n            this.validator = validator;\n        }\n\n        public void resume(ZuulMessage zuulMesg) {\n            try {\n                ZuulMessage zuulMessage = validator.apply(zuulMesg);\n                future.complete(zuulMessage);\n            } catch (Throwable e) {\n                future.completeExceptionally(e);\n            }\n        }\n    }\n\n    private class TestBaseZuulFilterRunner extends BaseZuulFilterRunner<ZuulMessage, ZuulMessage> {\n\n        protected TestBaseZuulFilterRunner(\n                FilterType filterType,\n                FilterUsageNotifier usageNotifier,\n                FilterRunner<ZuulMessage, ?> nextStage,\n                Registry registry) {\n            super(filterType, usageNotifier, nextStage, new FilterConstraints(List.of(constraint)), registry);\n        }\n\n        @Override\n        protected void resume(ZuulMessage zuulMesg) {\n            resumer.resume(zuulMesg);\n        }\n\n        @Override\n        public void filter(ZuulMessage zuulMesg) {}\n\n        @Override\n        public void filter(ZuulMessage zuulMesg, HttpContent chunk) {}\n    }\n\n    private static class ErrorCapturingHandler extends ChannelInboundHandlerAdapter {\n\n        private final AtomicReference<Throwable> error = new AtomicReference<>();\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            error.set(cause);\n        }\n    }\n\n    @Filter(\n            type = FilterType.INBOUND,\n            order = 1,\n            constraints = {TestConstraint.class})\n    private static class InboundFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n\n        private final boolean shouldFilter;\n\n        InboundFilter(boolean shouldFilter) {\n            this.shouldFilter = shouldFilter;\n        }\n\n        @Override\n        public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n            return Observable.just(input);\n        }\n\n        @Override\n        public boolean shouldFilter(ZuulMessage msg) {\n            return shouldFilter;\n        }\n    }\n\n    @Filter(type = FilterType.ENDPOINT, order = 1)\n    private static class EndpointFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n\n        private final boolean shouldFilter;\n\n        EndpointFilter(boolean shouldFilter) {\n            this.shouldFilter = shouldFilter;\n        }\n\n        @Override\n        public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n            return Observable.just(input);\n        }\n\n        @Override\n        public boolean shouldFilter(ZuulMessage msg) {\n            return shouldFilter;\n        }\n    }\n\n    @Filter(type = FilterType.INBOUND, order = 1)\n    private static class OverrideStopFilter extends BaseFilter<ZuulMessage, ZuulMessage> {\n\n        private final boolean shouldFilter;\n\n        OverrideStopFilter(boolean shouldFilter) {\n            this.shouldFilter = shouldFilter;\n        }\n\n        @Override\n        public boolean overrideStopFilterProcessing() {\n            return true;\n        }\n\n        @Override\n        public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n            return Observable.just(input);\n        }\n\n        @Override\n        public boolean shouldFilter(ZuulMessage msg) {\n            return shouldFilter;\n        }\n    }\n\n    private static class TestConstraint implements FilterConstraint {\n\n        boolean constrained;\n\n        @Override\n        public boolean isConstrained(@NonNull ZuulMessage msg) {\n            return constrained;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/filter/EventExecutorSchedulerTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.junit.jupiter.api.Assertions.fail;\n\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport io.netty.channel.EventLoop;\nimport io.netty.channel.MultiThreadIoEventLoopGroup;\nimport io.netty.channel.MultithreadEventLoopGroup;\nimport io.netty.channel.nio.NioIoHandler;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport rx.Scheduler.Worker;\nimport rx.Subscription;\nimport rx.functions.Action0;\n\n/**\n * @author Justin Guerra\n * @since 5/20/25\n */\nclass EventExecutorSchedulerTest {\n\n    private MultithreadEventLoopGroup group;\n    private EventLoop eventLoop;\n    private EventExecutorScheduler scheduler;\n\n    @BeforeEach\n    void setUp() {\n        group = new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());\n        eventLoop = group.next();\n        scheduler = new EventExecutorScheduler(eventLoop);\n    }\n\n    @AfterEach\n    void tearDown() {\n        group.shutdownGracefully();\n    }\n\n    @Test\n    void nullExecutor() {\n        assertThatThrownBy(() -> new EventExecutorScheduler(null)).isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void alreadyOnEventLoopImmediatelyRuns() {\n        Worker worker = scheduler.createWorker();\n\n        CountDownLatch latch = new CountDownLatch(1);\n        eventLoop.execute(() -> {\n            AtomicBoolean executed = new AtomicBoolean(false);\n            Action0 action = () -> {\n                executed.set(true);\n            };\n            Subscription schedule = worker.schedule(action);\n            assertThat(executed.get()).isTrue();\n            assertThat(schedule.isUnsubscribed()).isTrue();\n            latch.countDown();\n        });\n\n        if (!Uninterruptibles.awaitUninterruptibly(latch, 5, TimeUnit.SECONDS)) {\n            fail(\"schedule did not complete in a reasonable amount of time\");\n        }\n    }\n\n    @Test\n    void nonEventLoopThreadExecutedOnEventLoop() throws Exception {\n        AtomicBoolean inEventLoop = new AtomicBoolean(false);\n        CountDownLatch latch = new CountDownLatch(1);\n        Action0 action = () -> {\n            inEventLoop.set(eventLoop.inEventLoop());\n            if (!Uninterruptibles.awaitUninterruptibly(latch, 5, TimeUnit.SECONDS)) {\n                fail(\"action not completed in a reasonable amount of time\");\n            }\n        };\n\n        Worker worker = scheduler.createWorker();\n        Subscription schedule = worker.schedule(action);\n        assertThat(schedule.isUnsubscribed()).isFalse();\n\n        latch.countDown();\n        // ensure the original action finished\n        eventLoop.submit(() -> {}).get(5, TimeUnit.SECONDS);\n        assertThat(inEventLoop.get()).isTrue();\n        assertThat(schedule.isUnsubscribed()).isTrue();\n    }\n\n    @Test\n    void workerUnsubscribe() {\n        Worker worker = scheduler.createWorker();\n        assertThat(worker.isUnsubscribed()).isFalse();\n        worker.unsubscribe();\n        assertThat(worker.isUnsubscribed()).isTrue();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/filter/FilterConstraintsTest.java",
    "content": "/*\n * Copyright 2026 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport static org.assertj.core.api.AssertionsForClassTypes.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.zuul.FilterConstraint;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.util.HttpRequestBuilder;\nimport java.util.List;\nimport lombok.NonNull;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Justin Guerra\n * @since 2/13/26\n */\n@SuppressWarnings(\"unchecked\")\nclass FilterConstraintsTest {\n\n    private HttpRequestMessage request;\n    private FilterConstraints filterConstraints;\n\n    private boolean constraintAResult;\n    private boolean constraintBResult;\n\n    @BeforeEach\n    void setUp() {\n        request = new HttpRequestBuilder(new SessionContext()).build();\n        constraintAResult = false;\n        constraintBResult = false;\n        filterConstraints = new FilterConstraints(List.of(new ConstraintA(), new ConstraintB()));\n    }\n\n    @Test\n    void nullConstraintsFromFilter() {\n        ZuulFilter<?, ?> filter = mockFilter(null);\n        assertThat(filterConstraints.isConstrained(request, filter)).isFalse();\n    }\n\n    @Test\n    void emptyConstraintsFromFilter() {\n        ZuulFilter<?, ?> filter = mockFilter(new Class[0]);\n        assertThat(filterConstraints.isConstrained(request, filter)).isFalse();\n    }\n\n    @Test\n    void singleConstraintMatches() {\n        constraintAResult = true;\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintA.class});\n        assertThat(filterConstraints.isConstrained(request, filter)).isTrue();\n    }\n\n    @Test\n    void singleConstraintDoesNotMatch() {\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintA.class});\n        assertThat(filterConstraints.isConstrained(request, filter)).isFalse();\n    }\n\n    @Test\n    void multipleConstraintsFirstMatches() {\n        constraintAResult = true;\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintA.class, ConstraintB.class});\n        assertThat(filterConstraints.isConstrained(request, filter)).isTrue();\n    }\n\n    @Test\n    void multipleConstraintsSecondMatches() {\n        constraintBResult = true;\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintA.class, ConstraintB.class});\n        assertThat(filterConstraints.isConstrained(request, filter)).isTrue();\n    }\n\n    @Test\n    void multipleConstraintsNoneMatch() {\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintA.class, ConstraintB.class});\n        assertThat(filterConstraints.isConstrained(request, filter)).isFalse();\n    }\n\n    @Test\n    void constraintNotInLookup() {\n        FilterConstraints limited = new FilterConstraints(List.of(new ConstraintA()));\n        constraintBResult = true;\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintB.class});\n        assertThat(limited.isConstrained(request, filter)).isFalse();\n    }\n\n    @Test\n    public void constraintsCached() {\n        FilterConstraints limited = new FilterConstraints(List.of(new ConstraintA(), new ConstraintB()));\n        constraintAResult = false;\n        constraintBResult = true;\n        ZuulFilter<?, ?> filter = mockFilter(new Class[] {ConstraintA.class});\n        assertThat(limited.isConstrained(request, filter)).isFalse();\n\n        // this can't happen with a real annotation, but test the caching logic by changing the constraints. Because\n        // the initial constraints are cached the new one should be ignored\n        when(filter.constraints()).thenReturn(new Class[] {ConstraintA.class, ConstraintB.class});\n        assertThat(limited.isConstrained(request, filter)).isFalse();\n    }\n\n    private ZuulFilter<?, ?> mockFilter(Class<? extends FilterConstraint>[] constraints) {\n        ZuulFilter<?, ?> filter = mock(ZuulFilter.class);\n        when(filter.constraints()).thenReturn(constraints);\n        return filter;\n    }\n\n    private class ConstraintA implements FilterConstraint {\n        @Override\n        public boolean isConstrained(@NonNull ZuulMessage msg) {\n            return constraintAResult;\n        }\n    }\n\n    private class ConstraintB implements FilterConstraint {\n        @Override\n        public boolean isConstrained(@NonNull ZuulMessage msg) {\n            return constraintBResult;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/filter/ZuulEndPointRunnerTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.filter;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.FilterCategory;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.Endpoint;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.util.concurrent.ImmediateEventExecutor;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport rx.Observable;\n\nclass ZuulEndPointRunnerTest {\n    private static final String BASIC_ENDPOINT = \"basicEndpoint\";\n    private ZuulEndPointRunner endpointRunner;\n    private FilterUsageNotifier usageNotifier;\n    private FilterLoader filterLoader;\n    private FilterRunner filterRunner;\n    private Registry registry;\n    private HttpRequestMessageImpl request;\n\n    @BeforeEach\n    void beforeEachTest() {\n        usageNotifier = mock(FilterUsageNotifier.class);\n\n        filterLoader = mock(FilterLoader.class);\n        when(filterLoader.getFilterByNameAndType(ZuulEndPointRunner.DEFAULT_ERROR_ENDPOINT.get(), FilterType.ENDPOINT))\n                .thenReturn(new ErrorEndpoint());\n        when(filterLoader.getFilterByNameAndType(BASIC_ENDPOINT, FilterType.ENDPOINT))\n                .thenReturn(new BasicEndpoint());\n\n        filterRunner = mock(FilterRunner.class);\n        registry = new NoopRegistry();\n        endpointRunner = new ZuulEndPointRunner(\n                usageNotifier, filterLoader, filterRunner, new FilterConstraints(List.of()), registry);\n\n        SessionContext context = new SessionContext();\n        Headers headers = new Headers();\n        ChannelHandlerContext chc = mock(ChannelHandlerContext.class);\n        when(chc.executor()).thenReturn(ImmediateEventExecutor.INSTANCE);\n        context.put(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, chc);\n        request = new HttpRequestMessageImpl(\n                context,\n                \"http\",\n                \"GET\",\n                \"/foo/bar\",\n                new HttpQueryParams(),\n                headers,\n                \"127.0.0.1\",\n                \"http\",\n                8080,\n                \"server123\");\n        request.storeInboundRequest();\n    }\n\n    @Test\n    void nonErrorEndpoint() {\n        request.getContext().setShouldSendErrorResponse(false);\n        request.getContext().setEndpoint(BASIC_ENDPOINT);\n        assertThat(request.getContext().get(CommonContextKeys.ZUUL_ENDPOINT)).isNull();\n        endpointRunner.filter(request);\n        ZuulFilter<HttpRequestMessage, HttpResponseMessage> filter =\n                request.getContext().get(CommonContextKeys.ZUUL_ENDPOINT);\n        assertThat(filter instanceof BasicEndpoint).isTrue();\n\n        ArgumentCaptor<HttpResponseMessage> captor = ArgumentCaptor.forClass(HttpResponseMessage.class);\n        verify(filterRunner, times(1)).filter(captor.capture());\n        HttpResponseMessage capturedResponseMessage = captor.getValue();\n        assertThat(request.getInboundRequest()).isEqualTo(capturedResponseMessage.getInboundRequest());\n        assertThat(capturedResponseMessage.getContext().getEndpoint()).isEqualTo(\"basicEndpoint\");\n        assertThat(capturedResponseMessage.getContext().errorResponseSent()).isFalse();\n    }\n\n    @Test\n    void errorEndpoint() {\n        request.getContext().setShouldSendErrorResponse(true);\n        assertThat(request.getContext().get(CommonContextKeys.ZUUL_ENDPOINT)).isNull();\n        endpointRunner.filter(request);\n        ZuulFilter filter = request.getContext().get(CommonContextKeys.ZUUL_ENDPOINT);\n        assertThat(filter instanceof ErrorEndpoint).isTrue();\n\n        ArgumentCaptor<HttpResponseMessage> captor = ArgumentCaptor.forClass(HttpResponseMessage.class);\n        verify(filterRunner, times(1)).filter(captor.capture());\n        HttpResponseMessage capturedResponseMessage = captor.getValue();\n        assertThat(request.getInboundRequest()).isEqualTo(capturedResponseMessage.getInboundRequest());\n        assertThat(capturedResponseMessage.getContext().getEndpoint()).isNull();\n        assertThat(capturedResponseMessage.getContext().errorResponseSent()).isTrue();\n    }\n\n    @Filter(order = 10, type = FilterType.ENDPOINT)\n    static class ErrorEndpoint extends Endpoint {\n        @Override\n        public FilterCategory category() {\n            return super.category();\n        }\n\n        @Override\n        public Observable applyAsync(ZuulMessage input) {\n            return Observable.just(buildHttpResponseMessage(input));\n        }\n    }\n\n    @Filter(order = 20, type = FilterType.ENDPOINT)\n    static class BasicEndpoint extends Endpoint {\n\n        @Override\n        public FilterCategory category() {\n            return super.category();\n        }\n\n        @Override\n        public Observable applyAsync(ZuulMessage input) {\n            return Observable.just(buildHttpResponseMessage(input));\n        }\n    }\n\n    private static HttpResponseMessage buildHttpResponseMessage(ZuulMessage request) {\n        return new HttpResponseMessageImpl(request.getContext(), (HttpRequestMessage) request, 200);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/filter/ZuulFilterChainRunnerTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.netty.filter;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.verifyNoMoreInteractions;\n\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.ExecutionStatus;\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.filters.http.HttpInboundFilter;\nimport com.netflix.zuul.filters.http.HttpOutboundFilter;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport rx.Observable;\n\nclass ZuulFilterChainRunnerTest {\n    private HttpRequestMessage request;\n    private HttpResponseMessage response;\n\n    @BeforeEach\n    void before() {\n        SessionContext context = new SessionContext();\n        Headers headers = new Headers();\n\n        EmbeddedChannel channel = new EmbeddedChannel(new ChannelInboundHandlerAdapter());\n        ChannelHandlerContext ctx = channel.pipeline().context(ChannelInboundHandlerAdapter.class);\n        context.put(CommonContextKeys.NETTY_SERVER_CHANNEL_HANDLER_CONTEXT, ctx);\n        request = new HttpRequestMessageImpl(\n                context,\n                \"http\",\n                \"GET\",\n                \"/foo/bar\",\n                new HttpQueryParams(),\n                headers,\n                \"127.0.0.1\",\n                \"http\",\n                8080,\n                \"server123\");\n        request.storeInboundRequest();\n        response = new HttpResponseMessageImpl(context, request, 200);\n    }\n\n    @Test\n    void testInboundFilterChain() {\n        SimpleInboundFilter inbound1 = spy(new SimpleInboundFilter(true));\n        SimpleInboundFilter inbound2 = spy(new SimpleInboundFilter(false));\n\n        ZuulFilter[] filters = new ZuulFilter[] {inbound1, inbound2};\n\n        FilterUsageNotifier notifier = mock(FilterUsageNotifier.class);\n        Registry registry = mock(Registry.class);\n\n        ZuulFilterChainRunner runner =\n                new ZuulFilterChainRunner(filters, notifier, new FilterConstraints(List.of()), registry);\n\n        runner.filter(request);\n\n        verify(inbound1, times(1)).applyAsync(eq(request));\n        verify(inbound2, never()).applyAsync(eq(request));\n\n        verify(notifier).notify(eq(inbound1), eq(ExecutionStatus.SUCCESS));\n        verify(notifier).notify(eq(inbound2), eq(ExecutionStatus.SKIPPED));\n        verifyNoMoreInteractions(notifier);\n    }\n\n    @Test\n    void testOutboundFilterChain() {\n        SimpleOutboundFilter outbound1 = spy(new SimpleOutboundFilter(true));\n        SimpleOutboundFilter outbound2 = spy(new SimpleOutboundFilter(false));\n\n        ZuulFilter[] filters = new ZuulFilter[] {outbound1, outbound2};\n\n        FilterUsageNotifier notifier = mock(FilterUsageNotifier.class);\n        Registry registry = mock(Registry.class);\n\n        ZuulFilterChainRunner runner =\n                new ZuulFilterChainRunner(filters, notifier, new FilterConstraints(List.of()), registry);\n\n        runner.filter(response);\n\n        verify(outbound1, times(1)).applyAsync(any());\n        verify(outbound2, never()).applyAsync(any());\n\n        verify(notifier).notify(eq(outbound1), eq(ExecutionStatus.SUCCESS));\n        verify(notifier).notify(eq(outbound2), eq(ExecutionStatus.SKIPPED));\n        verifyNoMoreInteractions(notifier);\n    }\n\n    @Filter(order = 1)\n    class SimpleInboundFilter extends HttpInboundFilter {\n        private final boolean shouldFilter;\n\n        public SimpleInboundFilter(boolean shouldFilter) {\n            this.shouldFilter = shouldFilter;\n        }\n\n        @Override\n        public int filterOrder() {\n            return 0;\n        }\n\n        @Override\n        public FilterType filterType() {\n            return FilterType.INBOUND;\n        }\n\n        @Override\n        public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage input) {\n            return Observable.just(input);\n        }\n\n        @Override\n        public boolean shouldFilter(HttpRequestMessage msg) {\n            return this.shouldFilter;\n        }\n    }\n\n    @Filter(order = 1)\n    class SimpleOutboundFilter extends HttpOutboundFilter {\n        private final boolean shouldFilter;\n\n        public SimpleOutboundFilter(boolean shouldFilter) {\n            this.shouldFilter = shouldFilter;\n        }\n\n        @Override\n        public int filterOrder() {\n            return 0;\n        }\n\n        @Override\n        public FilterType filterType() {\n            return FilterType.OUTBOUND;\n        }\n\n        @Override\n        public Observable<HttpResponseMessage> applyAsync(HttpResponseMessage input) {\n            return Observable.just(input);\n        }\n\n        @Override\n        public boolean shouldFilter(HttpResponseMessage msg) {\n            return this.shouldFilter;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/insights/ServerStateHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.insights;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Id;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.netty.insights.ServerStateHandler.InboundHandler;\nimport com.netflix.zuul.netty.server.http2.DummyChannelHandler;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass ServerStateHandlerTest {\n\n    private Registry registry;\n\n    private Id connectsId;\n    private Id errorsId;\n    private Id closesId;\n\n    final String listener = \"test-conn-throttled\";\n\n    @BeforeEach\n    void init() {\n        registry = new DefaultRegistry();\n\n        connectsId = registry.createId(\"server.connections.connect\").withTags(\"id\", listener);\n        closesId = registry.createId(\"server.connections.close\").withTags(\"id\", listener);\n        errorsId = registry.createId(\"server.connections.errors\").withTags(\"id\", listener);\n    }\n\n    @Test\n    void verifyConnMetrics() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addLast(new DummyChannelHandler());\n        channel.pipeline().addLast(new InboundHandler(registry, listener));\n\n        Counter connects = (Counter) registry.get(connectsId);\n        Counter closes = (Counter) registry.get(closesId);\n        Counter errors = (Counter) registry.get(errorsId);\n\n        // Connects X 3\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelActive();\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelActive();\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelActive();\n\n        assertThat(connects.count()).isEqualTo(3);\n\n        // Closes X 1\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelInactive();\n\n        assertThat(connects.count()).isEqualTo(3);\n        assertThat(closes.count()).isEqualTo(1);\n        assertThat(errors.count()).isEqualTo(0);\n    }\n\n    @Test\n    void setPassportStateOnConnect() {\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addLast(new DummyChannelHandler());\n        channel.pipeline().addLast(new InboundHandler(registry, listener));\n\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelActive();\n\n        assertThat(CurrentPassport.fromChannel(channel).getState()).isEqualTo(PassportState.SERVER_CH_ACTIVE);\n    }\n\n    @Test\n    void setPassportStateOnDisconnect() {\n        EmbeddedChannel channel = new EmbeddedChannel();\n        channel.pipeline().addLast(new DummyChannelHandler());\n        channel.pipeline().addLast(new InboundHandler(registry, listener));\n\n        channel.pipeline().context(DummyChannelHandler.class).fireChannelInactive();\n\n        assertThat(CurrentPassport.fromChannel(channel).getState()).isEqualTo(PassportState.SERVER_CH_INACTIVE);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/BaseZuulChannelInitializerTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.PerEventLoopMetricsChannelHandler;\nimport com.netflix.netty.common.proxyprotocol.ElbProxyProtocolChannelHandler;\nimport com.netflix.netty.common.throttle.MaxInboundConnectionsHandler;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.zuul.netty.insights.ServerStateHandler;\nimport com.netflix.zuul.netty.ratelimiting.NullChannelHandlerProvider;\nimport io.netty.channel.Channel;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.channel.group.DefaultChannelGroup;\nimport io.netty.util.concurrent.GlobalEventExecutor;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link BaseZuulChannelInitializer}.\n */\nclass BaseZuulChannelInitializerTest {\n\n    @Test\n    void tcpHandlersAdded() {\n        ChannelConfig channelConfig = new ChannelConfig();\n        ChannelConfig channelDependencies = new ChannelConfig();\n        channelDependencies.set(ZuulDependencyKeys.registry, new NoopRegistry());\n        channelDependencies.set(\n                ZuulDependencyKeys.rateLimitingChannelHandlerProvider, new NullChannelHandlerProvider());\n        channelDependencies.set(\n                ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider, new NullChannelHandlerProvider());\n        ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);\n        BaseZuulChannelInitializer init =\n                new BaseZuulChannelInitializer(\"1234\", channelConfig, channelDependencies, channelGroup) {\n\n                    @Override\n                    protected void initChannel(Channel ch) {}\n                };\n        EmbeddedChannel channel = new EmbeddedChannel();\n\n        init.addTcpRelatedHandlers(channel.pipeline());\n\n        assertThat(channel.pipeline().context(SourceAddressChannelHandler.class))\n                .isNotNull();\n        assertThat(channel.pipeline().context(PerEventLoopMetricsChannelHandler.Connections.class))\n                .isNotNull();\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNotNull();\n        assertThat(channel.pipeline().context(MaxInboundConnectionsHandler.class))\n                .isNotNull();\n    }\n\n    @Test\n    void tcpHandlersAdded_withProxyProtocol() {\n        ChannelConfig channelConfig = new ChannelConfig();\n        channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n        ChannelConfig channelDependencies = new ChannelConfig();\n        channelDependencies.set(ZuulDependencyKeys.registry, new NoopRegistry());\n        channelDependencies.set(\n                ZuulDependencyKeys.rateLimitingChannelHandlerProvider, new NullChannelHandlerProvider());\n        channelDependencies.set(\n                ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider, new NullChannelHandlerProvider());\n        ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);\n        BaseZuulChannelInitializer init =\n                new BaseZuulChannelInitializer(\"1234\", channelConfig, channelDependencies, channelGroup) {\n\n                    @Override\n                    protected void initChannel(Channel ch) {}\n                };\n        EmbeddedChannel channel = new EmbeddedChannel();\n\n        init.addTcpRelatedHandlers(channel.pipeline());\n\n        assertThat(channel.pipeline().context(SourceAddressChannelHandler.class))\n                .isNotNull();\n        assertThat(channel.pipeline().context(PerEventLoopMetricsChannelHandler.Connections.class))\n                .isNotNull();\n        assertThat(channel.pipeline().context(ElbProxyProtocolChannelHandler.NAME))\n                .isNotNull();\n        assertThat(channel.pipeline().context(MaxInboundConnectionsHandler.class))\n                .isNotNull();\n    }\n\n    @Test\n    void serverStateHandlerAdded() {\n        ChannelConfig channelConfig = new ChannelConfig();\n        ChannelConfig channelDependencies = new ChannelConfig();\n        channelDependencies.set(ZuulDependencyKeys.registry, new NoopRegistry());\n        channelDependencies.set(\n                ZuulDependencyKeys.rateLimitingChannelHandlerProvider, new NullChannelHandlerProvider());\n        channelDependencies.set(\n                ZuulDependencyKeys.sslClientCertCheckChannelHandlerProvider, new NullChannelHandlerProvider());\n\n        ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);\n        BaseZuulChannelInitializer init =\n                new BaseZuulChannelInitializer(\"1234\", channelConfig, channelDependencies, channelGroup) {\n\n                    @Override\n                    protected void initChannel(Channel ch) {}\n                };\n        EmbeddedChannel channel = new EmbeddedChannel();\n\n        init.addPassportHandler(channel.pipeline());\n\n        assertThat(channel.pipeline().context(ServerStateHandler.InboundHandler.class))\n                .isNotNull();\n        assertThat(channel.pipeline().context(ServerStateHandler.OutboundHandler.class))\n                .isNotNull();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientConnectionsShutdownTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isA;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\n\nimport com.netflix.appinfo.InstanceInfo.InstanceStatus;\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.discovery.EurekaEventListener;\nimport com.netflix.discovery.StatusChangeEvent;\nimport com.netflix.zuul.netty.server.ClientConnectionsShutdown.ShutdownType;\nimport io.netty.bootstrap.Bootstrap;\nimport io.netty.bootstrap.ServerBootstrap;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.DefaultEventLoop;\nimport io.netty.channel.MultiThreadIoEventLoopGroup;\nimport io.netty.channel.MultithreadEventLoopGroup;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.channel.group.DefaultChannelGroup;\nimport io.netty.channel.local.LocalAddress;\nimport io.netty.channel.local.LocalChannel;\nimport io.netty.channel.local.LocalIoHandler;\nimport io.netty.channel.local.LocalServerChannel;\nimport io.netty.util.concurrent.EventExecutor;\nimport io.netty.util.concurrent.Promise;\nimport java.util.UUID;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.ArgumentMatchers;\nimport org.mockito.Mockito;\n\n/**\n * @author Justin Guerra\n * @since 2/28/23\n */\nclass ClientConnectionsShutdownTest {\n\n    // using LocalChannels instead of EmbeddedChannels to re-create threading behavior in an actual deployment\n    private static LocalAddress LOCAL_ADDRESS;\n    private static MultithreadEventLoopGroup SERVER_EVENT_LOOP;\n    private static MultithreadEventLoopGroup CLIENT_EVENT_LOOP;\n    private static DefaultEventLoop EVENT_LOOP;\n\n    @BeforeAll\n    static void staticSetup() throws InterruptedException {\n        LOCAL_ADDRESS = new LocalAddress(UUID.randomUUID().toString());\n\n        CLIENT_EVENT_LOOP = new MultiThreadIoEventLoopGroup(4, LocalIoHandler.newFactory());\n        SERVER_EVENT_LOOP = new MultiThreadIoEventLoopGroup(4, LocalIoHandler.newFactory());\n        ServerBootstrap serverBootstrap = new ServerBootstrap()\n                .group(SERVER_EVENT_LOOP)\n                .localAddress(LOCAL_ADDRESS)\n                .channel(LocalServerChannel.class)\n                .childHandler(new ChannelInitializer<LocalChannel>() {\n                    @Override\n                    protected void initChannel(LocalChannel ch) {}\n                });\n\n        serverBootstrap.bind().sync();\n        EVENT_LOOP = new DefaultEventLoop(Executors.newSingleThreadExecutor());\n    }\n\n    @AfterAll\n    static void staticCleanup() {\n        CLIENT_EVENT_LOOP.shutdownGracefully();\n        SERVER_EVENT_LOOP.shutdownGracefully();\n        EVENT_LOOP.shutdownGracefully();\n    }\n\n    private ChannelGroup channels;\n    private ClientConnectionsShutdown shutdown;\n\n    @BeforeEach\n    void setup() {\n        channels = new DefaultChannelGroup(EVENT_LOOP);\n        shutdown = new ClientConnectionsShutdown(channels, EVENT_LOOP, null);\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    void discoveryShutdown() {\n        String configName = \"server.outofservice.connections.shutdown\";\n        AbstractConfiguration configuration = ConfigurationManager.getConfigInstance();\n\n        try {\n            configuration.setProperty(configName, \"true\");\n            EurekaClient eureka = Mockito.mock(EurekaClient.class);\n            EventExecutor executor = Mockito.mock(EventExecutor.class);\n\n            ArgumentCaptor<EurekaEventListener> captor = ArgumentCaptor.forClass(EurekaEventListener.class);\n            shutdown = spy(new ClientConnectionsShutdown(channels, executor, eureka));\n            verify(eureka).registerEventListener(captor.capture());\n            doReturn(Mockito.mock(Promise.class)).when(shutdown).gracefullyShutdownClientChannels();\n\n            EurekaEventListener listener = captor.getValue();\n\n            listener.onEvent(new StatusChangeEvent(InstanceStatus.UP, InstanceStatus.DOWN));\n            verify(executor).schedule(ArgumentMatchers.isA(Callable.class), anyLong(), eq(TimeUnit.MILLISECONDS));\n\n            Mockito.reset(executor);\n            listener.onEvent(new StatusChangeEvent(InstanceStatus.UP, InstanceStatus.OUT_OF_SERVICE));\n            verify(executor).schedule(ArgumentMatchers.isA(Callable.class), anyLong(), eq(TimeUnit.MILLISECONDS));\n\n            Mockito.reset(executor);\n            listener.onEvent(new StatusChangeEvent(InstanceStatus.STARTING, InstanceStatus.OUT_OF_SERVICE));\n            verify(executor, never())\n                    .schedule(ArgumentMatchers.isA(Callable.class), anyLong(), eq(TimeUnit.MILLISECONDS));\n        } finally {\n            configuration.setProperty(configName, \"false\");\n        }\n    }\n\n    @Test\n    void allConnectionsGracefullyClosed() throws Exception {\n        createChannels(100);\n        Promise<Void> promise = shutdown.gracefullyShutdownClientChannels();\n        Promise<Object> testPromise = EVENT_LOOP.newPromise();\n\n        promise.addListener(future -> {\n            if (future.isSuccess()) {\n                testPromise.setSuccess(null);\n            } else {\n                testPromise.setFailure(future.cause());\n            }\n        });\n\n        channels.forEach(Channel::close);\n        testPromise.await(10, TimeUnit.SECONDS);\n        assertThat(channels.isEmpty()).isTrue();\n    }\n\n    @Test\n    void connectionNeedsToBeForceClosed() throws Exception {\n        String configName = \"server.outofservice.close.timeout\";\n        AbstractConfiguration configuration = ConfigurationManager.getConfigInstance();\n\n        try {\n            configuration.setProperty(configName, \"0\");\n            createChannels(10);\n            shutdown.gracefullyShutdownClientChannels().await(10, TimeUnit.SECONDS);\n\n            assertThat(channels.isEmpty())\n                    .as(\"All channels in group should have been force closed after the timeout was triggered\")\n                    .isTrue();\n        } finally {\n            configuration.setProperty(configName, \"30\");\n        }\n    }\n\n    @Test\n    void connectionNeedsToBeForceClosedAndOneChannelThrowsAnException() throws Exception {\n        String configName = \"server.outofservice.close.timeout\";\n        AbstractConfiguration configuration = ConfigurationManager.getConfigInstance();\n\n        try {\n            configuration.setProperty(configName, \"0\");\n            createChannels(5);\n            ChannelFuture connect = new Bootstrap()\n                    .group(CLIENT_EVENT_LOOP)\n                    .channel(LocalChannel.class)\n                    .handler(new ChannelInitializer<>() {\n                        @Override\n                        protected void initChannel(Channel ch) {\n                            ch.pipeline().addLast(new ChannelOutboundHandlerAdapter() {\n                                @Override\n                                public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {\n                                    throw new Exception();\n                                }\n                            });\n                        }\n                    })\n                    .remoteAddress(LOCAL_ADDRESS)\n                    .connect()\n                    .sync();\n            channels.add(connect.channel());\n\n            boolean await = shutdown.gracefullyShutdownClientChannels().await(10, TimeUnit.SECONDS);\n            assertThat(await)\n                    .as(\"the promise should finish even if a channel failed to close\")\n                    .isTrue();\n            assertThat(channels.size())\n                    .as(\"all other channels should have been closed\")\n                    .isEqualTo(1);\n        } finally {\n            configuration.setProperty(configName, \"30\");\n        }\n    }\n\n    @Test\n    void connectionsNotForceClosed() throws Exception {\n        String configName = \"server.outofservice.close.timeout\";\n        AbstractConfiguration configuration = ConfigurationManager.getConfigInstance();\n\n        DefaultEventLoop eventLoop = spy(EVENT_LOOP);\n        shutdown = new ClientConnectionsShutdown(channels, eventLoop, null);\n\n        try {\n            configuration.setProperty(configName, \"0\");\n            createChannels(10);\n            Promise<Void> promise = shutdown.gracefullyShutdownClientChannels(ShutdownType.OUT_OF_SERVICE);\n            verify(eventLoop, never()).schedule(isA(Runnable.class), anyLong(), isA(TimeUnit.class));\n            channels.forEach(Channel::close);\n\n            promise.await(10, TimeUnit.SECONDS);\n            assertThat(channels.isEmpty())\n                    .as(\"All channels in group should have been closed\")\n                    .isTrue();\n        } finally {\n            configuration.setProperty(configName, \"30\");\n        }\n    }\n\n    @Test\n    public void shutdownTypeForwardedToFlag() throws InterruptedException {\n        shutdown = spy(shutdown);\n        doNothing().when(shutdown).flagChannelForClose(any(), any());\n        createChannels(1);\n        Channel channel = channels.iterator().next();\n        for (ShutdownType type : ShutdownType.values()) {\n            shutdown.gracefullyShutdownClientChannels(type);\n            verify(shutdown).flagChannelForClose(channel, type);\n        }\n\n        channels.close().await(5, TimeUnit.SECONDS);\n    }\n\n    private void createChannels(int numChannels) throws InterruptedException {\n        ChannelInitializer<LocalChannel> initializer = new ChannelInitializer<>() {\n            @Override\n            protected void initChannel(LocalChannel ch) {}\n        };\n\n        for (int i = 0; i < numChannels; ++i) {\n            ChannelFuture connect = new Bootstrap()\n                    .group(CLIENT_EVENT_LOOP)\n                    .channel(LocalChannel.class)\n                    .handler(initializer)\n                    .remoteAddress(LOCAL_ADDRESS)\n                    .connect()\n                    .sync();\n\n            channels.add(connect.channel());\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientRequestReceiverTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.google.common.net.InetAddresses;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteEvent;\nimport com.netflix.netty.common.HttpLifecycleChannelHandler.CompleteReason;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport com.netflix.zuul.netty.insights.PassportLoggingHandler;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultFullHttpRequest;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpRequest;\nimport io.netty.handler.codec.http.HttpRequestEncoder;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.codec.http.HttpVersion;\nimport java.net.InetSocketAddress;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.checkerframework.checker.nullness.qual.NonNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Unit tests for {@link ClientRequestReceiver}.\n */\n@ExtendWith(MockitoExtension.class)\nclass ClientRequestReceiverTest {\n\n    @Test\n    void proxyProtocol_portSetInSessionContextAndInHttpRequestMessageImpl() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        InetSocketAddress hapmDestinationAddress = new InetSocketAddress(InetAddresses.forString(\"2.2.2.2\"), 444);\n        channel.attr(SourceAddressChannelHandler.ATTR_PROXY_PROTOCOL_DESTINATION_ADDRESS)\n                .set(hapmDestinationAddress);\n        channel.attr(SourceAddressChannelHandler.ATTR_LOCAL_ADDR).set(hapmDestinationAddress);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(\n                    new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n        assertThat(hapmDestinationAddress.getPort())\n                .isEqualTo((int) result.getClientDestinationPort().get());\n        int destinationPort = ((InetSocketAddress)\n                        result.getContext().get(CommonContextKeys.PROXY_PROTOCOL_DESTINATION_ADDRESS))\n                .getPort();\n        assertThat(destinationPort).isEqualTo(444);\n        assertThat(result.getOriginalPort()).isEqualTo(444);\n        channel.close();\n    }\n\n    @Test\n    void parseUriFromNetty_relative() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1,\n                    HttpMethod.POST,\n                    \"/foo/bar/somePath/%5E1.0.0?param1=foo&param2=bar&param3=baz\",\n                    Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/foo/bar/somePath/%5E1.0.0\");\n\n        channel.close();\n    }\n\n    @Test\n    void parseUriFromNetty_absolute() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1,\n                    HttpMethod.POST,\n                    \"https://www.netflix.com/foo/bar/somePath/%5E1.0.0?param1=foo&param2=bar&param3=baz\",\n                    Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/foo/bar/somePath/%5E1.0.0\");\n\n        channel.close();\n    }\n\n    @Test\n    void parseUriFromNetty_unknown() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(\n                    new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"asdf\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"asdf\");\n\n        channel.close();\n    }\n\n    @Test\n    void parseQueryParamsWithEncodedCharsInURI() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1,\n                    HttpMethod.POST,\n                    \"/foo/bar/somePath/%5E1.0.0?param1=foo&param2=bar&param3=baz\",\n                    Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getQueryParams().getFirst(\"param1\")).isEqualTo(\"foo\");\n        assertThat(result.getQueryParams().getFirst(\"param2\")).isEqualTo(\"bar\");\n        assertThat(result.getQueryParams().getFirst(\"param3\")).isEqualTo(\"baz\");\n\n        channel.close();\n    }\n\n    @Test\n    void largeResponse_atLimit() {\n        ClientRequestReceiver receiver = new ClientRequestReceiver(null);\n        EmbeddedChannel channel = new EmbeddedChannel(receiver);\n        // Required for messages\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n\n        int maxSize;\n        // Figure out the max size, since it isn't public.\n        {\n            ByteBuf buf = Unpooled.buffer(1).writeByte('a');\n            channel.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", buf));\n            HttpRequestMessageImpl res = channel.readInbound();\n            maxSize = res.getMaxBodySize();\n            res.disposeBufferedBody();\n        }\n\n        HttpRequestMessageImpl result;\n        {\n            ByteBuf buf = Unpooled.buffer(maxSize);\n            buf.writerIndex(maxSize);\n            channel.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", buf));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getContext().getError()).isNull();\n        assertThat(result.getContext().shouldSendErrorResponse()).isFalse();\n        channel.close();\n    }\n\n    @Test\n    void largeResponse_aboveLimit() {\n        ClientRequestReceiver receiver = new ClientRequestReceiver(null);\n        EmbeddedChannel channel = new EmbeddedChannel(receiver);\n        // Required for messages\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n\n        int maxSize;\n        // Figure out the max size, since it isn't public.\n        {\n            ByteBuf buf = Unpooled.buffer(1).writeByte('a');\n            channel.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", buf));\n            HttpRequestMessageImpl res = channel.readInbound();\n            maxSize = res.getMaxBodySize();\n            res.disposeBufferedBody();\n        }\n\n        HttpRequestMessageImpl result;\n        {\n            ByteBuf buf = Unpooled.buffer(maxSize + 1);\n            buf.writerIndex(maxSize + 1);\n            channel.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", buf));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getContext().getError()).isNotNull();\n        assertThat(result.getContext().getError().getMessage().contains(\"too large\"))\n                .isTrue();\n        assertThat(result.getContext().shouldSendErrorResponse()).isTrue();\n        assertThat(StatusCategoryUtils.getStatusCategory(result))\n                .isEqualTo(ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST);\n        assertThat(StatusCategoryUtils.getStatusCategoryReason(result.getContext()))\n                .startsWith(\"Invalid request provided: Request body size \");\n        channel.close();\n    }\n\n    @Test\n    void maxHeaderSizeExceeded_setBadRequestStatus() {\n\n        int maxInitialLineLength = BaseZuulChannelInitializer.MAX_INITIAL_LINE_LENGTH.get();\n        int maxHeaderSize = 10;\n        int maxChunkSize = BaseZuulChannelInitializer.MAX_CHUNK_SIZE.get();\n        ClientRequestReceiver receiver = new ClientRequestReceiver(null);\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestEncoder());\n        PassportLoggingHandler loggingHandler = new PassportLoggingHandler(new DefaultRegistry());\n\n        // Required for messages\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        channel.pipeline().addLast(new HttpServerCodec(maxInitialLineLength, maxHeaderSize, maxChunkSize, false));\n        channel.pipeline().addLast(receiver);\n        channel.pipeline().addLast(loggingHandler);\n\n        String str = \"test-header-value\";\n        ByteBuf buf = Unpooled.buffer(1);\n        HttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", buf);\n        for (int i = 0; i < 100; i++) {\n            httpRequest.headers().add(\"test-header\" + i, str);\n        }\n\n        channel.writeOutbound(httpRequest);\n        ByteBuf byteBuf = channel.readOutbound();\n        channel.writeInbound(byteBuf);\n        channel.readInbound();\n        channel.close();\n\n        HttpRequestMessage request = ClientRequestReceiver.getRequestFromChannel(channel);\n        assertThat(StatusCategoryUtils.getStatusCategory(request.getContext()))\n                .isEqualTo(ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST);\n        assertThat(StatusCategoryUtils.getStatusCategoryReason(request.getContext()))\n                .isEqualTo(\"Invalid request provided: Decode failure\");\n    }\n\n    @Test\n    void multipleHostHeaders_setBadRequestStatus() {\n        ClientRequestReceiver receiver = new ClientRequestReceiver(null);\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestEncoder());\n        PassportLoggingHandler loggingHandler = new PassportLoggingHandler(new DefaultRegistry());\n\n        // Required for messages\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        channel.pipeline().addLast(new HttpServerCodec());\n        channel.pipeline().addLast(receiver);\n        channel.pipeline().addLast(loggingHandler);\n\n        HttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\");\n        httpRequest.headers().add(\"Host\", \"foo.bar.com\");\n        httpRequest.headers().add(\"Host\", \"bar.foo.com\");\n\n        channel.writeOutbound(httpRequest);\n        ByteBuf byteBuf = channel.readOutbound();\n        channel.writeInbound(byteBuf);\n        channel.readInbound();\n        channel.close();\n\n        HttpRequestMessage request = ClientRequestReceiver.getRequestFromChannel(channel);\n        SessionContext context = request.getContext();\n        assertThat(StatusCategoryUtils.getStatusCategory(context))\n                .isEqualTo(ZuulStatusCategory.FAILURE_CLIENT_BAD_REQUEST);\n        assertThat(context.getError().getMessage()).isEqualTo(\"Multiple Host headers\");\n        assertThat(StatusCategoryUtils.getStatusCategoryReason(context))\n                .isEqualTo(\"Invalid request provided: Multiple Host headers\");\n    }\n\n    @Test\n    void setStatusCategoryForHttpPipelining() {\n\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n\n        DefaultFullHttpRequest request = new DefaultFullHttpRequest(\n                HttpVersion.HTTP_1_1, HttpMethod.POST, \"?ELhAWDLM1hwm8bhU0UT4\", Unpooled.buffer());\n\n        // Write the message and save a copy\n        channel.writeInbound(request);\n        HttpRequestMessage inboundRequest = ClientRequestReceiver.getRequestFromChannel(channel);\n\n        // Set the attr to emulate pipelining rejection\n        channel.attr(HttpLifecycleChannelHandler.ATTR_HTTP_PIPELINE_REJECT).set(Boolean.TRUE);\n\n        // Fire completion event\n        channel.pipeline()\n                .fireUserEventTriggered(new CompleteEvent(\n                        CompleteReason.PIPELINE_REJECT,\n                        request,\n                        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST)));\n        channel.close();\n\n        assertThat(StatusCategoryUtils.getStatusCategory(inboundRequest.getContext()))\n                .isEqualTo(ZuulStatusCategory.FAILURE_CLIENT_PIPELINE_REJECT);\n    }\n\n    @Test\n    void headersAllCopied() {\n        ClientRequestReceiver receiver = new ClientRequestReceiver(null);\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestEncoder());\n        PassportLoggingHandler loggingHandler = new PassportLoggingHandler(new DefaultRegistry());\n\n        // Required for messages\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        channel.pipeline().addLast(new HttpServerCodec());\n        channel.pipeline().addLast(receiver);\n        channel.pipeline().addLast(loggingHandler);\n\n        HttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\");\n        httpRequest.headers().add(\"Header1\", \"Value1\");\n        httpRequest.headers().add(\"Header2\", \"Value2\");\n        httpRequest.headers().add(\"Duplicate\", \"Duplicate1\");\n        httpRequest.headers().add(\"Duplicate\", \"Duplicate2\");\n\n        channel.writeOutbound(httpRequest);\n        ByteBuf byteBuf = channel.readOutbound();\n        channel.writeInbound(byteBuf);\n        channel.readInbound();\n        channel.close();\n\n        HttpRequestMessage request = ClientRequestReceiver.getRequestFromChannel(channel);\n        Headers headers = request.getHeaders();\n        assertThat(headers.size()).isEqualTo(4);\n        assertThat(headers.getFirst(\"Header1\")).isEqualTo(\"Value1\");\n        assertThat(headers.getFirst(\"Header2\")).isEqualTo(\"Value2\");\n\n        List<String> duplicates = headers.getAll(\"Duplicate\");\n        assertThat(duplicates).isEqualTo(Arrays.asList(\"Duplicate1\", \"Duplicate2\"));\n    }\n\n    @Test\n    void clientIpSet() {\n        String clientIp = \"123.456.789.012\";\n        ClientRequestReceiver receiver = new ClientRequestReceiver(null);\n        EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestEncoder());\n\n        // Required for messages\n        channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(clientIp);\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        channel.pipeline().addLast(new HttpServerCodec());\n        channel.pipeline().addLast(receiver);\n\n        HttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\");\n\n        channel.writeOutbound(httpRequest);\n        ByteBuf byteBuf = channel.readOutbound();\n        channel.writeInbound(byteBuf);\n        channel.readInbound();\n        channel.close();\n\n        HttpRequestMessage request = ClientRequestReceiver.getRequestFromChannel(channel);\n        assertThat(request.getClientIp()).isEqualTo(clientIp);\n    }\n\n    @Test\n    void handleClientChannelInactiveEventCalledOnInactiveComplete() {\n        TestClientRequestReceiver receiver = new TestClientRequestReceiver();\n        EmbeddedChannel channel = new EmbeddedChannel(receiver);\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n\n        DefaultFullHttpRequest request =\n                new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/post\", Unpooled.buffer());\n\n        channel.writeInbound(request);\n        channel.readInbound();\n\n        channel.pipeline()\n                .fireUserEventTriggered(new CompleteEvent(\n                        CompleteReason.INACTIVE,\n                        request,\n                        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)));\n        channel.close();\n\n        assertThat(receiver.handleClientCancelledEventCalled).isTrue();\n    }\n\n    private static class TestClientRequestReceiver extends ClientRequestReceiver {\n        boolean handleClientCancelledEventCalled = false;\n\n        TestClientRequestReceiver() {\n            super(null);\n        }\n\n        @Override\n        protected void handleClientChannelInactiveEvent(@NonNull HttpRequestMessage zuulRequest) {\n            handleClientCancelledEventCalled = true;\n            super.handleClientChannelInactiveEvent(zuulRequest);\n        }\n    }\n\n    @Test\n    void pathTraversal_basicDotDot() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"/public/../admin/\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/admin/\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_multipleDotDots() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"/a/b/c/../../d/\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/a/d/\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_dotSegments() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"/./foo/./bar/.\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/foo/bar/\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_multipleSlashes() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"//foo///bar////baz\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/bar/baz\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_escapeRoot() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"/../../../etc/passwd\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/etc/passwd\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_complexMix() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"/foo/./bar/../baz//qux/../\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/foo/baz/\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_withQueryString() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"/public/../admin/?param=value\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/admin/\");\n        assertThat(result.getQueryParams().getFirst(\"param\")).isEqualTo(\"value\");\n        channel.close();\n    }\n\n    @Test\n    void pathTraversal_withOpaqueURI() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(new DefaultFullHttpRequest(\n                    HttpVersion.HTTP_1_1, HttpMethod.GET, \"foo.netflix.net:443\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"foo.netflix.net:443\");\n        channel.close();\n    }\n\n    @Test\n    void pathNormalization_emptyPath() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(\n                    new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"\");\n        channel.close();\n    }\n\n    @Test\n    void pathNormalization_rootOnly() {\n        EmbeddedChannel channel = new EmbeddedChannel(new ClientRequestReceiver(null));\n        channel.attr(SourceAddressChannelHandler.ATTR_SERVER_LOCAL_PORT).set(1234);\n        HttpRequestMessageImpl result;\n        {\n            channel.writeInbound(\n                    new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\", Unpooled.buffer()));\n            result = channel.readInbound();\n            result.disposeBufferedBody();\n        }\n\n        assertThat(result.getPath()).isEqualTo(\"/\");\n        channel.close();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/ClientResponseWriterTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.netty.common.HttpLifecycleChannelHandler;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.BasicRequestCompleteHandler;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport com.netflix.zuul.message.util.HttpRequestBuilder;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.util.ReferenceCountUtil;\nimport java.util.concurrent.atomic.AtomicReference;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\nclass ClientResponseWriterTest {\n\n    @Test\n    void exemptClientTimeoutResponseBeforeRequestRead() {\n        ClientResponseWriter responseWriter = new ClientResponseWriter(new BasicRequestCompleteHandler());\n        EmbeddedChannel channel = new EmbeddedChannel();\n\n        SessionContext context = new SessionContext();\n        StatusCategoryUtils.setStatusCategory(context, ZuulStatusCategory.FAILURE_CLIENT_TIMEOUT);\n        HttpRequestMessage request = new HttpRequestBuilder(context).withDefaults();\n        channel.attr(ClientRequestReceiver.ATTR_ZUUL_REQ).set(request);\n\n        assertThat(responseWriter.shouldAllowPreemptiveResponse(channel)).isTrue();\n    }\n\n    @Test\n    void flagResponseBeforeRequestRead() {\n        ClientResponseWriter responseWriter = new ClientResponseWriter(new BasicRequestCompleteHandler());\n        EmbeddedChannel channel = new EmbeddedChannel();\n\n        SessionContext context = new SessionContext();\n        StatusCategoryUtils.setStatusCategory(context, ZuulStatusCategory.FAILURE_LOCAL);\n        HttpRequestMessage request = new HttpRequestBuilder(context).withDefaults();\n        channel.attr(ClientRequestReceiver.ATTR_ZUUL_REQ).set(request);\n\n        assertThat(responseWriter.shouldAllowPreemptiveResponse(channel)).isFalse();\n    }\n\n    @Test\n    void allowExtensionForPremptingResponse() {\n\n        ZuulStatusCategory customStatus = ZuulStatusCategory.SUCCESS_LOCAL_NO_ROUTE;\n        ClientResponseWriter responseWriter = new ClientResponseWriter(new BasicRequestCompleteHandler()) {\n            @Override\n            protected boolean shouldAllowPreemptiveResponse(Channel channel) {\n                StatusCategory status =\n                        StatusCategoryUtils.getStatusCategory(ClientRequestReceiver.getRequestFromChannel(channel));\n                return status == customStatus;\n            }\n        };\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        SessionContext context = new SessionContext();\n        StatusCategoryUtils.setStatusCategory(context, customStatus);\n        HttpRequestMessage request = new HttpRequestBuilder(context).withDefaults();\n        channel.attr(ClientRequestReceiver.ATTR_ZUUL_REQ).set(request);\n\n        assertThat(responseWriter.shouldAllowPreemptiveResponse(channel)).isTrue();\n    }\n\n    @Test\n    public void clearReferenceOnComplete() {\n        ClientResponseWriter responseWriter = new ClientResponseWriter(new BasicRequestCompleteHandler());\n        EmbeddedChannel channel = new EmbeddedChannel(responseWriter);\n\n        AtomicReference<HttpResponse> nettyResp = new AtomicReference<>();\n        channel.pipeline().addFirst(new ChannelOutboundHandlerAdapter() {\n            @Override\n            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {\n                if (msg instanceof HttpResponse response) {\n                    nettyResp.set(response);\n                }\n                ReferenceCountUtil.safeRelease(msg);\n            }\n        });\n\n        SessionContext ctx = new SessionContext();\n        HttpRequestMessage request = new HttpRequestBuilder(ctx).build();\n        request.storeInboundRequest();\n        HttpResponseMessageImpl response = new HttpResponseMessageImpl(ctx, request, 200);\n        response.setHeaders(new Headers());\n\n        channel.attr(ClientRequestReceiver.ATTR_ZUUL_REQ).set(request);\n        DefaultHttpRequest nettyRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\");\n        ctx.set(CommonContextKeys.NETTY_HTTP_REQUEST, nettyRequest);\n\n        channel.pipeline().fireUserEventTriggered(new HttpLifecycleChannelHandler.StartEvent(nettyRequest));\n        channel.writeInbound(response);\n\n        HttpResponseMessage zuulResponse = responseWriter.getZuulResponse();\n        assertThat(zuulResponse).isNotNull();\n        assertThat(nettyResp.get()).isNotNull();\n\n        channel.pipeline()\n                .fireUserEventTriggered(new HttpLifecycleChannelHandler.CompleteEvent(\n                        HttpLifecycleChannelHandler.CompleteReason.SESSION_COMPLETE, null, nettyResp.get()));\n        assertThat(responseWriter.getZuulResponse()).isNull();\n    }\n\n    @ParameterizedTest\n    @ValueSource(booleans = {false, true})\n    void warningOnlyForRequestsWithBody(boolean hasBodyChunk) {\n        Registry registry = new DefaultRegistry();\n        Counter warningCounter = registry.counter(\"server.http.requests.responseBeforeReceivedLastContent\");\n        ClientResponseWriter responseWriter = new ClientResponseWriter(new BasicRequestCompleteHandler(), registry);\n        EmbeddedChannel channel = new EmbeddedChannel(responseWriter);\n\n        SessionContext ctx = new SessionContext();\n        HttpRequestMessage request = new HttpRequestBuilder(ctx).build();\n\n        if (hasBodyChunk) {\n            request.bufferBodyContents(new io.netty.handler.codec.http.DefaultHttpContent(\n                    Unpooled.copiedBuffer(\"body\", java.nio.charset.StandardCharsets.UTF_8)));\n        }\n\n        request.storeInboundRequest();\n\n        HttpResponseMessageImpl response = new HttpResponseMessageImpl(ctx, request, 200);\n        response.setHeaders(new Headers());\n\n        channel.attr(ClientRequestReceiver.ATTR_ZUUL_REQ).set(request);\n        DefaultHttpRequest nettyRequest = new DefaultHttpRequest(\n                HttpVersion.HTTP_1_1,\n                hasBodyChunk ? HttpMethod.POST : HttpMethod.GET,\n                hasBodyChunk ? \"/api\" : \"/favicon.ico\");\n        ctx.set(CommonContextKeys.NETTY_HTTP_REQUEST, nettyRequest);\n\n        channel.pipeline().fireUserEventTriggered(new HttpLifecycleChannelHandler.StartEvent(nettyRequest));\n        channel.writeInbound(response);\n\n        assertThat(responseWriter.getZuulResponse()).isNotNull();\n        assertThat(request.hasBody()).isEqualTo(hasBodyChunk);\n\n        if (hasBodyChunk) {\n            assertThat(warningCounter.count())\n                    .as(\"should warn as is body expected\")\n                    .isEqualTo(1);\n        } else {\n            assertThat(warningCounter.count())\n                    .as(\"should not warn as no body expected\")\n                    .isEqualTo(0);\n        }\n\n        request.disposeBufferedBody();\n        channel.close();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/IoUringTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\n\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Spectator;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.group.DefaultChannelGroup;\nimport io.netty.channel.uring.IoUring;\nimport io.netty.channel.uring.IoUringServerSocketChannel;\nimport io.netty.util.concurrent.GlobalEventExecutor;\nimport io.netty.util.internal.PlatformDependent;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/*\n\n     Goals of this test:\n     1) verify that the server starts\n     2) verify that the server is listening on 2 ports\n     3) verify that the correct number of IOUringSocketChannel's are initialized\n     4) verify that the server stops\n\n*/\n@SuppressWarnings(\"AddressSelection\")\n@Disabled\nclass IoUringTest {\n    private static final Logger LOGGER = LoggerFactory.getLogger(IoUringTest.class);\n    private static final boolean IS_OS_LINUX = PlatformDependent.normalizedOs().equals(\"linux\");\n\n    @BeforeEach\n    void beforeTest() {\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"zuul.server.netty.socket.force_io_uring\", \"true\");\n        config.setProperty(\"zuul.server.netty.socket.force_nio\", \"false\");\n    }\n\n    @Test\n    void testIoUringServer() throws Exception {\n        LOGGER.info(\"IOUring.isAvailable: {}\", IoUring.isAvailable());\n        LOGGER.info(\"IS_OS_LINUX: {}\", IS_OS_LINUX);\n\n        if (IS_OS_LINUX) {\n            exerciseIoUringServer();\n        }\n    }\n\n    private void exerciseIoUringServer() throws Exception {\n        IoUring.ensureAvailability();\n\n        ServerStatusManager ssm = mock(ServerStatusManager.class);\n\n        Map<NamedSocketAddress, ChannelInitializer<?>> initializers = new HashMap<>();\n\n        List<IoUringServerSocketChannel> ioUringChannels =\n                Collections.synchronizedList(new ArrayList<IoUringServerSocketChannel>());\n\n        ChannelInitializer<Channel> init = new ChannelInitializer<Channel>() {\n            @Override\n            protected void initChannel(Channel ch) {\n                LOGGER.info(\"Channel: {}, isActive={}, isOpen={}\", ch.getClass().getName(), ch.isActive(), ch.isOpen());\n                if (ch instanceof IoUringServerSocketChannel) {\n                    ioUringChannels.add((IoUringServerSocketChannel) ch);\n                }\n            }\n        };\n        initializers.put(new NamedSocketAddress(\"test\", new InetSocketAddress(0)), init);\n        // The port to channel map keys on the port, post bind. This should be unique even if InetAddress is same\n        initializers.put(new NamedSocketAddress(\"test2\", new InetSocketAddress(0)), init);\n\n        ClientConnectionsShutdown ccs = new ClientConnectionsShutdown(\n                new DefaultChannelGroup(GlobalEventExecutor.INSTANCE),\n                GlobalEventExecutor.INSTANCE,\n                /* discoveryClient= */ null);\n        EventLoopGroupMetrics elgm = new EventLoopGroupMetrics(Spectator.globalRegistry());\n        EventLoopConfig elc = new EventLoopConfig() {\n            @Override\n            public int eventLoopCount() {\n                return 1;\n            }\n\n            @Override\n            public int acceptorCount() {\n                return 1;\n            }\n        };\n        Server s = new Server(new NoopRegistry(), ssm, initializers, ccs, elgm, elc);\n        s.start();\n\n        List<NamedSocketAddress> addresses = s.getListeningAddresses();\n        assertThat(addresses.size()).isEqualTo(2);\n\n        addresses.forEach(address -> {\n            assertThat(address.unwrap() instanceof InetSocketAddress).isTrue();\n            InetSocketAddress inetAddress = ((InetSocketAddress) address.unwrap());\n            assertThat(inetAddress.getPort()).isNotEqualTo(0);\n            checkConnection(inetAddress.getPort());\n        });\n\n        await().atMost(1, TimeUnit.SECONDS).until(() -> ioUringChannels.size() == 2);\n\n        s.stop();\n\n        assertThat(ioUringChannels.size()).isEqualTo(2);\n\n        for (IoUringServerSocketChannel ch : ioUringChannels) {\n            assertThat(ch.eventLoop().isShutdown()).as(\"isShutdown\").isTrue();\n        }\n    }\n\n    @SuppressWarnings(\"EmptyCatch\")\n    private static void checkConnection(int port) {\n        LOGGER.info(\"checkConnection port {}\", port);\n        Socket sock = null;\n        try {\n            InetSocketAddress socketAddress = new InetSocketAddress(\"127.0.0.1\", port);\n            sock = new Socket();\n            sock.setSoTimeout(100);\n            sock.connect(socketAddress, 100);\n            OutputStream out = sock.getOutputStream();\n            out.write(\"Hello\".getBytes(StandardCharsets.UTF_8));\n            out.flush();\n            out.close();\n        } catch (Exception exception) {\n            fail(\"checkConnection failed. port=\" + port + \" \" + exception);\n        } finally {\n            try {\n                sock.close();\n            } catch (Exception ignored) {\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/OriginResponseReceiverTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.netty.server;\n\nimport static java.nio.charset.StandardCharsets.UTF_8;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\n\nimport com.netflix.zuul.filters.endpoint.ProxyEndpoint;\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.SimpleChannelInboundHandler;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultFullHttpResponse;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultHttpResponse;\nimport io.netty.handler.codec.http.HttpContent;\nimport io.netty.handler.codec.http.HttpResponse;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.util.ReferenceCountUtil;\nimport java.lang.reflect.Field;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\nimport org.mockito.junit.jupiter.MockitoSettings;\nimport org.mockito.quality.Strictness;\n\n@ExtendWith(MockitoExtension.class)\n@MockitoSettings(strictness = Strictness.LENIENT)\nclass OriginResponseReceiverTest {\n\n    @Mock\n    private ProxyEndpoint proxyEndpoint;\n\n    private OriginResponseReceiver receiver;\n    private ChannelHandlerContext ctx;\n    private EmbeddedChannel channel;\n    private HttpContent chunk;\n\n    @BeforeEach\n    void setup() {\n        channel = new EmbeddedChannel();\n        channel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {\n            @Override\n            protected void channelRead0(ChannelHandlerContext ctx, String msg) {}\n        });\n\n        ctx = channel.pipeline().firstContext();\n        receiver = new OriginResponseReceiver(proxyEndpoint);\n\n        // allocate a chunk for testing\n        chunk = new DefaultHttpContent(Unpooled.wrappedBuffer(\"yo\".getBytes(UTF_8)));\n    }\n\n    @Test\n    void channelReadWithManualReadTriggersRead() throws Exception {\n        // when triggerRead=true (default), should manually trigger read after processing\n        receiver.channelReadInternal(ctx, chunk, true);\n\n        verify(proxyEndpoint, times(1)).invokeNext(any(HttpContent.class));\n        assertThat(chunk.refCnt()).isEqualTo(1);\n    }\n\n    @Test\n    void channelReadWithoutManualReadDoesNotTriggerRead() throws Exception {\n        HttpContent chunk2 = new DefaultHttpContent(Unpooled.wrappedBuffer(\"test data\".getBytes(UTF_8)));\n\n        // when triggerRead=false, should NOT trigger read\n        receiver.channelReadInternal(ctx, chunk2, false);\n\n        verify(proxyEndpoint, times(1)).invokeNext(any(HttpContent.class));\n        assertThat(chunk2.refCnt()).isEqualTo(1);\n\n        chunk2.release();\n    }\n\n    @Test\n    void unlinkFromClientRequestNullsEdgeProxyField() throws Exception {\n        verifyProxyLink(receiver, false);\n\n        // link is still there, so pipeline should be called & chunk retained\n        receiver.channelReadInternal(ctx, chunk, true);\n        verify(proxyEndpoint, times(1)).invokeNext(any(HttpContent.class));\n        assertThat(chunk.refCnt()).isEqualTo(1);\n\n        // when the client request is unlinked, field should be nulled\n        receiver.unlinkFromClientRequest();\n        verifyProxyLink(receiver, true);\n\n        // data frames should be ignored now that the link is gone\n        receiver.channelReadInternal(ctx, chunk, true);\n\n        // pipeline should not be called again, as link is gone, so still 1 time\n        verify(proxyEndpoint, times(1)).invokeNext(any(HttpContent.class));\n\n        // verify chunk was released\n        assertThat(chunk.refCnt()).isEqualTo(0);\n    }\n\n    @Test\n    void unlinkPreventsStaleChunksFromBeingProcessed() throws Exception {\n\n        // simulate retry scenario where connection is unlinked\n        receiver.unlinkFromClientRequest();\n        verifyProxyLink(receiver, true);\n\n        // stale chunk arrives after unlink\n        receiver.channelReadInternal(ctx, chunk, true);\n\n        // should NOT call invokeNext - chunk should be released instead\n        verify(proxyEndpoint, never()).invokeNext(any(HttpContent.class));\n\n        // verify chunk was released (no memory leak)\n        assertThat(chunk.refCnt()).isEqualTo(0);\n    }\n\n    @Test\n    void httpResponseProcessedCorrectly() throws Exception {\n        HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);\n\n        receiver.channelReadInternal(ctx, response, true);\n\n        verify(proxyEndpoint, times(1)).responseFromOrigin(any(HttpResponse.class));\n    }\n\n    @Test\n    void httpResponseReleasedWhenUnlinked() throws Exception {\n        // use FullHttpResponse which has a refCnt\n        HttpResponse response = new DefaultFullHttpResponse(\n                HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(\"body\".getBytes(UTF_8)));\n\n        assertThat(ReferenceCountUtil.refCnt(response)).isEqualTo(1);\n\n        // unlink first\n        receiver.unlinkFromClientRequest();\n\n        // process response - should be released since unlinked\n        receiver.channelReadInternal(ctx, response, true);\n\n        verify(proxyEndpoint, never()).responseFromOrigin(any(HttpResponse.class));\n        assertThat(ReferenceCountUtil.refCnt(response)).isEqualTo(0);\n    }\n\n    @Test\n    void chunksReleasedWhenUnlinked() throws Exception {\n        receiver.unlinkFromClientRequest();\n\n        receiver.channelReadInternal(ctx, chunk, true);\n\n        verify(proxyEndpoint, never()).invokeNext(any(HttpContent.class));\n        assertThat(chunk.refCnt()).isEqualTo(0);\n    }\n\n    private void verifyProxyLink(OriginResponseReceiver receiver, boolean expectedNull) throws Exception {\n        Field proxyField = OriginResponseReceiver.class.getDeclaredField(\"edgeProxy\");\n        proxyField.setAccessible(true);\n\n        if (expectedNull) {\n            assertThat(proxyField.get(receiver)).isNull();\n        } else {\n            assertThat(proxyField.get(receiver)).isNotNull();\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/ServerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\nimport static org.junit.jupiter.api.Assertions.fail;\nimport static org.mockito.Mockito.mock;\n\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.spectator.api.Spectator;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.group.DefaultChannelGroup;\nimport io.netty.channel.socket.ServerSocketChannel;\nimport io.netty.channel.socket.nio.NioSocketChannel;\nimport io.netty.util.concurrent.GlobalEventExecutor;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Tests for {@link Server}.\n */\n@SuppressWarnings(\"AddressSelection\")\nclass ServerTest {\n    private static final Logger LOGGER = LoggerFactory.getLogger(ServerTest.class);\n\n    @BeforeEach\n    void beforeTest() {\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"zuul.server.netty.socket.force_nio\", \"true\");\n        config.setProperty(\"zuul.server.netty.socket.force_io_uring\", \"false\");\n    }\n\n    @Test\n    void getListeningSockets() throws Exception {\n        ServerStatusManager ssm = mock(ServerStatusManager.class);\n        Map<NamedSocketAddress, ChannelInitializer<?>> initializers = new HashMap<>();\n        List<NioSocketChannel> nioChannels = Collections.synchronizedList(new ArrayList<NioSocketChannel>());\n        ChannelInitializer<Channel> init = new ChannelInitializer<Channel>() {\n            @Override\n            protected void initChannel(Channel ch) {\n                LOGGER.info(\"Channel: {}, isActive={}, isOpen={}\", ch.getClass().getName(), ch.isActive(), ch.isOpen());\n                if (ch instanceof NioSocketChannel) {\n                    nioChannels.add((NioSocketChannel) ch);\n                }\n            }\n        };\n        initializers.put(new NamedSocketAddress(\"test\", new InetSocketAddress(0)), init);\n        // The port to channel map keys on the port, post bind. This should be unique even if InetAddress is same\n        initializers.put(new NamedSocketAddress(\"test2\", new InetSocketAddress(0)), init);\n        ClientConnectionsShutdown ccs = new ClientConnectionsShutdown(\n                new DefaultChannelGroup(GlobalEventExecutor.INSTANCE),\n                GlobalEventExecutor.INSTANCE,\n                /* discoveryClient= */ null);\n        EventLoopGroupMetrics elgm = new EventLoopGroupMetrics(Spectator.globalRegistry());\n        EventLoopConfig elc = new EventLoopConfig() {\n            @Override\n            public int eventLoopCount() {\n                return 1;\n            }\n\n            @Override\n            public int acceptorCount() {\n                return 1;\n            }\n\n            @Override\n            public int getBacklogSize() {\n                return 1024;\n            }\n        };\n        Server s = new Server(new NoopRegistry(), ssm, initializers, ccs, elgm, elc);\n        s.start();\n\n        List<NamedSocketAddress> addrs = s.getListeningAddresses();\n        assertThat(addrs.size()).isEqualTo(2);\n        for (NamedSocketAddress address : addrs) {\n            assertThat(address.unwrap() instanceof InetSocketAddress).isTrue();\n            int port = ((InetSocketAddress) address.unwrap()).getPort();\n            assertThat(port).isNotEqualTo(0);\n            checkConnection(port);\n        }\n\n        await().atMost(1, TimeUnit.SECONDS).until(() -> nioChannels.size() == 2);\n\n        nioChannels.stream()\n                .map(NioSocketChannel::parent)\n                .map(ServerSocketChannel::config)\n                .forEach(config -> assertThat(config.getBacklog()).isEqualTo(elc.getBacklogSize()));\n\n        s.stop();\n\n        assertThat(nioChannels.size()).isEqualTo(2);\n\n        for (NioSocketChannel ch : nioChannels) {\n            assertThat(ch.isShutdown()).as(\"isShutdown\").isTrue();\n        }\n    }\n\n    @Test\n    void acceptorMetricsAreRegistered() throws Exception {\n        Registry registry = new DefaultRegistry();\n        ServerStatusManager ssm = mock(ServerStatusManager.class);\n        Map<NamedSocketAddress, ChannelInitializer<?>> initializers = new HashMap<>();\n        ChannelInitializer<Channel> init = new ChannelInitializer<>() {\n            @Override\n            protected void initChannel(Channel ch) {}\n        };\n        initializers.put(new NamedSocketAddress(\"test\", new InetSocketAddress(0)), init);\n\n        ClientConnectionsShutdown ccs = new ClientConnectionsShutdown(\n                new DefaultChannelGroup(GlobalEventExecutor.INSTANCE), GlobalEventExecutor.INSTANCE, null);\n        EventLoopGroupMetrics elgm = new EventLoopGroupMetrics(Spectator.globalRegistry());\n        EventLoopConfig elc = new EventLoopConfig() {\n            @Override\n            public int eventLoopCount() {\n                return 1;\n            }\n\n            @Override\n            public int acceptorCount() {\n                return 1;\n            }\n\n            @Override\n            public int getBacklogSize() {\n                return 1024;\n            }\n        };\n\n        Server s = new Server(registry, ssm, initializers, ccs, elgm, elc);\n        s.start();\n\n        List<NamedSocketAddress> addrs = s.getListeningAddresses();\n        int port = ((InetSocketAddress) addrs.getFirst().unwrap()).getPort();\n\n        checkConnection(port);\n        checkConnection(port);\n\n        await().atMost(1, TimeUnit.SECONDS).until(() -> {\n            Counter counter = registry.counter(\"zuul.conn.acceptor.accepts\", \"port\", String.valueOf(port));\n            return counter.count() >= 2;\n        });\n\n        s.stop();\n    }\n\n    @SuppressWarnings(\"EmptyCatch\")\n    private static void checkConnection(int port) {\n        Socket sock = null;\n        try {\n            InetSocketAddress socketAddress = new InetSocketAddress(\"127.0.0.1\", port);\n            sock = new Socket();\n            sock.setSoTimeout(100);\n            sock.connect(socketAddress, 100);\n            OutputStream out = sock.getOutputStream();\n            out.write(\"Hello\".getBytes(StandardCharsets.UTF_8));\n            out.flush();\n            out.close();\n        } catch (Exception exception) {\n            fail(\"checkConnection failed. port=\" + port + \" \" + exception);\n        } finally {\n            try {\n                sock.close();\n            } catch (Exception ignored) {\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/SocketAddressPropertyTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nimport com.netflix.zuul.netty.server.SocketAddressProperty.BindType;\nimport io.netty.channel.unix.DomainSocketAddress;\nimport java.net.Inet4Address;\nimport java.net.Inet6Address;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Arrays;\nimport org.junit.jupiter.api.Test;\n\nclass SocketAddressPropertyTest {\n\n    @Test\n    void defaultValueWorks() {\n        SocketAddressProperty prop = new SocketAddressProperty(\"com.netflix.zuul.netty.server.testprop\", \"=7001\");\n\n        SocketAddress address = prop.getValue();\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n    }\n\n    @Test\n    void bindTypeWorks_any() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"ANY=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n    }\n\n    @Test\n    void bindTypeWorks_blank() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n    }\n\n    @Test\n    void bindTypeWorks_ipv4Any() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"IPV4_ANY=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n        assertThat(inetSocketAddress.getAddress() instanceof Inet4Address).isTrue();\n        assertThat(inetSocketAddress.getAddress().isAnyLocalAddress()).isTrue();\n    }\n\n    @Test\n    void bindTypeWorks_ipv6Any() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"IPV6_ANY=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n        assertThat(inetSocketAddress.getAddress() instanceof Inet6Address).isTrue();\n        assertThat(inetSocketAddress.getAddress().isAnyLocalAddress()).isTrue();\n    }\n\n    @Test\n    void bindTypeWorks_anyLocal() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"ANY_LOCAL=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n        assertThat(inetSocketAddress.getAddress().isLoopbackAddress()).isTrue();\n    }\n\n    @Test\n    void bindTypeWorks_ipv4Local() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"IPV4_LOCAL=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n        assertThat(inetSocketAddress.getAddress() instanceof Inet4Address).isTrue();\n        assertThat(inetSocketAddress.getAddress().isLoopbackAddress()).isTrue();\n    }\n\n    @Test\n    void bindTypeWorks_ipv6Local() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"IPV6_LOCAL=7001\");\n\n        assertThat(address.getClass()).isEqualTo(InetSocketAddress.class);\n        InetSocketAddress inetSocketAddress = (InetSocketAddress) address;\n        assertThat(inetSocketAddress.getPort()).isEqualTo(7001);\n        assertThat(inetSocketAddress.isUnresolved()).isFalse();\n        assertThat(inetSocketAddress.getAddress() instanceof Inet6Address).isTrue();\n        assertThat(inetSocketAddress.getAddress().isLoopbackAddress()).isTrue();\n    }\n\n    @Test\n    void bindTypeWorks_uds() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"UDS=/var/run/zuul.sock\");\n\n        assertThat(address.getClass()).isEqualTo(DomainSocketAddress.class);\n        DomainSocketAddress domainSocketAddress = (DomainSocketAddress) address;\n        assertThat(domainSocketAddress.path()).isEqualTo(\"/var/run/zuul.sock\");\n    }\n\n    @Test\n    void bindTypeWorks_udsWithEquals() {\n        SocketAddress address = SocketAddressProperty.Decoder.INSTANCE.apply(\"UDS=/var/run/zuul=.sock\");\n\n        assertThat(address.getClass()).isEqualTo(DomainSocketAddress.class);\n        DomainSocketAddress domainSocketAddress = (DomainSocketAddress) address;\n        assertThat(domainSocketAddress.path()).isEqualTo(\"/var/run/zuul=.sock\");\n    }\n\n    @Test\n    void failsOnMissingEqual() {\n        assertThatThrownBy(() -> {\n                    SocketAddressProperty.Decoder.INSTANCE.apply(\"ANY\");\n                })\n                .isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    void failsOnBadPort() {\n        for (BindType type : Arrays.asList(\n                BindType.ANY,\n                BindType.IPV4_ANY,\n                BindType.IPV6_ANY,\n                BindType.ANY_LOCAL,\n                BindType.IPV4_LOCAL,\n                BindType.IPV6_LOCAL)) {\n            assertThatThrownBy(() -> {\n                        SocketAddressProperty.Decoder.INSTANCE.apply(type.name() + \"=bogus\");\n                    })\n                    .isInstanceOf(IllegalArgumentException.class)\n                    .hasMessageContaining(\"Port\");\n        }\n    }\n\n    @Test\n    public void failsOnBadAddress() throws Exception {\n        assertThatThrownBy(() -> {\n                    SocketAddressProperty.Decoder.INSTANCE.apply(\"\");\n                })\n                .isInstanceOf(IllegalArgumentException.class)\n                .hasMessage(\"Invalid address\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2ConnectionErrorHandlerTest.java",
    "content": "/**\n * Copyright 2023 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.netty.server.http2;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Justin Guerra\n * @since 11/15/23\n */\nclass Http2ConnectionErrorHandlerTest {\n\n    private EmbeddedChannel channel;\n    private ExceptionCapturingHandler exceptionCapturingHandler;\n\n    @BeforeEach\n    void setup() {\n        exceptionCapturingHandler = new ExceptionCapturingHandler();\n        channel = new EmbeddedChannel(new Http2ConnectionErrorHandler(), exceptionCapturingHandler);\n    }\n\n    @Test\n    public void nonHttp2ExceptionsPassedUpPipeline() {\n        RuntimeException exception = new RuntimeException();\n        channel.pipeline().fireExceptionCaught(exception);\n        assertThat(exceptionCapturingHandler.caught).isEqualTo(exception);\n    }\n\n    private static class ExceptionCapturingHandler extends ChannelInboundHandlerAdapter {\n\n        private Throwable caught;\n\n        @Override\n        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {\n            this.caught = cause;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2ContentLengthEnforcingHandlerTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.buffer.ByteBuf;\nimport io.netty.buffer.ByteBufUtil;\nimport io.netty.buffer.Unpooled;\nimport io.netty.buffer.UnpooledByteBufAllocator;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultFullHttpRequest;\nimport io.netty.handler.codec.http.DefaultHttpContent;\nimport io.netty.handler.codec.http.DefaultHttpRequest;\nimport io.netty.handler.codec.http.DefaultLastHttpContent;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpVersion;\nimport io.netty.handler.codec.http2.Http2ResetFrame;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass Http2ContentLengthEnforcingHandlerTest {\n\n    private EmbeddedChannel channel;\n\n    @BeforeEach\n    void setup() {\n        channel = new EmbeddedChannel(new Http2ContentLengthEnforcingHandler());\n    }\n\n    @Test\n    void validRequestPassesThrough() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/\");\n        req.headers().set(HttpHeaderNames.CONTENT_LENGTH, 3);\n        channel.writeInbound(req);\n\n        assertThat(channel.<Object>readOutbound()).isNull();\n        assertThat(channel.<Object>readInbound()).isSameAs(req);\n    }\n\n    @Test\n    void requestWithNoContentLengthPassesThrough() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\");\n        channel.writeInbound(req);\n\n        assertThat(channel.<Object>readOutbound()).isNull();\n        assertThat(channel.<Object>readInbound()).isSameAs(req);\n    }\n\n    @Test\n    void rejectsMultipleContentLengthHeaders() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"\");\n        req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 1);\n        req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 2);\n        channel.writeInbound(req);\n\n        assertThat((Object) channel.readOutbound()).isInstanceOf(Http2ResetFrame.class);\n    }\n\n    @Test\n    void failsOnNonNumericContentLength() {\n        EmbeddedChannel chan = new EmbeddedChannel();\n        chan.pipeline().addLast(new Http2ContentLengthEnforcingHandler());\n\n        ByteBuf content = Unpooled.buffer(8);\n        FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/\", content);\n        req.headers().set(HttpHeaderNames.CONTENT_LENGTH, \"not_a_number\");\n\n        chan.writeInbound(req);\n\n        Object out = chan.readOutbound();\n        assertThat(out).isInstanceOf(Http2ResetFrame.class);\n        assertThat(content.refCnt()).isEqualTo(0);\n    }\n\n    @Test\n    void rejectsNegativeContentLength() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/\");\n        req.headers().set(HttpHeaderNames.CONTENT_LENGTH, -5);\n        channel.writeInbound(req);\n\n        assertThat((Object) channel.readOutbound()).isInstanceOf(Http2ResetFrame.class);\n    }\n\n    @Test\n    void rejectsMixedContentLengthAndChunked() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"\");\n        req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 1);\n        req.headers().add(HttpHeaderNames.TRANSFER_ENCODING, \"identity, chunked\");\n        req.headers().add(HttpHeaderNames.TRANSFER_ENCODING, \"fzip\");\n        channel.writeInbound(req);\n\n        assertThat((Object) channel.readOutbound()).isInstanceOf(Http2ResetFrame.class);\n    }\n\n    @Test\n    void rejectsContentExceedingDeclaredLength() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"\");\n        req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 1);\n        channel.writeInbound(req);\n        assertThat(channel.<Object>readOutbound()).isNull();\n\n        DefaultHttpContent content =\n                new DefaultHttpContent(ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, \"a\"));\n        channel.writeInbound(content);\n        assertThat(channel.<Object>readOutbound()).isNull();\n\n        DefaultHttpContent content2 =\n                new DefaultHttpContent(ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, \"a\"));\n        channel.writeInbound(content2);\n        assertThat((Object) channel.readOutbound()).isInstanceOf(Http2ResetFrame.class);\n    }\n\n    @Test\n    void rejectsContentShorterThanDeclaredLength() {\n        DefaultHttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"\");\n        req.headers().add(HttpHeaderNames.CONTENT_LENGTH, 2);\n        channel.writeInbound(req);\n        assertThat(channel.<Object>readOutbound()).isNull();\n\n        DefaultHttpContent content =\n                new DefaultHttpContent(ByteBufUtil.writeUtf8(UnpooledByteBufAllocator.DEFAULT, \"a\"));\n        channel.writeInbound(content);\n        assertThat(channel.<Object>readOutbound()).isNull();\n\n        channel.writeInbound(new DefaultLastHttpContent());\n        assertThat((Object) channel.readOutbound()).isInstanceOf(Http2ResetFrame.class);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/http2/Http2OrHttpHandlerTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.http2;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.netty.common.Http2ConnectionCloseHandler;\nimport com.netflix.netty.common.Http2ConnectionExpiryHandler;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.ChannelConfigValue;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.Http2MetricsChannelHandlers;\nimport com.netflix.spectator.api.NoopRegistry;\nimport com.netflix.zuul.netty.server.BaseZuulChannelInitializer;\nimport io.netty.channel.ChannelInboundHandlerAdapter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http2.Http2FrameCodec;\nimport io.netty.handler.codec.http2.Http2MultiplexHandler;\nimport io.netty.handler.codec.http2.Http2Settings;\nimport io.netty.handler.ssl.ApplicationProtocolNames;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Argha C\n * @since November 18, 2020\n */\nclass Http2OrHttpHandlerTest {\n\n    private EmbeddedChannel channel;\n    private ChannelConfig channelConfig;\n\n    @BeforeEach\n    void setUp() {\n        channel = new EmbeddedChannel();\n        channelConfig = new ChannelConfig();\n    }\n\n    @AfterEach\n    void tearDown() {\n        channel.finishAndReleaseAll();\n    }\n\n    @Test\n    void swapInHttp2HandlerBasedOnALPN() throws Exception {\n        NoopRegistry registry = new NoopRegistry();\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.maxHttp2HeaderListSize, 32768));\n\n        Http2ConnectionCloseHandler connectionCloseHandler = new Http2ConnectionCloseHandler(registry);\n        Http2ConnectionExpiryHandler connectionExpiryHandler =\n                new Http2ConnectionExpiryHandler(100, 100, 20 * 60 * 1000);\n        Http2MetricsChannelHandlers http2MetricsChannelHandlers =\n                new Http2MetricsChannelHandlers(registry, \"server\", \"http2-443\");\n        Http2OrHttpHandler http2OrHttpHandler = new Http2OrHttpHandler(\n                new Http2StreamInitializer(\n                        channel,\n                        (x) -> {},\n                        http2MetricsChannelHandlers,\n                        connectionCloseHandler,\n                        connectionExpiryHandler),\n                channelConfig,\n                cp -> {});\n\n        channel.pipeline().addLast(\"codec_placeholder\", new DummyChannelHandler());\n        channel.pipeline().addLast(Http2OrHttpHandler.class.getSimpleName(), http2OrHttpHandler);\n\n        http2OrHttpHandler.configurePipeline(channel.pipeline().lastContext(), ApplicationProtocolNames.HTTP_2);\n\n        assertThat(channel.pipeline().get(Http2FrameCodec.class)).isInstanceOf(Http2FrameCodec.class);\n        assertThat(channel.pipeline().get(BaseZuulChannelInitializer.HTTP_CODEC_HANDLER_NAME))\n                .isInstanceOf(Http2MultiplexHandler.class);\n        assertThat(channel.attr(Http2OrHttpHandler.PROTOCOL_NAME).get()).isEqualTo(\"HTTP/2\");\n    }\n\n    @Test\n    void protocolCloseHandlerAddedByDefault() throws Exception {\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.maxHttp2HeaderListSize, 32768));\n\n        Http2OrHttpHandler http2OrHttpHandler =\n                new Http2OrHttpHandler(new ChannelInboundHandlerAdapter(), channelConfig, cp -> {});\n\n        channel.pipeline().addLast(\"codec_placeholder\", new DummyChannelHandler());\n        channel.pipeline().addLast(Http2OrHttpHandler.class.getSimpleName(), http2OrHttpHandler);\n\n        http2OrHttpHandler.configurePipeline(channel.pipeline().lastContext(), ApplicationProtocolNames.HTTP_2);\n        assertThat(channel.pipeline().context(Http2ConnectionErrorHandler.class))\n                .isNotNull();\n    }\n\n    @Test\n    void skipProtocolCloseHandler() throws Exception {\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.http2CatchConnectionErrors, false));\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.maxHttp2HeaderListSize, 32768));\n\n        Http2OrHttpHandler http2OrHttpHandler =\n                new Http2OrHttpHandler(new ChannelInboundHandlerAdapter(), channelConfig, cp -> {});\n\n        channel.pipeline().addLast(\"codec_placeholder\", new DummyChannelHandler());\n        channel.pipeline().addLast(Http2OrHttpHandler.class.getSimpleName(), http2OrHttpHandler);\n\n        http2OrHttpHandler.configurePipeline(channel.pipeline().lastContext(), ApplicationProtocolNames.HTTP_2);\n        assertThat(channel.pipeline().context(Http2ConnectionErrorHandler.class))\n                .isNull();\n    }\n\n    @Test\n    void validateHttp2Settings() throws Exception {\n\n        boolean connectProtocolEnabled = !CommonChannelConfigKeys.http2ConnectProtocolEnabled.defaultValue();\n        int maxConcurrentStreams = CommonChannelConfigKeys.maxConcurrentStreams.defaultValue() + 1;\n        int initialWindowSize = CommonChannelConfigKeys.initialWindowSize.defaultValue() + 1;\n        int maxHeaderTableSize = CommonChannelConfigKeys.maxHttp2HeaderTableSize.defaultValue() + 1;\n        int maxHeaderListSize = 1024;\n\n        channelConfig.add(\n                new ChannelConfigValue<>(CommonChannelConfigKeys.http2ConnectProtocolEnabled, connectProtocolEnabled));\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.maxConcurrentStreams, maxConcurrentStreams));\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.initialWindowSize, initialWindowSize));\n        channelConfig.add(\n                new ChannelConfigValue<>(CommonChannelConfigKeys.maxHttp2HeaderTableSize, maxHeaderTableSize));\n        channelConfig.add(new ChannelConfigValue<>(CommonChannelConfigKeys.maxHttp2HeaderListSize, maxHeaderListSize));\n\n        Http2OrHttpHandler http2OrHttpHandler =\n                new Http2OrHttpHandler(new ChannelInboundHandlerAdapter(), channelConfig, cp -> {});\n\n        channel.pipeline().addLast(\"codec_placeholder\", new DummyChannelHandler());\n        channel.pipeline().addLast(Http2OrHttpHandler.class.getSimpleName(), http2OrHttpHandler);\n\n        http2OrHttpHandler.configurePipeline(channel.pipeline().lastContext(), ApplicationProtocolNames.HTTP_2);\n        // triggers settings to be written\n        channel.pipeline().fireChannelActive();\n\n        Http2FrameCodec http2FrameCodec = channel.pipeline().get(Http2FrameCodec.class);\n        Http2Settings http2Settings = http2FrameCodec.encoder().pollSentSettings();\n\n        assertThat(http2Settings.connectProtocolEnabled()).isEqualTo(connectProtocolEnabled);\n        assertThat(http2Settings.maxConcurrentStreams()).isEqualTo(maxConcurrentStreams);\n        assertThat(http2Settings.initialWindowSize()).isEqualTo(initialWindowSize);\n        assertThat(http2Settings.headerTableSize()).isEqualTo(maxHeaderTableSize);\n        assertThat(http2Settings.maxHeaderListSize()).isEqualTo(maxHeaderListSize);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/push/PushAuthHandlerTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.push;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.buffer.Unpooled;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.DefaultFullHttpRequest;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpHeaderNames;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpVersion;\nimport org.junit.jupiter.api.Test;\n\nclass PushAuthHandlerTest {\n    @Test\n    void testIsInvalidOrigin() {\n        ZuulPushAuthHandlerTest authHandler = new ZuulPushAuthHandlerTest();\n\n        DefaultFullHttpRequest request =\n                new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, \"/ws\", Unpooled.buffer());\n\n        // Invalid input\n        assertThat(authHandler.isInvalidOrigin(request)).isTrue();\n        request.headers().add(HttpHeaderNames.ORIGIN, \"zuul-push.foo.com\");\n        assertThat(authHandler.isInvalidOrigin(request)).isTrue();\n\n        // Valid input\n        request.headers().remove(HttpHeaderNames.ORIGIN);\n        request.headers().add(HttpHeaderNames.ORIGIN, \"zuul-push.netflix.com\");\n        assertThat(authHandler.isInvalidOrigin(request)).isFalse();\n    }\n\n    class ZuulPushAuthHandlerTest extends PushAuthHandler {\n        public ZuulPushAuthHandlerTest() {\n            super(\"/ws\", \".netflix.com\");\n        }\n\n        @Override\n        protected boolean isDelayedAuth(FullHttpRequest req, ChannelHandlerContext ctx) {\n            return false;\n        }\n\n        @Override\n        protected PushUserAuth doAuth(FullHttpRequest req, ChannelHandlerContext ctx) {\n            return null;\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/push/PushConnectionRegistryTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.push;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport java.util.List;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass PushConnectionRegistryTest {\n    private PushConnectionRegistry pushConnectionRegistry;\n\n    private PushConnection pushConnection;\n\n    @BeforeEach\n    void setUp() {\n        pushConnectionRegistry = new PushConnectionRegistry();\n        pushConnection = mock(PushConnection.class);\n    }\n\n    @Test\n    void testPutAndGet() {\n        assertThat(pushConnectionRegistry.get(\"clientId1\")).isNull();\n\n        pushConnectionRegistry.put(\"clientId1\", pushConnection);\n\n        assertThat(pushConnectionRegistry.get(\"clientId1\")).isEqualTo(pushConnection);\n    }\n\n    @Test\n    void testGetAll() {\n        pushConnectionRegistry.put(\"clientId1\", pushConnection);\n        pushConnectionRegistry.put(\"clientId2\", pushConnection);\n\n        List<PushConnection> connections = pushConnectionRegistry.getAll();\n\n        assertThat(connections.size()).isEqualTo(2);\n    }\n\n    @Test\n    void testMintNewSecureToken() {\n        String token = pushConnectionRegistry.mintNewSecureToken();\n\n        assertThat(token).isNotNull();\n        assertThat(token.length()).isEqualTo(20); // 15 bytes become 20 characters when Base64-encoded\n    }\n\n    @Test\n    void testPutAssignsTokenToConnection() {\n        pushConnectionRegistry.put(\"clientId1\", pushConnection);\n\n        verify(pushConnection).setSecureToken(anyString());\n    }\n\n    @Test\n    void testRemove() {\n        pushConnectionRegistry.put(\"clientId1\", pushConnection);\n\n        assertThat(pushConnectionRegistry.remove(\"clientId1\")).isEqualTo(pushConnection);\n        assertThat(pushConnectionRegistry.get(\"clientId1\")).isNull();\n    }\n\n    @Test\n    void testSize() {\n        assertThat(pushConnectionRegistry.size()).isEqualTo(0);\n\n        pushConnectionRegistry.put(\"clientId1\", pushConnection);\n\n        assertThat(pushConnectionRegistry.size()).isEqualTo(1);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/push/PushMessageSenderInitializerTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.push;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\n\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link PushMessageSenderInitializer}.\n */\nclass PushMessageSenderInitializerTest {\n    private PushMessageSenderInitializer initializer;\n    private Channel channel;\n    private ChannelHandler handler;\n\n    @BeforeEach\n    void setUp() {\n        handler = mock(ChannelHandler.class); // Initialize mock handler\n\n        initializer = new PushMessageSenderInitializer() {\n            @Override\n            protected void addPushMessageHandlers(ChannelPipeline pipeline) {\n                pipeline.addLast(\"mockHandler\", handler);\n            }\n        };\n\n        channel = new EmbeddedChannel();\n    }\n\n    @Test\n    void testInitChannel() throws Exception {\n        initializer.initChannel(channel);\n\n        assertThat(channel.pipeline().context(HttpServerCodec.class)).isNotNull();\n        assertThat(channel.pipeline().context(HttpObjectAggregator.class)).isNotNull();\n        assertThat(channel.pipeline().get(\"mockHandler\")).isNotNull();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/push/PushRegistrationHandlerTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.netty.server.push;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.anyLong;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.verify;\n\nimport com.google.common.util.concurrent.MoreExecutors;\nimport io.netty.channel.Channel;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelFutureListener;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.DefaultEventLoop;\nimport io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.util.concurrent.ScheduledFuture;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentCaptor;\nimport org.mockito.Captor;\nimport org.mockito.Mock;\nimport org.mockito.Mockito;\nimport org.mockito.MockitoAnnotations;\n\n/**\n * @author Justin Guerra\n * @since 8/31/22\n */\nclass PushRegistrationHandlerTest {\n\n    private static ExecutorService EXECUTOR;\n\n    @Captor\n    private ArgumentCaptor<Runnable> scheduledCaptor;\n\n    @Captor\n    private ArgumentCaptor<Object> writeCaptor;\n\n    @Mock\n    private ChannelHandlerContext context;\n\n    @Mock\n    private ChannelFuture channelFuture;\n\n    @Mock\n    private ChannelPipeline pipelineMock;\n\n    @Mock\n    private Channel channel;\n\n    private PushConnectionRegistry registry;\n    private PushRegistrationHandler handler;\n    private DefaultEventLoop eventLoopSpy;\n    private TestAuth successfulAuth;\n\n    @BeforeAll\n    static void classSetup() {\n        EXECUTOR = Executors.newSingleThreadExecutor();\n    }\n\n    @AfterAll\n    static void classCleanup() {\n        MoreExecutors.shutdownAndAwaitTermination(EXECUTOR, 5, TimeUnit.SECONDS);\n    }\n\n    @BeforeEach\n    void setup() {\n        MockitoAnnotations.openMocks(this);\n        registry = new PushConnectionRegistry();\n        handler = new PushRegistrationHandler(registry, PushProtocol.WEBSOCKET);\n        successfulAuth = new TestAuth(true);\n\n        eventLoopSpy = spy(new DefaultEventLoop(EXECUTOR));\n        doReturn(eventLoopSpy).when(context).executor();\n        doReturn(channelFuture).when(context).writeAndFlush(writeCaptor.capture());\n        doReturn(pipelineMock).when(context).pipeline();\n        doReturn(channel).when(context).channel();\n    }\n\n    @Test\n    void closeIfNotAuthenticated() throws Exception {\n        doHandshakeComplete();\n\n        Runnable scheduledTask = scheduledCaptor.getValue();\n        scheduledTask.run();\n\n        validateConnectionClosed(1000, \"Server closed connection\");\n    }\n\n    @Test\n    void authFailed() throws Exception {\n        doHandshakeComplete();\n        handler.userEventTriggered(context, new TestAuth(false));\n        validateConnectionClosed(1008, \"Auth failed\");\n    }\n\n    @Test\n    void authSuccess() throws Exception {\n        doHandshakeComplete();\n        authenticateChannel();\n    }\n\n    @Test\n    void requestClientToCloseInactiveConnection() throws Exception {\n        doHandshakeComplete();\n        Mockito.reset(eventLoopSpy);\n        authenticateChannel();\n        verify(eventLoopSpy).schedule(scheduledCaptor.capture(), anyLong(), eq(TimeUnit.SECONDS));\n        Runnable requestClientToClose = scheduledCaptor.getValue();\n\n        requestClientToClose.run();\n        validateConnectionClosed(1000, \"Server closed connection\");\n    }\n\n    @Test\n    void requestClientToClose() throws Exception {\n        doHandshakeComplete();\n        Mockito.reset(eventLoopSpy);\n        authenticateChannel();\n        verify(eventLoopSpy).schedule(scheduledCaptor.capture(), anyLong(), eq(TimeUnit.SECONDS));\n        Runnable requestClientToClose = scheduledCaptor.getValue();\n\n        int taskListSize = handler.getScheduledFutures().size();\n        doReturn(true).when(channel).isActive();\n        requestClientToClose.run();\n        assertThat(handler.getScheduledFutures().size()).isEqualTo(taskListSize + 1);\n        Object capture = writeCaptor.getValue();\n        assertThat(capture instanceof TextWebSocketFrame).isTrue();\n        TextWebSocketFrame frame = (TextWebSocketFrame) capture;\n        assertThat(frame.text()).isEqualTo(\"_CLOSE_\");\n    }\n\n    @Test\n    void channelInactiveCancelsTasks() throws Exception {\n        doHandshakeComplete();\n        TestAuth testAuth = new TestAuth(true);\n        authenticateChannel();\n\n        List<ScheduledFuture<?>> copyOfFutures = new ArrayList<>(handler.getScheduledFutures());\n\n        handler.channelInactive(context);\n        assertThat(registry.get(testAuth.getClientIdentity())).isNull();\n        assertThat(handler.getScheduledFutures().isEmpty()).isTrue();\n        copyOfFutures.forEach(f -> assertThat(f.isCancelled()).isTrue());\n        verify(context).close();\n    }\n\n    private void doHandshakeComplete() throws Exception {\n        handler.userEventTriggered(context, PushProtocol.WEBSOCKET.getHandshakeCompleteEvent());\n        assertThat(handler.getPushConnection()).isNotNull();\n        verify(eventLoopSpy).schedule(scheduledCaptor.capture(), anyLong(), eq(TimeUnit.SECONDS));\n    }\n\n    private void authenticateChannel() throws Exception {\n        handler.userEventTriggered(context, successfulAuth);\n        assertThat(registry.get(successfulAuth.getClientIdentity())).isNotNull();\n        assertThat(handler.getScheduledFutures().size()).isEqualTo(2);\n        verify(pipelineMock).remove(PushAuthHandler.NAME);\n    }\n\n    private void validateConnectionClosed(int expected, String messaged) {\n        Object capture = writeCaptor.getValue();\n        assertThat(capture instanceof CloseWebSocketFrame).isTrue();\n        CloseWebSocketFrame closeFrame = (CloseWebSocketFrame) capture;\n        assertThat(closeFrame.statusCode()).isEqualTo(expected);\n        assertThat(closeFrame.reasonText()).isEqualTo(messaged);\n        verify(channelFuture).addListener(ChannelFutureListener.CLOSE);\n    }\n\n    private static class TestAuth implements PushUserAuth {\n\n        private final boolean success;\n\n        public TestAuth(boolean success) {\n            this.success = success;\n        }\n\n        @Override\n        public boolean isSuccess() {\n            return success;\n        }\n\n        @Override\n        public int statusCode() {\n            return 0;\n        }\n\n        @Override\n        public String getClientIdentity() {\n            return \"whatever\";\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/server/ssl/SslHandshakeInfoHandlerTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.server.ssl;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.netty.common.SourceAddressChannelHandler;\nimport com.netflix.netty.common.ssl.SslHandshakeInfo;\nimport com.netflix.spectator.api.Counter;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.passport.PassportState;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelOutboundHandlerAdapter;\nimport io.netty.channel.ChannelPromise;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.ssl.ClientAuth;\nimport io.netty.handler.ssl.SslContextBuilder;\nimport io.netty.handler.ssl.SslHandler;\nimport io.netty.handler.ssl.SslHandshakeCompletionEvent;\nimport io.netty.handler.ssl.util.SelfSignedCertificate;\nimport io.netty.util.ReferenceCountUtil;\nimport java.nio.channels.ClosedChannelException;\nimport java.security.cert.Certificate;\nimport java.security.cert.X509Certificate;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Objects;\nimport javax.net.ssl.ExtendedSSLSession;\nimport javax.net.ssl.SNIHostName;\nimport javax.net.ssl.SNIServerName;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.SSLException;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\n/**\n * Unit tests for {@link SslHandshakeInfoHandler}.\n */\npublic class SslHandshakeInfoHandlerTest {\n\n    @BeforeEach\n    public void setup() {\n        SslHandshakeInfoHandler.SNI_LOGGING_ENABLED =\n                new DynamicBooleanProperty(\"zuul.ssl.handshake.snilogging.enabled\", true);\n    }\n\n    @Test\n    public void sslEarlyHandshakeFailure() throws Exception {\n        EmbeddedChannel clientChannel = new EmbeddedChannel();\n        SSLEngine clientEngine = SslContextBuilder.forClient().build().newEngine(clientChannel.alloc());\n        clientChannel.pipeline().addLast(new SslHandler(clientEngine));\n\n        EmbeddedChannel serverChannel = new EmbeddedChannel();\n        SelfSignedCertificate cert = new SelfSignedCertificate(\"localhorse\");\n        SSLEngine serverEngine =\n                SslContextBuilder.forServer(cert.key(), cert.cert()).build().newEngine(serverChannel.alloc());\n\n        serverChannel.pipeline().addLast(new ChannelOutboundHandlerAdapter() {\n            @Override\n            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {\n                // Simulate an early closure form the client.\n                ReferenceCountUtil.safeRelease(msg);\n                promise.setFailure(new ClosedChannelException());\n            }\n        });\n        serverChannel.pipeline().addLast(new SslHandler(serverEngine));\n        serverChannel.pipeline().addLast(new SslHandshakeInfoHandler());\n\n        Object clientHello = clientChannel.readOutbound();\n        assertThat(clientHello).isNotNull();\n        ReferenceCountUtil.retain(clientHello);\n\n        serverChannel.writeInbound(clientHello);\n\n        // Assert that the handler removes itself from the pipeline, since it was torn down.\n        assertThat(serverChannel.pipeline().context(SslHandshakeInfoHandler.class))\n                .isNull();\n    }\n\n    @Test\n    public void getFailureCauses() {\n        SslHandshakeInfoHandler handler = new SslHandshakeInfoHandler();\n\n        RuntimeException noMessage = new RuntimeException();\n        assertThat(handler.getFailureCause(noMessage)).isEqualTo(noMessage.toString());\n\n        RuntimeException withMessage = new RuntimeException(\"some unexpected message\");\n        assertThat(handler.getFailureCause(withMessage)).isEqualTo(\"some unexpected message\");\n\n        RuntimeException openSslMessage = new RuntimeException(\"javax.net.ssl.SSLHandshakeException: error:1000008e:SSL\"\n                + \" routines:OPENSSL_internal:DIGEST_CHECK_FAILED\");\n\n        assertThat(handler.getFailureCause(openSslMessage)).isEqualTo(\"DIGEST_CHECK_FAILED\");\n    }\n\n    @Test\n    public void handshakeFailureWithSSLException() throws Exception {\n        Registry registry = new DefaultRegistry();\n\n        // Mock SSL engine and session\n        SSLEngine sslEngine = mock(SSLEngine.class);\n        ExtendedSSLSession sslSession = mock(ExtendedSSLSession.class);\n        X509Certificate serverCert = mock(X509Certificate.class);\n\n        // Setup SSL session\n        when(sslEngine.getSession()).thenReturn(sslSession);\n        when(sslEngine.getNeedClientAuth()).thenReturn(false);\n        when(sslEngine.getWantClientAuth()).thenReturn(false);\n        when(sslSession.getProtocol()).thenReturn(\"TLSv1.3\");\n        when(sslSession.getCipherSuite()).thenReturn(\"TLS_AES_256_GCM_SHA384\");\n        when(sslSession.getLocalCertificates()).thenReturn(new Certificate[] {serverCert});\n        when(sslSession.getPeerCertificates()).thenReturn(new Certificate[0]);\n\n        // Setup SNI with www.netflix.com\n        List<SNIServerName> sniNames = Arrays.asList(new SNIHostName(\"www.netflix.com\"));\n        when(sslSession.getRequestedServerNames()).thenReturn(sniNames);\n\n        // Create channel and context\n        EmbeddedChannel channel = new EmbeddedChannel();\n        CurrentPassport.fromChannel(channel);\n        channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(\"192.168.1.1\");\n\n        SslHandshakeInfoHandler handler = new SslHandshakeInfoHandler(registry, false);\n\n        SslHandler sslHandler = mock(SslHandler.class);\n        when(sslHandler.engine()).thenReturn(sslEngine);\n        channel.pipeline().addLast(\"ssl\", sslHandler);\n        channel.pipeline().addLast(handler);\n\n        ChannelHandlerContext ctx = channel.pipeline().context(handler);\n\n        // Create a failed handshake event\n        SSLException sslException = new SSLException(\"Received fatal alert: certificate_unknown\");\n        SslHandshakeCompletionEvent failedEvent = new SslHandshakeCompletionEvent(sslException);\n\n        // Trigger the event\n        handler.userEventTriggered(ctx, failedEvent);\n\n        // Verify success counter was incremented\n        assertThat(registry.counter(\n                                \"server.ssl.handshake\",\n                                \"success\",\n                                \"false\",\n                                \"sni\",\n                                \"www.netflix.com\",\n                                \"failure_cause\",\n                                \"Received fatal alert: certificate_unknown\")\n                        .count())\n                .isEqualTo(1);\n\n        // Verify handler was removed from pipeline\n        assertThat(channel.pipeline().context(SslHandshakeInfoHandler.class)).isNull();\n    }\n\n    @Test\n    public void handshakeFailureWithClosedChannelException() throws Exception {\n        // Setup mocks\n        Registry registry = mock(Registry.class);\n        Counter counter = mock(Counter.class);\n        when(registry.counter(anyString())).thenReturn(counter);\n\n        // Create handler with mocked registry\n        SslHandshakeInfoHandler handler = new SslHandshakeInfoHandler(registry, false);\n\n        // Create channel and context\n        EmbeddedChannel channel = new EmbeddedChannel();\n        CurrentPassport.fromChannel(channel);\n        channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(\"192.168.1.1\");\n        channel.pipeline().addLast(handler);\n\n        ChannelHandlerContext ctx = channel.pipeline().context(handler);\n\n        // Create a failed handshake event with ClosedChannelException\n        ClosedChannelException closedException = new ClosedChannelException();\n        SslHandshakeCompletionEvent failedEvent = new SslHandshakeCompletionEvent(closedException);\n\n        // Trigger the event\n        handler.userEventTriggered(ctx, failedEvent);\n\n        // Verify handler was removed from pipeline\n        assertThat(channel.pipeline().context(SslHandshakeInfoHandler.class)).isNull();\n    }\n\n    @Test\n    public void handshakeFailureWithHandshakeTimeout() throws Exception {\n        // Setup mocks\n        Registry registry = mock(Registry.class);\n        Counter counter = mock(Counter.class);\n        when(registry.counter(anyString())).thenReturn(counter);\n\n        // Create handler with mocked registry\n        SslHandshakeInfoHandler handler = new SslHandshakeInfoHandler(registry, false);\n\n        // Create channel and context\n        EmbeddedChannel channel = new EmbeddedChannel();\n        CurrentPassport.fromChannel(channel);\n        channel.attr(SourceAddressChannelHandler.ATTR_SOURCE_ADDRESS).set(\"192.168.1.1\");\n        channel.pipeline().addLast(handler);\n\n        ChannelHandlerContext ctx = channel.pipeline().context(handler);\n\n        // Create a failed handshake event with timeout\n        SSLException timeoutException = new SSLException(\"handshake timed out\");\n        SslHandshakeCompletionEvent failedEvent = new SslHandshakeCompletionEvent(timeoutException);\n\n        // Trigger the event\n        handler.userEventTriggered(ctx, failedEvent);\n\n        // Verify handler was removed from pipeline\n        assertThat(channel.pipeline().context(SslHandshakeInfoHandler.class)).isNull();\n\n        // Note: Counters should NOT be incremented for timeout as it's handled separately\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = {\"www.netflix.com\", \"\"})\n    public void handshakeSuccessWithSNI(String sni) throws Exception {\n        Registry registry = new DefaultRegistry();\n\n        // Create handler\n        SslHandshakeInfoHandler handler = new SslHandshakeInfoHandler(registry, false);\n\n        // Create channel\n        EmbeddedChannel channel = new EmbeddedChannel();\n        CurrentPassport.fromChannel(channel);\n\n        // Mock SSL engine and session\n        SSLEngine sslEngine = mock(SSLEngine.class);\n        ExtendedSSLSession sslSession = mock(ExtendedSSLSession.class);\n        X509Certificate serverCert = mock(X509Certificate.class);\n\n        // Setup SSL session\n        when(sslEngine.getSession()).thenReturn(sslSession);\n        when(sslEngine.getNeedClientAuth()).thenReturn(false);\n        when(sslEngine.getWantClientAuth()).thenReturn(false);\n        when(sslSession.getProtocol()).thenReturn(\"TLSv1.3\");\n        when(sslSession.getCipherSuite()).thenReturn(\"TLS_AES_256_GCM_SHA384\");\n        when(sslSession.getLocalCertificates()).thenReturn(new Certificate[] {serverCert});\n        when(sslSession.getPeerCertificates()).thenReturn(new Certificate[0]);\n\n        if (!Objects.equals(sni, \"\")) {\n            List<SNIServerName> sniNames = Arrays.asList(new SNIHostName(\"www.netflix.com\"));\n            when(sslSession.getRequestedServerNames()).thenReturn(sniNames);\n        }\n\n        // Add SSL handler and info handler to pipeline\n        SslHandler sslHandler = mock(SslHandler.class);\n        when(sslHandler.engine()).thenReturn(sslEngine);\n        channel.pipeline().addLast(\"ssl\", sslHandler);\n        channel.pipeline().addLast(handler);\n\n        ChannelHandlerContext ctx = channel.pipeline().context(handler);\n\n        // Create successful handshake event\n        SslHandshakeCompletionEvent successEvent = SslHandshakeCompletionEvent.SUCCESS;\n\n        // Trigger the event\n        handler.userEventTriggered(ctx, successEvent);\n\n        String verifySni = Objects.equals(sni, \"\") ? \"none\" : sni;\n\n        assertThat(registry.counter(\n                                \"server.ssl.handshake\",\n                                \"success\",\n                                \"true\",\n                                \"sni\",\n                                verifySni,\n                                \"protocol\",\n                                \"TLSv1.3\",\n                                \"ciphersuite\",\n                                \"TLS_AES_256_GCM_SHA384\",\n                                \"clientauth\",\n                                \"NONE\",\n                                \"namedgroup\",\n                                \"unknown\")\n                        .count())\n                .isEqualTo(1);\n\n        SslHandshakeInfo info =\n                channel.attr(SslHandshakeInfoHandler.ATTR_SSL_INFO).get();\n        assertThat(info).isNotNull();\n        assertThat(info.getRequestedSni()).isEqualTo(verifySni);\n        assertThat(info.getProtocol()).isEqualTo(\"TLSv1.3\");\n        assertThat(info.getCipherSuite()).isEqualTo(\"TLS_AES_256_GCM_SHA384\");\n        assertThat(info.getNamedGroup()).isNull();\n        assertThat(info.getClientAuthRequirement()).isEqualTo(ClientAuth.NONE);\n        assertThat(info.getClientCertificate()).isNull();\n\n        assertThat(CurrentPassport.fromChannel(channel).getState())\n                .isEqualTo(PassportState.SERVER_CH_SSL_HANDSHAKE_COMPLETE);\n\n        assertThat(channel.pipeline().context(SslHandshakeInfoHandler.class)).isNull();\n    }\n\n    @Test\n    public void handshakeSuccessWithNamedGroup() throws Exception {\n        Registry registry = new DefaultRegistry();\n\n        SslHandshakeInfoHandler handler = new SslHandshakeInfoHandler(registry, false);\n\n        EmbeddedChannel channel = new EmbeddedChannel();\n        CurrentPassport.fromChannel(channel);\n\n        channel.attr(SslHandshakeInfoHandler.ATTR_SSL_NAMED_GROUP).set(\"x25519\");\n\n        SSLEngine sslEngine = mock(SSLEngine.class);\n        ExtendedSSLSession sslSession = mock(ExtendedSSLSession.class);\n        X509Certificate serverCert = mock(X509Certificate.class);\n\n        when(sslEngine.getSession()).thenReturn(sslSession);\n        when(sslEngine.getNeedClientAuth()).thenReturn(false);\n        when(sslEngine.getWantClientAuth()).thenReturn(false);\n        when(sslSession.getProtocol()).thenReturn(\"TLSv1.3\");\n        when(sslSession.getCipherSuite()).thenReturn(\"TLS_AES_128_GCM_SHA256\");\n        when(sslSession.getLocalCertificates()).thenReturn(new Certificate[] {serverCert});\n        when(sslSession.getPeerCertificates()).thenReturn(new Certificate[0]);\n        when(sslSession.getRequestedServerNames()).thenReturn(List.of(new SNIHostName(\"www.netflix.com\")));\n\n        SslHandler sslHandler = mock(SslHandler.class);\n        when(sslHandler.engine()).thenReturn(sslEngine);\n        channel.pipeline().addLast(\"ssl\", sslHandler);\n        channel.pipeline().addLast(handler);\n\n        ChannelHandlerContext ctx = channel.pipeline().context(handler);\n        handler.userEventTriggered(ctx, SslHandshakeCompletionEvent.SUCCESS);\n\n        assertThat(registry.counter(\n                                \"server.ssl.handshake\",\n                                \"success\",\n                                \"true\",\n                                \"sni\",\n                                \"www.netflix.com\",\n                                \"protocol\",\n                                \"TLSv1.3\",\n                                \"ciphersuite\",\n                                \"TLS_AES_128_GCM_SHA256\",\n                                \"clientauth\",\n                                \"NONE\",\n                                \"namedgroup\",\n                                \"x25519\")\n                        .count())\n                .isEqualTo(1);\n\n        SslHandshakeInfo info =\n                channel.attr(SslHandshakeInfoHandler.ATTR_SSL_INFO).get();\n        assertThat(info).isNotNull();\n        assertThat(info.getNamedGroup()).isEqualTo(\"x25519\");\n        assertThat(info.getProtocol()).isEqualTo(\"TLSv1.3\");\n        assertThat(info.getCipherSuite()).isEqualTo(\"TLS_AES_128_GCM_SHA256\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/ssl/BaseSslContextFactoryTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ssl;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.handler.ssl.OpenSsl;\nimport io.netty.handler.ssl.SslProvider;\nimport java.lang.reflect.Field;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests for {@link BaseSslContextFactory}.\n */\nclass BaseSslContextFactoryTest {\n    @Test\n    void testDefaultSslProviderIsOpenSsl() {\n        assertThat(BaseSslContextFactory.chooseSslProvider()).isEqualTo(SslProvider.OPENSSL);\n    }\n\n    @Test\n    void defaultNamedGroupsMatchNettyDefaults() throws Exception {\n        Field nettyDefaultsField = OpenSsl.class.getDeclaredField(\"DEFAULT_NAMED_GROUPS\");\n        nettyDefaultsField.setAccessible(true);\n        String[] nettyDefaultNamedGroups = (String[]) nettyDefaultsField.get(null);\n\n        Field zuulField = BaseSslContextFactory.class.getDeclaredField(\"DEFAULT_NAMED_GROUPS\");\n        zuulField.setAccessible(true);\n        String[] zuulGroups = (String[]) zuulField.get(null);\n\n        assertThat(zuulGroups).as(\"should match netty defaults\").containsExactly(nettyDefaultNamedGroups);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/ssl/ClientSslContextFactoryTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ssl;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.spectator.api.DefaultRegistry;\nimport io.netty.handler.ssl.OpenSslClientContext;\nimport io.netty.handler.ssl.SslContext;\nimport java.util.Arrays;\nimport java.util.List;\nimport javax.net.ssl.SSLSessionContext;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests for {@link ClientSslContextFactory}.\n */\nclass ClientSslContextFactoryTest {\n\n    @Test\n    void enableTls13() {\n        String[] protos = ClientSslContextFactory.maybeAddTls13(true, \"TLSv1.2\");\n\n        assertThat(Arrays.asList(protos)).isEqualTo(Arrays.asList(\"TLSv1.3\", \"TLSv1.2\"));\n    }\n\n    @Test\n    void disableTls13() {\n        String[] protos = ClientSslContextFactory.maybeAddTls13(false, \"TLSv1.2\");\n\n        assertThat(Arrays.asList(protos)).isEqualTo(Arrays.asList(\"TLSv1.2\"));\n    }\n\n    @Test\n    void testGetSslContext() {\n        ClientSslContextFactory factory = new ClientSslContextFactory(new DefaultRegistry());\n        SslContext sslContext = factory.getClientSslContext();\n        assertThat(sslContext).isInstanceOf(OpenSslClientContext.class);\n        assertThat(sslContext.isClient()).isTrue();\n        assertThat(sslContext.isServer()).isFalse();\n        SSLSessionContext sessionContext = sslContext.sessionContext();\n        assertThat(sessionContext.getSessionCacheSize()).isEqualTo(20480);\n        assertThat(sessionContext.getSessionTimeout()).isEqualTo(300);\n    }\n\n    @Test\n    void testGetProtocols() {\n        ClientSslContextFactory factory = new ClientSslContextFactory(new DefaultRegistry());\n        assertThat(factory.getProtocols()).isEqualTo(new String[] {\"TLSv1.2\"});\n    }\n\n    @Test\n    void testGetCiphers() throws Exception {\n        ClientSslContextFactory factory = new ClientSslContextFactory(new DefaultRegistry());\n        List<String> ciphers = factory.getCiphers();\n        assertThat(ciphers).isNotEmpty();\n        assertThat(ciphers).doesNotHaveDuplicates();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/ssl/OpenSslTest.java",
    "content": "/*\n * Copyright 2023 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.ssl;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport io.netty.handler.ssl.OpenSsl;\nimport io.netty.handler.ssl.SslProvider;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nclass OpenSslTest {\n    @BeforeEach\n    void beforeEach() {\n        OpenSsl.ensureAvailability();\n        assertThat(OpenSsl.isAvailable()).isTrue();\n    }\n\n    @Test\n    void testBoringSsl() {\n        assertThat(OpenSsl.versionString()).isEqualTo(\"BoringSSL\");\n        assertThat(SslProvider.isAlpnSupported(SslProvider.OPENSSL)).isTrue();\n        assertThat(SslProvider.isTlsv13Supported(SslProvider.OPENSSL)).isTrue();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/timeouts/HttpHeadersTimeoutHandlerTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.timeouts;\n\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nimport com.netflix.spectator.api.Counter;\nimport io.netty.channel.embedded.EmbeddedChannel;\nimport io.netty.handler.codec.http.DefaultFullHttpRequest;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpServerCodec;\nimport io.netty.handler.codec.http.HttpVersion;\nimport java.nio.channels.ClosedChannelException;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n@ExtendWith(MockitoExtension.class)\npublic class HttpHeadersTimeoutHandlerTest {\n    @Mock\n    private Counter timeoutCounter;\n\n    @Test\n    public void testTimeout() {\n        EmbeddedChannel ch = new EmbeddedChannel(\n                new HttpServerCodec(),\n                new HttpHeadersTimeoutHandler.InboundHandler(() -> true, () -> 0, timeoutCounter, null));\n        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\");\n\n        assertThrows(ClosedChannelException.class, () -> ch.writeInbound(request));\n\n        verify(timeoutCounter).increment();\n        assertNull(ch.attr(HttpHeadersTimeoutHandler.HTTP_HEADERS_READ_TIMEOUT_FUTURE)\n                .get());\n        assertNull(\n                ch.attr(HttpHeadersTimeoutHandler.HTTP_HEADERS_READ_START_TIME).get());\n    }\n\n    @Test\n    public void testNoTimeout() {\n        EmbeddedChannel ch = new EmbeddedChannel(\n                new HttpServerCodec(),\n                new HttpHeadersTimeoutHandler.InboundHandler(() -> true, () -> 10000, timeoutCounter, null));\n        DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/\");\n\n        ch.writeInbound(request);\n\n        verify(timeoutCounter, never()).increment();\n        assertNull(ch.attr(HttpHeadersTimeoutHandler.HTTP_HEADERS_READ_TIMEOUT_FUTURE)\n                .get());\n        assertNull(\n                ch.attr(HttpHeadersTimeoutHandler.HTTP_HEADERS_READ_START_TIME).get());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/netty/timeouts/OriginTimeoutManagerTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.netty.timeouts;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.client.config.CommonClientConfigKey;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.zuul.context.CommonContextKeys;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.origins.NettyOrigin;\nimport java.time.Duration;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mock;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Origin Timeout Manager Test\n *\n * @author Arthur Gonigberg\n * @since March 23, 2021\n */\n@ExtendWith(MockitoExtension.class)\nclass OriginTimeoutManagerTest {\n\n    @Mock\n    private NettyOrigin origin;\n\n    @Mock\n    private HttpRequestMessage request;\n\n    private SessionContext context;\n    private IClientConfig requestConfig;\n    private IClientConfig originConfig;\n\n    private OriginTimeoutManager originTimeoutManager;\n\n    @BeforeEach\n    void before() {\n        originTimeoutManager = new OriginTimeoutManager(origin);\n\n        context = new SessionContext();\n        when(request.getContext()).thenReturn(context);\n\n        requestConfig = new DefaultClientConfigImpl();\n        originConfig = new DefaultClientConfigImpl();\n\n        context.put(CommonContextKeys.REST_CLIENT_CONFIG, requestConfig);\n        when(origin.getClientConfig()).thenReturn(originConfig);\n    }\n\n    @Test\n    void computeReadTimeout_default() {\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(OriginTimeoutManager.MAX_OUTBOUND_READ_TIMEOUT_MS.get());\n    }\n\n    @Test\n    void computeReadTimeout_requestOnly() {\n        requestConfig.set(CommonClientConfigKey.ReadTimeout, 1000);\n\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(1000);\n    }\n\n    @Test\n    void computeReadTimeout_originOnly() {\n        originConfig.set(CommonClientConfigKey.ReadTimeout, 1000);\n\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(1000);\n    }\n\n    @Test\n    void computeReadTimeout_bolth_equal() {\n        requestConfig.set(CommonClientConfigKey.ReadTimeout, 1000);\n        originConfig.set(CommonClientConfigKey.ReadTimeout, 1000);\n\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(1000);\n    }\n\n    @Test\n    void computeReadTimeout_bolth_originLower() {\n        requestConfig.set(CommonClientConfigKey.ReadTimeout, 1000);\n        originConfig.set(CommonClientConfigKey.ReadTimeout, 100);\n\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(100);\n    }\n\n    @Test\n    void computeReadTimeout_bolth_requestLower() {\n        requestConfig.set(CommonClientConfigKey.ReadTimeout, 100);\n        originConfig.set(CommonClientConfigKey.ReadTimeout, 1000);\n\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(100);\n    }\n\n    @Test\n    void computeReadTimeout_bolth_enforceMax() {\n        requestConfig.set(\n                CommonClientConfigKey.ReadTimeout,\n                (int) OriginTimeoutManager.MAX_OUTBOUND_READ_TIMEOUT_MS.get() + 1000);\n        originConfig.set(\n                CommonClientConfigKey.ReadTimeout,\n                (int) OriginTimeoutManager.MAX_OUTBOUND_READ_TIMEOUT_MS.get() + 10000);\n\n        Duration timeout = originTimeoutManager.computeReadTimeout(request, 1);\n\n        assertThat(timeout.toMillis()).isEqualTo(OriginTimeoutManager.MAX_OUTBOUND_READ_TIMEOUT_MS.get());\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/niws/RequestAttemptTest.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.niws;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.spy;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.zuul.exception.OutboundErrorType;\nimport com.netflix.zuul.netty.connectionpool.OriginConnectException;\nimport io.netty.handler.codec.http2.DefaultHttp2Connection;\nimport io.netty.handler.codec.http2.Http2Error;\nimport io.netty.handler.codec.http2.Http2Exception;\nimport java.io.IOException;\nimport java.security.cert.CertificateException;\nimport javax.net.ssl.SSLHandshakeException;\nimport org.junit.jupiter.api.Test;\n\npublic class RequestAttemptTest {\n\n    @Test\n    void exceptionHandled() {\n\n        RequestAttempt attempt = new RequestAttempt(1, null, null, \"target\", \"chosen\", 200, null, null, 0, 0, 0);\n        attempt.setException(new RuntimeException(\"runtime failure\"));\n\n        assertThat(attempt.getError()).isEqualTo(\"runtime failure\");\n    }\n\n    @Test\n    void originConnectExceptionUnwrapped() {\n\n        RequestAttempt attempt = new RequestAttempt(1, null, null, \"target\", \"chosen\", 200, null, null, 0, 0, 0);\n        attempt.setException(new OriginConnectException(\n                \"origin connect failure\",\n                new SSLHandshakeException(\"Invalid tls cert\"),\n                OutboundErrorType.CONNECT_ERROR));\n\n        assertThat(attempt.getError()).isEqualTo(\"ORIGIN_CONNECT_ERROR\");\n        assertThat(attempt.getCause()).isEqualTo(\"Invalid tls cert\");\n    }\n\n    @Test\n    void originConnectExceptionWithSSLHandshakeCauseUnwrapped() {\n\n        SSLHandshakeException handshakeException = mock(SSLHandshakeException.class);\n        when(handshakeException.getCause()).thenReturn(new CertificateException(\"Cert doesn't match expected\"));\n\n        RequestAttempt attempt = new RequestAttempt(1, null, null, \"target\", \"chosen\", 200, null, null, 0, 0, 0);\n        attempt.setException(new OriginConnectException(\n                \"origin connect failure\", handshakeException, OutboundErrorType.CONNECT_ERROR));\n\n        assertThat(attempt.getError()).isEqualTo(\"ORIGIN_CONNECT_ERROR\");\n        assertThat(attempt.getCause()).isEqualTo(\"Cert doesn't match expected\");\n    }\n\n    @Test\n    void originConnectExceptionWithCauseNotUnwrapped() {\n        RequestAttempt attempt = new RequestAttempt(1, null, null, \"target\", \"chosen\", 200, null, null, 0, 0, 0);\n        attempt.setException(new OriginConnectException(\n                \"origin connect failure\",\n                new IOException(new RuntimeException(\"socket failure\")),\n                OutboundErrorType.CONNECT_ERROR));\n\n        assertThat(attempt.getError()).isEqualTo(\"ORIGIN_CONNECT_ERROR\");\n        assertThat(attempt.getCause()).isEqualTo(\"java.lang.RuntimeException: socket failure\");\n    }\n\n    @Test\n    void h2ExceptionCauseHandled() {\n        // mock out a real-ish h2 stream exception\n        Exception h2Exception = spy(Http2Exception.streamError(\n                100,\n                Http2Error.REFUSED_STREAM,\n                \"Cannot create stream 100 greater than Last-Stream-ID 99 from GOAWAY.\",\n                new Object[] {100, 99}));\n\n        // mock a stacktrace to ensure we don't actually capture it completely\n        when(h2Exception.getStackTrace()).thenReturn(new StackTraceElement[] {\n            new StackTraceElement(\n                    DefaultHttp2Connection.class.getCanonicalName(),\n                    \"createStream\",\n                    \"DefaultHttp2Connection.java\",\n                    772),\n            new StackTraceElement(\n                    DefaultHttp2Connection.class.getCanonicalName(),\n                    \"checkNewStreamAllowed\",\n                    \"DefaultHttp2Connection.java\",\n                    902)\n        });\n\n        RequestAttempt attempt = new RequestAttempt(1, null, null, \"target\", \"chosen\", 200, null, null, 0, 0, 0);\n        attempt.setException(h2Exception);\n\n        assertThat(attempt.getError())\n                .isEqualTo(\"Cannot create stream 100 greater than Last-Stream-ID 99 from GOAWAY.\");\n        assertThat(attempt.getExceptionType()).isEqualTo(\"StreamException\");\n\n        assertThat(attempt.getCause())\n                .isEqualTo(\n                        \"io.netty.handler.codec.http2.DefaultHttp2Connection.createStream(DefaultHttp2Connection.java:772)\");\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/origins/OriginNameTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.origins;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nimport org.junit.jupiter.api.Test;\n\nclass OriginNameTest {\n    @Test\n    void getAuthority() {\n        OriginName trusted = OriginName.fromVipAndApp(\"woodly-doodly\", \"westerndigital\");\n\n        assertThat(trusted.getAuthority()).isEqualTo(\"westerndigital\");\n    }\n\n    @Test\n    void getMetrics() {\n        OriginName trusted = OriginName.fromVipAndApp(\"WOODLY-doodly\", \"westerndigital\");\n\n        assertThat(trusted.getMetricId()).isEqualTo(\"woodly-doodly\");\n        assertThat(trusted.getNiwsClientName()).isEqualTo(\"WOODLY-doodly\");\n    }\n\n    @Test\n    void equals() {\n        OriginName name1 = OriginName.fromVipAndApp(\"woodly-doodly\", \"westerndigital\");\n        OriginName name2 = OriginName.fromVipAndApp(\"woodly-doodly\", \"westerndigital\", \"woodly-doodly\");\n\n        assertThat(name2).isEqualTo(name1);\n        assertThat(name2.hashCode()).isEqualTo(name1.hashCode());\n    }\n\n    @Test\n    @SuppressWarnings(\"deprecation\")\n    void equals_legacy_niws() {\n        OriginName name1 = OriginName.fromVip(\"woodly-doodly\", \"westerndigital\");\n        OriginName name2 = OriginName.fromVipAndApp(\"woodly-doodly\", \"woodly\", \"westerndigital\");\n\n        assertThat(name2).isEqualTo(name1);\n        assertThat(name2.hashCode()).isEqualTo(name1.hashCode());\n    }\n\n    @Test\n    void equals_legacy() {\n        OriginName name1 = OriginName.fromVip(\"woodly-doodly\");\n        OriginName name2 = OriginName.fromVipAndApp(\"woodly-doodly\", \"woodly\", \"woodly-doodly\");\n\n        assertThat(name2).isEqualTo(name1);\n        assertThat(name2.hashCode()).isEqualTo(name1.hashCode());\n    }\n\n    @Test\n    void noNull() {\n        assertThatThrownBy(() -> OriginName.fromVipAndApp(null, \"app\")).isInstanceOf(NullPointerException.class);\n        assertThatThrownBy(() -> OriginName.fromVipAndApp(\"vip\", null)).isInstanceOf(NullPointerException.class);\n        assertThatThrownBy(() -> OriginName.fromVipAndApp(null, \"app\", \"niws\"))\n                .isInstanceOf(NullPointerException.class);\n        assertThatThrownBy(() -> OriginName.fromVipAndApp(\"vip\", null, \"niws\"))\n                .isInstanceOf(NullPointerException.class);\n        assertThatThrownBy(() -> OriginName.fromVipAndApp(\"vip\", \"app\", null)).isInstanceOf(NullPointerException.class);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/passport/CurrentPassportTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.passport;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\n\nclass CurrentPassportTest {\n    @Test\n    void test_findEachPairOf_1pair() {\n        CurrentPassport passport = CurrentPassport.parseFromToString(\n                \"CurrentPassport {start_ms=0, [+0=IN_REQ_HEADERS_RECEIVED, +5=FILTERS_INBOUND_START,\"\n                        + \" +50=IN_REQ_LAST_CONTENT_RECEIVED, +200=MISC_IO_START, +250=MISC_IO_STOP,\"\n                        + \" +350=FILTERS_INBOUND_END, +1117794707=NOW]}\");\n\n        List<StartAndEnd> pairs = passport.findEachPairOf(\n                PassportState.IN_REQ_HEADERS_RECEIVED, PassportState.IN_REQ_LAST_CONTENT_RECEIVED);\n        assertThat(pairs.size()).isEqualTo(1);\n        assertThat(pairs.get(0).startTime).isEqualTo(0);\n        assertThat(pairs.get(0).endTime).isEqualTo(50);\n    }\n\n    @Test\n    void test_findEachPairOf_2pairs() {\n        CurrentPassport passport = CurrentPassport.parseFromToString(\n                \"CurrentPassport {start_ms=0, [+0=IN_REQ_HEADERS_RECEIVED, +5=FILTERS_INBOUND_START,\"\n                        + \" +50=IN_REQ_LAST_CONTENT_RECEIVED, +200=MISC_IO_START, +250=MISC_IO_STOP, +300=MISC_IO_START,\"\n                        + \" +350=FILTERS_INBOUND_END, +400=MISC_IO_STOP, +1117794707=NOW]}\");\n\n        List<StartAndEnd> pairs = passport.findEachPairOf(PassportState.MISC_IO_START, PassportState.MISC_IO_STOP);\n        assertThat(pairs.size()).isEqualTo(2);\n        assertThat(pairs.get(0).startTime).isEqualTo(200);\n        assertThat(pairs.get(0).endTime).isEqualTo(250);\n        assertThat(pairs.get(1).startTime).isEqualTo(300);\n        assertThat(pairs.get(1).endTime).isEqualTo(400);\n    }\n\n    @Test\n    void test_findEachPairOf_noneFound() {\n        CurrentPassport passport = CurrentPassport.parseFromToString(\n                \"CurrentPassport {start_ms=0, [+0=FILTERS_INBOUND_START, +200=MISC_IO_START, +1117794707=NOW]}\");\n\n        List<StartAndEnd> pairs = passport.findEachPairOf(\n                PassportState.IN_REQ_HEADERS_RECEIVED, PassportState.IN_REQ_LAST_CONTENT_RECEIVED);\n        assertThat(pairs.size()).isEqualTo(0);\n    }\n\n    @Test\n    void test_findEachPairOf_endButNoStart() {\n        CurrentPassport passport = CurrentPassport.parseFromToString(\n                \"CurrentPassport {start_ms=0, [+0=FILTERS_INBOUND_START, +50=IN_REQ_LAST_CONTENT_RECEIVED,\"\n                        + \" +200=MISC_IO_START, +1117794707=NOW]}\");\n\n        List<StartAndEnd> pairs = passport.findEachPairOf(\n                PassportState.IN_REQ_HEADERS_RECEIVED, PassportState.IN_REQ_LAST_CONTENT_RECEIVED);\n        assertThat(pairs.size()).isEqualTo(0);\n    }\n\n    @Test\n    void test_findEachPairOf_wrongOrder() {\n        CurrentPassport passport = CurrentPassport.parseFromToString(\n                \"CurrentPassport {start_ms=0, [+0=FILTERS_INBOUND_START, +50=IN_REQ_LAST_CONTENT_RECEIVED,\"\n                        + \" +200=MISC_IO_START, +250=IN_REQ_HEADERS_RECEIVED, +1117794707=NOW]}\");\n\n        List<StartAndEnd> pairs = passport.findEachPairOf(\n                PassportState.IN_REQ_HEADERS_RECEIVED, PassportState.IN_REQ_LAST_CONTENT_RECEIVED);\n        assertThat(pairs.size()).isEqualTo(0);\n    }\n\n    @Test\n    void testFindBackwards() {\n        CurrentPassport passport = CurrentPassport.parseFromToString(\n                \"CurrentPassport {start_ms=0, [+0=FILTERS_INBOUND_START, +50=IN_REQ_LAST_CONTENT_RECEIVED,\"\n                        + \" +200=MISC_IO_START, +250=IN_REQ_HEADERS_RECEIVED, +1117794707=NOW]}\");\n\n        assertThat(passport.findStateBackwards(PassportState.MISC_IO_START).getTime())\n                .isEqualTo(200);\n    }\n\n    @Test\n    void testGetStateWithNoHistory() {\n        assertThat(CurrentPassport.create().getState()).isNull();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsDataTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Unit tests for {@link ErrorStatsData}.\n */\n@ExtendWith(MockitoExtension.class)\nclass ErrorStatsDataTest {\n\n    @Test\n    void testUpdateStats() {\n        ErrorStatsData sd = new ErrorStatsData(\"route\", \"test\");\n        sd.update();\n        assertThat(sd.getCount()).isEqualTo(1);\n        sd.update();\n        assertThat(sd.getCount()).isEqualTo(2);\n    }\n\n    @Test\n    void testEquals() {\n        ErrorStatsData sd = new ErrorStatsData(\"route\", \"test\");\n        ErrorStatsData sd1 = new ErrorStatsData(\"route\", \"test\");\n        ErrorStatsData sd2 = new ErrorStatsData(\"route\", \"test1\");\n        ErrorStatsData sd3 = new ErrorStatsData(\"route\", \"test\");\n\n        assertThat(sd1).isEqualTo(sd);\n        assertThat(sd).isEqualTo(sd1);\n        assertThat(sd).isNotEqualTo(sd2);\n        assertThat(sd2).isNotEqualTo(sd3);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/stats/ErrorStatsManagerTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Unit tests for {@link ErrorStatsManager}.\n */\n@ExtendWith(MockitoExtension.class)\nclass ErrorStatsManagerTest {\n\n    @Test\n    void testPutStats() {\n        ErrorStatsManager sm = new ErrorStatsManager();\n        assertThat(sm).isNotNull();\n        sm.putStats(\"test\", \"cause\");\n        assertThat(sm.routeMap.get(\"test\")).isNotNull();\n        ConcurrentHashMap<String, ErrorStatsData> map = sm.routeMap.get(\"test\");\n        ErrorStatsData sd = map.get(\"cause\");\n        assertThat(sd.getCount()).isEqualTo(1);\n        sm.putStats(\"test\", \"cause\");\n        assertThat(sd.getCount()).isEqualTo(2);\n    }\n\n    @Test\n    void testGetStats() {\n        ErrorStatsManager sm = new ErrorStatsManager();\n        assertThat(sm).isNotNull();\n        sm.putStats(\"test\", \"cause\");\n        assertThat(sm.getStats(\"test\", \"cause\")).isNotNull();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/stats/RouteStatusCodeMonitorTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link RouteStatusCodeMonitor}.\n */\nclass RouteStatusCodeMonitorTest {\n    @Test\n    void testUpdateStats() {\n        RouteStatusCodeMonitor sd = new RouteStatusCodeMonitor(\"test\", 200);\n        assertThat(sd.route).isEqualTo(\"test\");\n        sd.update();\n        assertThat(sd.getCount()).isEqualTo(1);\n        sd.update();\n        assertThat(sd.getCount()).isEqualTo(2);\n    }\n\n    @Test\n    void testEquals() {\n        RouteStatusCodeMonitor sd = new RouteStatusCodeMonitor(\"test\", 200);\n        RouteStatusCodeMonitor sd1 = new RouteStatusCodeMonitor(\"test\", 200);\n        RouteStatusCodeMonitor sd2 = new RouteStatusCodeMonitor(\"test1\", 200);\n        RouteStatusCodeMonitor sd3 = new RouteStatusCodeMonitor(\"test\", 201);\n\n        assertThat(sd1).isEqualTo(sd);\n        assertThat(sd).isEqualTo(sd1);\n        assertThat(sd).isNotEqualTo(sd2);\n        assertThat(sd).isNotEqualTo(sd3);\n        assertThat(sd2).isNotEqualTo(sd3);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/stats/StatsManagerTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpRequestInfo;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mockito;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * Unit tests for {@link StatsManager}.\n */\n@ExtendWith(MockitoExtension.class)\nclass StatsManagerTest {\n\n    @Test\n    void testCollectRouteStats() {\n        String route = \"test\";\n        int status = 500;\n\n        StatsManager sm = StatsManager.getManager();\n        assertThat(sm).isNotNull();\n\n        // 1st request\n        sm.collectRouteStats(route, status);\n\n        ConcurrentHashMap<Integer, RouteStatusCodeMonitor> routeStatusMap = sm.routeStatusMap.get(\"test\");\n        assertThat(routeStatusMap).isNotNull();\n\n        // 2nd request\n        sm.collectRouteStats(route, status);\n    }\n\n    @Test\n    void testGetRouteStatusCodeMonitor() {\n        StatsManager sm = StatsManager.getManager();\n        assertThat(sm).isNotNull();\n        sm.collectRouteStats(\"test\", 500);\n        assertThat(sm.getRouteStatusCodeMonitor(\"test\", 500)).isNotNull();\n    }\n\n    @Test\n    void testCollectRequestStats() {\n        String host = \"api.netflix.com\";\n        String proto = \"https\";\n\n        HttpRequestInfo req = Mockito.mock(HttpRequestInfo.class);\n        Headers headers = new Headers();\n        when(req.getHeaders()).thenReturn(headers);\n        headers.set(StatsManager.HOST_HEADER, host);\n        headers.set(StatsManager.X_FORWARDED_PROTO_HEADER, proto);\n        when(req.getClientIp()).thenReturn(\"127.0.0.1\");\n\n        StatsManager sm = StatsManager.getManager();\n        sm.collectRequestStats(req);\n\n        NamedCountingMonitor hostMonitor = sm.getHostMonitor(host);\n        assertThat(hostMonitor).as(\"hostMonitor should not be null\").isNotNull();\n\n        NamedCountingMonitor protoMonitor = sm.getProtocolMonitor(proto);\n        assertThat(protoMonitor).as(\"protoMonitor should not be null\").isNotNull();\n\n        assertThat(hostMonitor.getCount()).isEqualTo(1);\n        assertThat(protoMonitor.getCount()).isEqualTo(1);\n    }\n\n    @Test\n    void createsNormalizedHostKey() {\n        assertThat(StatsManager.hostKey(\"ec2-174-129-179-89.compute-1.amazonaws.com\"))\n                .isEqualTo(\"host_EC2.amazonaws.com\");\n        assertThat(StatsManager.hostKey(\"12.345.6.789\")).isEqualTo(\"host_IP\");\n        assertThat(StatsManager.hostKey(\"ip-10-86-83-168\")).isEqualTo(\"host_IP\");\n        assertThat(StatsManager.hostKey(\"002.ie.llnw.nflxvideo.net\")).isEqualTo(\"host_CDN.nflxvideo.net\");\n        assertThat(StatsManager.hostKey(\"netflix-635.vo.llnwd.net\")).isEqualTo(\"host_CDN.llnwd.net\");\n        assertThat(StatsManager.hostKey(\"cdn-0.nflximg.com\")).isEqualTo(\"host_CDN.nflximg.com\");\n    }\n\n    @Test\n    void extractsClientIpFromXForwardedFor() {\n        String ip1 = \"hi\";\n        String ip2 = \"hey\";\n        assertThat(StatsManager.extractClientIpFromXForwardedFor(ip1)).isEqualTo(ip1);\n        assertThat(StatsManager.extractClientIpFromXForwardedFor(String.format(\"%s,%s\", ip1, ip2)))\n                .isEqualTo(ip1);\n        assertThat(StatsManager.extractClientIpFromXForwardedFor(String.format(\"%s, %s\", ip1, ip2)))\n                .isEqualTo(ip1);\n    }\n\n    @Test\n    void isIPv6() {\n        assertThat(StatsManager.isIPv6(\"0:0:0:0:0:0:0:1\")).isTrue();\n        assertThat(StatsManager.isIPv6(\"2607:fb10:2:232:72f3:95ff:fe03:a6e7\")).isTrue();\n        assertThat(StatsManager.isIPv6(\"127.0.0.1\")).isFalse();\n        assertThat(StatsManager.isIPv6(\"10.2.233.134\")).isFalse();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/stats/status/ZuulStatusCategoryTest.java",
    "content": "/*\n * Copyright 2024 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.stats.status;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.Arrays;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.junit.jupiter.api.Test;\n\n/**\n * @author Justin Guerra\n * @since 10/29/24\n */\npublic class ZuulStatusCategoryTest {\n\n    @Test\n    public void categoriesUseUniqueIds() {\n        ZuulStatusCategory[] values = ZuulStatusCategory.values();\n        Set<String> ids = Arrays.stream(values).map(ZuulStatusCategory::getId).collect(Collectors.toSet());\n        assertThat(ids.size()).isEqualTo(values.length);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/util/HttpUtilsTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.util;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.ZuulMessage;\nimport com.netflix.zuul.message.ZuulMessageImpl;\nimport com.netflix.zuul.message.http.HttpQueryParams;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpRequestMessageImpl;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link HttpUtils}.\n */\nclass HttpUtilsTest {\n\n    @Test\n    void detectsGzip() {\n        assertThat(HttpUtils.isCompressed(\"gzip\")).isTrue();\n    }\n\n    @Test\n    void detectsDeflate() {\n        assertThat(HttpUtils.isCompressed(\"deflate\")).isTrue();\n    }\n\n    @Test\n    void detectsCompress() {\n        assertThat(HttpUtils.isCompressed(\"compress\")).isTrue();\n    }\n\n    @Test\n    void detectsBR() {\n        assertThat(HttpUtils.isCompressed(\"br\")).isTrue();\n    }\n\n    @Test\n    void detectsNonGzip() {\n        assertThat(HttpUtils.isCompressed(\"identity\")).isFalse();\n    }\n\n    @Test\n    void detectsGzipAmongOtherEncodings() {\n        assertThat(HttpUtils.isCompressed(\"gzip, deflate\")).isTrue();\n    }\n\n    @Test\n    void acceptsGzip() {\n        Headers headers = new Headers();\n        headers.add(\"Accept-Encoding\", \"gzip, deflate\");\n        assertThat(HttpUtils.acceptsGzip(headers)).isTrue();\n    }\n\n    @Test\n    void acceptsGzip_only() {\n        Headers headers = new Headers();\n        headers.add(\"Accept-Encoding\", \"deflate\");\n        assertThat(HttpUtils.acceptsGzip(headers)).isFalse();\n    }\n\n    @Test\n    void stripMaliciousHeaderChars() {\n        assertThat(HttpUtils.stripMaliciousHeaderChars(\"some\\r\\nthing\")).isEqualTo(\"something\");\n        assertThat(HttpUtils.stripMaliciousHeaderChars(\"some thing\")).isEqualTo(\"some thing\");\n        assertThat(HttpUtils.stripMaliciousHeaderChars(\"\\nsome\\r\\nthing\\r\")).isEqualTo(\"something\");\n        assertThat(HttpUtils.stripMaliciousHeaderChars(\"\\r\")).isEqualTo(\"\");\n        assertThat(HttpUtils.stripMaliciousHeaderChars(\"\")).isEqualTo(\"\");\n        assertThat(HttpUtils.stripMaliciousHeaderChars(null)).isNull();\n    }\n\n    @Test\n    void getBodySizeIfKnown_returnsContentLengthValue() {\n        SessionContext context = new SessionContext();\n        Headers headers = new Headers();\n        headers.add(com.netflix.zuul.message.http.HttpHeaderNames.CONTENT_LENGTH, \"23450\");\n        ZuulMessage msg = new ZuulMessageImpl(context, headers);\n        assertThat(HttpUtils.getBodySizeIfKnown(msg)).isEqualTo(Integer.valueOf(23450));\n    }\n\n    @Test\n    void getBodySizeIfKnown_returnsResponseBodySize() {\n        SessionContext context = new SessionContext();\n        Headers headers = new Headers();\n        HttpQueryParams queryParams = new HttpQueryParams();\n        HttpRequestMessage request = new HttpRequestMessageImpl(\n                context, \"http\", \"GET\", \"/path\", queryParams, headers, \"127.0.0.1\", \"scheme\", 6666, \"server-name\");\n        request.storeInboundRequest();\n        HttpResponseMessage response = new HttpResponseMessageImpl(context, request, 200);\n        response.setBodyAsText(\"Hello world\");\n        assertThat(HttpUtils.getBodySizeIfKnown(response)).isEqualTo(Integer.valueOf(11));\n    }\n\n    @Test\n    void getBodySizeIfKnown_returnsNull() {\n        SessionContext context = new SessionContext();\n        Headers headers = new Headers();\n        ZuulMessage msg = new ZuulMessageImpl(context, headers);\n        assertThat(HttpUtils.getBodySizeIfKnown(msg)).isNull();\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/util/JsonUtilityTest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.util;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link JsonUtility}.\n */\nclass JsonUtilityTest {\n\n    // I'm using LinkedHashMap in the testing so I get consistent ordering for the expected results\n\n    @Test\n    void testSimpleOne() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        jsonData.put(\"myKey\", \"myValue\");\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected = \"{\\\"myKey\\\":\\\"myValue\\\"}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testSimpleTwo() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        jsonData.put(\"myKey\", \"myValue\");\n        jsonData.put(\"myKey2\", \"myValue2\");\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected = \"{\\\"myKey\\\":\\\"myValue\\\",\\\"myKey2\\\":\\\"myValue2\\\"}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testNestedMapOne() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        jsonData.put(\"myKey\", \"myValue\");\n\n        Map<String, Object> jsonData2 = new LinkedHashMap<String, Object>();\n        jsonData2.put(\"myNestedKey\", \"myNestedValue\");\n\n        jsonData.put(\"myNestedData\", jsonData2);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected = \"{\\\"myKey\\\":\\\"myValue\\\",\\\"myNestedData\\\":{\\\"myNestedKey\\\":\\\"myNestedValue\\\"}}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testNestedMapTwo() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        jsonData.put(\"myKey\", \"myValue\");\n\n        Map<String, Object> jsonData2 = new LinkedHashMap<String, Object>();\n        jsonData2.put(\"myNestedKey\", \"myNestedValue\");\n        jsonData2.put(\"myNestedKey2\", \"myNestedValue2\");\n\n        jsonData.put(\"myNestedData\", jsonData2);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected =\n                \"{\\\"myKey\\\":\\\"myValue\\\",\\\"myNestedData\\\":{\\\"myNestedKey\\\":\\\"myNestedValue\\\",\\\"myNestedKey2\\\":\\\"myNestedValue2\\\"}}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testArrayOne() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        int[] numbers = {1, 2, 3, 4};\n        jsonData.put(\"myKey\", numbers);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected = \"{\\\"myKey\\\":[1,2,3,4]}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testArrayTwo() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        String[] values = {\"one\", \"two\", \"three\", \"four\"};\n        jsonData.put(\"myKey\", values);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected = \"{\\\"myKey\\\":[\\\"one\\\",\\\"two\\\",\\\"three\\\",\\\"four\\\"]}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testCollectionOne() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        ArrayList<String> values = new ArrayList<String>();\n        values.add(\"one\");\n        values.add(\"two\");\n        values.add(\"three\");\n        values.add(\"four\");\n        jsonData.put(\"myKey\", values);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected = \"{\\\"myKey\\\":[\\\"one\\\",\\\"two\\\",\\\"three\\\",\\\"four\\\"]}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testMapAndList() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        jsonData.put(\"myKey\", \"myValue\");\n        int[] numbers = {1, 2, 3, 4};\n        jsonData.put(\"myNumbers\", numbers);\n\n        Map<String, Object> jsonData2 = new LinkedHashMap<String, Object>();\n        jsonData2.put(\"myNestedKey\", \"myNestedValue\");\n        jsonData2.put(\"myNestedKey2\", \"myNestedValue2\");\n        String[] values = {\"one\", \"two\", \"three\", \"four\"};\n        jsonData2.put(\"myStringNumbers\", values);\n\n        jsonData.put(\"myNestedData\", jsonData2);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected =\n                \"{\\\"myKey\\\":\\\"myValue\\\",\\\"myNumbers\\\":[1,2,3,4],\\\"myNestedData\\\":{\\\"myNestedKey\\\":\\\"myNestedValue\\\",\\\"myNestedKey2\\\":\\\"myNestedValue2\\\",\\\"myStringNumbers\\\":[\\\"one\\\",\\\"two\\\",\\\"three\\\",\\\"four\\\"]}}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n\n    @Test\n    void testArrayOfMaps() {\n        Map<String, Object> jsonData = new LinkedHashMap<String, Object>();\n        ArrayList<Map<String, Object>> messages = new ArrayList<Map<String, Object>>();\n\n        Map<String, Object> message1 = new LinkedHashMap<String, Object>();\n        message1.put(\"a\", \"valueA1\");\n        message1.put(\"b\", \"valueB1\");\n        messages.add(message1);\n\n        Map<String, Object> message2 = new LinkedHashMap<String, Object>();\n        message2.put(\"a\", \"valueA2\");\n        message2.put(\"b\", \"valueB2\");\n        messages.add(message2);\n\n        jsonData.put(\"messages\", messages);\n\n        String json = JsonUtility.jsonFromMap(jsonData);\n        String expected =\n                \"{\\\"messages\\\":[{\\\"a\\\":\\\"valueA1\\\",\\\"b\\\":\\\"valueB1\\\"},{\\\"a\\\":\\\"valueA2\\\",\\\"b\\\":\\\"valueB2\\\"}]}\";\n\n        assertThat(json).isEqualTo(expected);\n    }\n}\n"
  },
  {
    "path": "zuul-core/src/test/java/com/netflix/zuul/util/VipUtilsTest.java",
    "content": "/*\n * Copyright 2019 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.util;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nimport org.junit.jupiter.api.Test;\n\n/**\n * Unit tests for {@link VipUtils}.\n */\nclass VipUtilsTest {\n    @Test\n    void testGetVIPPrefix() {\n        assertThatThrownBy(() -> {\n                    assertThat(VipUtils.getVIPPrefix(\"api-test.netflix.net:7001\"))\n                            .isEqualTo(\"api-test\");\n                    assertThat(VipUtils.getVIPPrefix(\"api-test.netflix.net\")).isEqualTo(\"api-test\");\n                    assertThat(VipUtils.getVIPPrefix(\"api-test:7001\")).isEqualTo(\"api-test\");\n                    assertThat(VipUtils.getVIPPrefix(\"api-test\")).isEqualTo(\"api-test\");\n                    assertThat(VipUtils.getVIPPrefix(\"\")).isEqualTo(\"\");\n                    VipUtils.getVIPPrefix(null);\n                })\n                .isInstanceOf(NullPointerException.class);\n    }\n\n    @Test\n    void testExtractAppNameFromVIP() {\n        assertThatThrownBy(() -> {\n                    assertThat(VipUtils.extractUntrustedAppNameFromVIP(\"api-test.netflix.net:7001\"))\n                            .isEqualTo(\"api\");\n                    assertThat(VipUtils.extractUntrustedAppNameFromVIP(\"api-test-blah.netflix.net:7001\"))\n                            .isEqualTo(\"api\");\n                    assertThat(VipUtils.extractUntrustedAppNameFromVIP(\"api\")).isEqualTo(\"api\");\n                    assertThat(VipUtils.extractUntrustedAppNameFromVIP(\"\")).isEqualTo(\"\");\n                    VipUtils.extractUntrustedAppNameFromVIP(null);\n                })\n                .isInstanceOf(NullPointerException.class);\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/build.gradle",
    "content": "apply plugin: \"java-library\"\n\ndependencies {\n\n    implementation libraries.guava\n    implementation libraries.slf4j\n\n    api \"com.netflix.ribbon:ribbon-loadbalancer:${versions_ribbon}\"\n    implementation \"com.netflix.ribbon:ribbon-core:${versions_ribbon}\"\n    implementation \"com.netflix.ribbon:ribbon-eureka:${versions_ribbon}\"\n    implementation \"com.netflix.ribbon:ribbon-archaius:${versions_ribbon}\"\n    // Eureka\n    implementation \"com.netflix.eureka:eureka-client:2.0.4\"\n    // unfortunately, servo is still a transitive dependency of eureka-client that stopped getting picked up\n    // after switching to version 2\n    api \"com.netflix.servo:servo-core:0.13.2\"\n\n\n    testImplementation libraries.jupiterApi, libraries.jupiterParams, libraries.jupiterEngine, libraries.junitPlatformLauncher,\n            libraries.mockito,\n            libraries.assertj\n}\n\ntest {\n    testLogging {\n        showStandardStreams = false\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/discovery/DiscoveryResult.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.discovery;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.netflix.appinfo.AmazonInfo;\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.appinfo.InstanceInfo.PortType;\nimport com.netflix.loadbalancer.LoadBalancerStats;\nimport com.netflix.loadbalancer.ServerStats;\nimport com.netflix.niws.loadbalancer.DiscoveryEnabledServer;\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.Optional;\nimport javax.annotation.Nullable;\n\n/**\n * @author Argha C\n * @since 2/25/21\n * <p>\n * Wraps a single instance of discovery enabled server, and stats related to it.\n */\npublic final class DiscoveryResult implements ResolverResult {\n\n    private final DiscoveryEnabledServer server;\n    private final ServerStats serverStats;\n    /**\n     * This exists to allow for a semblance of type safety, and encourages avoiding null checks on the underlying Server,\n     * thus representing a sentinel value for an empty resolution result.\n     */\n    public static final DiscoveryResult EMPTY = DiscoveryResult.from(\n            InstanceInfo.Builder.newBuilder()\n                    .setAppName(\"undefined\")\n                    .setHostName(\"undefined\")\n                    .setPort(-1)\n                    .build(),\n            false);\n\n    public DiscoveryResult(DiscoveryEnabledServer server, LoadBalancerStats lbStats) {\n        this.server = server;\n        Objects.requireNonNull(lbStats, \"Loadbalancer stats must be a valid instance\");\n        this.serverStats = lbStats.getSingleServerStat(server);\n    }\n\n    /**\n     * This solely exists to create a result object from incomplete InstanceInfo.\n     * Usage of this for production code is strongly discouraged, since the underlying instances are prone to memory leaks\n     */\n    public DiscoveryResult(DiscoveryEnabledServer server) {\n        this.server = server;\n        this.serverStats = new ServerStats() {\n            @Override\n            public String toString() {\n                return \"no stats configured for server\";\n            }\n        };\n    }\n\n    /**\n     * This convenience method exists for usage in tests. For production usage, please use the constructor linked:\n     *\n     * @see DiscoveryResult#DiscoveryResult(DiscoveryEnabledServer, LoadBalancerStats)\n     */\n    @VisibleForTesting\n    public static DiscoveryResult from(InstanceInfo instanceInfo, boolean useSecurePort) {\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, useSecurePort);\n        return new DiscoveryResult(server);\n    }\n\n    public Optional<String> getIPAddr() {\n        if (this.equals(DiscoveryResult.EMPTY)) {\n            return Optional.empty();\n        }\n        if (server.getInstanceInfo() != null) {\n            String ip = server.getInstanceInfo().getIPAddr();\n            if (ip != null && !ip.isEmpty()) {\n                return Optional.of(ip);\n            }\n            return Optional.empty();\n        }\n        return Optional.empty();\n    }\n\n    @Override\n    public String getHost() {\n        return server == null ? \"undefined\" : server.getHost();\n    }\n\n    @Override\n    public boolean isDiscoveryEnabled() {\n        return server != null;\n    }\n\n    @Override\n    public int getPort() {\n        return server == null ? -1 : server.getPort();\n    }\n\n    public int getSecurePort() {\n        return server.getInstanceInfo().getSecurePort();\n    }\n\n    public boolean isSecurePortEnabled() {\n        return server.getInstanceInfo().isPortEnabled(PortType.SECURE);\n    }\n\n    public String getTarget() {\n        InstanceInfo instanceInfo = server.getInstanceInfo();\n        if (server.getPort() == instanceInfo.getSecurePort()) {\n            return instanceInfo.getSecureVipAddress();\n        } else {\n            return instanceInfo.getVIPAddress();\n        }\n    }\n\n    public SimpleMetaInfo getMetaInfo() {\n        return new SimpleMetaInfo(server.getMetaInfo());\n    }\n\n    @Nullable\n    public String getAvailabilityZone() {\n        InstanceInfo instanceInfo = server.getInstanceInfo();\n        if (instanceInfo.getDataCenterInfo() instanceof AmazonInfo) {\n            return ((AmazonInfo) instanceInfo.getDataCenterInfo()).getMetadata().get(\"availability-zone\");\n        }\n        return null;\n    }\n\n    public String getZone() {\n        return server.getZone();\n    }\n\n    public String getServerId() {\n        return server.getInstanceInfo().getId();\n    }\n\n    public DiscoveryEnabledServer getServer() {\n        return server;\n    }\n\n    @VisibleForTesting\n    ServerStats getServerStats() {\n        return this.serverStats;\n    }\n\n    public String getASGName() {\n        return server.getInstanceInfo().getASGName();\n    }\n\n    public String getAppName() {\n        return server.getInstanceInfo().getAppName().toLowerCase(Locale.ROOT);\n    }\n\n    public void noteResponseTime(double msecs) {\n        serverStats.noteResponseTime(msecs);\n    }\n\n    public boolean isCircuitBreakerTripped() {\n        return serverStats.isCircuitBreakerTripped();\n    }\n\n    public void incrementActiveRequestsCount() {\n        serverStats.incrementActiveRequestsCount();\n    }\n\n    public void incrementOpenConnectionsCount() {\n        serverStats.incrementOpenConnectionsCount();\n    }\n\n    public void incrementSuccessiveConnectionFailureCount() {\n        serverStats.incrementSuccessiveConnectionFailureCount();\n    }\n\n    public void incrementNumRequests() {\n        serverStats.incrementNumRequests();\n    }\n\n    public int getOpenConnectionsCount() {\n        return serverStats.getOpenConnectionsCount();\n    }\n\n    public long getTotalRequestsCount() {\n        return serverStats.getTotalRequestsCount();\n    }\n\n    public int getActiveRequestsCount() {\n        return serverStats.getActiveRequestsCount();\n    }\n\n    public void decrementOpenConnectionsCount() {\n        serverStats.decrementOpenConnectionsCount();\n    }\n\n    public void decrementActiveRequestsCount() {\n        serverStats.decrementActiveRequestsCount();\n    }\n\n    public void clearSuccessiveConnectionFailureCount() {\n        serverStats.clearSuccessiveConnectionFailureCount();\n    }\n\n    public void addToFailureCount() {\n        serverStats.addToFailureCount();\n    }\n\n    public void stopPublishingStats() {\n        serverStats.close();\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hashCode(server);\n    }\n\n    /**\n     * Two instances are deemed identical if they wrap the same underlying discovery server instance.\n     */\n    @Override\n    public boolean equals(Object obj) {\n        if (obj == this) {\n            return true;\n        }\n\n        if (!(obj instanceof DiscoveryResult)) {\n            return false;\n        }\n        DiscoveryResult other = (DiscoveryResult) obj;\n        return server.equals(other.server);\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/discovery/DynamicServerResolver.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.discovery;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Throwables;\nimport com.google.common.collect.Sets;\nimport com.netflix.client.config.CommonClientConfigKey;\nimport com.netflix.client.config.IClientConfig;\nimport com.netflix.loadbalancer.DynamicServerListLoadBalancer;\nimport com.netflix.loadbalancer.Server;\nimport com.netflix.niws.loadbalancer.DiscoveryEnabledServer;\nimport com.netflix.zuul.resolver.Resolver;\nimport com.netflix.zuul.resolver.ResolverListener;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport javax.annotation.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * @author Argha C\n * @since 2/25/21\n * <p>\n * Implements a resolver, wrapping a ribbon load-balancer.\n */\npublic class DynamicServerResolver implements Resolver<DiscoveryResult> {\n\n    private static final Logger LOG = LoggerFactory.getLogger(DynamicServerResolver.class);\n\n    private final DynamicServerListLoadBalancer<?> loadBalancer;\n    private ResolverListener<DiscoveryResult> listener;\n\n    @Deprecated\n    public DynamicServerResolver(IClientConfig clientConfig, ResolverListener<DiscoveryResult> listener) {\n        this.loadBalancer = createLoadBalancer(clientConfig);\n        this.loadBalancer.addServerListChangeListener(this::onUpdate);\n        this.listener = listener;\n    }\n\n    public DynamicServerResolver(IClientConfig clientConfig) {\n        this(createLoadBalancer(clientConfig));\n    }\n\n    public DynamicServerResolver(DynamicServerListLoadBalancer<?> loadBalancer) {\n        this.loadBalancer = Objects.requireNonNull(loadBalancer);\n    }\n\n    @Override\n    public void setListener(ResolverListener<DiscoveryResult> listener) {\n        if (this.listener != null) {\n            LOG.warn(\"Ignoring call to setListener, because a listener was already set\");\n            return;\n        }\n\n        this.listener = Objects.requireNonNull(listener);\n        this.loadBalancer.addServerListChangeListener(this::onUpdate);\n    }\n\n    @Override\n    public DiscoveryResult resolve(@Nullable Object key) {\n        Server server = loadBalancer.chooseServer(key);\n        return server != null\n                ? new DiscoveryResult((DiscoveryEnabledServer) server, loadBalancer.getLoadBalancerStats())\n                : DiscoveryResult.EMPTY;\n    }\n\n    @Override\n    public boolean hasServers() {\n        return !loadBalancer.getReachableServers().isEmpty();\n    }\n\n    @Override\n    public void shutdown() {\n        loadBalancer.shutdown();\n    }\n\n    private static DynamicServerListLoadBalancer<?> createLoadBalancer(IClientConfig clientConfig) {\n        // TODO(argha-c): Revisit this style of LB initialization post modularization. Ideally the LB should be\n        // pluggable.\n\n        // Use a hard coded string for the LB default name to avoid a dependency on Ribbon classes.\n        String loadBalancerClassName = clientConfig.get(\n                CommonClientConfigKey.NFLoadBalancerClassName, \"com.netflix.loadbalancer.ZoneAwareLoadBalancer\");\n\n        DynamicServerListLoadBalancer<?> lb;\n        try {\n            Class<?> clazz = Class.forName(loadBalancerClassName);\n            lb = clazz.asSubclass(DynamicServerListLoadBalancer.class)\n                    .getConstructor()\n                    .newInstance();\n            lb.initWithNiwsConfig(clientConfig);\n        } catch (Exception e) {\n            Throwables.throwIfUnchecked(e);\n            throw new IllegalStateException(\"Could not instantiate LoadBalancer \" + loadBalancerClassName, e);\n        }\n\n        return lb;\n    }\n\n    @VisibleForTesting\n    void onUpdate(List<Server> oldList, List<Server> newList) {\n        Set<Server> oldSet = new HashSet<>(oldList);\n        Set<Server> newSet = new HashSet<>(newList);\n        List<DiscoveryResult> discoveryResults = Sets.difference(oldSet, newSet).stream()\n                .map(server ->\n                        new DiscoveryResult((DiscoveryEnabledServer) server, loadBalancer.getLoadBalancerStats()))\n                .collect(Collectors.toList());\n        listener.onChange(discoveryResults);\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/discovery/NonDiscoveryServer.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.discovery;\n\nimport com.netflix.loadbalancer.Server;\nimport java.util.Objects;\n\n/**\n * @author Argha C\n * @since 3/1/21\n * <p>\n * This exists merely to wrap a resolver lookup result, that is not discovery enabled.\n */\npublic final class NonDiscoveryServer implements ResolverResult {\n\n    private final Server server;\n\n    public NonDiscoveryServer(String host, int port) {\n        Objects.requireNonNull(host, \"host name\");\n        this.server = new Server(host, validatePort(port));\n    }\n\n    @Override\n    public String getHost() {\n        return server.getHost();\n    }\n\n    @Override\n    public int getPort() {\n        return server.getPort();\n    }\n\n    @Override\n    public boolean isDiscoveryEnabled() {\n        return false;\n    }\n\n    private int validatePort(int port) {\n        if (port < 0 || port > 0xFFFF) {\n            throw new IllegalArgumentException(\"port out of range:\" + port);\n        }\n        return port;\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/discovery/ResolverResult.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.discovery;\n\n/**\n * @author Argha C\n * @since 2/25/21\n *\n * Wraps the result of a resolution attempt.\n * At this time, it doesn't encapsulate a collection of instances, but ideally should.\n */\npublic interface ResolverResult {\n\n    // TODO(argha-c): This should ideally model returning a collection of host/port pairs.\n    public String getHost();\n\n    public int getPort();\n\n    public boolean isDiscoveryEnabled();\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/discovery/SimpleMetaInfo.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.discovery;\n\nimport com.netflix.loadbalancer.Server.MetaInfo;\n\n/**\n * @author Argha C\n * @since 2/25/21\n *\n * placeholder to mimic metainfo for a non-Eureka enabled server.\n * This exists to preserve compatibility with some current logic, but should be revisited.\n */\npublic final class SimpleMetaInfo {\n\n    private final MetaInfo metaInfo;\n\n    public SimpleMetaInfo(MetaInfo metaInfo) {\n        this.metaInfo = metaInfo;\n    }\n\n    public String getServerGroup() {\n        return metaInfo.getServerGroup();\n    }\n\n    public String getServiceIdForDiscovery() {\n        return metaInfo.getServiceIdForDiscovery();\n    }\n\n    public String getInstanceId() {\n        return metaInfo.getInstanceId();\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/resolver/Resolver.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.resolver;\n\n/**\n * @author Argha C\n * @since 2/25/21\n *\n * Resolves a key to a discovery result type.\n */\npublic interface Resolver<T> {\n\n    /**\n     *\n     * @param key unique identifier that may be used by certain resolvers as part of lookup. Implementations\n     *            can narrow this down to be nullable.\n     * @return the result of a resolver lookup\n     */\n    // TODO(argha-c) Param needs to be typed, once the ribbon LB lookup API is figured out.\n    T resolve(Object key);\n\n    /**\n     * @return true if the resolver has available servers, false otherwise\n     */\n    boolean hasServers();\n\n    /**\n     * hook to perform activities on shutdown\n     */\n    void shutdown();\n\n    default void setListener(ResolverListener<T> listener) {}\n}\n"
  },
  {
    "path": "zuul-discovery/src/main/java/com/netflix/zuul/resolver/ResolverListener.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.resolver;\n\nimport java.util.List;\n\n/**\n * @author Argha C\n * @since 2/25/21\n *\n * Listener for resolver updates.\n */\npublic interface ResolverListener<T> {\n\n    /**\n     * Hook to respond to resolver updates\n     * @param removedSet the servers removed from the latest resolver update, but included in the previous update.\n     */\n    void onChange(List<T> removedSet);\n}\n"
  },
  {
    "path": "zuul-discovery/src/test/java/com/netflix/zuul/discovery/DiscoveryResultTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.discovery;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.appinfo.InstanceInfo.PortType;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.loadbalancer.DynamicServerListLoadBalancer;\nimport com.netflix.loadbalancer.Server;\nimport com.netflix.niws.loadbalancer.DiscoveryEnabledServer;\nimport java.util.Optional;\nimport org.junit.jupiter.api.Test;\n\nclass DiscoveryResultTest {\n\n    @Test\n    void hashCodeForNull() {\n        DiscoveryResult discoveryResult = new DiscoveryResult(null);\n        assertThat(discoveryResult.hashCode()).isEqualTo(0);\n    }\n\n    @Test\n    void serverStatsForEmptySentinel() {\n        assertThat(DiscoveryResult.EMPTY.getServerStats().toString()).isEqualTo(\"no stats configured for server\");\n    }\n\n    @Test\n    void hostAndPortForNullServer() {\n        DiscoveryResult discoveryResult = new DiscoveryResult(null);\n\n        assertThat(discoveryResult.getHost()).isEqualTo(\"undefined\");\n        assertThat(discoveryResult.getPort()).isEqualTo(-1);\n    }\n\n    @Test\n    void serverStatsCacheForSameServer() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"serverstats-cache\")\n                .setHostName(\"serverstats-cache\")\n                .setPort(7777)\n                .build();\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false);\n        DiscoveryEnabledServer serverSecure = new DiscoveryEnabledServer(instanceInfo, true);\n\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n        DiscoveryResult result1 = new DiscoveryResult(serverSecure, lb.getLoadBalancerStats());\n\n        assertThat(result.getServerStats()).isSameAs(result1.getServerStats());\n    }\n\n    @Test\n    void serverStatsDifferForDifferentServer() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"serverstats-cache\")\n                .setHostName(\"serverstats-cache\")\n                .setPort(7777)\n                .build();\n        InstanceInfo otherInstance = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"serverstats-cache-2\")\n                .setHostName(\"serverstats-cache-2\")\n                .setPort(7777)\n                .build();\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false);\n        DiscoveryEnabledServer serverSecure = new DiscoveryEnabledServer(otherInstance, false);\n\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n        DiscoveryResult result1 = new DiscoveryResult(serverSecure, lb.getLoadBalancerStats());\n\n        assertThat(result.getServerStats()).isNotSameAs(result1.getServerStats());\n    }\n\n    @Test\n    void ipAddrV4FromInstanceInfo() {\n        String ipAddr = \"100.1.0.1\";\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"ipAddrv4\")\n                .setHostName(\"ipAddrv4\")\n                .setIPAddr(ipAddr)\n                .setPort(7777)\n                .build();\n\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false);\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n\n        assertThat(result.getIPAddr()).isEqualTo(Optional.of(ipAddr));\n    }\n\n    @Test\n    void ipAddrEmptyForIncompleteInstanceInfo() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"ipAddrMissing\")\n                .setHostName(\"ipAddrMissing\")\n                .setPort(7777)\n                .build();\n\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false);\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n\n        assertThat(result.getIPAddr()).isEqualTo(Optional.empty());\n    }\n\n    @Test\n    void sameUnderlyingInstanceInfoEqualsSameResult() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"server-equality\")\n                .setHostName(\"server-equality\")\n                .setPort(7777)\n                .build();\n\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false);\n        DiscoveryEnabledServer otherServer = new DiscoveryEnabledServer(instanceInfo, false);\n\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n        DiscoveryResult otherResult = new DiscoveryResult(otherServer, lb.getLoadBalancerStats());\n\n        assertThat(result).isEqualTo(otherResult);\n    }\n\n    @Test\n    void serverInstancesExposingDiffPortsAreNotEqual() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"server-equality\")\n                .setHostName(\"server-equality\")\n                .setPort(7777)\n                .build();\n        InstanceInfo otherPort = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"server-equality\")\n                .setHostName(\"server-equality\")\n                .setPort(9999)\n                .build();\n\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, false);\n        DiscoveryEnabledServer otherServer = new DiscoveryEnabledServer(otherPort, false);\n\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n        DiscoveryResult otherResult = new DiscoveryResult(otherServer, lb.getLoadBalancerStats());\n\n        assertThat(result).isNotEqualTo(otherResult);\n    }\n\n    @Test\n    void securePortMustCheckInstanceInfo() {\n        InstanceInfo instanceInfo = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"secure-port\")\n                .setHostName(\"secure-port\")\n                .setPort(7777)\n                .enablePort(PortType.SECURE, false)\n                .build();\n        InstanceInfo secureEnabled = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"secure-port\")\n                .setHostName(\"secure-port\")\n                .setPort(7777)\n                .enablePort(PortType.SECURE, true)\n                .build();\n\n        DiscoveryEnabledServer server = new DiscoveryEnabledServer(instanceInfo, true);\n        DiscoveryEnabledServer secureServer = new DiscoveryEnabledServer(secureEnabled, true);\n        DynamicServerListLoadBalancer<Server> lb = new DynamicServerListLoadBalancer<>(new DefaultClientConfigImpl());\n\n        DiscoveryResult result = new DiscoveryResult(server, lb.getLoadBalancerStats());\n        DiscoveryResult secure = new DiscoveryResult(secureServer, lb.getLoadBalancerStats());\n\n        assertThat(result.isSecurePortEnabled()).isFalse();\n        assertThat(secure.isSecurePortEnabled()).isTrue();\n    }\n}\n"
  },
  {
    "path": "zuul-discovery/src/test/java/com/netflix/zuul/discovery/DynamicServerResolverTest.java",
    "content": "/*\n * Copyright 2021 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.discovery;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.google.common.collect.ImmutableList;\nimport com.google.common.collect.Lists;\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.client.config.DefaultClientConfigImpl;\nimport com.netflix.niws.loadbalancer.DiscoveryEnabledServer;\nimport com.netflix.zuul.resolver.ResolverListener;\nimport java.util.List;\nimport org.junit.jupiter.api.Test;\n\nclass DynamicServerResolverTest {\n\n    @Test\n    void verifyListenerUpdates() {\n\n        class CustomListener implements ResolverListener<DiscoveryResult> {\n\n            private List<DiscoveryResult> resultSet = Lists.newArrayList();\n\n            @Override\n            public void onChange(List<DiscoveryResult> changedSet) {\n                resultSet = changedSet;\n            }\n\n            public List<DiscoveryResult> updatedList() {\n                return resultSet;\n            }\n        }\n\n        CustomListener listener = new CustomListener();\n        DynamicServerResolver resolver = new DynamicServerResolver(new DefaultClientConfigImpl());\n        resolver.setListener(listener);\n\n        InstanceInfo first = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"zuul-discovery-1\")\n                .setHostName(\"zuul-discovery-1\")\n                .setIPAddr(\"100.10.10.1\")\n                .setPort(443)\n                .build();\n        InstanceInfo second = InstanceInfo.Builder.newBuilder()\n                .setAppName(\"zuul-discovery-2\")\n                .setHostName(\"zuul-discovery-2\")\n                .setIPAddr(\"100.10.10.2\")\n                .setPort(443)\n                .build();\n        DiscoveryEnabledServer server1 = new DiscoveryEnabledServer(first, true);\n        DiscoveryEnabledServer server2 = new DiscoveryEnabledServer(second, true);\n\n        resolver.onUpdate(ImmutableList.of(server1, server2), ImmutableList.of());\n\n        assertThat(listener.updatedList()).containsExactly(new DiscoveryResult(server1), new DiscoveryResult(server2));\n    }\n\n    @Test\n    void properSentinelValueWhenServersUnavailable() {\n        DynamicServerResolver resolver = new DynamicServerResolver(new DefaultClientConfigImpl());\n\n        DiscoveryResult nonExistentServer = resolver.resolve(null);\n\n        assertThat(nonExistentServer).isSameAs(DiscoveryResult.EMPTY);\n        assertThat(nonExistentServer.getHost()).isEqualTo(\"undefined\");\n        assertThat(nonExistentServer.getPort()).isEqualTo(-1);\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/build.gradle",
    "content": "apply plugin: \"java\"\napply plugin: 'application'\n\ndependencies {\n    implementation project(\":zuul-core\"),\n            project(\":zuul-discovery\")\n\n    implementation \"io.netty:netty-transport-classes-epoll\"\n    implementation \"com.netflix.eureka:eureka-client:2.0.4\"\n    implementation \"com.netflix.ribbon:ribbon-eureka:$versions_ribbon\"\n    implementation \"com.netflix.ribbon:ribbon-loadbalancer:$versions_ribbon\"\n    implementation 'commons-configuration:commons-configuration:1.10'\n    annotationProcessor project(\":zuul-processor\")\n\n    testImplementation 'org.wiremock:wiremock:3.10.0'\n    testImplementation 'javax.servlet:javax.servlet-api:4.0.1'\n    testImplementation libraries.assertj, libraries.jupiterApi, libraries.jupiterParams, libraries.jupiterEngine, libraries.junitPlatformLauncher, libraries.jupiterMockito, libraries.okhttp\n    testImplementation \"io.netty:netty-transport-native-io_uring\"\n//    testImplementation \"io.netty.incubator:netty-transport-native-io_uring::linux-x86_64\"\n    testImplementation \"org.slf4j:slf4j-api:2.0.16\"\n    testImplementation 'org.apache.commons:commons-lang3:3.18.0'\n    testImplementation \"com.aayushatharva.brotli4j:brotli4j:$versions_brotli4j\"\n    testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.25.2'\n    testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl:2.25.2'\n    testRuntimeOnly \"com.aayushatharva.brotli4j:native-osx-aarch64:$versions_brotli4j\"\n    testRuntimeOnly \"com.aayushatharva.brotli4j:native-osx-x86_64:$versions_brotli4j\"\n    testRuntimeOnly \"com.aayushatharva.brotli4j:native-linux-x86_64:$versions_brotli4j\"\n    testRuntimeOnly \"com.aayushatharva.brotli4j:native-linux-aarch64:$versions_brotli4j\"\n    testRuntimeOnly \"io.netty:netty-transport-native-epoll::linux-x86_64\"\n}\n\ntasks.withType(Test).all {\n    systemProperty(\"io.netty.leakDetection.level\", \"paranoid\")\n}\n\ntasks.withType(PublishToMavenRepository) {\n    onlyIf { false }\n}\n\ntasks.withType(PublishToMavenLocal) {\n    onlyIf { false }\n}\n\ntasks.withType(PublishToIvyRepository) {\n    onlyIf { false }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/netty/common/metrics/CustomLeakDetector.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.netty.common.metrics;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.stream.Collectors;\n\npublic class CustomLeakDetector extends InstrumentedResourceLeakDetector {\n    private static final List<CustomLeakDetector> GLOBAL_REGISTRY = new CopyOnWriteArrayList<>();\n\n    public static void assertZeroLeaks() {\n        List<CustomLeakDetector> leaks = GLOBAL_REGISTRY.stream()\n                .filter(detector -> detector.leakCounter.get() > 0)\n                .collect(Collectors.toList());\n        assertThat(leaks.isEmpty()).as(\"LEAKS DETECTED: \" + leaks).isTrue();\n    }\n\n    private final String resourceTypeName;\n\n    public CustomLeakDetector(Class<?> resourceType, int samplingInterval) {\n        super(resourceType, samplingInterval);\n        this.resourceTypeName = resourceType.getSimpleName();\n        GLOBAL_REGISTRY.add(this);\n    }\n\n    public CustomLeakDetector(Class<?> resourceType, int samplingInterval, long maxActive) {\n        this(resourceType, samplingInterval);\n    }\n\n    @Override\n    public String toString() {\n        return \"CustomLeakDetector: \" + this.resourceTypeName + \" leakCount=\" + leakCounter.get();\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/BaseIntegrationTest.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration;\n\nimport static com.github.tomakehurst.wiremock.client.WireMock.aResponse;\nimport static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;\nimport static com.github.tomakehurst.wiremock.client.WireMock.get;\nimport static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.ok;\nimport static com.github.tomakehurst.wiremock.client.WireMock.post;\nimport static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.stubFor;\nimport static com.github.tomakehurst.wiremock.client.WireMock.verify;\nimport static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.aayushatharva.brotli4j.decoder.DecoderJNI;\nimport com.aayushatharva.brotli4j.decoder.DirectDecompress;\nimport com.github.tomakehurst.wiremock.client.WireMock;\nimport com.github.tomakehurst.wiremock.common.Slf4jNotifier;\nimport com.github.tomakehurst.wiremock.core.Options;\nimport com.github.tomakehurst.wiremock.http.Fault;\nimport com.github.tomakehurst.wiremock.junit5.WireMockExtension;\nimport com.github.tomakehurst.wiremock.stubbing.Scenario;\nimport com.google.common.collect.ImmutableSet;\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.netty.common.metrics.CustomLeakDetector;\nimport com.netflix.zuul.integration.server.HeaderNames;\nimport com.netflix.zuul.integration.server.TestUtil;\nimport io.netty.channel.epoll.Epoll;\nimport io.netty.handler.codec.compression.Brotli;\nimport io.netty.util.ResourceLeakDetector;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\nimport java.net.Socket;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.UUID;\nimport java.util.concurrent.BlockingQueue;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Stream;\nimport java.util.zip.GZIPInputStream;\nimport java.util.zip.Inflater;\nimport okhttp3.Call;\nimport okhttp3.Callback;\nimport okhttp3.HttpUrl;\nimport okhttp3.MediaType;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okio.BufferedSink;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.apache.commons.io.IOUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.condition.EnabledOnOs;\nimport org.junit.jupiter.api.condition.OS;\nimport org.junit.jupiter.api.extension.RegisterExtension;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nabstract class BaseIntegrationTest {\n\n    static {\n        System.setProperty(\"io.netty.customResourceLeakDetector\", CustomLeakDetector.class.getCanonicalName());\n    }\n\n    private static final Duration CLIENT_READ_TIMEOUT = Duration.ofSeconds(3);\n    static final Duration ORIGIN_READ_TIMEOUT = Duration.ofSeconds(1);\n\n    @RegisterExtension\n    static WireMockExtension wireMockExtension = WireMockExtension.newInstance()\n            .configureStaticDsl(true)\n            .options(wireMockConfig()\n                    .maxLoggedResponseSize(1000)\n                    .dynamicPort()\n                    .useChunkedTransferEncoding(Options.ChunkedEncodingPolicy.ALWAYS)\n                    .notifier(new Slf4jNotifier(true)))\n            .build();\n\n    @BeforeAll\n    static void beforeAll() {\n        assertThat(ResourceLeakDetector.isEnabled()).isTrue();\n        assertThat(ResourceLeakDetector.getLevel()).isEqualTo(ResourceLeakDetector.Level.PARANOID);\n        int wireMockPort = wireMockExtension.getPort();\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"api.ribbon.listOfServers\", \"127.0.0.1:\" + wireMockPort);\n        CustomLeakDetector.assertZeroLeaks();\n    }\n\n    @AfterAll\n    static void afterAll() {\n        CustomLeakDetector.assertZeroLeaks();\n        ConfigurationManager.getConfigInstance().clear();\n    }\n\n    private final int zuulServerPort;\n    private final String zuulBaseUri;\n\n    private String pathSegment;\n    private WireMock wireMock;\n\n    public BaseIntegrationTest(int zuulServerPort) {\n        this.zuulServerPort = zuulServerPort;\n        zuulBaseUri = \"http://localhost:\" + this.zuulServerPort;\n    }\n\n    @BeforeEach\n    void beforeEachTest() {\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"server.http.request.headers.read.timeout.enabled\", false);\n        config.setProperty(\"server.http.request.headers.read.timeout\", 10000);\n\n        this.pathSegment = randomPathSegment();\n\n        this.wireMock = wireMockExtension.getRuntimeInfo().getWireMock();\n    }\n\n    private static OkHttpClient setupOkHttpClient(Protocol... protocols) {\n        return new OkHttpClient.Builder()\n                .connectTimeout(30, TimeUnit.MILLISECONDS)\n                .readTimeout(CLIENT_READ_TIMEOUT)\n                .followRedirects(false)\n                .followSslRedirects(false)\n                .retryOnConnectionFailure(false)\n                .protocols(Arrays.asList(protocols))\n                .build();\n    }\n\n    private Request.Builder setupRequestBuilder(boolean requestBodyBuffering, boolean responseBodyBuffering) {\n        HttpUrl url = new HttpUrl.Builder()\n                .scheme(\"http\")\n                .host(\"localhost\")\n                .port(zuulServerPort)\n                .addPathSegment(pathSegment)\n                .addQueryParameter(\"bufferRequestBody\", \"\" + requestBodyBuffering)\n                .addQueryParameter(\"bufferResponseBody\", \"\" + responseBodyBuffering)\n                .build();\n        return new Request.Builder().url(url);\n    }\n\n    static Stream<Arguments> arguments() {\n        List<Arguments> list = new ArrayList<Arguments>();\n        for (Protocol protocol : ImmutableSet.of(Protocol.HTTP_1_1)) {\n            for (boolean requestBodyBuffering : ImmutableSet.of(Boolean.TRUE, Boolean.FALSE)) {\n                for (boolean responseBodyBuffering : ImmutableSet.of(Boolean.TRUE, Boolean.FALSE)) {\n                    list.add(Arguments.of(\n                            protocol.name(), setupOkHttpClient(protocol), requestBodyBuffering, responseBodyBuffering));\n                }\n            }\n        }\n        return list.stream();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpGetHappyPath(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl()).willReturn(ok().withBody(\"hello world\")));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"hello world\");\n        verifyResponseHeaders(response);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpPostHappyPath(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(post(anyUrl()).willReturn(ok().withBody(\"Thank you next\")));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .post(RequestBody.create(\"Simple POST request body\".getBytes(StandardCharsets.UTF_8)))\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"Thank you next\");\n        verifyResponseHeaders(response);\n    }\n\n    @Test\n    void httpPostWithSeveralChunks() throws Exception {\n        OkHttpClient okHttp = setupOkHttpClient(Protocol.HTTP_1_1);\n\n        wireMock.register(post(anyUrl()).willReturn(ok().withBody(\"Thank you next\")));\n\n        StreamingRequestBody body = new StreamingRequestBody();\n        Request request = setupRequestBuilder(true, false).post(body).build();\n\n        SimpleCallback simpleCallback = new SimpleCallback();\n        okHttp.newCall(request).enqueue(simpleCallback);\n\n        body.write(\"chunk1\");\n        body.write(\"chunk2\");\n        body.write(\"chunk3\");\n        body.stop();\n\n        Response response = simpleCallback.getResponse();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"Thank you next\");\n        verifyResponseHeaders(response);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpPostWithInvalidHostHeader(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(post(anyUrl()).willReturn(ok().withBody(\"Thank you next\")));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .addHeader(\"Host\", \"_invalid_hostname_\")\n                .post(RequestBody.create(\"Simple POST request body\".getBytes(StandardCharsets.UTF_8)))\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(500);\n\n        verify(0, anyRequestedFor(anyUrl()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpGetFailsDueToOriginReadTimeout(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl())\n                .willReturn(ok().withFixedDelay((int) ORIGIN_READ_TIMEOUT.toMillis() + 50)\n                        .withBody(\"Slow poke\")));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(504);\n        assertThat(response.body().string()).isEqualTo(\"\");\n        verifyResponseHeaders(response);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpGetHappyPathWithHeadersReadTimeout(\n            final String description,\n            final OkHttpClient okHttp,\n            final boolean requestBodyBuffering,\n            final boolean responseBodyBuffering)\n            throws Exception {\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"server.http.request.headers.read.timeout.enabled\", true);\n\n        wireMock.register(get(anyUrl()).willReturn(ok().withBody(\"hello world\")));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"hello world\");\n        verifyResponseHeaders(response);\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpPostHappyPathWithHeadersReadTimeout(\n            final String description,\n            final OkHttpClient okHttp,\n            final boolean requestBodyBuffering,\n            final boolean responseBodyBuffering)\n            throws Exception {\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"server.http.request.headers.read.timeout.enabled\", true);\n\n        wireMock.register(post(anyUrl()).willReturn(ok().withBody(\"Thank you next\")));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .post(RequestBody.create(\"Simple POST request body\".getBytes(StandardCharsets.UTF_8)))\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"Thank you next\");\n        verifyResponseHeaders(response);\n    }\n\n    @Test\n    void httpGetFailsDueToHeadersReadTimeout() throws Exception {\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"server.http.request.headers.read.timeout.enabled\", true);\n        config.setProperty(\"server.http.request.headers.read.timeout\", 100);\n\n        Socket slowClient = new Socket(\"localhost\", zuulServerPort);\n        Thread.sleep(500);\n        // end of stream reached because zuul closed the connection\n        assertThat(slowClient.getInputStream().read()).isEqualTo(-1);\n        slowClient.close();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpGetFailsDueToMalformedResponseChunk(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl()).willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK)));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        int expectedStatusCode = responseBodyBuffering ? 504 : 200;\n        assertThat(response.code()).isEqualTo(expectedStatusCode);\n        response.close();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void zuulWillRetryHttpGetWhenOriginReturns500(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl()).willReturn(aResponse().withStatus(500)));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(500);\n        assertThat(response.body().string()).isEqualTo(\"\");\n        verify(2, getRequestedFor(anyUrl()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void zuulWillRetryHttpGetWhenOriginReturns503(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl()).willReturn(aResponse().withStatus(503)));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(503);\n        assertThat(response.body().string()).isEqualTo(\"\");\n        verify(2, getRequestedFor(anyUrl()));\n    }\n\n    @Test\n    void zuulWillRetryHttpGetWhenOriginReturns503AndRetryIsSuccessful() throws Exception {\n        OkHttpClient okHttp = setupOkHttpClient(Protocol.HTTP_1_1);\n\n        stubFor(get(anyUrl())\n                .inScenario(\"getRetry\")\n                .whenScenarioStateIs(Scenario.STARTED)\n                .willSetStateTo(\"failFirst\")\n                .willReturn(aResponse().withStatus(200)));\n\n        stubFor(get(anyUrl())\n                .inScenario(\"getRetry\")\n                .whenScenarioStateIs(\"failFirst\")\n                .willSetStateTo(\"allowSecond\")\n                .willReturn(aResponse().withStatus(503)));\n\n        stubFor(get(anyUrl())\n                .inScenario(\"getRetry\")\n                .whenScenarioStateIs(\"allowSecond\")\n                .willReturn(aResponse().withStatus(200).withBody(\"yo\")));\n\n        // first call just to ensure a pooled connection will be used later\n        Request getRequest = setupRequestBuilder(false, false).get().build();\n        assertThat(okHttp.newCall(getRequest).execute().code()).isEqualTo(200);\n\n        Request request = setupRequestBuilder(false, false).get().build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"yo\");\n        verify(3, getRequestedFor(anyUrl()));\n    }\n\n    @Test\n    void zuulWillRetryHttpPostWhenOriginReturns503AndBodyBuffered() throws Exception {\n        OkHttpClient okHttp = setupOkHttpClient(Protocol.HTTP_1_1);\n\n        // make sure a pooled connection will be used in ProxyEndpoint\n        wireMock.register(get(anyUrl()).willReturn(aResponse().withStatus(200)));\n        Request getRequest = setupRequestBuilder(false, false).get().build();\n        assertThat(okHttp.newCall(getRequest).execute().code()).isEqualTo(200);\n\n        stubFor(post(anyUrl())\n                .inScenario(\"retry200\")\n                .whenScenarioStateIs(Scenario.STARTED)\n                .willSetStateTo(\"secondPasses\")\n                .willReturn(aResponse().withStatus(503)));\n\n        stubFor(post(anyUrl())\n                .inScenario(\"retry200\")\n                .whenScenarioStateIs(\"secondPasses\")\n                .willReturn(aResponse().withStatus(200).withBody(\"yo\")));\n\n        StreamingRequestBody body = new StreamingRequestBody();\n        Request request = setupRequestBuilder(true, false).post(body).build();\n\n        body.write(\"yo\");\n        body.write(\"yo1\");\n        body.write(\"yo2\");\n        body.write(\"yo3\");\n        body.stop();\n\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(200);\n        assertThat(response.body().string()).isEqualTo(\"yo\");\n        verify(2, postRequestedFor(anyUrl()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpGetReturnsStatus500DueToConnectionResetByPeer(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl()).willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(500);\n        assertThat(response.body().string()).isEqualTo(\"\");\n        verify(1, getRequestedFor(anyUrl()));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void httpGet_ServerChunkedDribbleDelay(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl())\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withBody(\"Hello world, is anybody listening?\")\n                        .withChunkedDribbleDelay(10, (int) CLIENT_READ_TIMEOUT.toMillis() + 500)));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        int expectedStatusCode = responseBodyBuffering ? 504 : 200;\n        assertThat(response.code()).isEqualTo(expectedStatusCode);\n        response.close();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"arguments\")\n    void blockRequestWithMultipleHostHeaders(\n            String description, OkHttpClient okHttp, boolean requestBodyBuffering, boolean responseBodyBuffering)\n            throws Exception {\n\n        wireMock.register(get(anyUrl()).willReturn(aResponse().withStatus(200)));\n\n        Request request = setupRequestBuilder(requestBodyBuffering, responseBodyBuffering)\n                .get()\n                .addHeader(\"Host\", \"aaa.example.com\")\n                .addHeader(\"Host\", \"aaa.foobar.com\")\n                .build();\n        Response response = okHttp.newCall(request).execute();\n        assertThat(response.code()).isEqualTo(500);\n        verify(0, anyRequestedFor(anyUrl()));\n        response.close();\n    }\n\n    @Test\n    @Disabled\n    void deflateOnly() throws Exception {\n        String expectedResponseBody = TestUtil.COMPRESSIBLE_CONTENT;\n\n        wireMock.register(get(anyUrl())\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withBody(expectedResponseBody)\n                        .withHeader(\"Content-Type\", TestUtil.COMPRESSIBLE_CONTENT_TYPE)));\n        URL url = new URL(zuulBaseUri);\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        connection.setRequestMethod(\"GET\");\n        connection.setAllowUserInteraction(false);\n        connection.setRequestProperty(\"Accept-Encoding\", \"deflate\");\n        InputStream inputStream = connection.getInputStream();\n        assertThat(connection.getResponseCode()).isEqualTo(200);\n        assertThat(connection.getHeaderField(\"Content-Type\")).isEqualTo(\"text/plain\");\n        assertThat(connection.getHeaderField(\"Content-Encoding\")).isEqualTo(\"deflate\");\n        byte[] compressedData = IOUtils.toByteArray(inputStream);\n        Inflater inflater = new Inflater();\n        inflater.setInput(compressedData);\n        byte[] result = new byte[1000];\n        int nBytes = inflater.inflate(result);\n        String text = new String(result, 0, nBytes, TestUtil.CHARSET);\n        assertThat(text).isEqualTo(expectedResponseBody);\n        inputStream.close();\n        connection.disconnect();\n    }\n\n    @Test\n    void gzipOnly() throws Exception {\n        String expectedResponseBody = TestUtil.COMPRESSIBLE_CONTENT;\n\n        wireMock.register(get(anyUrl())\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withBody(expectedResponseBody)\n                        .withHeader(\"Content-Type\", TestUtil.COMPRESSIBLE_CONTENT_TYPE)));\n\n        URL url = new URL(zuulBaseUri);\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        connection.setRequestMethod(\"GET\");\n        connection.setAllowUserInteraction(false);\n        connection.setRequestProperty(\"Accept-Encoding\", \"gzip\");\n        InputStream inputStream = connection.getInputStream();\n        assertThat(connection.getResponseCode()).isEqualTo(200);\n        assertThat(connection.getHeaderField(\"Content-Type\")).isEqualTo(\"text/plain\");\n        assertThat(connection.getHeaderField(\"Content-Encoding\")).isEqualTo(\"gzip\");\n        GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream);\n        byte[] data = IOUtils.toByteArray(gzipInputStream);\n        String text = new String(data, TestUtil.CHARSET);\n        assertThat(text).isEqualTo(expectedResponseBody);\n        inputStream.close();\n        gzipInputStream.close();\n        connection.disconnect();\n    }\n\n    @Test\n    void brotliOnly() throws Throwable {\n        Brotli.ensureAvailability();\n        String expectedResponseBody = TestUtil.COMPRESSIBLE_CONTENT;\n\n        wireMock.register(get(anyUrl())\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withBody(expectedResponseBody)\n                        .withHeader(\"Content-Type\", TestUtil.COMPRESSIBLE_CONTENT_TYPE)));\n\n        URL url = new URL(zuulBaseUri);\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        connection.setRequestMethod(\"GET\");\n        connection.setAllowUserInteraction(false);\n        connection.setRequestProperty(\"Accept-Encoding\", \"br\");\n        InputStream inputStream = connection.getInputStream();\n        assertThat(connection.getResponseCode()).isEqualTo(200);\n        assertThat(connection.getHeaderField(\"Content-Type\")).isEqualTo(\"text/plain\");\n        assertThat(connection.getHeaderField(\"Content-Encoding\")).isEqualTo(\"br\");\n        byte[] compressedData = IOUtils.toByteArray(inputStream);\n        assertThat(compressedData.length > 0).isTrue();\n        DirectDecompress decompressResult = DirectDecompress.decompress(compressedData);\n        assertThat(decompressResult.getResultStatus()).isEqualTo(DecoderJNI.Status.DONE);\n        assertThat(new String(decompressResult.getDecompressedData(), TestUtil.CHARSET))\n                .isEqualTo(\"Hello Hello Hello Hello Hello\");\n\n        inputStream.close();\n        connection.disconnect();\n    }\n\n    @Test\n    void noCompression() throws Exception {\n        String expectedResponseBody = TestUtil.COMPRESSIBLE_CONTENT;\n\n        wireMock.register(get(anyUrl())\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withBody(expectedResponseBody)\n                        .withHeader(\"Content-Type\", TestUtil.COMPRESSIBLE_CONTENT_TYPE)));\n\n        URL url = new URL(zuulBaseUri);\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        connection.setRequestMethod(\"GET\");\n        connection.setAllowUserInteraction(false);\n        connection.setRequestProperty(\"Accept-Encoding\", \"\"); // no compression\n        InputStream inputStream = connection.getInputStream();\n        assertThat(connection.getResponseCode()).isEqualTo(200);\n        assertThat(connection.getHeaderField(\"Content-Type\")).isEqualTo(\"text/plain\");\n        assertThat(connection.getHeaderField(\"Content-Encoding\")).isNull();\n        byte[] data = IOUtils.toByteArray(inputStream);\n        String text = new String(data, TestUtil.CHARSET);\n        assertThat(text).isEqualTo(expectedResponseBody);\n        inputStream.close();\n        connection.disconnect();\n    }\n\n    @Test\n    void jumboOriginResponseShouldBeChunked() throws Exception {\n        String expectedResponseBody = TestUtil.JUMBO_RESPONSE_BODY;\n\n        wireMock.register(get(anyUrl())\n                .willReturn(aResponse()\n                        .withStatus(200)\n                        .withBody(expectedResponseBody)\n                        .withHeader(\"Content-Type\", TestUtil.COMPRESSIBLE_CONTENT_TYPE)));\n\n        URL url = new URL(zuulBaseUri);\n        HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n        connection.setRequestMethod(\"GET\");\n        connection.setAllowUserInteraction(false);\n        connection.setRequestProperty(\"Accept-Encoding\", \"\"); // no compression\n        InputStream inputStream = connection.getInputStream();\n        assertThat(connection.getResponseCode()).isEqualTo(200);\n        assertThat(connection.getHeaderField(\"Content-Type\")).isEqualTo(\"text/plain\");\n        assertThat(connection.getHeaderField(\"Content-Encoding\")).isNull();\n        assertThat(connection.getHeaderField(\"Transfer-Encoding\")).isEqualTo(\"chunked\");\n        byte[] data = IOUtils.toByteArray(inputStream);\n        String text = new String(data, TestUtil.CHARSET);\n        assertThat(text).isEqualTo(expectedResponseBody);\n        inputStream.close();\n        connection.disconnect();\n    }\n\n    @Test\n    @EnabledOnOs(value = {OS.LINUX})\n    void epollIsAvailableOnLinux() {\n        if (Epoll.unavailabilityCause() != null) {\n            Epoll.unavailabilityCause().printStackTrace();\n        }\n        assertThat(Epoll.isAvailable()).isTrue();\n    }\n\n    private static String randomPathSegment() {\n        return UUID.randomUUID().toString();\n    }\n\n    private static void verifyResponseHeaders(Response response) {\n        assertThat(response.header(HeaderNames.REQUEST_ID)).startsWith(\"RQ-\");\n    }\n\n    private static class SimpleCallback implements Callback {\n        private final CompletableFuture<Response> future = new CompletableFuture<>();\n\n        @Override\n        public void onFailure(@NotNull Call call, @NotNull IOException e) {\n            future.completeExceptionally(e);\n        }\n\n        @Override\n        public void onResponse(@NotNull Call call, @NotNull Response response) {\n            future.complete(response);\n        }\n\n        public Response getResponse() {\n            try {\n                return future.get(5, TimeUnit.SECONDS);\n            } catch (Exception e) {\n                throw new AssertionError(\"did not get response in a reasonable amount of time\");\n            }\n        }\n    }\n\n    private static class StreamingRequestBody extends RequestBody {\n\n        private static final String SENTINEL = \"sentinel\";\n\n        private final BlockingQueue<String> data = new LinkedBlockingQueue<>();\n\n        void write(String text) {\n            data.add(text);\n        }\n\n        void stop() {\n            data.add(SENTINEL);\n        }\n\n        @Override\n        public MediaType contentType() {\n            return MediaType.get(\"text/plain\");\n        }\n\n        @Override\n        public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException {\n            String next;\n            try {\n                while (!(next = data.take()).equals(SENTINEL)) {\n                    bufferedSink.writeUtf8(next);\n                    bufferedSink.flush();\n                }\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/MultiEventLoopIntegrationTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\n/**\n * Runs integration tests using multiple event loop threads to exercise less deterministic behavior around connection\n * pooling\n *\n * @author Justin Guerra\n * @since 6/9/25\n */\npublic class MultiEventLoopIntegrationTest extends BaseIntegrationTest {\n\n    @RegisterExtension\n    static ZuulServerExtension ZUUL_EXTENSION = ZuulServerExtension.newBuilder()\n            .withEventLoopThreads(4)\n            .withOriginReadTimeout(ORIGIN_READ_TIMEOUT)\n            .build();\n\n    public MultiEventLoopIntegrationTest() {\n        super(ZUUL_EXTENSION.getServerPort());\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/SingleEventLoopIntegrationTest.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration;\n\nimport org.junit.jupiter.api.extension.RegisterExtension;\n\n/**\n * Runs integration tests using a single event loop thread for deterministic results around connection pooling\n *\n * @author Justin Guerra\n * @since 6/9/25\n */\npublic class SingleEventLoopIntegrationTest extends BaseIntegrationTest {\n\n    @RegisterExtension\n    static ZuulServerExtension ZUUL_EXTENSION = ZuulServerExtension.newBuilder()\n            .withEventLoopThreads(1)\n            .withOriginReadTimeout(ORIGIN_READ_TIMEOUT)\n            .build();\n\n    public SingleEventLoopIntegrationTest() {\n        super(ZUUL_EXTENSION.getServerPort());\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/ZuulServerExtension.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.client.config.CommonClientConfigKey;\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.zuul.integration.server.Bootstrap;\nimport java.io.IOException;\nimport java.net.ServerSocket;\nimport java.time.Duration;\nimport java.util.Objects;\nimport org.apache.commons.configuration.AbstractConfiguration;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.BeforeAllCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\n/**\n * Simple extension for managing the lifecycle of a zuul server for use in integration testing\n *\n * @author Justin Guerra\n * @since 6/9/25\n */\npublic class ZuulServerExtension implements AfterAllCallback, BeforeAllCallback {\n\n    private final int eventLoopThreads;\n    private final Duration originReadTimeout;\n\n    private Bootstrap bootstrap;\n    private int serverPort;\n\n    private ZuulServerExtension(Builder builder) {\n        this.eventLoopThreads = builder.eventLoopThreads;\n        this.originReadTimeout = builder.originReadTimeout;\n    }\n\n    @Override\n    public void beforeAll(ExtensionContext context) throws Exception {\n        serverPort = findAvailableTcpPort();\n\n        AbstractConfiguration config = ConfigurationManager.getConfigInstance();\n        config.setProperty(\"zuul.server.netty.socket.force_nio\", \"true\");\n        config.setProperty(\"zuul.server.netty.threads.worker\", String.valueOf(eventLoopThreads));\n        config.setProperty(\"zuul.server.port.main\", serverPort);\n        config.setProperty(\"api.ribbon.\" + CommonClientConfigKey.ReadTimeout.key(), originReadTimeout.toMillis());\n        config.setProperty(\n                \"api.ribbon.NIWSServerListClassName\", \"com.netflix.zuul.integration.server.OriginServerList\");\n\n        // short circuit graceful shutdown\n        config.setProperty(\"server.outofservice.close.timeout\", \"0\");\n        bootstrap = new Bootstrap();\n        bootstrap.start();\n        assertThat(bootstrap.isRunning()).isTrue();\n    }\n\n    @Override\n    public void afterAll(ExtensionContext context) throws Exception {\n        if (bootstrap != null) {\n            bootstrap.stop();\n        }\n    }\n\n    public int getServerPort() {\n        return serverPort;\n    }\n\n    public static Builder newBuilder() {\n        return new Builder();\n    }\n\n    private static int findAvailableTcpPort() {\n        try (ServerSocket sock = new ServerSocket(0)) {\n            return sock.getLocalPort();\n        } catch (IOException e) {\n            return -1;\n        }\n    }\n\n    public static class Builder {\n        private int eventLoopThreads = 1;\n        private Duration originReadTimeout;\n\n        public Builder withEventLoopThreads(int eventLoopThreads) {\n            this.eventLoopThreads = eventLoopThreads;\n            return this;\n        }\n\n        public Builder withOriginReadTimeout(Duration originReadTimeout) {\n            this.originReadTimeout = originReadTimeout;\n            return this;\n        }\n\n        public ZuulServerExtension build() {\n            Objects.requireNonNull(originReadTimeout, \"originReadTimeout cannot be null\");\n            return new ZuulServerExtension(this);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/Bootstrap.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server;\n\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.BasicRequestCompleteHandler;\nimport com.netflix.zuul.DefaultFilterFactory;\nimport com.netflix.zuul.StaticFilterLoader;\nimport com.netflix.zuul.context.ZuulSessionContextDecorator;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.integration.server.filters.CrossThreadBoundaryFilter;\nimport com.netflix.zuul.integration.server.filters.InboundRoutesFilter;\nimport com.netflix.zuul.integration.server.filters.NeedsBodyBufferedInboundFilter;\nimport com.netflix.zuul.integration.server.filters.NeedsBodyBufferedOutboundFilter;\nimport com.netflix.zuul.integration.server.filters.RequestHeaderFilter;\nimport com.netflix.zuul.integration.server.filters.ResponseHeaderFilter;\nimport com.netflix.zuul.netty.server.ClientRequestReceiver;\nimport com.netflix.zuul.netty.server.DirectMemoryMonitor;\nimport com.netflix.zuul.netty.server.Server;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.origins.BasicNettyOriginManager;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class Bootstrap {\n\n    private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class);\n\n    private static final Set<? extends Class<? extends ZuulFilter<?, ?>>> FILTER_TYPES;\n\n    static {\n        Set<Class<? extends ZuulFilter<?, ?>>> classes = new LinkedHashSet<>();\n        classes.add(InboundRoutesFilter.class);\n        classes.add(NeedsBodyBufferedInboundFilter.class);\n        classes.add(RequestHeaderFilter.class);\n        classes.add(CrossThreadBoundaryFilter.class);\n        classes.add(ResponseHeaderFilter.class);\n        classes.add(NeedsBodyBufferedOutboundFilter.class);\n\n        FILTER_TYPES = Collections.unmodifiableSet(classes);\n    }\n\n    private Server server;\n\n    public void start() {\n        long startNanos = System.nanoTime();\n        logger.info(\"Zuul: starting up.\");\n\n        try {\n            Registry registry = new DefaultRegistry();\n            AccessLogPublisher accessLogPublisher = new AccessLogPublisher(\n                    \"ACCESS\", (channel, httpRequest) -> ClientRequestReceiver.getRequestFromChannel(channel)\n                            .getContext()\n                            .getUUID());\n            ServerStartup serverStartup = new ServerStartup(\n                    new NoOpServerStatusManager(),\n                    new StaticFilterLoader(new DefaultFilterFactory(), FILTER_TYPES),\n                    new ZuulSessionContextDecorator(new BasicNettyOriginManager(registry)),\n                    (f, s) -> {},\n                    new BasicRequestCompleteHandler(),\n                    registry,\n                    new DirectMemoryMonitor(registry),\n                    new EventLoopGroupMetrics(registry),\n                    null,\n                    new ApplicationInfoManager(null, null, null),\n                    accessLogPublisher,\n                    new PushConnectionRegistry());\n            serverStartup.init();\n            server = serverStartup.server();\n\n            server.start();\n            long startupDuration = System.nanoTime() - startNanos;\n            logger.info(\"Zuul: finished startup. Duration = {}ms\", TimeUnit.NANOSECONDS.toMillis(startupDuration));\n            // server.awaitTermination();\n        } catch (Throwable t) {\n\n            throw new RuntimeException(t);\n        }\n    }\n\n    public Server getServer() {\n        return this.server;\n    }\n\n    public boolean isRunning() {\n        return (server != null) && (server.getListeningAddresses().size() > 0);\n    }\n\n    public void stop() {\n        if (server != null) {\n            server.stop();\n        }\n    }\n\n    private static class NoOpServerStatusManager extends ServerStatusManager {\n\n        public NoOpServerStatusManager() {\n            super(null);\n        }\n\n        @Override\n        public void localStatus(InstanceInfo.InstanceStatus status) {}\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/HeaderNames.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server;\n\npublic class HeaderNames {\n    private HeaderNames() {}\n\n    public static final String REQUEST_ID = \"request-id\";\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/OriginServerList.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server;\n\nimport com.google.common.base.Strings;\nimport com.google.common.collect.Lists;\nimport com.netflix.loadbalancer.ConfigurationBasedServerList;\nimport com.netflix.loadbalancer.Server;\nimport java.util.List;\n\npublic class OriginServerList extends ConfigurationBasedServerList {\n    @Override\n    protected List<Server> derive(String value) {\n        List<Server> list = Lists.newArrayList();\n        if (!Strings.isNullOrEmpty(value)) {\n            for (String s : value.split(\",\", -1)) {\n                String[] hostAndPort = s.split(\":\", -1);\n                String host = hostAndPort[0];\n                int port = Integer.parseInt(hostAndPort[1]);\n                list.add(TestUtil.makeDiscoveryEnabledServer(\"\", host, port));\n            }\n        }\n        return list;\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/ServerStartup.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server;\n\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.RequestCompleteHandler;\nimport com.netflix.zuul.context.SessionContextDecorator;\nimport com.netflix.zuul.netty.server.BaseServerStartup;\nimport com.netflix.zuul.netty.server.DefaultEventLoopConfig;\nimport com.netflix.zuul.netty.server.DirectMemoryMonitor;\nimport com.netflix.zuul.netty.server.Http1MutualSslChannelInitializer;\nimport com.netflix.zuul.netty.server.NamedSocketAddress;\nimport com.netflix.zuul.netty.server.SocketAddressProperty;\nimport com.netflix.zuul.netty.server.ZuulDependencyKeys;\nimport com.netflix.zuul.netty.server.ZuulServerChannelInitializer;\nimport com.netflix.zuul.netty.server.http2.Http2SslChannelInitializer;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.netty.ssl.BaseSslContextFactory;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.codec.compression.CompressionOptions;\nimport io.netty.handler.codec.http.HttpContentCompressor;\nimport io.netty.handler.ssl.ClientAuth;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\nimport java.io.File;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Singleton\npublic class ServerStartup extends BaseServerStartup {\n\n    enum ServerType {\n        HTTP,\n        HTTP2,\n        HTTP_MUTUAL_TLS,\n        WEBSOCKET,\n        SSE\n    }\n\n    private static final String[] WWW_PROTOCOLS = new String[] {\"TLSv1.3\", \"TLSv1.2\", \"TLSv1.1\", \"TLSv1\", \"SSLv3\"};\n    private static final ServerType SERVER_TYPE = ServerType.HTTP;\n    private final PushConnectionRegistry pushConnectionRegistry;\n    //    private final SamplePushMessageSenderInitializer pushSenderInitializer;\n\n    @Inject\n    public ServerStartup(\n            ServerStatusManager serverStatusManager,\n            FilterLoader filterLoader,\n            SessionContextDecorator sessionCtxDecorator,\n            FilterUsageNotifier usageNotifier,\n            RequestCompleteHandler reqCompleteHandler,\n            Registry registry,\n            DirectMemoryMonitor directMemoryMonitor,\n            EventLoopGroupMetrics eventLoopGroupMetrics,\n            EurekaClient discoveryClient,\n            ApplicationInfoManager applicationInfoManager,\n            AccessLogPublisher accessLogPublisher,\n            PushConnectionRegistry pushConnectionRegistry) {\n        super(\n                serverStatusManager,\n                filterLoader,\n                sessionCtxDecorator,\n                usageNotifier,\n                reqCompleteHandler,\n                registry,\n                directMemoryMonitor,\n                eventLoopGroupMetrics,\n                new DefaultEventLoopConfig(),\n                discoveryClient,\n                applicationInfoManager,\n                accessLogPublisher);\n        this.pushConnectionRegistry = pushConnectionRegistry;\n        // this.pushSenderInitializer = pushSenderInitializer;\n    }\n\n    @Override\n    protected Map<NamedSocketAddress, ChannelInitializer<?>> chooseAddrsAndChannels(ChannelGroup clientChannels) {\n        Map<NamedSocketAddress, ChannelInitializer<?>> addrsToChannels = new HashMap<>();\n        SocketAddress sockAddr;\n        String metricId;\n        {\n            int port = new DynamicIntProperty(\"zuul.server.port.main\", 7001).get();\n            sockAddr = new SocketAddressProperty(\"zuul.server.addr.main\", \"=\" + port).getValue();\n            if (sockAddr instanceof InetSocketAddress) {\n                metricId = String.valueOf(((InetSocketAddress) sockAddr).getPort());\n            } else {\n                // Just pick something.   This would likely be a UDS addr or a LocalChannel addr.\n                metricId = sockAddr.toString();\n            }\n        }\n\n        SocketAddress pushSockAddr;\n        {\n            int pushPort = new DynamicIntProperty(\"zuul.server.port.http.push\", 7008).get();\n            pushSockAddr = new SocketAddressProperty(\"zuul.server.addr.http.push\", \"=\" + pushPort).getValue();\n        }\n\n        String mainListenAddressName = \"main\";\n        ServerSslConfig sslConfig;\n        ChannelConfig channelConfig = defaultChannelConfig(mainListenAddressName);\n        ChannelConfig channelDependencies = defaultChannelDependencies(mainListenAddressName);\n\n        /* These settings may need to be tweaked depending if you're running behind an ELB HTTP listener, TCP listener,\n         * or directly on the internet.\n         */\n        switch (SERVER_TYPE) {\n            /* The below settings can be used when running behind an ELB HTTP listener that terminates SSL for you\n             * and passes XFF headers.\n             */\n            case HTTP:\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.ALWAYS);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, false);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, false);\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"http\", sockAddr),\n                        new ZuulServerChannelInitializer(metricId, channelConfig, channelDependencies, clientChannels) {\n                            @Override\n                            protected void addHttp1Handlers(ChannelPipeline pipeline) {\n                                super.addHttp1Handlers(pipeline);\n                                pipeline.addLast(new HttpContentCompressor((CompressionOptions[]) null));\n                            }\n                        });\n                logAddrConfigured(sockAddr);\n                break;\n\n            /* The below settings can be used when running behind an ELB TCP listener with proxy protocol, terminating\n             * SSL in Zuul.\n             */\n            case HTTP2:\n                sslConfig = ServerSslConfig.builder()\n                        .protocols(WWW_PROTOCOLS)\n                        .ciphers(ServerSslConfig.getDefaultCiphers())\n                        .certChainFile(loadFromResources(\"server.cert\"))\n                        .keyFile(loadFromResources(\"server.key\"))\n                        .build();\n\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.serverSslConfig, sslConfig);\n                channelConfig.set(\n                        CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig));\n\n                addHttp2DefaultConfig(channelConfig, mainListenAddressName);\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"http2\", sockAddr),\n                        new Http2SslChannelInitializer(metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr, sslConfig);\n                break;\n\n            /* The below settings can be used when running behind an ELB TCP listener with proxy protocol, terminating\n             * SSL in Zuul.\n             *\n             * Can be tested using certs in resources directory:\n             *  curl https://localhost:7001/test -vk --cert src/main/resources/ssl/client.cert:zuul123 --key src/main/resources/ssl/client.key\n             */\n            case HTTP_MUTUAL_TLS:\n                sslConfig = ServerSslConfig.builder()\n                        .protocols(WWW_PROTOCOLS)\n                        .ciphers(ServerSslConfig.getDefaultCiphers())\n                        .certChainFile(loadFromResources(\"server.cert\"))\n                        .keyFile(loadFromResources(\"server.key\"))\n                        .clientAuth(ClientAuth.REQUIRE)\n                        .clientAuthTrustStoreFile(loadFromResources(\"truststore.jks\"))\n                        .clientAuthTrustStorePasswordFile(loadFromResources(\"truststore.key\"))\n                        .build();\n\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n                channelConfig.set(CommonChannelConfigKeys.serverSslConfig, sslConfig);\n                channelConfig.set(\n                        CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig));\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"http_mtls\", sockAddr),\n                        new Http1MutualSslChannelInitializer(\n                                metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr, sslConfig);\n                break;\n\n            /* Settings to be used when running behind an ELB TCP listener with proxy protocol as a Push notification\n             * server using WebSockets */\n            case WEBSOCKET:\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n\n                channelDependencies.set(ZuulDependencyKeys.pushConnectionRegistry, pushConnectionRegistry);\n\n                /*\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"websocket\", sockAddr),\n                        new SampleWebSocketPushChannelInitializer(\n                                metricId, channelConfig, channelDependencies, clientChannels)); */\n                logAddrConfigured(sockAddr);\n\n                // port to accept push message from the backend, should be accessible on internal network only.\n                // TODO ? addrsToChannels.put(new NamedSocketAddress(\"http.push\", pushSockAddr), pushSenderInitializer);\n                logAddrConfigured(pushSockAddr);\n                break;\n\n            /* Settings to be used when running behind an ELB TCP listener with proxy protocol as a Push notification\n             * server using Server Sent Events (SSE) */\n            case SSE:\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n\n                channelDependencies.set(ZuulDependencyKeys.pushConnectionRegistry, pushConnectionRegistry);\n\n                /*\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"sse\", sockAddr),\n                        new SampleSSEPushChannelInitializer(\n                                metricId, channelConfig, channelDependencies, clientChannels)); */\n                logAddrConfigured(sockAddr);\n                // port to accept push message from the backend, should be accessible on internal network only.\n                // todo ? addrsToChannels.put(new NamedSocketAddress(\"http.push\", pushSockAddr), pushSenderInitializer);\n                logAddrConfigured(pushSockAddr);\n                break;\n        }\n\n        return Collections.unmodifiableMap(addrsToChannels);\n    }\n\n    private File loadFromResources(String s) {\n        return new File(ClassLoader.getSystemResource(\"ssl/\" + s).getFile());\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/TestUtil.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server;\n\nimport com.netflix.appinfo.InstanceInfo;\nimport com.netflix.niws.loadbalancer.DiscoveryEnabledServer;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.UUID;\nimport org.apache.commons.lang3.StringUtils;\n\npublic class TestUtil {\n    private TestUtil() {}\n\n    public static final Charset CHARSET = StandardCharsets.UTF_8;\n\n    public static final String COMPRESSIBLE_CONTENT = \"Hello Hello Hello Hello Hello\";\n    public static final String COMPRESSIBLE_CONTENT_TYPE = \"text/plain\";\n    public static final String JUMBO_RESPONSE_BODY = StringUtils.repeat(\"abc\", 1_000_000);\n\n    public static DiscoveryEnabledServer makeDiscoveryEnabledServer(String appName, String ipAddress, int port) {\n        InstanceInfo instanceInfo = new InstanceInfo(\n                UUID.randomUUID().toString(),\n                appName,\n                appName,\n                ipAddress,\n                \"sid123\",\n                new InstanceInfo.PortWrapper(true, port),\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                1,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null,\n                null);\n        return new DiscoveryEnabledServer(instanceInfo, false, true);\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/BodyUtil.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\npublic class BodyUtil {\n    public static boolean needsRequestBodyBuffering(HttpRequestMessage request) {\n        return request.getQueryParams().contains(\"bufferRequestBody\", \"true\");\n    }\n\n    public static boolean needsResponseBodyBuffering(HttpRequestMessage request) {\n        return request.getQueryParams().contains(\"bufferResponseBody\", \"true\");\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/CrossThreadBoundaryFilter.java",
    "content": "/*\n * Copyright 2025 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterSyncType;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpInboundFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport rx.Observable;\n\n/**\n * @author Justin Guerra\n * @since 5/20/25\n */\n@Filter(order = 30, type = FilterType.INBOUND, sync = FilterSyncType.ASYNC)\npublic class CrossThreadBoundaryFilter extends HttpInboundFilter {\n\n    private final ExecutorService executor;\n\n    public CrossThreadBoundaryFilter() {\n        executor = Executors.newSingleThreadExecutor();\n    }\n\n    @Override\n    public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage input) {\n        // force a thread boundary change\n        Future<HttpRequestMessage> future = executor.submit(() -> input);\n        return Observable.from(future);\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage msg) {\n        return true;\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/InboundRoutesFilter.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.endpoint.ProxyEndpoint;\nimport com.netflix.zuul.filters.http.HttpInboundSyncFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\n@Filter(order = 0, type = FilterType.INBOUND)\npublic class InboundRoutesFilter extends HttpInboundSyncFilter {\n    @Override\n    public int filterOrder() {\n        return 0;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage msg) {\n        return true;\n    }\n\n    @Override\n    public HttpRequestMessage apply(HttpRequestMessage input) {\n        // uncomment this line to trigger a resource leak\n        // ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer();\n\n        SessionContext context = input.getContext();\n        context.setEndpoint(ProxyEndpoint.class.getCanonicalName());\n        context.setRouteVIP(\"api\");\n        return input;\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/NeedsBodyBufferedInboundFilter.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpInboundFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport rx.Observable;\n\n@Filter(order = 20, type = FilterType.INBOUND)\npublic class NeedsBodyBufferedInboundFilter extends HttpInboundFilter {\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage msg) {\n        return msg.hasBody();\n    }\n\n    @Override\n    public boolean needsBodyBuffered(HttpRequestMessage message) {\n        return BodyUtil.needsRequestBodyBuffering(message);\n    }\n\n    @Override\n    public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage input) {\n        return Observable.just(input);\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/NeedsBodyBufferedOutboundFilter.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpOutboundFilter;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport rx.Observable;\n\n@Filter(order = 450, type = FilterType.OUTBOUND)\npublic class NeedsBodyBufferedOutboundFilter extends HttpOutboundFilter {\n    @Override\n    public boolean shouldFilter(HttpResponseMessage msg) {\n        return msg.hasBody();\n    }\n\n    @Override\n    public boolean needsBodyBuffered(HttpResponseMessage message) {\n        return BodyUtil.needsResponseBodyBuffering(message.getOutboundRequest());\n    }\n\n    @Override\n    public Observable<HttpResponseMessage> applyAsync(HttpResponseMessage response) {\n        return Observable.just(response);\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/RequestHeaderFilter.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpInboundFilter;\nimport com.netflix.zuul.integration.server.HeaderNames;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport java.util.UUID;\nimport rx.Observable;\n\n@Filter(order = 10, type = FilterType.INBOUND)\npublic class RequestHeaderFilter extends HttpInboundFilter {\n    @Override\n    public boolean shouldFilter(HttpRequestMessage msg) {\n        return true;\n    }\n\n    @Override\n    public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage request) {\n        request.getHeaders().set(HeaderNames.REQUEST_ID, \"RQ-\" + UUID.randomUUID());\n        request.storeInboundRequest();\n        return Observable.just(request);\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/java/com/netflix/zuul/integration/server/filters/ResponseHeaderFilter.java",
    "content": "/*\n * Copyright 2022 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.integration.server.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.http.HttpOutboundFilter;\nimport com.netflix.zuul.integration.server.HeaderNames;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport rx.Observable;\n\n@Filter(order = 400, type = FilterType.OUTBOUND)\npublic class ResponseHeaderFilter extends HttpOutboundFilter {\n    @Override\n    public boolean shouldFilter(HttpResponseMessage msg) {\n        return true;\n    }\n\n    @Override\n    public Observable<HttpResponseMessage> applyAsync(HttpResponseMessage response) {\n        String requestId = response.getInboundRequest().getHeaders().getFirst(HeaderNames.REQUEST_ID);\n        if (requestId != null) {\n            response.getHeaders().set(HeaderNames.REQUEST_ID, requestId);\n            response.storeInboundResponse();\n        }\n        return Observable.just(response);\n    }\n}\n"
  },
  {
    "path": "zuul-integration-test/src/test/resources/log4j2-test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2023 Netflix, Inc.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n\n-->\n<Configuration status=\"warn\">\n    <Appenders>\n        <Console name=\"Console\" target=\"SYSTEM_OUT\">\n            <PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n\"/>\n        </Console>\n    </Appenders>\n    <Loggers>\n        <Root level=\"INFO\">\n            <AppenderRef ref=\"Console\"/>\n        </Root>\n        <Logger name=\"org.apache\" level=\"WARN\"/>\n        <Logger name=\"org.eclipse\" level=\"WARN\"/>\n        <Logger name=\"WireMock\" level=\"WARN\"/>\n        <Logger name=\"zuul.server.nettylog\" level=\"WARN\"/>\n        <Logger name=\"zuul.origin.nettylog\" level=\"WARN\"/>\n    </Loggers>\n</Configuration>"
  },
  {
    "path": "zuul-processor/build.gradle",
    "content": "apply plugin: \"java\"\n\ndependencies {\n    implementation libraries.guava\n    implementation project(\":zuul-core\")\n\n    testImplementation libraries.jupiterApi, libraries.jupiterParams, libraries.jupiterEngine, libraries.junitPlatformLauncher,\n            libraries.assertj\n    testAnnotationProcessor project(\":zuul-processor\")\n}\n\n// Silences log statements during tests.   This still allows normal failures to be printed.\ntest {\n    testLogging {\n        showStandardStreams = false\n    }\n}\n"
  },
  {
    "path": "zuul-processor/src/main/java/com/netflix/zuul/filters/processor/FilterProcessor.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.processor;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Reader;\nimport java.io.Writer;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.NoSuchFileException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\nimport javax.annotation.processing.AbstractProcessor;\nimport javax.annotation.processing.Filer;\nimport javax.annotation.processing.RoundEnvironment;\nimport javax.annotation.processing.SupportedAnnotationTypes;\nimport javax.annotation.processing.SupportedSourceVersion;\nimport javax.lang.model.SourceVersion;\nimport javax.lang.model.element.Element;\nimport javax.lang.model.element.Modifier;\nimport javax.lang.model.element.TypeElement;\nimport javax.tools.FileObject;\nimport javax.tools.StandardLocation;\n\n@SupportedAnnotationTypes(FilterProcessor.FILTER_TYPE)\n@SupportedSourceVersion(SourceVersion.RELEASE_21)\npublic final class FilterProcessor extends AbstractProcessor {\n\n    static final String FILTER_TYPE = \"com.netflix.zuul.Filter\";\n\n    private final Set<String> annotatedElements = new HashSet<>();\n\n    @Override\n    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {\n        Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(\n                processingEnv.getElementUtils().getTypeElement(FILTER_TYPE));\n        for (Element el : annotated) {\n            if (el.getModifiers().contains(Modifier.ABSTRACT)) {\n                continue;\n            }\n            annotatedElements.add(processingEnv\n                    .getElementUtils()\n                    .getBinaryName((TypeElement) el)\n                    .toString());\n        }\n\n        if (roundEnv.processingOver()) {\n            try {\n                addNewClasses(processingEnv.getFiler(), annotatedElements);\n            } catch (IOException e) {\n                throw new RuntimeException(e);\n            } finally {\n                annotatedElements.clear();\n            }\n        }\n        return false;\n    }\n\n    static void addNewClasses(Filer filer, Collection<String> elements) throws IOException {\n        String resourceName = \"META-INF/zuul/allfilters\";\n        List<String> existing = Collections.emptyList();\n        try {\n            FileObject existingFilters = filer.getResource(StandardLocation.CLASS_OUTPUT, \"\", resourceName);\n            try (InputStream is = existingFilters.openInputStream();\n                    InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {\n                existing = readResourceFile(reader);\n            }\n        } catch (FileNotFoundException | NoSuchFileException e) {\n            // Perhaps log this.\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n\n        int sizeBefore = existing.size();\n        Set<String> existingSet = new LinkedHashSet<>(existing);\n        List<String> newElements = new ArrayList<>(existingSet);\n        for (String element : elements) {\n            if (existingSet.add(element)) {\n                newElements.add(element);\n            }\n        }\n        if (newElements.size() == sizeBefore) {\n            // nothing to do.\n            return;\n        }\n        newElements.sort(String::compareTo);\n\n        FileObject dest = filer.createResource(StandardLocation.CLASS_OUTPUT, \"\", resourceName);\n        try (OutputStream os = dest.openOutputStream();\n                OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {\n            writeResourceFile(osw, newElements);\n        }\n    }\n\n    @VisibleForTesting\n    static List<String> readResourceFile(Reader reader) throws IOException {\n        BufferedReader br = new BufferedReader(reader);\n        String line;\n        List<String> lines = new ArrayList<>();\n        while ((line = br.readLine()) != null) {\n            if (line.trim().isEmpty()) {\n                continue;\n            }\n            lines.add(line);\n        }\n        return Collections.unmodifiableList(lines);\n    }\n\n    @VisibleForTesting\n    static void writeResourceFile(Writer writer, Collection<?> elements) throws IOException {\n        BufferedWriter bw = new BufferedWriter(writer);\n        for (Object element : elements) {\n            bw.write(String.valueOf(element));\n            bw.newLine();\n        }\n        bw.flush();\n    }\n}\n"
  },
  {
    "path": "zuul-processor/src/main/resources/META-INF/gradle/incremental.annotation.processors",
    "content": "com.netflix.zuul.filters.processor.FilterProcessor,aggregating"
  },
  {
    "path": "zuul-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor",
    "content": "com.netflix.zuul.filters.processor.FilterProcessor"
  },
  {
    "path": "zuul-processor/src/test/java/com/netflix/zuul/filters/processor/FilterProcessorTest.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.processor;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nimport com.netflix.zuul.StaticFilterLoader;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.filters.processor.override.SubpackageFilter;\nimport com.netflix.zuul.filters.processor.subpackage.OverrideFilter;\nimport java.util.Collection;\nimport org.junit.jupiter.api.Test;\n\n/**\n * Tests for {@link FilterProcessor}.\n */\nclass FilterProcessorTest {\n\n    @Test\n    void allFilterClassedRecorded() throws Exception {\n        Collection<Class<ZuulFilter<?, ?>>> filters =\n                StaticFilterLoader.loadFilterTypesFromResources(getClass().getClassLoader());\n\n        @SuppressWarnings(\"unchecked\")\n        Class<ZuulFilter<?, ?>>[] expected = new Class[] {\n            OuterClassFilter.class,\n            TopLevelFilter.class,\n            TopLevelFilter.StaticSubclassFilter.class,\n            TopLevelFilter.SubclassFilter.class,\n            OverrideFilter.class,\n            SubpackageFilter.class\n        };\n        assertThat(filters).containsExactlyInAnyOrder(expected);\n    }\n}\n"
  },
  {
    "path": "zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TestFilter.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.processor;\n\nimport com.netflix.zuul.exception.ZuulFilterConcurrencyExceededException;\nimport com.netflix.zuul.filters.FilterSyncType;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.message.ZuulMessage;\nimport io.netty.handler.codec.http.HttpContent;\nimport rx.Observable;\n\n/**\n * A dummy filter which is used for testing.\n */\npublic abstract class TestFilter implements ZuulFilter<ZuulMessage, ZuulMessage> {\n\n    @Override\n    public boolean isDisabled() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public String filterName() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public int filterOrder() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public FilterType filterType() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean overrideStopFilterProcessing() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void incrementConcurrency() throws ZuulFilterConcurrencyExceededException {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public Observable<ZuulMessage> applyAsync(ZuulMessage input) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public void decrementConcurrency() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public FilterSyncType getSyncType() {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public ZuulMessage getDefaultOutput(ZuulMessage input) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean needsBodyBuffered(ZuulMessage input) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public HttpContent processContentChunk(ZuulMessage zuulMessage, HttpContent chunk) {\n        throw new UnsupportedOperationException();\n    }\n\n    @Override\n    public boolean shouldFilter(ZuulMessage msg) {\n        throw new UnsupportedOperationException();\n    }\n}\n"
  },
  {
    "path": "zuul-processor/src/test/java/com/netflix/zuul/filters/processor/TopLevelFilter.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.processor;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\n\n/**\n * Used to test generated code.\n */\n@Filter(order = 20, type = FilterType.INBOUND)\nfinal class TopLevelFilter extends TestFilter {\n\n    @Filter(order = 21, type = FilterType.INBOUND)\n    static final class StaticSubclassFilter extends TestFilter {}\n\n    @SuppressWarnings(\"unused\") // This should be ignored by the processor, since it is abstract\n    @Filter(order = 22, type = FilterType.INBOUND)\n    abstract static class AbstractSubclassFilter extends TestFilter {}\n\n    @SuppressWarnings(\"InnerClassMayBeStatic\") // The purpose of this test\n    @Filter(order = 23, type = FilterType.INBOUND)\n    final class SubclassFilter extends TestFilter {}\n\n    static {\n        // This should be ignored by the processor, since it is private.\n        // See https://bugs.openjdk.java.net/browse/JDK-6587158\n        @SuppressWarnings(\"unused\")\n        @Filter(order = 23, type = FilterType.INBOUND)\n        final class MethodClassFilter {}\n    }\n}\n\n@Filter(order = 24, type = FilterType.INBOUND)\nfinal class OuterClassFilter extends TestFilter {}\n"
  },
  {
    "path": "zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/SubpackageFilter.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.processor.override;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.processor.TestFilter;\n\n@Filter(order = 30, type = FilterType.INBOUND)\npublic final class SubpackageFilter extends TestFilter {}\n"
  },
  {
    "path": "zuul-processor/src/test/java/com/netflix/zuul/filters/processor/override/package-info.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\n@com.netflix.zuul.Filter.FilterPackageName(\"MySubpackage\")\npackage com.netflix.zuul.filters.processor.override;\n"
  },
  {
    "path": "zuul-processor/src/test/java/com/netflix/zuul/filters/processor/subpackage/OverrideFilter.java",
    "content": "/*\n * Copyright 2020 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.filters.processor.subpackage;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.FilterType;\nimport com.netflix.zuul.filters.processor.TestFilter;\n\n@Filter(order = 30, type = FilterType.INBOUND)\npublic final class OverrideFilter extends TestFilter {}\n"
  },
  {
    "path": "zuul-sample/build.gradle",
    "content": "apply plugin: \"java\"\napply plugin: 'application'\n\napplication {\n    mainClass = \"com.netflix.zuul.sample.Bootstrap\"\n    applicationDefaultJvmArgs = [\"-DTZ=GMT\",\n                                 \"-Darchaius.deployment.environment=test\",\n                                 \"-Dcom.sun.management.jmxremote\",\n                                 \"-Dcom.sun.management.jmxremote.local.only=false\",\n                                 \"-Deureka.validateInstanceId=false\",\n                                 \"-Deureka.mt.num_retries=1\",\n                                 \"-Dlog4j.configurationFile=log4j2.xml\"]\n}\n\ndependencies {\n    implementation project(\":zuul-core\")\n    implementation \"com.netflix.eureka:eureka-client:2.0.4\"\n    implementation 'commons-configuration:commons-configuration:1.10'\n    implementation \"jakarta.inject:jakarta.inject-api:2.0.1\"\n    annotationProcessor project(\":zuul-processor\")\n\n    implementation 'org.apache.logging.log4j:log4j-core:2.25.1'\n    implementation 'org.apache.logging.log4j:log4j-api:2.25.1'\n    implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.25.1'\n    implementation 'org.slf4j:slf4j-simple:2.0.17'\n}\n\n/*\n * Run regular:   ./gradlew run\n * Run benchmark: ./gradlew run -Pbench\n */\nrun {\n    if (project.hasProperty('bench')) {\n        println 'Running benchmark configuration...'\n        jvmArgs \"-Darchaius.deployment.environment=benchmark\"\n    }\n}\n\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/Bootstrap.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample;\n\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.appinfo.InstanceInfo.InstanceStatus;\nimport com.netflix.config.ConfigurationManager;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.DefaultRegistry;\nimport com.netflix.zuul.BasicFilterUsageNotifier;\nimport com.netflix.zuul.BasicRequestCompleteHandler;\nimport com.netflix.zuul.DefaultFilterFactory;\nimport com.netflix.zuul.FilterFactory;\nimport com.netflix.zuul.StaticFilterLoader;\nimport com.netflix.zuul.context.ZuulSessionContextDecorator;\nimport com.netflix.zuul.filters.ZuulFilter;\nimport com.netflix.zuul.netty.server.ClientRequestReceiver;\nimport com.netflix.zuul.netty.server.DirectMemoryMonitor;\nimport com.netflix.zuul.netty.server.Server;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.origins.BasicNettyOriginManager;\nimport com.netflix.zuul.sample.filters.Debug;\nimport com.netflix.zuul.sample.filters.endpoint.Healthcheck;\nimport com.netflix.zuul.sample.filters.inbound.DebugRequest;\nimport com.netflix.zuul.sample.filters.inbound.Routes;\nimport com.netflix.zuul.sample.filters.inbound.SampleServiceFilter;\nimport com.netflix.zuul.sample.filters.outbound.ZuulResponseFilter;\nimport com.netflix.zuul.sample.push.SamplePushMessageSenderInitializer;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Bootstrap\n * <p>\n * Author: Arthur Gonigberg\n * Date: November 20, 2017\n */\npublic class Bootstrap {\n\n    private static final Logger logger = LoggerFactory.getLogger(Bootstrap.class);\n\n    private static final Set<? extends Class<? extends ZuulFilter<?, ?>>> FILTER_TYPES;\n\n    static {\n        Set<Class<? extends ZuulFilter<?, ?>>> classes = new LinkedHashSet<>();\n        classes.add(Healthcheck.class);\n        classes.add(Debug.class);\n        classes.add(Routes.class);\n        classes.add(SampleServiceFilter.class);\n        classes.add(ZuulResponseFilter.class);\n        classes.add(DebugRequest.class);\n\n        FILTER_TYPES = Collections.unmodifiableSet(classes);\n    }\n\n    public static void main(String[] args) {\n        new Bootstrap().start();\n    }\n\n    public void start() {\n        long startNanos = System.nanoTime();\n        logger.info(\"Zuul Sample: starting up.\");\n        int exitCode = 0;\n\n        Server server = null;\n\n        try {\n            ConfigurationManager.loadCascadedPropertiesFromResources(\"application\");\n\n            AccessLogPublisher accessLogPublisher = new AccessLogPublisher(\n                    \"ACCESS\", (channel, httpRequest) -> ClientRequestReceiver.getRequestFromChannel(channel)\n                            .getContext()\n                            .getUUID());\n\n            ApplicationInfoManager instance = new ApplicationInfoManager(null, null, null);\n\n            PushConnectionRegistry pushConnectionRegistry = new PushConnectionRegistry();\n            SamplePushMessageSenderInitializer pushMessageSenderInitializer =\n                    new SamplePushMessageSenderInitializer(pushConnectionRegistry);\n            DefaultRegistry registry = new DefaultRegistry();\n            SampleServerStartup serverStartup = new SampleServerStartup(\n                    new ServerStatusManager(instance) {\n                        @Override\n                        public void localStatus(InstanceStatus status) {}\n                    },\n                    new StaticFilterLoader(new SampleFilterFactory(), FILTER_TYPES),\n                    new ZuulSessionContextDecorator(new BasicNettyOriginManager(registry)),\n                    new BasicFilterUsageNotifier(registry),\n                    new BasicRequestCompleteHandler(),\n                    registry,\n                    new DirectMemoryMonitor(registry),\n                    new EventLoopGroupMetrics(registry),\n                    null,\n                    instance,\n                    accessLogPublisher,\n                    pushConnectionRegistry,\n                    pushMessageSenderInitializer);\n            serverStartup.init();\n            server = serverStartup.server();\n\n            server.start();\n            long startupDuration = System.nanoTime() - startNanos;\n            logger.info(\n                    \"Zuul Sample: finished startup. Duration = {}ms\", TimeUnit.NANOSECONDS.toMillis(startupDuration));\n            server.awaitTermination();\n        } catch (Throwable t) {\n            // Don't use logger here, as we may be shutting down the JVM and the logs won't be printed.\n            t.printStackTrace();\n            System.err.println(\"###############\");\n            System.err.println(\"Zuul Sample: initialization failed. Forcing shutdown now.\");\n            System.err.println(\"###############\");\n            exitCode = 1;\n        } finally {\n            // server shutdown\n            if (server != null) {\n                server.stop();\n            }\n\n            System.exit(exitCode);\n        }\n    }\n\n    private static class SampleFilterFactory implements FilterFactory {\n\n        private final DefaultFilterFactory filterFactory;\n\n        public SampleFilterFactory() {\n            filterFactory = new DefaultFilterFactory();\n        }\n\n        @Override\n        public ZuulFilter<?, ?> newInstance(Class<?> clazz)\n                throws InstantiationException, IllegalAccessException, IllegalArgumentException,\n                        InvocationTargetException, NoSuchMethodException {\n            if (clazz.equals(SampleServiceFilter.class)) {\n                return new SampleServiceFilter(new SampleService());\n            } else {\n                return filterFactory.newInstance(clazz);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/SampleServerStartup.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample;\n\nimport com.netflix.appinfo.ApplicationInfoManager;\nimport com.netflix.config.DynamicIntProperty;\nimport com.netflix.discovery.EurekaClient;\nimport com.netflix.netty.common.accesslog.AccessLogPublisher;\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.netty.common.channel.config.CommonChannelConfigKeys;\nimport com.netflix.netty.common.metrics.EventLoopGroupMetrics;\nimport com.netflix.netty.common.proxyprotocol.StripUntrustedProxyHeadersHandler;\nimport com.netflix.netty.common.ssl.ServerSslConfig;\nimport com.netflix.netty.common.status.ServerStatusManager;\nimport com.netflix.spectator.api.Registry;\nimport com.netflix.zuul.FilterLoader;\nimport com.netflix.zuul.FilterUsageNotifier;\nimport com.netflix.zuul.RequestCompleteHandler;\nimport com.netflix.zuul.context.SessionContextDecorator;\nimport com.netflix.zuul.netty.server.BaseServerStartup;\nimport com.netflix.zuul.netty.server.DefaultEventLoopConfig;\nimport com.netflix.zuul.netty.server.DirectMemoryMonitor;\nimport com.netflix.zuul.netty.server.Http1MutualSslChannelInitializer;\nimport com.netflix.zuul.netty.server.NamedSocketAddress;\nimport com.netflix.zuul.netty.server.SocketAddressProperty;\nimport com.netflix.zuul.netty.server.ZuulDependencyKeys;\nimport com.netflix.zuul.netty.server.ZuulServerChannelInitializer;\nimport com.netflix.zuul.netty.server.http2.Http2SslChannelInitializer;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.netty.ssl.BaseSslContextFactory;\nimport com.netflix.zuul.sample.push.SamplePushMessageSenderInitializer;\nimport com.netflix.zuul.sample.push.SampleSSEPushChannelInitializer;\nimport com.netflix.zuul.sample.push.SampleWebSocketPushChannelInitializer;\nimport io.netty.channel.ChannelInitializer;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.ssl.ClientAuth;\nimport java.io.File;\nimport java.net.InetSocketAddress;\nimport java.net.SocketAddress;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Sample Server Startup - class that configures the Netty server startup settings\n * <p>\n * Author: Arthur Gonigberg\n * Date: November 20, 2017\n */\npublic class SampleServerStartup extends BaseServerStartup {\n\n    enum ServerType {\n        HTTP,\n        HTTP2,\n        HTTP_MUTUAL_TLS,\n        WEBSOCKET,\n        SSE\n    }\n\n    private static final String[] WWW_PROTOCOLS = new String[] {\"TLSv1.3\", \"TLSv1.2\", \"TLSv1.1\", \"TLSv1\", \"SSLv3\"};\n    private static final ServerType SERVER_TYPE = ServerType.HTTP;\n    private final PushConnectionRegistry pushConnectionRegistry;\n    private final SamplePushMessageSenderInitializer pushSenderInitializer;\n\n    public SampleServerStartup(\n            ServerStatusManager serverStatusManager,\n            FilterLoader filterLoader,\n            SessionContextDecorator sessionCtxDecorator,\n            FilterUsageNotifier usageNotifier,\n            RequestCompleteHandler reqCompleteHandler,\n            Registry registry,\n            DirectMemoryMonitor directMemoryMonitor,\n            EventLoopGroupMetrics eventLoopGroupMetrics,\n            EurekaClient discoveryClient,\n            ApplicationInfoManager applicationInfoManager,\n            AccessLogPublisher accessLogPublisher,\n            PushConnectionRegistry pushConnectionRegistry,\n            SamplePushMessageSenderInitializer pushSenderInitializer) {\n        super(\n                serverStatusManager,\n                filterLoader,\n                sessionCtxDecorator,\n                usageNotifier,\n                reqCompleteHandler,\n                registry,\n                directMemoryMonitor,\n                eventLoopGroupMetrics,\n                new DefaultEventLoopConfig(),\n                discoveryClient,\n                applicationInfoManager,\n                accessLogPublisher);\n        this.pushConnectionRegistry = pushConnectionRegistry;\n        this.pushSenderInitializer = pushSenderInitializer;\n    }\n\n    @Override\n    protected Map<NamedSocketAddress, ChannelInitializer<?>> chooseAddrsAndChannels(ChannelGroup clientChannels) {\n        Map<NamedSocketAddress, ChannelInitializer<?>> addrsToChannels = new HashMap<>();\n        SocketAddress sockAddr;\n        String metricId;\n        {\n            int port = new DynamicIntProperty(\"zuul.server.port.main\", 7001).get();\n            sockAddr = new SocketAddressProperty(\"zuul.server.addr.main\", \"=\" + port).getValue();\n            if (sockAddr instanceof InetSocketAddress) {\n                metricId = String.valueOf(((InetSocketAddress) sockAddr).getPort());\n            } else {\n                // Just pick something.   This would likely be a UDS addr or a LocalChannel addr.\n                metricId = sockAddr.toString();\n            }\n        }\n\n        SocketAddress pushSockAddr;\n        {\n            int pushPort = new DynamicIntProperty(\"zuul.server.port.http.push\", 7008).get();\n            pushSockAddr = new SocketAddressProperty(\"zuul.server.addr.http.push\", \"=\" + pushPort).getValue();\n        }\n\n        String mainListenAddressName = \"main\";\n        ServerSslConfig sslConfig;\n        ChannelConfig channelConfig = defaultChannelConfig(mainListenAddressName);\n        ChannelConfig channelDependencies = defaultChannelDependencies(mainListenAddressName);\n\n        /* These settings may need to be tweaked depending if you're running behind an ELB HTTP listener, TCP listener,\n         * or directly on the internet.\n         */\n        switch (SERVER_TYPE) {\n            /* The below settings can be used when running behind an ELB HTTP listener that terminates SSL for you\n             * and passes XFF headers.\n             */\n            case HTTP:\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.ALWAYS);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, false);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, false);\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"http\", sockAddr),\n                        new ZuulServerChannelInitializer(metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr);\n                break;\n\n            /* The below settings can be used when running behind an ELB TCP listener with proxy protocol, terminating\n             * SSL in Zuul.\n             */\n            case HTTP2:\n                sslConfig = ServerSslConfig.builder()\n                        .protocols(WWW_PROTOCOLS)\n                        .ciphers(ServerSslConfig.getDefaultCiphers())\n                        .certChainFile(loadFromResources(\"server.cert\"))\n                        .keyFile(loadFromResources(\"server.key\"))\n                        .build();\n\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.serverSslConfig, sslConfig);\n                channelConfig.set(\n                        CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig));\n\n                addHttp2DefaultConfig(channelConfig, mainListenAddressName);\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"http2\", sockAddr),\n                        new Http2SslChannelInitializer(metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr, sslConfig);\n                break;\n\n            /* The below settings can be used when running behind an ELB TCP listener with proxy protocol, terminating\n             * SSL in Zuul.\n             *\n             * Can be tested using certs in resources directory:\n             *  curl https://localhost:7001/test -vk --cert src/main/resources/ssl/client.cert:zuul123 --key src/main/resources/ssl/client.key\n             */\n            case HTTP_MUTUAL_TLS:\n                sslConfig = ServerSslConfig.builder()\n                        .protocols(WWW_PROTOCOLS)\n                        .ciphers(ServerSslConfig.getDefaultCiphers())\n                        .certChainFile(loadFromResources(\"server.cert\"))\n                        .keyFile(loadFromResources(\"server.key\"))\n                        .clientAuth(ClientAuth.REQUIRE)\n                        .clientAuthTrustStoreFile(loadFromResources(\"truststore.jks\"))\n                        .clientAuthTrustStorePasswordFile(loadFromResources(\"truststore.key\"))\n                        .build();\n\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n                channelConfig.set(CommonChannelConfigKeys.serverSslConfig, sslConfig);\n                channelConfig.set(\n                        CommonChannelConfigKeys.sslContextFactory, new BaseSslContextFactory(registry, sslConfig));\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"http_mtls\", sockAddr),\n                        new Http1MutualSslChannelInitializer(\n                                metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr, sslConfig);\n                break;\n\n            /* Settings to be used when running behind an ELB TCP listener with proxy protocol as a Push notification\n             * server using WebSockets */\n            case WEBSOCKET:\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n\n                channelDependencies.set(ZuulDependencyKeys.pushConnectionRegistry, pushConnectionRegistry);\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"websocket\", sockAddr),\n                        new SampleWebSocketPushChannelInitializer(\n                                metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr);\n\n                // port to accept push message from the backend, should be accessible on internal network only.\n                addrsToChannels.put(new NamedSocketAddress(\"http.push\", pushSockAddr), pushSenderInitializer);\n                logAddrConfigured(pushSockAddr);\n                break;\n\n            /* Settings to be used when running behind an ELB TCP listener with proxy protocol as a Push notification\n             * server using Server Sent Events (SSE) */\n            case SSE:\n                channelConfig.set(\n                        CommonChannelConfigKeys.allowProxyHeadersWhen,\n                        StripUntrustedProxyHeadersHandler.AllowWhen.NEVER);\n                channelConfig.set(CommonChannelConfigKeys.preferProxyProtocolForClientIp, true);\n                channelConfig.set(CommonChannelConfigKeys.isSSlFromIntermediary, false);\n                channelConfig.set(CommonChannelConfigKeys.withProxyProtocol, true);\n\n                channelDependencies.set(ZuulDependencyKeys.pushConnectionRegistry, pushConnectionRegistry);\n\n                addrsToChannels.put(\n                        new NamedSocketAddress(\"sse\", sockAddr),\n                        new SampleSSEPushChannelInitializer(\n                                metricId, channelConfig, channelDependencies, clientChannels));\n                logAddrConfigured(sockAddr);\n                // port to accept push message from the backend, should be accessible on internal network only.\n                addrsToChannels.put(new NamedSocketAddress(\"http.push\", pushSockAddr), pushSenderInitializer);\n                logAddrConfigured(pushSockAddr);\n                break;\n        }\n\n        return Collections.unmodifiableMap(addrsToChannels);\n    }\n\n    private File loadFromResources(String s) {\n        return new File(ClassLoader.getSystemResource(\"ssl/\" + s).getFile());\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/SampleService.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport rx.Observable;\n\n/**\n * Sample Service for demonstration in SampleServiceFilter.\n * <p>\n * Author: Arthur Gonigberg\n * Date: January 04, 2018\n */\npublic class SampleService {\n\n    private final AtomicBoolean status;\n\n    public SampleService() {\n        // change to true for demo\n        this.status = new AtomicBoolean(false);\n    }\n\n    public boolean isHealthy() {\n        return status.get();\n    }\n\n    public Observable<String> makeSlowRequest() {\n        return Observable.just(\"test\").delay(500, TimeUnit.MILLISECONDS);\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/filters/Debug.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.filters;\n\nimport com.netflix.zuul.Filter;\nimport com.netflix.zuul.filters.http.HttpInboundSyncFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\n/**\n * Determine if requests need to be debugged.\n *\n * In order to test this, set query parameter \"debugRequest=true\"\n *\n * Author: Arthur Gonigberg\n * Date: December 22, 2017\n */\n@Filter(order = 20)\npublic class Debug extends HttpInboundSyncFilter {\n    @Override\n    public int filterOrder() {\n        return 20;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage request) {\n        return \"true\".equalsIgnoreCase(request.getQueryParams().getFirst(\"debugRequest\"));\n    }\n\n    @Override\n    public HttpRequestMessage apply(HttpRequestMessage request) {\n        request.getContext().setDebugRequest(true);\n        request.getContext().setDebugRouting(true);\n\n        return request;\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/filters/endpoint/Healthcheck.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.filters.endpoint;\n\nimport com.netflix.zuul.filters.http.HttpSyncEndpoint;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.message.http.HttpResponseMessageImpl;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport com.netflix.zuul.stats.status.ZuulStatusCategory;\n\n/**\n *  Healthcheck Sample Endpoint\n *\n * Author: Arthur Gonigberg\n * Date: November 21, 2017\n */\npublic class Healthcheck extends HttpSyncEndpoint {\n\n    @Override\n    public HttpResponseMessage apply(HttpRequestMessage request) {\n        HttpResponseMessage resp = new HttpResponseMessageImpl(request.getContext(), request, 200);\n        resp.setBodyAsText(\"healthy\");\n\n        // need to set this manually since we are not going through the ProxyEndpoint\n        StatusCategoryUtils.setStatusCategory(request.getContext(), ZuulStatusCategory.SUCCESS);\n\n        return resp;\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/filters/inbound/DebugRequest.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.filters.inbound;\n\nimport com.netflix.zuul.context.Debug;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.http.HttpInboundSyncFilter;\nimport com.netflix.zuul.message.Header;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\n\n/**\n *  Add debug request info to the context if request is marked as debug.\n *\n * Author: Arthur Gonigberg\n * Date: December 22, 2017\n */\npublic class DebugRequest extends HttpInboundSyncFilter {\n\n    @Override\n    public int filterOrder() {\n        return 21;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage request) {\n        return request.getContext().debugRequest();\n    }\n\n    @Override\n    public boolean needsBodyBuffered(HttpRequestMessage request) {\n        return shouldFilter(request);\n    }\n\n    @Override\n    public HttpRequestMessage apply(HttpRequestMessage request) {\n        SessionContext ctx = request.getContext();\n\n        Debug.addRequestDebug(\n                ctx,\n                \"REQUEST:: \" + request.getOriginalScheme() + \" \" + request.getOriginalHost() + \":\"\n                        + request.getOriginalPort());\n\n        Debug.addRequestDebug(\n                ctx,\n                \"REQUEST:: > \" + request.getMethod() + \" \" + request.reconstructURI() + \" \" + request.getProtocol());\n\n        for (Header header : request.getHeaders().entries()) {\n            Debug.addRequestDebug(ctx, \"REQUEST:: > \" + header.getName() + \":\" + header.getValue());\n        }\n\n        if (request.hasBody()) {\n            Debug.addRequestDebug(ctx, \"REQUEST:: > \" + request.getBodyAsText());\n        }\n\n        return request;\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/filters/inbound/Routes.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.filters.inbound;\n\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.filters.endpoint.ProxyEndpoint;\nimport com.netflix.zuul.filters.http.HttpInboundSyncFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.sample.filters.endpoint.Healthcheck;\n\n/**\n * Routes configuration\n *\n * Author: Arthur Gonigberg\n * Date: November 21, 2017\n */\npublic class Routes extends HttpInboundSyncFilter {\n\n    @Override\n    public int filterOrder() {\n        return 0;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage httpRequestMessage) {\n        return true;\n    }\n\n    @Override\n    public HttpRequestMessage apply(HttpRequestMessage request) {\n        SessionContext context = request.getContext();\n        String path = request.getPath();\n\n        // Route healthchecks to the healthcheck endpoint.\n        if (path.equalsIgnoreCase(\"/healthcheck\")) {\n            context.setEndpoint(Healthcheck.class.getCanonicalName());\n        } else {\n            context.setEndpoint(ProxyEndpoint.class.getCanonicalName());\n            context.setRouteVIP(\"api\");\n        }\n\n        return request;\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/filters/inbound/SampleServiceFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.filters.inbound;\n\nimport com.netflix.zuul.filters.http.HttpInboundFilter;\nimport com.netflix.zuul.message.http.HttpRequestMessage;\nimport com.netflix.zuul.sample.SampleService;\nimport jakarta.inject.Inject;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport rx.Observable;\n\n/**\n * Sample Service Filter to demonstrate Guice injection of services and\n * making external requests to slow endpoints.\n *\n * Author: Arthur Gonigberg\n * Date: January 04, 2018\n */\npublic class SampleServiceFilter extends HttpInboundFilter {\n    private static final Logger log = LoggerFactory.getLogger(SampleServiceFilter.class);\n\n    private final SampleService sampleService;\n\n    @Inject\n    public SampleServiceFilter(SampleService sampleService) {\n        this.sampleService = sampleService;\n    }\n\n    @Override\n    public int filterOrder() {\n        return 500;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpRequestMessage msg) {\n        return sampleService.isHealthy();\n    }\n\n    @Override\n    public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage request) {\n        return sampleService.makeSlowRequest().map(response -> {\n            log.info(\"Fetched sample service result: {}\", response);\n            return request;\n        });\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/filters/outbound/ZuulResponseFilter.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.filters.outbound;\n\nimport static com.netflix.zuul.constants.ZuulHeaders.CONNECTION;\nimport static com.netflix.zuul.constants.ZuulHeaders.KEEP_ALIVE;\nimport static com.netflix.zuul.constants.ZuulHeaders.X_ORIGINATING_URL;\nimport static com.netflix.zuul.constants.ZuulHeaders.X_ZUUL;\nimport static com.netflix.zuul.constants.ZuulHeaders.X_ZUUL_ERROR_CAUSE;\nimport static com.netflix.zuul.constants.ZuulHeaders.X_ZUUL_INSTANCE;\nimport static com.netflix.zuul.constants.ZuulHeaders.X_ZUUL_PROXY_ATTEMPTS;\nimport static com.netflix.zuul.constants.ZuulHeaders.X_ZUUL_STATUS;\n\nimport com.netflix.config.DynamicBooleanProperty;\nimport com.netflix.zuul.context.Debug;\nimport com.netflix.zuul.context.SessionContext;\nimport com.netflix.zuul.exception.ZuulException;\nimport com.netflix.zuul.filters.http.HttpOutboundSyncFilter;\nimport com.netflix.zuul.message.Headers;\nimport com.netflix.zuul.message.http.HttpResponseMessage;\nimport com.netflix.zuul.niws.RequestAttempts;\nimport com.netflix.zuul.passport.CurrentPassport;\nimport com.netflix.zuul.stats.status.StatusCategory;\nimport com.netflix.zuul.stats.status.StatusCategoryUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n *  Sample Response Filter - adding custom response headers for better analysis of how the request was proxied\n *\n * Author: Arthur Gonigberg\n * Date: December 21, 2017\n */\npublic class ZuulResponseFilter extends HttpOutboundSyncFilter {\n    private static final Logger logger = LoggerFactory.getLogger(ZuulResponseFilter.class);\n\n    private static final DynamicBooleanProperty SEND_RESPONSE_HEADERS =\n            new DynamicBooleanProperty(\"zuul.responseFilter.send.headers\", true);\n\n    @Override\n    public int filterOrder() {\n        return 999;\n    }\n\n    @Override\n    public boolean shouldFilter(HttpResponseMessage request) {\n        return true;\n    }\n\n    @Override\n    public HttpResponseMessage apply(HttpResponseMessage response) {\n        SessionContext context = response.getContext();\n\n        if (SEND_RESPONSE_HEADERS.get()) {\n            Headers headers = response.getHeaders();\n\n            StatusCategory statusCategory = StatusCategoryUtils.getStatusCategory(response);\n            if (statusCategory != null) {\n                headers.set(X_ZUUL_STATUS, statusCategory.name());\n            }\n\n            RequestAttempts attempts = RequestAttempts.getFromSessionContext(response.getContext());\n            String headerStr = \"\";\n            if (attempts != null) {\n                headerStr = attempts.toString();\n            }\n            headers.set(X_ZUUL_PROXY_ATTEMPTS, headerStr);\n\n            headers.set(X_ZUUL, \"zuul\");\n            headers.set(\n                    X_ZUUL_INSTANCE,\n                    System.getenv(\"EC2_INSTANCE_ID\") != null ? System.getenv(\"EC2_INSTANCE_ID\") : \"unknown\");\n            headers.set(CONNECTION, KEEP_ALIVE);\n            headers.set(X_ORIGINATING_URL, response.getInboundRequest().reconstructURI());\n\n            if (response.getStatus() >= 400 && context.getError() != null) {\n                Throwable error = context.getError();\n                headers.set(\n                        X_ZUUL_ERROR_CAUSE,\n                        error instanceof ZuulException ? ((ZuulException) error).getErrorCause() : \"UNKNOWN_CAUSE\");\n            }\n\n            if (response.getStatus() >= 500) {\n                logger.info(\"Passport: {}\", CurrentPassport.fromSessionContext(context));\n            }\n\n            if (logger.isDebugEnabled()) {\n                logger.debug(\"Filter execution summary :: {}\", context.getFilterExecutionSummary());\n            }\n        }\n\n        if (context.debugRequest()) {\n            Debug.getRequestDebug(context).forEach(s -> logger.info(\"REQ_DEBUG: {}\", s));\n            Debug.getRoutingDebug(context).forEach(s -> logger.info(\"ZUUL_DEBUG: {}\", s));\n        }\n\n        return response;\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushAuthHandler.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.sample.push;\n\nimport com.google.common.base.Strings;\nimport com.netflix.zuul.message.http.Cookies;\nimport com.netflix.zuul.netty.server.push.PushAuthHandler;\nimport com.netflix.zuul.netty.server.push.PushUserAuth;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.cookie.Cookie;\n\n/**\n * Takes cookie value of the cookie \"userAuthCookie\" as a customerId WITHOUT ANY actual validation.\n * For sample puprose only. In real life the cookies at minimum should be HMAC signed to prevent tampering/spoofing,\n * probably encrypted too if it can be exchanged on plain HTTP.\n *\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\n@ChannelHandler.Sharable\npublic class SamplePushAuthHandler extends PushAuthHandler {\n\n    public SamplePushAuthHandler(String path) {\n        super(path, \".sample.netflix.com\");\n    }\n\n    /**\n     * We support only cookie based auth in this sample\n     */\n    @Override\n    protected boolean isDelayedAuth(FullHttpRequest req, ChannelHandlerContext ctx) {\n        return false;\n    }\n\n    @Override\n    protected PushUserAuth doAuth(FullHttpRequest req, ChannelHandlerContext ctx) {\n        Cookies cookies = parseCookies(req);\n        for (Cookie c : cookies.getAll()) {\n            if (c.name().equals(\"userAuthCookie\")) {\n                String customerId = c.value();\n                if (!Strings.isNullOrEmpty(customerId)) {\n                    return new SamplePushUserAuth(customerId);\n                }\n            }\n        }\n        return new SamplePushUserAuth(HttpResponseStatus.UNAUTHORIZED.code());\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSender.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.sample.push;\n\nimport com.google.common.base.Strings;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.netty.server.push.PushMessageSender;\nimport com.netflix.zuul.netty.server.push.PushUserAuth;\nimport io.netty.channel.ChannelHandler;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport jakarta.inject.Singleton;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\n@Singleton\n@ChannelHandler.Sharable\npublic class SamplePushMessageSender extends PushMessageSender {\n\n    public SamplePushMessageSender(PushConnectionRegistry pushConnectionRegistry) {\n        super(pushConnectionRegistry);\n    }\n\n    @Override\n    protected PushUserAuth getPushUserAuth(FullHttpRequest request) {\n        String cid = request.headers().get(\"X-CUSTOMER_ID\");\n        if (Strings.isNullOrEmpty(cid)) {\n            return new SamplePushUserAuth(HttpResponseStatus.UNAUTHORIZED.code());\n        }\n        return new SamplePushUserAuth(cid);\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushMessageSenderInitializer.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n * <p>\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * <p>\n * http://www.apache.org/licenses/LICENSE-2.0\n * <p>\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.sample.push;\n\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.netty.server.push.PushMessageSender;\nimport com.netflix.zuul.netty.server.push.PushMessageSenderInitializer;\nimport io.netty.channel.ChannelPipeline;\nimport jakarta.inject.Inject;\nimport jakarta.inject.Singleton;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\n@Singleton\npublic class SamplePushMessageSenderInitializer extends PushMessageSenderInitializer {\n\n    private final PushMessageSender pushMessageSender;\n\n    @Inject\n    public SamplePushMessageSenderInitializer(PushConnectionRegistry pushConnectionRegistry) {\n        super();\n        pushMessageSender = new SamplePushMessageSender(pushConnectionRegistry);\n    }\n\n    @Override\n    protected void addPushMessageHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(pushMessageSender);\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SamplePushUserAuth.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.sample.push;\n\nimport com.netflix.zuul.netty.server.push.PushUserAuth;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\npublic class SamplePushUserAuth implements PushUserAuth {\n\n    private final String customerId;\n    private final int statusCode;\n\n    private SamplePushUserAuth(String customerId, int statusCode) {\n        this.customerId = customerId;\n        this.statusCode = statusCode;\n    }\n\n    // Successful auth\n    public SamplePushUserAuth(String customerId) {\n        this(customerId, 200);\n    }\n\n    // Failed auth\n    public SamplePushUserAuth(int statusCode) {\n        this(\"\", statusCode);\n    }\n\n    @Override\n    public boolean isSuccess() {\n        return statusCode == 200;\n    }\n\n    @Override\n    public int statusCode() {\n        return statusCode;\n    }\n\n    @Override\n    public String getClientIdentity() {\n        return customerId;\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SampleSSEPushChannelInitializer.java",
    "content": "/*\n * Copyright 2018 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.push;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.zuul.netty.server.ZuulDependencyKeys;\nimport com.netflix.zuul.netty.server.push.PushAuthHandler;\nimport com.netflix.zuul.netty.server.push.PushChannelInitializer;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.netty.server.push.PushProtocol;\nimport com.netflix.zuul.netty.server.push.PushRegistrationHandler;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\n\n/**\n * Author: Susheel Aroskar\n * Date: 6/8/18\n */\npublic class SampleSSEPushChannelInitializer extends PushChannelInitializer {\n\n    private final PushConnectionRegistry pushConnectionRegistry;\n    private final PushAuthHandler pushAuthHandler;\n\n    public SampleSSEPushChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        super(metricId, channelConfig, channelDependencies, channels);\n        pushConnectionRegistry = channelDependencies.get(ZuulDependencyKeys.pushConnectionRegistry);\n        pushAuthHandler = new SamplePushAuthHandler(PushProtocol.SSE.getPath());\n    }\n\n    @Override\n    protected void addPushHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(PushAuthHandler.NAME, pushAuthHandler);\n        pipeline.addLast(new PushRegistrationHandler(pushConnectionRegistry, PushProtocol.SSE));\n        pipeline.addLast(new SampleSSEPushClientProtocolHandler());\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SampleSSEPushClientProtocolHandler.java",
    "content": "/*\n * Copyright 2016 Netflix, Inc.\n *\n *      Licensed under the Apache License, Version 2.0 (the \"License\");\n *      you may not use this file except in compliance with the License.\n *      You may obtain a copy of the License at\n *\n *          http://www.apache.org/licenses/LICENSE-2.0\n *\n *      Unless required by applicable law or agreed to in writing, software\n *      distributed under the License is distributed on an \"AS IS\" BASIS,\n *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n *      See the License for the specific language governing permissions and\n *      limitations under the License.\n */\n\npackage com.netflix.zuul.sample.push;\n\nimport com.netflix.config.CachedDynamicIntProperty;\nimport com.netflix.zuul.netty.server.push.PushClientProtocolHandler;\nimport com.netflix.zuul.netty.server.push.PushProtocol;\nimport io.netty.channel.ChannelFuture;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.handler.codec.http.DefaultHttpResponse;\nimport io.netty.handler.codec.http.FullHttpRequest;\nimport io.netty.handler.codec.http.HttpContentCompressor;\nimport io.netty.handler.codec.http.HttpHeaders;\nimport io.netty.handler.codec.http.HttpMethod;\nimport io.netty.handler.codec.http.HttpObjectAggregator;\nimport io.netty.handler.codec.http.HttpResponseStatus;\nimport io.netty.handler.codec.http.HttpVersion;\n\n/**\n * Created by saroskar on 10/10/16.\n */\npublic class SampleSSEPushClientProtocolHandler extends PushClientProtocolHandler {\n\n    public static final CachedDynamicIntProperty SSE_RETRY_BASE_INTERVAL =\n            new CachedDynamicIntProperty(\"zuul.push.sse.retry.base\", 5000);\n\n    @Override\n    public void channelRead(ChannelHandlerContext ctx, Object mesg) throws Exception {\n        if (mesg instanceof FullHttpRequest req) {\n\n            if (req.method().equals(HttpMethod.GET)\n                    && PushProtocol.SSE.getPath().equals(req.uri())) {\n                ctx.pipeline().fireUserEventTriggered(PushProtocol.SSE.getHandshakeCompleteEvent());\n\n                DefaultHttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);\n                HttpHeaders headers = resp.headers();\n                headers.add(\"Connection\", \"keep-alive\");\n                headers.add(\"Content-Type\", \"text/event-stream\");\n                headers.add(\"Transfer-Encoding\", \"chunked\");\n\n                ChannelFuture cf = ctx.channel().writeAndFlush(resp);\n                cf.addListener(future -> {\n                    if (future.isSuccess()) {\n                        ChannelPipeline pipeline = ctx.pipeline();\n                        if (pipeline.get(HttpObjectAggregator.class) != null) {\n                            pipeline.remove(HttpObjectAggregator.class);\n                        }\n                        if (pipeline.get(HttpContentCompressor.class) != null) {\n                            pipeline.remove(HttpContentCompressor.class);\n                        }\n                        String reconnectInterval = \"retry: \" + SSE_RETRY_BASE_INTERVAL.get() + \"\\r\\n\\r\\n\";\n                        ctx.writeAndFlush(reconnectInterval);\n                    }\n                });\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SampleWebSocketPushChannelInitializer.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.sample.push;\n\nimport com.netflix.netty.common.channel.config.ChannelConfig;\nimport com.netflix.zuul.netty.server.ZuulDependencyKeys;\nimport com.netflix.zuul.netty.server.push.PushAuthHandler;\nimport com.netflix.zuul.netty.server.push.PushChannelInitializer;\nimport com.netflix.zuul.netty.server.push.PushConnectionRegistry;\nimport com.netflix.zuul.netty.server.push.PushProtocol;\nimport com.netflix.zuul.netty.server.push.PushRegistrationHandler;\nimport io.netty.channel.ChannelPipeline;\nimport io.netty.channel.group.ChannelGroup;\nimport io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;\nimport io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\npublic class SampleWebSocketPushChannelInitializer extends PushChannelInitializer {\n\n    private final PushConnectionRegistry pushConnectionRegistry;\n    private final PushAuthHandler pushAuthHandler;\n\n    private static final int MAX_CONTENT_LENGTH = 65536; // 64KB\n\n    public SampleWebSocketPushChannelInitializer(\n            String metricId, ChannelConfig channelConfig, ChannelConfig channelDependencies, ChannelGroup channels) {\n        super(metricId, channelConfig, channelDependencies, channels);\n        pushConnectionRegistry = channelDependencies.get(ZuulDependencyKeys.pushConnectionRegistry);\n        pushAuthHandler = new SamplePushAuthHandler(PushProtocol.WEBSOCKET.getPath());\n    }\n\n    @Override\n    protected void addPushHandlers(ChannelPipeline pipeline) {\n        pipeline.addLast(PushAuthHandler.NAME, pushAuthHandler);\n        pipeline.addLast(new WebSocketServerCompressionHandler(MAX_CONTENT_LENGTH));\n        pipeline.addLast(new WebSocketServerProtocolHandler(PushProtocol.WEBSOCKET.getPath(), null, true));\n        pipeline.addLast(new PushRegistrationHandler(pushConnectionRegistry, PushProtocol.WEBSOCKET));\n        pipeline.addLast(new SampleWebSocketPushClientProtocolHandler());\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/java/com/netflix/zuul/sample/push/SampleWebSocketPushClientProtocolHandler.java",
    "content": "/**\n * Copyright 2018 Netflix, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.netflix.zuul.sample.push;\n\nimport com.netflix.zuul.netty.server.push.PushClientProtocolHandler;\nimport com.netflix.zuul.netty.server.push.PushProtocol;\nimport io.netty.channel.ChannelHandlerContext;\nimport io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.PingWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.PongWebSocketFrame;\nimport io.netty.handler.codec.http.websocketx.TextWebSocketFrame;\nimport io.netty.util.ReferenceCountUtil;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n/**\n * Author: Susheel Aroskar\n * Date: 5/16/18\n */\npublic class SampleWebSocketPushClientProtocolHandler extends PushClientProtocolHandler {\n\n    private static final Logger logger = LoggerFactory.getLogger(SampleWebSocketPushClientProtocolHandler.class);\n\n    @Override\n    public final void channelRead(ChannelHandlerContext ctx, Object msg) {\n        try {\n            if (!isAuthenticated()) {\n                // Do not entertain ANY message from unauthenticated client\n                PushProtocol.WEBSOCKET.sendErrorAndClose(ctx, 1007, \"Missing authentication\");\n            } else if (msg instanceof PingWebSocketFrame) {\n                logger.debug(\"received ping frame\");\n                ctx.writeAndFlush(new PongWebSocketFrame());\n            } else if (msg instanceof CloseWebSocketFrame) {\n                logger.debug(\"received close frame\");\n                ctx.close();\n            } else if (msg instanceof TextWebSocketFrame tf) {\n\n                String text = tf.text();\n                logger.debug(\"received test frame: {}\", text);\n                if (text != null && text.startsWith(\"ECHO \")) { // echo protocol\n                    ctx.channel().writeAndFlush(tf.copy());\n                }\n            } else if (msg instanceof BinaryWebSocketFrame) {\n                logger.debug(\"received binary frame\");\n                PushProtocol.WEBSOCKET.sendErrorAndClose(ctx, 1003, \"Binary WebSocket frames not supported\");\n            }\n        } finally {\n            ReferenceCountUtil.release(msg);\n        }\n    }\n}\n"
  },
  {
    "path": "zuul-sample/src/main/resources/application-benchmark.properties",
    "content": "### Benchmark settings\n\n# disable safety throttles\napi.ribbon.MaxConnectionsPerHost=-1\napi.netty.client.maxRequestsPerConnection=10000\napi.netty.client.perServerWaterline=-1\nzuul.origin.api.concurrency.protect.enabled=false\n\n# disable info headers\nzuul.responseFilter.send.headers=false\n"
  },
  {
    "path": "zuul-sample/src/main/resources/application-test.properties",
    "content": "# Test properties"
  },
  {
    "path": "zuul-sample/src/main/resources/application.properties",
    "content": "### Instance env settings\n\nregion=us-east-1\nenvironment=test\n\n### Eureka instance registration for this app\n\n#Name of the application to be identified by other services\neureka.name=zuul\n\n#The port where the service will be running and serving requests\neureka.port=7001\n\n#Virtual host name by which the clients identifies this service\neureka.vipAddress=${eureka.name}:${eureka.port}\n\n#For eureka clients running in eureka server, it needs to connect to servers in other zones\neureka.preferSameZone=false\n\n# Don't register locally running instances.\neureka.registration.enabled=false\n\n# By default don't validate eureka instance is from the cloud\neureka.validateInstanceId=false\n\n# Loading Filters\nzuul.filters.packages=com.netflix.zuul.filters.common,\\\n  com.netflix.zuul.sample.filters,\\\n  com.netflix.zuul.sample.filters.endpoint,\\\n  com.netflix.zuul.sample.filters.inbound,\\\n  com.netflix.zuul.sample.filters.outbound\n\n### Load balancing backends with Eureka\n\neureka.shouldUseDns=true\neureka.eurekaServer.context=discovery/v2\neureka.eurekaServer.domainName=discovery${environment}.netflix.net\neureka.eurekaServer.gzipContent=true\n\neureka.serviceUrl.default=http://${region}.${eureka.eurekaServer.domainName}:7001/${eureka.eurekaServer.context}\n\napi.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList\napi.ribbon.DeploymentContextBasedVipAddresses=api-test.netflix.net:7001\n\n\n### Load balancing backends without Eureka\n\n#eureka.shouldFetchRegistry=false\n\n#api.ribbon.listOfServers=100.66.23.88:7001,100.65.155.22:7001\n#api.ribbon.client.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList\n#api.ribbon.DeploymentContextBasedVipAddresses=api-test.netflix.net:7001\n\n\n\n\n# This has to be the last line\n@next=application-${environment}.properties\n"
  },
  {
    "path": "zuul-sample/src/main/resources/log4j2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright 2025 Netflix, Inc.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n          http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n\n-->\n<Configuration>\n  <Properties>\n    <Property name=\"defaultPattern\">%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5level [%t] %c: %msg%n\n    </Property>\n  </Properties>\n\n  <Appenders>\n    <Console name=\"STDOUT\" target=\"SYSTEM_OUT\">\n      <PatternLayout pattern=\"${defaultPattern}\"/>\n    </Console>\n  </Appenders>\n  <Loggers>\n    <Root level=\"info\">\n      <AppenderRef ref=\"STDOUT\"/>\n    </Root>\n  </Loggers>\n</Configuration>"
  },
  {
    "path": "zuul-sample/src/main/resources/ssl/client.cert",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE6jCCAtICAQEwDQYJKoZIhvcNAQEFBQAwOzELMAkGA1UEBhMCVVMxCzAJBgNV\nBAgMAkNBMQswCQYDVQQHDAJMRzESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTE3MTIx\nMjAwMzAzOVoXDTE4MTIxMjAwMzAzOVowOzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM\nAkNBMQswCQYDVQQHDAJMRzESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG\n9w0BAQEFAAOCAg8AMIICCgKCAgEAvSVlDAU4ivsh/2V38cmsm7VvphaMe6v7xZzn\nelieZqxQj0b9NJ94q9dYkUvbI0JlJDfpRAiYHA32MW5GDkmIOeGZS3P+ystLnxu8\nTIckP+dWqKez9qpXmW211vTeqbOvqOB8zyowStpVQHLF0bQ111ttFEW358ZXKFty\ngdcbqxJW4CXQ2DjTTGFy5EYB0+9e2PFo9KgKgWPsN1CU/o7/C0rUuotWRa9/WcqJ\nIHkdTKNM5Zpi/kkSWvLlPi8NJMLjOqnstE6GUBM2ur3H1akbF+uBT+sQszEScUlx\n0hujtcDOBubc5iT3U3ZbWCPN+RW90J5WwFoWvMRDDT5OQYWBmJ5wmjWum5J0s0vs\naupxM4oWdbeUdcjqQjI6euLZ+qc9VmoTP3f0O5bcKWvcRjnf8t9K+jkIEBjd8bFm\nXOuozG6vha+fl5730ihERN56iMkZv0SRMHPjrxxEWPhKmbVMoWXou/vHx/nyLpru\nbkHuPg4wVkno3UOmJ2GyHmLh1VRiYSsSBokwiOK+8iocYQwGcz4ayA5WYkghwMZV\nd9tLahflZkMhB+ikQwRHuspMcMoA/BimkpD2mtFPMsav6u6lbKrO27qU2SCrHdm0\nzAHL/b/buPBw3JMTBHRKxbh4tafxgL06BGcLZAsYFdimpJXp5VoQr99xBkYukdcH\n28NTjGECAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAvCzmIxiPxSDmAW1jF+9659xF\naKo56pkWpQOSvBkrKRo4Ia1cVXPk5DfTlOffts6w3eSpSuaaW+4NyILrV21SThsy\nnPnM/JLwOLOgxVmRVDQe1a2MiCfNr7tsAej+6IxbRMHFwvdjA22nhYRGNyVbknq8\n9fzLu/u3v0HphPbqc5DSKWtsZjH8CXnSjLwohjCqJsfWO/EwVEcksbOdwl5LWq7E\nVO5BMQ8Yyrm+qftnFNWF36o2vSLRWSh6vti+vcKR1tZm7J1XEN0Z/vKoQCiSF4iY\nY5KUuDWjyij9UW8TJJOhn9XsSZrwdrbEbTXB3qpVAqaUEEzwUrK8G5w65bCW7zLr\n627cFbDnorX90KmZ+7QMaJu6dCt1CZrPBM2a6oJid63NR7Fmt0jEJ3/zMf7qQQCZ\nnKhWlC2FqcA1NhPt21m8mKgYQKmd81LGyskJXVTaz/4zm5f1A1cRSDlzCIg2hi0e\nDQbiw8Ciz9YE3EXcecZu80PI1B/iaPWEmGPgxPfSFEw+v/9wLFaRpU3OsfCkDJX6\n7M9QPJXMFyDCN+3qq6chvwJ1EI0vKfa08OM46Y3al4pL6Y9UQkJfS18LD5x54C0K\nRvXaxi7glFAyQf4HB7f3ecrqUgIv0WMW2XK+SG/w+tZ9p0jKGQq1FqAy7Hb2LWC7\nfjDECaO9mbf8U3pr3u8=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "zuul-sample/src/main/resources/ssl/client.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,7381252110CA2BE7\n\nwGH97s5ZDmRS0vDZcwewJ6WSIHGTP8Nr6ddqnFb0cr90LZDLnPr5IEXATbhBTHjw\nQ9YXVIUUOv16qOLy40jXk04d0GoinEMoxeOODIN98V0fNzZXaiaLoNU1Rhdui0f/\nUlU8Hi5CgDCkvioWbSLNkmHPnqkfqn3YKGj+ybDblGMxvVIA3iPczhYthCcXVzcG\nZZfrteKC8kFqaeWAmVyd+SWk6B8gdgdjcfiQY2wqxrpcNTKf3quC2LDiBG+qdvwN\ntmaCnCJ6Aa/hp3kc1HjpKeFprxOt0GQ8j++G6rnlrM/0itj+SsTo3XCdaDPxFepQ\nGB8NLGQ/8CEbjgmT+mmli7mOIbFzZn/S6BwNAtDZwB7w0KgTno8WhzAgF/grC55Q\naot3PWFxAQyBRGLkh+8TO/rELYVc/3Kf1/ch9obl00KupuToK9LH/4woLWWkp2aM\n7EQER0zufWiykctSqYA+CxtJXO83c7FJrI4PwNsHxzOKpodZ7iry82ntmEXTsDrH\nDEyeCbFMNMw+vKiqsaP2D/d4NDA7LFr2U0MfgDkbe/fRSDWZxPSUphAxfmUGLVrT\nmxzf7KixrFWd4PLvU8XZPsVgtbmAM5lxYZK5lYFMO/zrpibei/FJHmtnuH57OZOJ\nvBhtmoEh7JtwWmIfRqAspUYnhYyQIKnuB8ccNFzZm74PT/LrURnC4pm1bwl7t1FZ\nflg/SEeF1apdPSFkkd0snzgIXI0a8+u3KxxVvPNWbUzxilDS4ADyrZGnhSFOnP3o\no5bG5F/Ufm8r/2YPCUpEqGQ/Kx+PmHNpnbUZRNJntFEwBPFdDg+cCOc3xkexJIKl\nQeaC+/7AKxEqV2xIQ0qlBgJFpW7I+TJZpwof4VjOWZWa93QUdl6iLE3G6auRusKT\nRxIct2jPuE1V+vu1PRxPtVARydzMMMCwR7jacOy8xqS9pSJyvVNOPuZeMdKmS4gO\ndbdrA2piMF3FCq6AujrP74YM3dIdXWMUItZtakC3NpRQXiHULOnHHhZhXrAQeIsU\n45wP1lZljHXJauTQDWF43q8G/D30FiYw3wtGz5nTCWk9bNgLzcF85MdZD7UXlgYK\nWfkR8yuUoJmzuLuioL/3S8uVEhtFCzmW4VQ/2PSoauGkCz8PIvq+BBvlPcN3fDuM\n8gAVn0XfIa8Qe753m60aTuJuLnNZhDJpckIpCM9A1+WctMlW5vliOSvTnu8leoE1\nVEbSjMZR5llKSgRyv4ktnvE8bfZqIUhUPYdpDAiNThpc4+UeTsfv9zfWBPZ0HYSs\nKuSmYcn2pq8Jxbl5OFWFkfznOJozRUfTZd5Fr/U+KmlK7XepEuXZ9ZrnME0o3TxN\nq8aqmIb/MiIO5Wn5i1W6iX3z8sLmaY3asDqYWco4p1RgrkQAALPWAdg0LuWPKXGQ\nrsU2w1InsvjDOd+2GleA2MRJdSeu9RYVXNcPWH1XnuCirZE2TswjcuS+agAUfnM5\nMYq1FhXTD7LNwXMZwaRyn21b/S55xe4c3Ge12FIMZsQtZX3fMOO14EF5EHy1RV/4\nilBc5n0F84KIgElpzUsH+D7bmheUWobn+CSCZwmZMao0SnSV43jXLl0Yg7xBk6OL\nUUfJuGOK1QB7KRvkbPHQByvtAVuhzsNpgYLcz4xOdx5gjq2RWxWRaCCn/3+2a/8j\nWzT9aDjGE4u58cBq+QLy3wfvSKPv3nXn7gz3BtkIzTx6yuri7ontuvh/zr4bCPc8\nRWiZFvsuS477dngICIiTQsRzgB0cMfvWxjjqBR1e6yJ1cRZ5UTqinc4ABLJxWWu0\nThelmCkV1P5VsmocAdmHeX6SH4L+isnlLbc8RXJXdeJZ8XLiPJbiVCilG8CJV1Cf\nYD5j91v3GV6Ns/BC4pkpXmWEhcyISAWy5tSQRt4+lyvZIho8IEWMOly+nDi62PuW\nJQ0q/arCKpfsJ8fWBUv5q6/yBnYqZPLAbIn7zrm0xN0j7AsKc3rxdNv2pzVOny0E\nlXJVFVl4PvdHZ8WlnzYRWnsTESlgk3mFkoYjDnrruPrYCfNB8O6z2fRT/bZkckmM\nr0Raaybv7LSm2/YqP3NtvzbsrdKCuct/csINBMJT41Z2pZpAH4CYubtfcW/qQcQx\nzgANOlmY//VO3lVJYQTf79/vOrRPZA1DIm2JUFFg56lESYhDCOmos/4rNgsrfgvY\nDUORz3nBBwsb/78Y397MjzUW2ZryQ7UdFnfvhaR2hRoPUnau7ajgXx4JxFHkjpnR\ngGiXjHp9X+IgTxio1uCtPUbippJqI4H4WD12EFc9+UZ0o1fBmySUKhl/LXWPyvVQ\nRA5Qy2zVxbe8XtedZXDOpHadvwgdMCR20zcMP3Aq5WsTKUaZqgG4Os2xDWg5MlH1\nd2MAlY92jlQxWzSTfTH2A1j+Uu1cyT8Q8r85ag447ZXbe47jfAQjbvXzGO+NVek+\nya+NTCz6qOaqkJNjcRptsmza1vX6mKFjwFvt+EMe3rm7NIskChDXiaJqDuEEpOCV\nKojSFMerIzqYrSpcGS5T7rZpW+rW/Ub+C1vqw2KC7+e3mnNqTtJ9yKFtVtuFE/iv\nBlvwPRhYJM/XN3to+zGPjgeKTes37jD8Yad9Cw6QoMd+Bd/trpUdwLtqlant6C3d\nXAN1FCiPHnTMCOP7zT53UCm13RiIKwWoRAEyLGA03xsIDwWLE7cZPOME4NC+tMFR\nw4lqJFanUiEW6KCu1XAWjGsa1G+PqAcj08nUCqPSszR84eEGLMm6Af3RLqPjOwXj\n9QGHy/K3L+ktlb94qwGFEXplvGWmYay9uIbu4/K4ws+sT967JenYnn3suJMYBVwy\nLsXcPxqpIoRQ/dwZc0LPyX89GWhbiijPVzlb9WV4W5YHbZdw+mlga4CMGqdHXB+q\ne7aqG/hetCmnhgW0uWWTSVzG/h6TQ0fcufSAP/t8LMBFH0tVYLWRmWz6Kp6gkafw\nWm5NbSb7qPlLZxpVB1IymI24+uFB6aReNU/hmJCnej2s9XtGkZpbQH5f9IjZOuuR\nEbktIRPJcuJDdqJA5HFjlABDOR61ujaXvkfi/Mn6r4zeUsbn6kpouPQpDNkPqiPC\nm9uPslBM2P8XQdAAb67pCBqRpMucNUsXz2xfztiARj2JCvuoIlHqn5TDi3a3kOit\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "zuul-sample/src/main/resources/ssl/server.cert",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFVjCCAz4CCQCOVDcKFZeqxzANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJV\nUzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxHMQswCQYDVQQKDAJORjELMAkGA1UE\nCwwCRUUxEjAQBgNVBAMMCWxvY2FsaG9zdDEWMBQGCSqGSIb3DQEJARYHYUBiLmNv\nbTAeFw0xNzEyMTEyMzI0MDFaFw0xODEyMTEyMzI0MDFaMG0xCzAJBgNVBAYTAlVT\nMQswCQYDVQQIDAJDQTELMAkGA1UEBwwCTEcxCzAJBgNVBAoMAk5GMQswCQYDVQQL\nDAJFRTESMBAGA1UEAwwJbG9jYWxob3N0MRYwFAYJKoZIhvcNAQkBFgdhQGIuY29t\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw9Oud0mmtMWouKbguGq0\n8vPP8gwGybDwRi/t4rmWUb/+xGHW0iqRYbVDwE4i8gCuM4g0HN3+ESglBFMvuxui\nBIPx60dR1sEP3IlX/22HcBv97Zj+vjchBMY/3AIP06nKgHGmnorKYYs5xczEEyxB\nD+0mZECeQhK+IbhoZdTphrQruOTQYpkGpkyEbpygX3u2chpGoGSxOTlz0WxBhpbR\nwr2jiz0IYxplLsIq2VL7XBlUd5yoaxOyrcc1to+uPH27R9sbrj5513LzZ22ebcWa\ndQJGtOdGfoSG+wH8k/94MSAr1bsBOJ1jq3XIuD38fbdkUyH1DPwkzCnSD6qD6sYt\nXMYTgfAysCYxxjw/GrcIEG1TNbys04YJCwRp5IB6alsya4ovbkqa40ToLCBAtC6v\nc59Ppo3ejWZPcpLSe5oIB4loRtWYlWjJnUf6kjGuCmyltTzRrgvng4P/VULq91QE\nFcCqsbKhI7/eoO20Pt/XYR7gDXc4z7YCuW/twaEpRsclrUiXY0E0Bq41FrhyW2rp\nHl8x9kOJREGELMHp3hZycTh3FpxeN6Ti2znriz758dhNWKns5xAe8ZjSY/PpbcNq\nnHhDm2RQb1QiCw6ttJy4bMJmKofAEiEbE1Of4mPftRvM7zDM744nuOUPWZO1jQql\np1stf6qKY5b6n771+qTQXJ0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAWe22QQmw\n+t2Cmau2i0dTdgHjmYU1KCwath5fCSdb95c8lrLHWhQHQt70DCkU1KmbyPY0y/2G\ng9Ofkc49eQCPrzl/FrcYKb6a9OIgByTMCrEAhDzSf4iB+eeCF/nMzqYciXYj3/DF\nVFenBhLw9ZfqePgamlkNZWf7RICOklQu5vHCgs+/xUXlenB6CXNm+9Qy8BD9FfnP\nvTpr3wJVz0UK6JZafuLKcGpjaVCpUTeMmToVlr1PxyyTlgVFBbK7JfKlTXh7cZRw\n4FUarTAjgk3Vk3Q+Tdir6R8+DabqghJnTjOF/tETHeNt8msAc59VzRuzhy9ZeGkI\nvWq+LNqs+/+deaw5hsbkbD9AoDmHmV2U9Ki7zIYnRizl545f1nZQdPNeyYHO/QIl\ncP/U51pPid/bqlPiVwLg7eNGC1rI19cd0wZH4cjTwUtgqQhX/I3e5Boot9kdtvEr\nTr0I656jmmzksjF8863wDKqW/RJeje8lzKXbo0vc2/zv2KX11zIGC4OJ7z404BF5\ncEYnFmswIZQfR1mjTMN4ZfjCtBtWuc2uueDUUsgQ9YUss5bKpd8Xm1g9rguQYnBt\nM84aFXiDvbKhvI5t1Z5Xa9LN6FO0u0rIG+b8tOU8rlqhLp7SGYpRalb6UU2keZRh\nqNCCixZMahJMgVovuI8LkttwvdpTc7705W8=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "zuul-sample/src/main/resources/ssl/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDD0653Saa0xai4\npuC4arTy88/yDAbJsPBGL+3iuZZRv/7EYdbSKpFhtUPATiLyAK4ziDQc3f4RKCUE\nUy+7G6IEg/HrR1HWwQ/ciVf/bYdwG/3tmP6+NyEExj/cAg/TqcqAcaaeisphiznF\nzMQTLEEP7SZkQJ5CEr4huGhl1OmGtCu45NBimQamTIRunKBfe7ZyGkagZLE5OXPR\nbEGGltHCvaOLPQhjGmUuwirZUvtcGVR3nKhrE7KtxzW2j648fbtH2xuuPnnXcvNn\nbZ5txZp1Aka050Z+hIb7AfyT/3gxICvVuwE4nWOrdci4Pfx9t2RTIfUM/CTMKdIP\nqoPqxi1cxhOB8DKwJjHGPD8atwgQbVM1vKzThgkLBGnkgHpqWzJrii9uSprjROgs\nIEC0Lq9zn0+mjd6NZk9yktJ7mggHiWhG1ZiVaMmdR/qSMa4KbKW1PNGuC+eDg/9V\nQur3VAQVwKqxsqEjv96g7bQ+39dhHuANdzjPtgK5b+3BoSlGxyWtSJdjQTQGrjUW\nuHJbaukeXzH2Q4lEQYQsweneFnJxOHcWnF43pOLbOeuLPvnx2E1YqeznEB7xmNJj\n8+ltw2qceEObZFBvVCILDq20nLhswmYqh8ASIRsTU5/iY9+1G8zvMMzvjie45Q9Z\nk7WNCqWnWy1/qopjlvqfvvX6pNBcnQIDAQABAoICAQCNP1c9NYOoKlYLclruyhDg\nmNnpxaDzw8tbZODaQ3DYYHr73XJcv1WDu4I16GYuVi1QgDLOi5ThfSpOF057UHAp\nf550TUoLc8/kC7DMTY1+YMJkqZE9VHhdgD25jEcsLfEhelhrTMKzXv/52zumdKot\nOaoSb8V29RvtKJ0srkkO31AWGfzw0V3Jf7GaMyE+Hfa4EJnWwSpPk1Alw0b0ND7y\nj2SyXwB4syY+dtD/Vmp1wNN7PyT/rwKXc/QbTUGo4iu+pZ0urmOl8oT8mkXG+dvy\nAAVOIO9o/GB8Fq+/dGqWTJUxoaQ82NF2cAuqUROY/jm/+ONPTWOhW5znrd9e7Tjz\nyOgLA1ZlICHCzjJ8FDPtBlgr1ANhhg+axLkK02KJaHMohwxg6SrOentEzyryoYSE\nJMCHsH4E1dWI2mUclqp7XbLgOAcvz7MCg0hOQYi7VqdSf4egnkpESTpsx9WKjumO\nXwqAAeTos3AqsxBKAwHdoe2z9gjxaTRP6IbmsJU5iw56DGgqIUemhIv/fhjZGAYg\nPxX3FLxU/wSod+OcOy0NcN9wCi+zuSX8M2DDjHo6Ewz/rN/3tP+OH4mnnSiJ8WR8\n9DfD31fLiKbvbuJeM1nRKtB/3WX2Rc0XWZFaUpHImzCzOdb18KFUHfzvA6GJrnk/\noV6f1jL91JQzSySL94gV5QKCAQEA8oLIeocN82F4O12dqR3pNRYM/CYEVJwWOUEm\nukPvHziiVKl8Gnjd3tNu5N+vAeZEsTKs9fd4qVuP+STiP3F+5zUoVZHVvkL3LfSI\nr9HtnpRdzpFIA6RWTebd7snw44faDbeafCLt/0O2kaiEMGZwH+mqJ8CbEdc30OvN\nY8F3mmrsFerTZnfl8gjgx5AWIpooVb4wE4gpfblT9ixKbiAPkkKlnuRuTrkVWD7r\nBdCmmEUYuprpejrsUwg55R1yguFnr1E6QVuVSOviVEBpdZXWX+M3Bx8+zl0Qouyk\nHTv5/CWq8iByE4MvIje6sup+Fz37t5Uu9tiXx1N11AMYcAJuLwKCAQEAzrglGQNW\nINyVCNGIXoPGmq6rOotArJjQAlmx+K29Mja6RvY/y7f8ZB3Uk39LXoQwzW6RtMTw\nfKru/HKc05JedMA00tsCBhwfiJ8gmEI8HwWOcALNCX9uw6ZOO9llg03G3CmwuIhN\nKBj+sZ4UXD9mzsGsWhAdn8/Y1EEyMQakaAZNLjinnDUu+LdTcy+0/uuyiTRmbklk\nqJYbdtGABC9Oib2/nvsKaTjhbFiOBSxE/8EYjEmWfT+WK+NffSvELQ3EVl7vLmgj\nqo08XFZx3AxHITe4Or22x2nfih8E9TRzW0LTsfydaVwyub5qnr3kNJ6qRsEX7Qkf\nS2/RMqWCtn8a8wKCAQBA473BC2IwPWRufh4xok9EZSIUVhfSi/FmYIh8TrEtKXpG\nLROIAc9cUDbcBv5NA9BdmbGuHwmqR1W+1J+1WikatJ6WRu9qeYCqS0RHx2RNimWP\nYFBkqRRuw9eejWpnd3JhOT+c97u3EedIEk9MpBxcbamZ+W+E1pGY1X+fsaTPLMz/\nEFaAlJRyru12eJdzqswgJUO39jcj7PMKa89+qBWCjVLDsVvStLOBaVR5udrZ46M6\nSzkt+5ZAoXLcW4TIgIe94X40/sxzNqrY4GNXk0BJaALRZQrpLP3GmotPRz0cuveC\n0iu0DOYPwdmzBgu3LF6uQLzQUCRMsYhVsn5Xek8BAoIBAGWMLB0neG3YLhYQ6E6V\nqUBfQZoWwgSHZNdivHyOzHwYSlWFrj0i+ocr6Ds0sw+RHHAuOsF0ZTa4uYGlw8hj\nBKeRq+FQ2KOruQniMZ7aGrKahigcGCDsSrstvQzFdIqV8HRCvp9Hxa9G6AbUwue1\n9YjntwTfGc5hygAqrr9KpgS747oq9ptTvOlNFV9mNiFsI14nMZJH13zBkGhD7gEg\nRBKB9dnhNHIQERyqO8nqv1JrxuVTWOvaCqkwnr3cfBgtxR8wr4o6ehrUGqy5gmE4\nXtDAkG26uEkphzhQmJzj0S8pmti6YZFaS0jXc4Tbf3kh4D+1p003x/nEyh15FMcV\nlWUCggEBAKmXRrTQaY7C48drOyo60k5mh8puEpVK0K3VR7+5cOhr1bwbpOahocTh\ntj3JX00TS8q5h6DFmqLQOGRx+TW4E5ZuIHNv6WApqTy0WaXwNBfoiMIEs21NzCwq\nTt3oa17kXJa+ih96MoJiFLwN2SOJmCEWRhNsDegEkrwhgoZj++F+/Pu2vR7rm5S/\n6xHMCk66RQoH9yGhJ+Ra6Zjqfa6xdHrGUMDdmewnU8g8dPHtURWZbF8Qd+7GaUdf\n75Ar+B/phO8sK4Gv3na18LAIAYnHejq1aJ+90eDbiM33pxrYU9cGhzkFtzW5/u1u\nsT8eOekbbJpw4wdpOHysWuw/qdtn/ZY=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "zuul-sample/src/main/resources/ssl/truststore.key",
    "content": "zuul123\n"
  }
]