Compare commits

...
67 Commits
Author SHA1 Message Date
Ryan Baxter 85b29b436c Merge branch '5.0.x' 2026-09-17 15:10:13 -04:00
Ryan Baxter f501e887be Merge pull request #4603 from yashdotdev13/issue-3793-cert-authentication-5.0.x
Fix TLS configuration for WebClient Eureka transport.  Fixes #3793
2026-09-17 15:09:32 -04:00
98001yash afbb8fd97a Fix TLS configuration for WebClient Eureka transport
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-17 20:12:47 +05:30
98001yash 3ce1e124af Fix hostname verification for WebClient transport
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-17 14:43:33 +05:30
98001yash c89d7f13d6 Wire hostname verifier into WebClient transport
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-17 01:06:20 +05:30
98001yash f6795eefeb Enable hostname verification for WebClient TLS
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-16 21:35:13 +05:30
Prahlad Bhakat 0654980024 Add test verifying newClient() rebuilds after shutdown
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-16 11:25:24 -04:00
Prahlad Bhakat 9ebf9cff58 Move shared HTTP client caching from supplier to transport factory
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-16 11:25:24 -04:00
Prahlad Bhakat 5a1391f196 Fix Eureka HTTP client shutdown
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-16 11:25:07 -04:00
Ryan Baxter 1556cbc5a2 Merge pull request #4585 from PRAHLAD09-dev/fix/4569-eureka-deregistration-shutdown
gh-4569: Deterministically close shared HTTP client on TransportClientFactory#shutdown()
2026-09-16 11:17:50 -04:00
98001yash f8f3082460 Support TLS configuration for WebClient Eureka transport
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-16 01:10:28 +05:30
Prahlad Bhakat 6cc39452dc Add test verifying newClient() rebuilds after shutdown
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-10 18:37:02 +05:30
Ryan Baxter 51884beff4 Merge branch '5.0.x' 2026-09-09 15:00:35 -04:00
98001yash 925db750b7 Improve Eureka Server documentation
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-09 15:00:22 -04:00
Ryan Baxter 47b2feb15a Merge pull request #4600 from yashdotdev13/docs/598-improve-eureka-server-setup
Improve Eureka Server documentation
2026-09-09 14:59:47 -04:00
98001yash ede54db95f Improve Eureka Server documentation
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-09 23:58:31 +05:30
Ryan Baxter e64ecc7f85 Merge branch '5.0.x' 2026-09-09 14:25:29 -04:00
98001yash 0847c816cb Document Eureka virtual hostname discovery
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-09 14:25:15 -04:00
Ryan Baxter 889984ca48 Merge pull request #4599 from yashdotdev13/docs/1424-eureka-virtual-hostname
Document Eureka virtual hostname discovery.  Fixes #1424
2026-09-09 14:24:29 -04:00
98001yash fe1538d298 Document Eureka virtual hostname discovery
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-09 23:12:25 +05:30
Ryan Baxter b3c573a146 Merge branch '5.0.x' 2026-09-09 08:59:12 -04:00
98001yash 65a8b97459 Use DiscoveryClient TLS settings for RestClient transport
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-09 08:58:55 -04:00
Ryan Baxter d1032a9bb4 Merge pull request #4597 from yashdotdev13/test-4109-baseline
Use DiscoveryClient TLS settings for RestClient transport
2026-09-09 08:58:17 -04:00
Prahlad Bhakat cc51cd8735 Move shared HTTP client caching from supplier to transport factory
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-09 15:25:05 +05:30
Prahlad Bhakat 8a1569162b Simplify Eureka HTTP client synchronization
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-09 15:25:05 +05:30
Prahlad Bhakat b2d3f8225b Fix Eureka HTTP client lifecycle
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-09 15:25:05 +05:30
Prahlad Bhakat f37374d472 Fix Eureka HTTP client shutdown
Signed-off-by: Prahlad Bhakat <prahladbhakat05@gmail.com>
2026-09-09 15:25:05 +05:30
98001yash 4c1b865012 Use DiscoveryClient TLS settings for RestClient transport
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-09 07:52:22 +05:30
Ryan Baxter 2d567b1d8b Merge branch '5.0.x' 2026-09-08 12:29:53 -04:00
Ryan Baxter 004caa8192 Use bouncy castle dependency managed in spring cloud commons 2026-09-08 12:29:33 -04:00
dependabot[bot] 17e7a17f23 Bump antora from 3.2.0-rc.3 to 3.2.0 in /docs (#4594)
Bumps [antora](https://gitlab.com/antora/antora/tree/HEAD/packages/antora) from 3.2.0-rc.3 to 3.2.0.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-rc.3...v3.2.0)

---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 11:28:53 +01:00
dependabot[bot] 640658cf76 Bump antora from 3.2.0-rc.3 to 3.2.0 in /docs (#4596)
Bumps [antora](https://gitlab.com/antora/antora/tree/HEAD/packages/antora) from 3.2.0-rc.3 to 3.2.0.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-rc.3...v3.2.0)

---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-04 11:28:46 +01:00
Ryan Baxter a48f6f5b36 Revert "Retarget workflow triggers to 5.0.x [skip actions]"
This reverts commit ab72b970c0.
2026-09-03 18:14:56 -04:00
Ryan Baxter 2ab399a0fd Merge branch '5.0.x' 2026-09-03 18:14:49 -04:00
Ryan Baxter 0df610d62e Merge pull request #4592 from yashdotdev13/issue-4135-log-health-contributor-status
Log health contributor statuses when Eureka health status changes
2026-09-02 17:03:07 -04:00
Ryan Baxter 3b6ba97cd8 Merge pull request #4592 from yashdotdev13/issue-4135-log-health-contributor-status
Log health contributor statuses when Eureka health status changes
2026-09-02 17:02:40 -04:00
98001yash c9d9905237 Log individual Eureka health contributor statuses
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-09-03 02:19:30 +05:30
Spring Builds 6bbda54f62 Updating project versions to 2026.0.0-SNAPSHOT 2026-08-31 19:09:33 +00:00
Spring Builds 4a7b540cf9 Add Dependabot entries for 5.0.x 2026-08-31 19:09:29 +00:00
Spring Builds ab72b970c0 Retarget workflow triggers to 5.0.x [skip actions] 2026-08-31 19:09:25 +00:00
98001yash 70788002d0 Log health contributor statuses in Eureka health checks
Signed-off-by: 98001yash <yashchauhan.gaya@gmail.com>
2026-08-29 01:21:41 +05:30
Spring Builds cae667d82a Pin spring-cloud-github-actions to v1.1.1 2026-08-26 20:23:18 +00:00
Spring Buildsandspring-builds 5ad0371e97 Update Maven wrapper to 3.9.16 (#4587)
Signed-off-by: spring-builds <spring-builds@users.noreply.github.com>
Co-authored-by: spring-builds <spring-builds@users.noreply.github.com>
2026-08-21 19:05:07 +01:00
dependabot[bot] 68eaa21dc2 Bump antora from 3.2.0-rc.2 to 3.2.0-rc.3 in /docs (#4590)
Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-rc.2 to 3.2.0-rc.3.
- [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc)
- [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-rc.2...v3.2.0-rc.3)

---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-rc.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-21 11:29:58 +01:00
Spring Builds 3fa392a27d Updating project versions to 2025.1.4-SNAPSHOT 2026-08-20 19:14:09 +00:00
Ryan Baxter 6d102063e9 Merge pull request #4583 from spring-cloud/dependabot/npm_and_yarn/docs/main/springio/antora-extensions-1.14.13 2026-08-13 13:49:07 -04:00
dependabot[bot] 36703d873a Bump @springio/antora-extensions from 1.14.12 to 1.14.13 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.14.12 to 1.14.13.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.14.12...v1.14.13)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-version: 1.14.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-13 16:43:05 +00:00
Spring Builds fee8feb984 Use canonical deploy-docs trigger workflow [skip actions] 2026-07-30 21:41:27 +00:00
spring-builds 72bffe037f Update generated GitHub Actions workflow files [skip actions] 2026-07-20 21:05:28 +00:00
spring-builds c354057488 Update generated GitHub Actions workflow files [skip actions] 2026-07-17 17:45:39 +00:00
Ryan Baxter 42f4149ff8 Merge pull request #4582 from spring-cloud/ryanjbaxter-update-dependabot-ignores
Ignore release-train workflow updates in Dependabot
2026-07-16 12:55:38 -04:00
Ryan BaxterandCopilot App 4d374ee357 Ignore release-train workflow updates in Dependabot
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-16 12:54:58 -04:00
spring-builds 9117a25069 Update generated GitHub Actions workflow files [skip actions] 2026-07-16 15:25:06 +00:00
spring-builds 25e725e854 Update generated GitHub Actions workflow files [skip actions] 2026-07-14 20:49:43 +00:00
spring-builds dd72a3e684 Update generated GitHub Actions workflow files 2026-07-08 22:03:19 +00:00
spring-builds 54fa25f9c5 Update generated GitHub Actions workflow files 2026-07-08 19:45:47 +00:00
spring-builds f84e17324d Update generated GitHub Actions workflow files 2026-07-08 19:28:33 +00:00
spring-builds db8be6af5a Update generated GitHub Actions workflow files 2026-07-07 19:01:37 +00:00
Spring Builds 68b32f5b87 Remove Dependabot entries for retired branch 4.3.x 2026-07-01 13:58:33 +00:00
Ryan Baxter d08a176b17 Merge pull request #4577 from spring-cloud/dependabot/npm_and_yarn/docs/main/antora-3.2.0-rc.2 2026-06-25 18:23:35 -04:00
dependabot[bot] b70c400c53 Bump antora from 3.2.0-alpha.12 to 3.2.0-rc.2 in /docs
---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-rc.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 16:42:55 +00:00
spring-builds 2a998461e7 Bumping versions 2026-06-23 10:25:57 +00:00
Ryan Baxter 12f426a85a Merge pull request #4575 from spring-cloud/dependabot/github_actions/main/actions/checkout-7
Bump actions/checkout from 6 to 7
2026-06-18 13:39:55 -04:00
dependabot[bot] 7cd9878aa1 Bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-18 16:42:25 +00:00
spring-builds bb66fcd1b5 Bumping dependency versions after release 2026-06-11 15:35:02 +00:00
spring-builds c497d39fc6 Bumping versions to 5.0.3-SNAPSHOT after release 2026-06-11 15:34:14 +00:00
spring-builds cf12bee60e Going back to snapshots 2026-06-11 15:34:13 +00:00
43 changed files with 1610 additions and 452 deletions
@@ -0,0 +1,13 @@
name: Build Release
runs:
using: composite
steps:
- name: Build Release
shell: bash
# If we are on Java 8 we need to build the docs zip for jubilee projects so it is distributed as part of the release
run: |
if java -version 2>&1 | grep -q '1\.8\.'; then
./mvnw --batch-mode --settings release-train-settings.xml clean deploy --activate-profiles releaseTrain,docs -DaltDeploymentRepository="release-train::default::file://$(pwd)/deployment-repository" -Dmaven-deploy-plugin.deployZipUrl="file://$(pwd)/deployment-repository" -Dmaven-deploy-plugin-default.phase=deploy -Dupload-docs-zip.packaging=zip -DskipTests
else
./mvnw --batch-mode --settings release-train-settings.xml clean deploy --activate-profiles releaseTrain -DaltDeploymentRepository="release-train::default::file://$(pwd)/deployment-repository" -DskipTests
fi
@@ -0,0 +1,10 @@
artifactory:
artifacts:
- pattern: "/**/spring-cloud-*-docs-*.zip"
properties:
zip.deployed: "false"
zip.type: "docs"
- pattern: "/**/spring-cloud-docs-*.zip"
properties:
zip.deployed: "false"
zip.type: "docs"
@@ -0,0 +1,7 @@
name: Test Release
runs:
using: composite
steps:
- name: Test Release
shell: bash
run: ./mvnw --batch-mode --settings release-train-settings.xml clean verify --activate-profiles releaseTrain
+25 -19
View File
@@ -1,28 +1,13 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "4.3.x"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "main"
schedule:
interval: "weekly"
- package-ecosystem: maven
directory: /
schedule:
interval: daily
target-branch: 4.3.x
ignore:
# glob 7.2.3+ conflicts with jshint (minimatch) in wro4j-maven-plugin
- dependency-name: "org.webjars.npm:glob"
# only upgrade patch versions for maintenance branch
- dependency-name: "*"
update-types:
- version-update:semver-major
- version-update:semver-minor
exclude-paths:
- ".github/workflows/release-train-test.yml"
- ".github/workflows/release-train-build.yml"
- package-ecosystem: maven
directory: /
schedule:
@@ -46,8 +31,29 @@ updates:
directory: /docs
schedule:
interval: weekly
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "5.0.x"
schedule:
interval: "weekly"
exclude-paths:
- ".github/workflows/release-train-test.yml"
- ".github/workflows/release-train-build.yml"
- package-ecosystem: maven
directory: /
schedule:
interval: daily
target-branch: 5.0.x
ignore:
# glob 7.2.3+ conflicts with jshint (minimatch) in wro4j-maven-plugin
- dependency-name: "org.webjars.npm:glob"
# only upgrade patch versions for maintenance branch
- dependency-name: "*"
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: npm
target-branch: 4.3.x
target-branch: 5.0.x
directory: /docs
schedule:
interval: weekly
+17
View File
@@ -0,0 +1,17 @@
workflow:
generator:
project:
java:
versions:
primary: 17
workflows:
release-train:
build:
env:
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
GH_ACTIONS_REPO_TOKEN: secrets.GH_ACTIONS_REPO_TOKEN
test:
env:
COMMERCIAL_REPO_USERNAME: secrets.COMMERCIAL_ARTIFACTORY_USERNAME
COMMERCIAL_REPO_PASSWORD: secrets.COMMERCIAL_ARTIFACTORY_PASSWORD
+1 -1
View File
@@ -21,7 +21,7 @@ on:
jobs:
deploy:
uses: spring-cloud/spring-cloud-github-actions/.github/workflows/deploy.yml@main
uses: spring-cloud/spring-cloud-github-actions/.github/workflows/deploy.yml@b6a6b1963b8762420526591c3d363bd7d09e7d4b # v1.1.1
with:
branches: ${{ inputs.branches }}
secrets:
+22 -6
View File
@@ -1,30 +1,46 @@
# Generated by the rollout-deploy-docs-trigger workflow in
# spring-cloud/spring-cloud-github-actions.
#
# Do not edit this file directly - update examples/deploy-docs-trigger.yml
# there and re-run the rollout, otherwise your change will be overwritten.
name: Deploy Docs
on:
push:
branches-ignore: [ gh-pages ]
# Allow-list of exactly this branch. A topic or Dependabot branch cut from
# it carries this file too, and without this filter every push to one of
# those would dispatch a full docs build for a ref that is not in the
# Antora playbook.
branches:
- main
tags: '**'
repository_dispatch:
types: request-build-reference # legacy
#schedule:
#- cron: '0 10 * * *' # Once per day at 10am UTC
workflow_dispatch:
permissions:
actions: write
contents: read # required to check out private commercial repositories
actions: write # required to dispatch the docs build
jobs:
build:
runs-on: ubuntu-latest
# if: github.repository_owner == 'spring-cloud'
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: docs-build
fetch-depth: 1
# A branch push rebuilds just that branch's docs; a tag push rebuilds
# everything so the new version appears in the version dropdown.
- name: Dispatch (partial build)
if: github.ref_type == 'branch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }}
- name: Dispatch (full build)
if: github.ref_type == 'tag'
env:
+1 -1
View File
@@ -6,4 +6,4 @@ on:
jobs:
build:
uses: spring-cloud/spring-cloud-github-actions/.github/workflows/pr.yml@main
uses: spring-cloud/spring-cloud-github-actions/.github/workflows/pr.yml@b6a6b1963b8762420526591c3d363bd7d09e7d4b # v1.1.1
+91
View File
@@ -0,0 +1,91 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Build"
run-name: "${{ inputs.callback-ref }} Build"
"on":
workflow_dispatch:
inputs:
callback:
description: "Repository to which a callback should be made upon completion"
required: true
type: "string"
callback-ref:
description: "Ref in the callback repository to which a callback should be made upon completion"
required: true
type: "string"
release-train-maven-repository-url:
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
required: true
type: "string"
permissions:
contents: "read"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
jobs:
build-release:
name: "Build Release"
runs-on: "ubuntu22-2-8"
steps:
- name: "Prevent Re-runs"
id: "prevent-re-runs"
run: |-
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
echo "Re-runs are prohibited. Use the 'Release Train  Retry' workflow to retry build failures"
exit 1
fi
- name: "Set up Java"
id: "set-up-java"
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
with:
distribution: "liberica"
java-version: "17"
- name: "Check Out Code"
id: "check-out-code"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
- name: "Build Release"
id: "build-release"
uses: "./.github/actions/release-train-build"
env:
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
GH_ACTIONS_REPO_TOKEN: "${{ secrets.GH_ACTIONS_REPO_TOKEN }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
- name: "Upload Deployment Repository"
id: "upload-deployment-repository"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "deployment-repository"
path: "deployment-repository/**"
- name: "Upload Deployment Spec"
id: "upload-deployment-spec"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
archive: "false"
if-no-files-found: "ignore"
name: "deployment-spec"
path: ".github/actions/release-train-build/deployment-spec.yml"
- name: "Save Build System Caches"
id: "save-build-system-caches"
uses: "actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
with:
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
path: "~/.m2/repository"
- name: "Send Callback"
id: "send-callback"
if: "${{ !cancelled() }}"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run callback \
--repo ${{ inputs.callback }} \
--ref ${{ inputs.callback-ref }} \
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
--field deployment-repository-artifact-identifier=${{ steps.upload-deployment-repository.outputs.artifact-id }} \
--field deployment-spec-artifact-identifier=${{ steps.upload-deployment-spec.outputs.artifact-id }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }} \
--field result=${{ job.status == 'success' && 'built' || 'build-failed' }} \
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+55
View File
@@ -0,0 +1,55 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Join"
run-name: "${{ inputs.release-train }} Join"
"on":
workflow_dispatch:
inputs:
deployment-destination:
description: "Destination to which the release should be deployed"
options:
- "Maven Central"
- "Spring Enterprise"
required: true
type: "choice"
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
join-release-train:
name: "Join Release Train"
runs-on: "ubuntu-latest"
steps:
- name: "Join Release Train"
id: "join-release-train"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run join \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field commit-hash=${{ github.sha }} \
--field deployment-destination=${{ inputs.deployment-destination == 'Maven Central' && 'maven-central' || 'spring-enterprise' }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
+46
View File
@@ -0,0 +1,46 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Leave"
run-name: "${{ inputs.release-train }} Leave"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
leave:
name: "Leave"
runs-on: "ubuntu-latest"
steps:
- name: "Leave"
id: "leave"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run leave \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
+47
View File
@@ -0,0 +1,47 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Ready"
run-name: "${{ inputs.release-train }} Ready"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
ready:
name: "Ready"
runs-on: "ubuntu-latest"
steps:
- name: "Ready"
id: "ready"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
run_url=$(
gh workflow run ready \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field commit-hash=${{ github.sha }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo ${{ inputs.release-train-repository }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
+34
View File
@@ -0,0 +1,34 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Retry"
run-name: "${{ inputs.release-train }} Retry"
"on":
workflow_dispatch:
inputs:
release-train:
description: "Release train"
required: true
type: "string"
release-train-repository:
default: "spring-io/release-train"
description: "Release train repository"
required: true
type: "string"
permissions:
contents: "none"
jobs:
trigger-retry:
name: "Trigger Retry"
runs-on: "ubuntu-latest"
steps:
- name: "Trigger Retry"
id: "trigger-retry"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run retry \
--repo ${{ inputs.release-train-repository }} \
--ref ${{ inputs.release-train }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }}
+83
View File
@@ -0,0 +1,83 @@
# This file was auto-generated by github-actions-workflow-generator 0.0.6. Do not edit.
# To update it, modify .github/workflow-generator.yml as needed and re-run the generator.
name: "Release Train Test"
run-name: "${{ inputs.callback-ref }} Test"
"on":
workflow_dispatch:
inputs:
callback:
description: "Repository to which a callback should be made upon completion"
required: true
type: "string"
callback-ref:
description: "Ref in the callback repository to which a callback should be made upon completion"
required: true
type: "string"
release-train-maven-repository-url:
description: "URL of a Maven repository to be used to resolve artifacts of projects earlier in the train"
required: true
type: "string"
permissions:
contents: "read"
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
jobs:
test-release:
name: "Test Release"
runs-on: "ubuntu22-2-8"
steps:
- name: "Prevent Re-runs"
id: "prevent-re-runs"
run: |-
if [ "$GITHUB_RUN_ATTEMPT" -gt 1 ]; then
echo "Re-runs are prohibited. Use the 'Release Train  Retry' workflow to retry test failures"
exit 1
fi
- name: "Set up Java"
id: "set-up-java"
uses: "actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95" # v5.6.0
with:
distribution: "liberica"
java-version: "17"
- name: "Check Out Code"
id: "check-out-code"
uses: "actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0" # v7.0.0
- name: "Restore Build System Caches"
id: "restore-build-system-caches"
uses: "actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9" # v6.1.0
with:
key: "release-train-${{ inputs.callback-ref }}-${{ github.ref_name }}"
path: "~/.m2/repository"
- name: "Test Release"
id: "test-release"
uses: "./.github/actions/release-train-test"
env:
COMMERCIAL_REPO_PASSWORD: "${{ secrets.COMMERCIAL_ARTIFACTORY_PASSWORD }}"
COMMERCIAL_REPO_USERNAME: "${{ secrets.COMMERCIAL_ARTIFACTORY_USERNAME }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_PASSWORD }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_URL: "${{ inputs.release-train-maven-repository-url }}"
RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_MAVEN_REPOSITORY_USERNAME }}"
- name: "Send Callback"
id: "send-callback"
if: "${{ !cancelled() }}"
env:
GH_TOKEN: "${{ secrets.RELEASE_TRAIN_PARTICIPANT_GITHUB_TOKEN }}"
run: |-
gh workflow run callback \
--repo ${{ inputs.callback }} \
--ref ${{ inputs.callback-ref }} \
--field commit-hash=${{ steps.check-out-code.outputs.commit }} \
--field release-branch=${{ github.ref_name }} \
--field release-repository=${{ github.repository }} \
--field result=${{ job.status == 'success' && 'tested' || 'test-failed' }} \
--field workflow-run-url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: "Upload Build System Reports"
id: "upload-build-system-reports"
if: "${{ failure() }}"
uses: "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" # v7.0.1
with:
name: "build-system-reports"
path: |-
**/target/surefire-reports
**/target/failsafe-reports
BIN
View File
Binary file not shown.
+4 -18
View File
@@ -1,18 +1,4 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
wrapperVersion=3.3.4
distributionType=bin
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar
+254 -241
View File
@@ -1,6 +1,257 @@
[[features]]
= Spring Cloud Netflix Features
[[spring-cloud-eureka-server]]
== Service Discovery: Eureka Server
Eureka Server acts as a service registry that Eureka clients use to discover other services.
You can run Eureka as a standalone server or configure multiple peer-aware servers for higher availability.
The following sections show how to get started with Eureka Server and then describe standalone and peer-aware deployments.
[[netflix-eureka-server-starter]]
=== How to Include Eureka Server
To include Eureka Server in your project, use the starter with a group ID of `org.springframework.cloud` and an artifact ID of `spring-cloud-starter-netflix-eureka-server`.
See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] for details on setting up your build system with the current Spring Cloud Release Train.
NOTE: If your project already uses Thymeleaf as its template engine, the Freemarker templates of the Eureka server may not be loaded correctly. In this case it is necessary to configure the template loader manually:
.application.yml
----
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
----
[[spring-cloud-running-eureka-server]]
=== Quick Start
The following example shows a minimal Eureka server:
[source,java,indent=0]
----
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
----
For a single standalone Eureka server, disable the client-side behavior so that the server does not attempt to register with or fetch the registry from a peer:
.application.yml
----
eureka:
client:
registerWithEureka: false
fetchRegistry: false
----
The server has a home page with a UI and HTTP API endpoints for the normal Eureka functionality under `/eureka/*`.
[TIP]
====
Due to Gradle's dependency resolution rules and the lack of a parent bom feature, depending on `spring-cloud-starter-netflix-eureka-server` can cause failures on application startup.
To remedy this issue, add the Spring Boot Gradle plugin and import the Spring cloud starter parent bom as follows:
.build.gradle
[source,java,indent=0]
----
buildscript {
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}")
}
}
apply plugin: "spring-boot"
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}"
}
}
----
====
=== `defaultOpenForTrafficCount` and its effect on EurekaServer warmup time
Netflix Eureka's `waitTimeInMsWhenSyncEmpty` setting is not taken into account in Spring Cloud Eureka server at the beginning. In order to enable the warmup time, set `eureka.server.defaultOpenForTrafficCount=0`.
[[spring-cloud-eureka-server-zones-and-regions]]
=== High Availability, Zones and Regions
The Eureka server does not have a back end store, but the service instances in the registry all have to send heartbeats to keep their registrations up to date (so this can be done in memory).
Clients also have an in-memory cache of Eureka registrations (so they do not have to go to the registry for every request to a service).
By default, every Eureka server is also a Eureka client and requires (at least one) service URL to locate a peer.
If you do not provide it, the service runs and works, but it fills your logs with a lot of noise about not being able to register with the peer.
[[spring-cloud-eureka-server-standalone-mode]]
=== Standalone Mode
The combination of the two caches (client and server) and the heartbeats make a standalone Eureka server fairly resilient to failure, as long as there is some sort of monitor or elastic runtime (such as Cloud Foundry) keeping it alive.
In standalone mode, you might prefer to switch off the client side behavior so that it does not keep trying and failing to reach its peers.
The following example shows how to switch off the client-side behavior:
.application.yml (Standalone Eureka Server)
----
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
----
Notice that the `serviceUrl` is pointing to the same host as the local instance.
[[spring-cloud-eureka-server-peer-awareness]]
=== Peer Awareness
Eureka can be made even more resilient and available by running multiple instances and asking them to register with each other.
In fact, this is the default behavior, so all you need to do to make it work is add a valid `serviceUrl` to a peer, as shown in the following example:
.application.yml (Two Peer Aware Eureka Servers)
----
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: https://peer2/eureka/
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: https://peer1/eureka/
----
In the preceding example, we have a YAML file that can be used to run the same server on two hosts (`peer1` and `peer2`) by running it in different Spring profiles.
You could use this configuration to test the peer awareness on a single host (there is not much value in doing that in production) by manipulating `/etc/hosts` to resolve the host names.
In fact, the `eureka.instance.hostname` is not needed if you are running on a machine that knows its own hostname (by default, it is looked up by using `java.net.InetAddress`).
You can add multiple peers to a system, and, as long as they are all connected to each other by at least one edge, they synchronize
the registrations amongst themselves.
If the peers are physically separated (inside a data center or between multiple data centers), then the system can, in principle, survive "`split-brain`" type failures.
You can add multiple peers to a system, and as long as they are all
directly connected to each other, they will synchronize
the registrations amongst themselves.
.application.yml (Three Peer Aware Eureka Servers)
----
eureka:
client:
serviceUrl:
defaultZone: https://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
---
spring:
profiles: peer3
eureka:
instance:
hostname: peer3
----
[[spring-cloud-eureka-server-prefer-ip-address]]
=== When to Prefer IP Address
In some cases, it is preferable for Eureka to advertise the IP addresses of services rather than the hostname.
Set `eureka.instance.preferIpAddress` to `true` and, when the application registers with eureka, it uses its IP address rather than its hostname.
[TIP]
====
If the hostname cannot be determined by Java, then the IP address is sent to Eureka.
Only explicit way of setting the hostname is by setting `eureka.instance.hostname` property.
You can set your hostname at the run-time by using an environment variable -- for example, `eureka.instance.hostname=$\{HOST_NAME}`.
====
=== Securing The Eureka Server
You can secure your Eureka server simply by adding Spring Security to your
server's classpath via `spring-boot-starter-security`. By default, when Spring Security is on the classpath it will require that
a valid CSRF token be sent with every request to the app. Eureka clients will not generally possess a valid
cross site request forgery (CSRF) token you will need to disable this requirement for the `/eureka/**` endpoints.
For example:
[source,java,indent=0]
----
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated())
.httpBasic(withDefaults());
http.csrf().ignoringRequestMatchers("/eureka/**");
return http.build();
}
----
For more information on CSRF see the https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf[Spring Security documentation].
A demo Eureka Server can be found in the Spring Cloud Samples https://github.com/spring-cloud-samples/eureka/tree/Eureka-With-Security-4.x[repo].
=== JDK 11 Support
The JAXB modules which the Eureka server depends upon were removed in JDK 11. If you intend to use JDK 11
when running a Eureka server you must include these dependencies in your POM or Gradle file.
[source,xml,indent=0]
----
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
----
=== AOT and Native Image Support
Spring Cloud Netflix Eureka Server does not support Spring AOT transformations or native images.
=== Metrics
`EurekaInstanceMonitor` listens to events related to Eureka instance registration and creates/updates `Gauge`s for Eureka instance information in Micrometer's `MeterRegistry`. By default, this behavior is disabled. If you want to enable it, you need to set `eureka.server.metrics.enabled` to `true`.
By default, the `Gauge`s are named `eureka.server.instances` and have the following tags:
- `application`: application name
- `status`: instance status (`UP`, `DOWN`, `STARTING`, `OUT_OF_SERVICE`, `UNKNOWN`, see: `com.netflix.appinfo.InstanceInfo.InstanceStatus`)
You can add additional tags by injecting your own implementation of `EurekaInstanceTagsProvider`.
== Service Discovery: Eureka Clients
Service Discovery is one of the key tenets of a microservice-based architecture.
@@ -59,6 +310,8 @@ WARNING: The `defaultZone` property is case sensitive and requires camel case be
The default application name (that is, the service ID), virtual host, and non-secure port (taken from the `Environment`) are `${spring.application.name}`, `${spring.application.name}` and `${server.port}`, respectively.
NOTE: By default, the Eureka application name and virtual host are both derived from `spring.application.name`. If you configure `eureka.instance.virtualHostName` differently, use the virtual host name when looking up instances through Spring Cloud's `DiscoveryClient`.
Having `spring-cloud-starter-netflix-eureka-client` on the classpath makes the app into both a Eureka "`instance`" (that is, it registers itself) and a "`client`" (it can query the registry to locate other services).
The instance behaviour is driven by `eureka.instance.*` configuration keys, but the defaults are fine if you ensure that your application has a value for `spring.application.name` (this is the default for the Eureka service ID or VIP).
@@ -407,249 +660,9 @@ WARNING: If you want to run Eureka Client in AOT or native image modes, make sur
NOTE: Given the AOT and native image closed-world assumption, using random port with Eureka clients is not supported for ahead of time compilation or native images.
[[spring-cloud-eureka-server]]
== Service Discovery: Eureka Server
This section describes how to set up a Eureka server.
[[netflix-eureka-server-starter]]
=== How to Include Eureka Server
To include Eureka Server in your project, use the starter with a group ID of `org.springframework.cloud` and an artifact ID of `spring-cloud-starter-netflix-eureka-server`.
See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] for details on setting up your build system with the current Spring Cloud Release Train.
NOTE: If your project already uses Thymeleaf as its template engine, the Freemarker templates of the Eureka server may not be loaded correctly. In this case it is necessary to configure the template loader manually:
.application.yml
----
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
----
[[spring-cloud-running-eureka-server]]
=== How to Run a Eureka Server
The following example shows a minimal Eureka server:
[source,java,indent=0]
----
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceTestApplication.class, args);
}
}
----
The server has a home page with a UI and HTTP API endpoints for the normal Eureka functionality under `/eureka/*`.
The following links have some Eureka background reading: https://github.com/cfregly/fluxcapacitor/wiki/NetflixOSS-FAQ#eureka-service-discovery-load-balancer[flux capacitor] and https://groups.google.com/forum/?fromgroups#!topic/eureka_netflix/g3p2r7gHnN0[google group discussion].
[TIP]
====
Due to Gradle's dependency resolution rules and the lack of a parent bom feature, depending on `spring-cloud-starter-netflix-eureka-server` can cause failures on application startup.
To remedy this issue, add the Spring Boot Gradle plugin and import the Spring cloud starter parent bom as follows:
.build.gradle
[source,java,indent=0]
----
buildscript {
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}")
}
}
apply plugin: "spring-boot"
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}"
}
}
----
====
=== `defaultOpenForTrafficCount` and its effect on EurekaServer warmup time
Netflix Eureka's `waitTimeInMsWhenSyncEmpty` setting is not taken into account in Spring Cloud Eureka server at the beginning. In order to enable the warmup time, set `eureka.server.defaultOpenForTrafficCount=0`.
[[spring-cloud-eureka-server-zones-and-regions]]
=== High Availability, Zones and Regions
The Eureka server does not have a back end store, but the service instances in the registry all have to send heartbeats to keep their registrations up to date (so this can be done in memory).
Clients also have an in-memory cache of Eureka registrations (so they do not have to go to the registry for every request to a service).
By default, every Eureka server is also a Eureka client and requires (at least one) service URL to locate a peer.
If you do not provide it, the service runs and works, but it fills your logs with a lot of noise about not being able to register with the peer.
[[spring-cloud-eureka-server-standalone-mode]]
=== Standalone Mode
The combination of the two caches (client and server) and the heartbeats make a standalone Eureka server fairly resilient to failure, as long as there is some sort of monitor or elastic runtime (such as Cloud Foundry) keeping it alive.
In standalone mode, you might prefer to switch off the client side behavior so that it does not keep trying and failing to reach its peers.
The following example shows how to switch off the client-side behavior:
.application.yml (Standalone Eureka Server)
----
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
----
Notice that the `serviceUrl` is pointing to the same host as the local instance.
[[spring-cloud-eureka-server-peer-awareness]]
=== Peer Awareness
Eureka can be made even more resilient and available by running multiple instances and asking them to register with each other.
In fact, this is the default behavior, so all you need to do to make it work is add a valid `serviceUrl` to a peer, as shown in the following example:
.application.yml (Two Peer Aware Eureka Servers)
----
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: https://peer2/eureka/
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: https://peer1/eureka/
----
In the preceding example, we have a YAML file that can be used to run the same server on two hosts (`peer1` and `peer2`) by running it in different Spring profiles.
You could use this configuration to test the peer awareness on a single host (there is not much value in doing that in production) by manipulating `/etc/hosts` to resolve the host names.
In fact, the `eureka.instance.hostname` is not needed if you are running on a machine that knows its own hostname (by default, it is looked up by using `java.net.InetAddress`).
You can add multiple peers to a system, and, as long as they are all connected to each other by at least one edge, they synchronize
the registrations amongst themselves.
If the peers are physically separated (inside a data center or between multiple data centers), then the system can, in principle, survive "`split-brain`" type failures.
You can add multiple peers to a system, and as long as they are all
directly connected to each other, they will synchronize
the registrations amongst themselves.
.application.yml (Three Peer Aware Eureka Servers)
----
eureka:
client:
serviceUrl:
defaultZone: https://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
---
spring:
profiles: peer3
eureka:
instance:
hostname: peer3
----
[[spring-cloud-eureka-server-prefer-ip-address]]
=== When to Prefer IP Address
In some cases, it is preferable for Eureka to advertise the IP addresses of services rather than the hostname.
Set `eureka.instance.preferIpAddress` to `true` and, when the application registers with eureka, it uses its IP address rather than its hostname.
[TIP]
====
If the hostname cannot be determined by Java, then the IP address is sent to Eureka.
Only explicit way of setting the hostname is by setting `eureka.instance.hostname` property.
You can set your hostname at the run-time by using an environment variable -- for example, `eureka.instance.hostname=$\{HOST_NAME}`.
====
=== Securing The Eureka Server
You can secure your Eureka server simply by adding Spring Security to your
server's classpath via `spring-boot-starter-security`. By default, when Spring Security is on the classpath it will require that
a valid CSRF token be sent with every request to the app. Eureka clients will not generally possess a valid
cross site request forgery (CSRF) token you will need to disable this requirement for the `/eureka/**` endpoints.
For example:
[source,java,indent=0]
----
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated())
.httpBasic(withDefaults());
http.csrf().ignoringRequestMatchers("/eureka/**");
return http.build();
}
----
For more information on CSRF see the https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf[Spring Security documentation].
A demo Eureka Server can be found in the Spring Cloud Samples https://github.com/spring-cloud-samples/eureka/tree/Eureka-With-Security-4.x[repo].
=== JDK 11 Support
The JAXB modules which the Eureka server depends upon were removed in JDK 11. If you intend to use JDK 11
when running a Eureka server you must include these dependencies in your POM or Gradle file.
[source,xml,indent=0]
----
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
----
=== AOT and Native Image Support
Spring Cloud Netflix Eureka Server does not support Spring AOT transformations or native images.
=== Metrics
`EurekaInstanceMonitor` listens to events related to Eureka instance registration and creates/updates `Gauge`s for Eureka instance information in Micrometer's `MeterRegistry`. By default, this behavior is disabled. If you want to enable it, you need to set `eureka.server.metrics.enabled` to `true`.
By default, the `Gauge`s are named `eureka.server.instances` and have the following tags:
- `application`: application name
- `status`: instance status (`UP`, `DOWN`, `STARTING`, `OUT_OF_SERVICE`, `UNKNOWN`, see: `com.netflix.appinfo.InstanceInfo.InstanceStatus`)
You can add additional tags by injecting your own implementation of `EurekaInstanceTagsProvider`.
To see the list of all Spring Cloud Netflix related configuration properties please check link:appendix.html[the Appendix page].
== Configuration properties
To see the list of all Spring Cloud Netflix related configuration properties please check link:appendix.html[the Appendix page].
@@ -260,6 +260,7 @@
|spring.cloud.refresh.enabled | `+++true+++` | Enables autoconfiguration for the refresh scope and associated features.
|spring.cloud.refresh.extra-refreshable | `+++true+++` | Additional bean names or class names for beans to post process into refresh scope.
|spring.cloud.refresh.never-refreshable | `+++true+++` | Comma separated list of bean names or class names for beans to never be refreshed or rebound.
|spring.cloud.refresh.never-reset-nested-types | | Fully qualified class name prefixes of nested configuration properties values that should never be recursively reset when a bean is rebound. JDK and standard API types are always skipped; use this to additionally exclude library types whose object graphs are cyclic or hold internal state that must not be mutated. Each entry is matched against the fully qualified class name using a prefix comparison, so either a package or an individual class name can be supplied.
|spring.cloud.refresh.on-restart.enabled | `+++true+++` | Enable refreshing context on start.
|spring.cloud.service-registry.auto-registration.enabled | `+++true+++` | Whether service auto-registration is enabled. Defaults to true.
|spring.cloud.service-registry.auto-registration.fail-fast | `+++false+++` | Whether startup fails if there is no AutoServiceRegistration. Defaults to false.
+2 -2
View File
@@ -1,10 +1,10 @@
{
"dependencies": {
"antora": "3.2.0-alpha.12",
"antora": "3.2.0",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.12",
"@springio/antora-extensions": "1.14.13",
"@springio/asciidoctor-extensions": "1.0.0-alpha.18"
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<packaging>jar</packaging>
Vendored
+146 -116
View File
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.2.0
# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Required ENV vars:
# ------------------
@@ -33,75 +33,84 @@
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -z "$MAVEN_SKIP_RC" ]; then
if [ -f /usr/local/etc/mavenrc ] ; then
if [ -f /usr/local/etc/mavenrc ]; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
if [ -f /etc/mavenrc ]; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
if [ -f "$HOME/.mavenrc" ]; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
cygwin=false
darwin=false
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true ;;
Darwin*)
darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"
export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"
export JAVA_HOME
fi
;;
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
if [ -z "$JAVA_HOME" ]; then
if [ -r /etc/gentoo-release ]; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
if $mingw; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
&& JAVA_HOME="$(
cd "$JAVA_HOME" || (
echo "cannot cd into $JAVA_HOME." >&2
exit 1
)
pwd
)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
if $darwin; then
javaHome="$(dirname "$javaExecutable")"
javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
javaExecutable="$(readlink -f "$javaExecutable")"
fi
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome="$(dirname "$javaExecutable")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
@@ -109,52 +118,60 @@ if [ -z "$JAVA_HOME" ]; then
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
if [ -z "$JAVACMD" ]; then
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
JAVACMD="$(
\unset -f command 2>/dev/null
\command -v java
)"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
if [ ! -x "$JAVACMD" ]; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
if [ -z "$JAVA_HOME" ]; then
echo "Warning: JAVA_HOME environment variable is not set." >&2
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
if [ -z "$1" ]; then
echo "Path not specified to find_maven_basedir" >&2
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
while [ "$wdir" != '/' ]; do
if [ -d "$wdir"/.mvn ]; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(cd "$wdir/.." || exit 1; pwd)
wdir=$(
cd "$wdir/.." || exit 1
pwd
)
fi
# end of workaround
done
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
printf '%s' "$(
cd "$basedir" || exit 1
pwd
)"
}
# concatenates all lines of a file
@@ -165,7 +182,7 @@ concat_lines() {
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
tr -s '\r\n' ' ' <"$1"
fi
}
@@ -177,75 +194,85 @@ log() {
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
exit 1
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
fi
while IFS="=" read -r key value; do
case "$key" in wrapperUrl)
wrapperUrl=$(trim "${value-}")
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget >/dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
elif command -v curl >/dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if command -v wget > /dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
@@ -254,22 +281,25 @@ fi
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
case "$key" in wrapperSha256Sum)
wrapperSha256Sum=$(trim "${value-}")
break
;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
if command -v sha256sum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
elif command -v shasum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $wrapperSha256Result = false ]; then
@@ -284,12 +314,12 @@ MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] \
&& MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
Vendored
+11 -10
View File
@@ -18,7 +18,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@@ -59,22 +59,22 @@ set ERROR_CODE=0
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo. >&2
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
echo. >&2
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo. >&2
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
echo. >&2
goto error
@REM ==== END VALIDATION ====
@@ -119,7 +119,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
@@ -133,7 +133,7 @@ if exist %WRAPPER_JAR% (
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
@@ -160,11 +160,12 @@ FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapp
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
+4 -4
View File
@@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Spring Cloud Netflix</name>
<description>Spring Cloud Netflix</description>
@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath />
</parent>
<scm>
@@ -80,8 +80,8 @@
</developers>
<properties>
<bintray.package>netflix</bintray.package>
<spring-cloud-commons.version>5.0.2</spring-cloud-commons.version>
<spring-cloud-config.version>5.0.4</spring-cloud-config.version>
<spring-cloud-commons.version>5.1.0-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-config.version>5.1.0-SNAPSHOT</spring-cloud-config.version>
<testcontainers.version>1.21.4</testcontainers.version>
<mockserverclient.version>5.15.0</mockserverclient.version>
+73
View File
@@ -0,0 +1,73 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>release-train</id>
<username>${env.RELEASE_TRAIN_MAVEN_REPOSITORY_USERNAME}</username>
<password>${env.RELEASE_TRAIN_MAVEN_REPOSITORY_PASSWORD}</password>
</server>
<server>
<id>spring-commercial-snapshot</id>
<username>${env.COMMERCIAL_REPO_USERNAME}</username>
<password>${env.COMMERCIAL_REPO_PASSWORD}</password>
</server>
<server>
<id>spring-commercial-release</id>
<username>${env.COMMERCIAL_REPO_USERNAME}</username>
<password>${env.COMMERCIAL_REPO_PASSWORD}</password>
</server>
<server>
<id>repo-spring-io-spring-commercial-snapshot</id>
<username>${env.COMMERCIAL_REPO_USERNAME}</username>
<password>${env.COMMERCIAL_REPO_PASSWORD}</password>
</server>
<server>
<id>repo-spring-io-spring-commercial-release</id>
<username>${env.COMMERCIAL_REPO_USERNAME}</username>
<password>${env.COMMERCIAL_REPO_PASSWORD}</password>
</server>
</servers>
<mirrors>
<mirror>
<!-- This redirects the spring-snapshots repo to the release-train repo -->
<id>release-train</id>
<mirrorOf>spring-snapshots</mirrorOf>
<url>${env.RELEASE_TRAIN_MAVEN_REPOSITORY_URL}</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>releaseTrain</id>
<repositories>
<repository>
<id>release-train</id>
<url>${env.RELEASE_TRAIN_MAVEN_REPOSITORY_URL}</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>release-train</id>
<url>${env.RELEASE_TRAIN_MAVEN_REPOSITORY_URL}</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>
+2 -2
View File
@@ -5,11 +5,11 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-cloud-netflix-dependencies</name>
<description>Spring Cloud Netflix Dependencies</description>
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-client-tls-tests</artifactId>
@@ -22,7 +22,6 @@
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
@@ -48,6 +47,10 @@
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
@@ -102,11 +105,9 @@
<artifactId>junit-platform-suite-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<artifactId>bcpkix-jdk18on</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -26,7 +26,7 @@ import org.junit.platform.suite.api.Suite;
* That means that when the new context starts we will fail cause the executor service is
* already shutdown.
*/
@SelectClasses({ EurekaClientTests.class, RestClientEurekaClientTests.class })
@SelectClasses({ EurekaClientTests.class, RestClientEurekaClientTests.class, WebClientEurekaClientTests.class })
@Suite
public class EurekaClientSuite {
@@ -0,0 +1,99 @@
/*
* Copyright 2018-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.eureka;
import java.io.IOException;
import java.security.GeneralSecurityException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.cloud.netflix.eureka.http.WebClientDiscoveryClientOptionalArgs;
import org.springframework.cloud.netflix.eureka.http.WebClientTransportClientFactories;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration.setupTLS;
/**
* Tests for verifying TLS setup with {@link WebClientTransportClientFactories}.
*
* @author Yash Chauhan
*/
public class WebClientEurekaClientTests extends BaseCertTests {
private static final Log LOG = LogFactory.getLog(WebClientEurekaClientTests.class);
private static EurekaServerRunner server;
private static EurekaClientRunner service;
@BeforeAll
public static void setupAll() {
server = startEurekaServer(TestEurekaServer.class);
service = startService(server, TestApp.class);
assertThat(service.discoveryClientOptionalArgs()).isInstanceOf(WebClientDiscoveryClientOptionalArgs.class);
LOG.info("Successfully asserted that WebClient will be used");
waitForRegistration(() -> new WebClientEurekaClientTests().createEurekaClient());
}
@AfterAll
public static void tearDownAll() {
stopService(service);
stopEurekaServer(server);
}
@Override
EurekaClientRunner createEurekaClient() {
return new EurekaClientRunner(TestApp.class, server);
}
@SpringBootConfiguration
@EnableAutoConfiguration
public static class TestApp {
@Bean
public WebClientDiscoveryClientOptionalArgs forceWebClientDiscoveryClientOptionalArgs(
TlsProperties tlsProperties) throws GeneralSecurityException, IOException {
WebClientDiscoveryClientOptionalArgs result = new WebClientDiscoveryClientOptionalArgs(WebClient::builder);
result.setHostnameVerifier((hostname, session) -> true);
setupTLS(result, tlsProperties);
return result;
}
@Bean
public WebClientTransportClientFactories forceWebClientTransportClientFactories() {
return new WebClientTransportClientFactories(WebClient::builder);
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@EnableEurekaServer
public static class TestEurekaServer {
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
@@ -24,6 +24,8 @@ import java.util.Set;
import com.netflix.appinfo.HealthCheckHandler;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
@@ -68,6 +70,8 @@ import org.springframework.util.Assert;
public class EurekaHealthCheckHandler
implements HealthCheckHandler, ApplicationContextAware, InitializingBean, Ordered, Lifecycle {
private static final Log log = LogFactory.getLog(EurekaHealthCheckHandler.class);
private static final Map<Status, InstanceInfo.InstanceStatus> STATUS_MAPPING = new HashMap<>() {
{
put(Status.UNKNOWN, InstanceStatus.UNKNOWN);
@@ -127,7 +131,11 @@ public class EurekaHealthCheckHandler
@Override
public InstanceStatus getStatus(InstanceStatus instanceStatus) {
if (running) {
return getHealthStatus();
InstanceStatus status = getHealthStatus();
if (status != instanceStatus) {
log.info("Eureka health status changed to " + status);
}
return status;
}
else {
// Return nothing if the context is not running, so the status held by the
@@ -143,37 +151,41 @@ public class EurekaHealthCheckHandler
}
protected Status getStatus(StatusAggregator statusAggregator) {
Set<Status> statusSet = new HashSet<>();
for (HealthContributor contributor : healthContributors.values()) {
processContributor(statusSet, contributor);
Set<Status> statuses = new HashSet<>();
for (Map.Entry<String, HealthContributor> entry : healthContributors.entrySet()) {
processContributor(statuses, entry.getKey(), entry.getValue());
}
for (ReactiveHealthContributor contributor : reactiveHealthContributors.values()) {
processContributor(statusSet, contributor);
for (Map.Entry<String, ReactiveHealthContributor> entry : reactiveHealthContributors.entrySet()) {
processContributor(statuses, entry.getKey(), entry.getValue());
}
return statusAggregator.getAggregateStatus(statusSet);
return statusAggregator.getAggregateStatus(statuses);
}
private void processContributor(Set<Status> statusSet, HealthContributor contributor) {
private void processContributor(Set<Status> statuses, String name, HealthContributor contributor) {
if (contributor instanceof CompositeHealthContributor) {
for (HealthContributors.Entry contrib : (CompositeHealthContributor) contributor) {
processContributor(statusSet, contrib.contributor());
processContributor(statuses, contrib.name(), contrib.contributor());
}
}
else if (contributor instanceof HealthIndicator) {
statusSet.add(((HealthIndicator) contributor).health().getStatus());
Status status = ((HealthIndicator) contributor).health().getStatus();
log.debug("Health contributor " + name + " has status " + status);
statuses.add(status);
}
}
private void processContributor(Set<Status> statusSet, ReactiveHealthContributor contributor) {
private void processContributor(Set<Status> statuses, String name, ReactiveHealthContributor contributor) {
if (contributor instanceof CompositeReactiveHealthContributor) {
for (ReactiveHealthContributors.Entry contrib : (CompositeReactiveHealthContributor) contributor) {
processContributor(statusSet, contrib.contributor());
processContributor(statuses, contrib.name(), contrib.contributor());
}
}
else if (contributor instanceof ReactiveHealthIndicator) {
Health health = ((ReactiveHealthIndicator) contributor).health().block();
if (health != null) {
statusSet.add(health.getStatus());
Status status = health.getStatus();
log.debug("Health contributor " + name + " has status " + status);
statuses.add(status);
}
}
}
@@ -34,6 +34,7 @@ import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.util.Timeout;
import org.springframework.cloud.netflix.eureka.TimeoutProperties;
import org.springframework.cloud.netflix.eureka.http.EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.lang.Nullable;
@@ -42,6 +43,13 @@ import org.springframework.lang.Nullable;
* Supplier for the {@link ClientHttpRequestFactory} to be used by Eureka client that uses
* {@link HttpClients}.
*
* <p>
* This supplier is intentionally stateless: each call to {@link #get} builds a fresh
* {@link CloseableHttpClient}. Caching and lifecycle management of the shared client is
* the responsibility of the owning {@code TransportClientFactory}
* ({@link RestClientTransportClientFactory}), which is already created once per Eureka
* client and is the natural owner of that client's lifecycle.
*
* @author Marcin Grzejszczak
* @author Olga Maciaszek-Sharma
* @author Jiwon Jeon
@@ -67,8 +75,8 @@ public class DefaultEurekaClientHttpRequestFactorySupplier implements EurekaClie
.setConnectionManager(buildConnectionManager(sslContext, hostnameVerifier, timeoutProperties));
}
httpClientBuilder.setDefaultRequestConfig(buildRequestConfig());
CloseableHttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return requestFactory;
@@ -51,7 +51,7 @@ public class RestClientTransportClientFactories implements TransportClientFactor
public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
final Collection<Void> additionalFilters, final InstanceInfo myInstanceInfo,
final Optional<SSLContext> sslContext, final Optional<HostnameVerifier> hostnameVerifier) {
return new RestClientTransportClientFactory(args.getSSLContext(), args.getHostnameVerifier(),
return new RestClientTransportClientFactory(sslContext, hostnameVerifier,
args.getEurekaClientHttpRequestFactorySupplier(), args.getRestClientBuilderSupplier());
}
@@ -16,6 +16,8 @@
package org.springframework.cloud.netflix.eureka.http;
import java.io.Closeable;
import java.io.IOException;
import java.util.Optional;
import java.util.function.Supplier;
@@ -31,6 +33,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.web.client.RestClient;
import org.springframework.web.util.UriComponentsBuilder;
@@ -44,6 +47,15 @@ import static org.springframework.cloud.netflix.eureka.http.EurekaHttpClientUtil
* {@link RestClientEurekaHttpClient}. Relies on Jackson for serialization and
* deserialization.
*
* <p>
* A single {@link ClientHttpRequestFactory} (and the underlying HTTP client it wraps) is
* lazily built on the first call to {@link #newClient} and reused for every subsequent
* call on this factory instance. Since each {@code RestClientTransportClientFactory} is
* already scoped to a single Eureka client, caching the request factory here - rather
* than in a potentially shared {@link EurekaClientHttpRequestFactorySupplier} - ties the
* shared client's lifecycle directly to this factory instance, so {@link #shutdown()}
* only ever closes a client owned exclusively by this factory.
*
* @author Wonchul Heo
* @author Olga Maciaszek-Sharma
* @since 4.2.0
@@ -58,6 +70,8 @@ public class RestClientTransportClientFactory implements TransportClientFactory
private final Supplier<RestClient.Builder> builderSupplier;
private ClientHttpRequestFactory cachedRequestFactory;
public RestClientTransportClientFactory(Optional<SSLContext> sslContext,
Optional<HostnameVerifier> hostnameVerifier,
EurekaClientHttpRequestFactorySupplier eurekaClientHttpRequestFactorySupplier,
@@ -84,8 +98,7 @@ public class RestClientTransportClientFactory implements TransportClientFactory
// we want a copy to modify. Don't change the original
final RestClient.Builder builder = builderSupplier.get().clone();
ClientHttpRequestFactory requestFactory = this.eurekaClientHttpRequestFactorySupplier
.get(this.sslContext.orElse(null), this.hostnameVerifier.orElse(null));
ClientHttpRequestFactory requestFactory = getOrCreateRequestFactory();
builder.requestFactory(requestFactory);
setUrl(builder, endpoint.getServiceUrl());
@@ -105,8 +118,26 @@ public class RestClientTransportClientFactory implements TransportClientFactory
return new RestClientEurekaHttpClient(builder.build());
}
private synchronized ClientHttpRequestFactory getOrCreateRequestFactory() {
if (this.cachedRequestFactory == null) {
this.cachedRequestFactory = this.eurekaClientHttpRequestFactorySupplier.get(this.sslContext.orElse(null),
this.hostnameVerifier.orElse(null));
}
return this.cachedRequestFactory;
}
@Override
public void shutdown() {
public synchronized void shutdown() {
if (this.cachedRequestFactory instanceof HttpComponentsClientHttpRequestFactory httpRequestFactory
&& httpRequestFactory.getHttpClient() instanceof Closeable closeableHttpClient) {
try {
closeableHttpClient.close();
}
catch (IOException ex) {
// best-effort close during shutdown; nothing actionable if it fails
}
}
this.cachedRequestFactory = null;
}
private static void setUrl(RestClient.Builder builder, String serviceUrl) {
@@ -52,7 +52,7 @@ public class WebClientTransportClientFactories implements TransportClientFactori
public TransportClientFactory newTransportClientFactory(final EurekaClientConfig clientConfig,
final Collection<Void> additionalFilters, final InstanceInfo myInstanceInfo,
final Optional<SSLContext> sslContext, final Optional<HostnameVerifier> hostnameVerifier) {
return new WebClientTransportClientFactory(builder);
return new WebClientTransportClientFactory(builder, sslContext, hostnameVerifier);
}
}
@@ -16,15 +16,25 @@
package org.springframework.cloud.netflix.eureka.http;
import java.util.Optional;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLPeerUnverifiedException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.discovery.shared.resolver.EurekaEndpoint;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.TransportClientFactory;
import io.netty.channel.Channel;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.JdkSslContext;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpClientSecurityUtils;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.resources.LoopResources;
@@ -58,12 +68,23 @@ public class WebClientTransportClientFactory implements TransportClientFactory {
private final Supplier<WebClient.Builder> builderSupplier;
private final Optional<SSLContext> sslContext;
private final Optional<HostnameVerifier> hostnameVerifier;
private final ConnectionProvider connectionProvider;
private final LoopResources loopResources;
public WebClientTransportClientFactory(Supplier<WebClient.Builder> builderSupplier) {
this(builderSupplier, Optional.empty(), Optional.empty());
}
public WebClientTransportClientFactory(Supplier<WebClient.Builder> builderSupplier, Optional<SSLContext> sslContext,
Optional<HostnameVerifier> hostnameVerifier) {
this.builderSupplier = builderSupplier;
this.sslContext = sslContext;
this.hostnameVerifier = hostnameVerifier;
this.connectionProvider = ConnectionProvider.create("eureka-webclient");
this.loopResources = LoopResources.create("eureka-webclient");
}
@@ -78,8 +99,36 @@ public class WebClientTransportClientFactory implements TransportClientFactory {
// Use dedicated Reactor Netty resources independent of the reactive web server
// to prevent RejectedExecutionException during graceful shutdown when the
// server's event loop terminates before DiscoveryClient deregisters.
builder.clientConnector(
new ReactorClientHttpConnector(HttpClient.create(this.connectionProvider).runOn(this.loopResources)));
HttpClient httpClient = HttpClient.create(this.connectionProvider).runOn(this.loopResources);
if (this.sslContext.isPresent()) {
JdkSslContext sslContext = new JdkSslContext(this.sslContext.get(), true, ClientAuth.NONE);
httpClient = httpClient.secure(sslContextSpec -> {
if (this.hostnameVerifier.isPresent()) {
HostnameVerifier verifier = this.hostnameVerifier.get();
sslContextSpec.sslContext(sslContext)
.handlerConfigurator(sslHandler -> sslHandler.handshakeFuture().addListener(handshake -> {
if (handshake.isSuccess()) {
SSLEngine engine = sslHandler.engine();
if (!verifier.verify(engine.getPeerHost(), engine.getSession())) {
Channel channel = (Channel) handshake.getNow();
channel.pipeline()
.fireExceptionCaught(new SSLPeerUnverifiedException(
"Hostname verification failed for " + engine.getPeerHost()));
channel.close();
}
}
}));
}
else {
sslContextSpec.sslContext(sslContext)
.handlerConfigurator(HttpClientSecurityUtils.HOSTNAME_VERIFICATION_CONFIGURER);
}
});
}
builder.clientConnector(new ReactorClientHttpConnector(httpClient));
return new WebClientEurekaHttpClient(builder.build());
}
@@ -30,6 +30,7 @@ import org.springframework.boot.health.actuate.endpoint.SimpleStatusAggregator;
import org.springframework.boot.health.contributor.AbstractHealthIndicator;
import org.springframework.boot.health.contributor.AbstractReactiveHealthIndicator;
import org.springframework.boot.health.contributor.CompositeHealthContributor;
import org.springframework.boot.health.contributor.CompositeReactiveHealthContributor;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthContributor;
import org.springframework.boot.health.contributor.HealthContributors;
@@ -41,6 +42,7 @@ import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicato
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@@ -195,6 +197,33 @@ class EurekaHealthCheckHandlerTests {
}
public static class NamedUpHealthConfiguration {
@Bean
public HealthIndicator upHealthIndicator() {
return () -> Health.up().build();
}
}
public static class NamedDownHealthConfiguration {
@Bean
public HealthIndicator downHealthIndicator() {
return () -> Health.down().build();
}
}
public static class NamedReactiveUpHealthConfiguration {
@Bean
public ReactiveHealthIndicator reactiveUpHealthIndicator() {
return () -> Mono.just(Health.up().build());
}
}
public static class FatalHealthConfiguration {
@Bean
@@ -327,4 +356,15 @@ class EurekaHealthCheckHandlerTests {
}
@Configuration
static class NamedReactiveDownHealthConfiguration {
@Bean
CompositeReactiveHealthContributor namedReactiveDownHealthContributor() {
return CompositeReactiveHealthContributor.fromMap(Map.of("reactiveDownHealthIndicator",
(ReactiveHealthIndicator) () -> Mono.just(Health.down().build())));
}
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2013-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.eureka.http;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.cloud.netflix.eureka.TimeoutProperties;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultEurekaClientHttpRequestFactorySupplier}.
*
* <p>
* These specifically guard against regressing gh-4275: an earlier fix (gh-4258) made this
* class a Spring {@code DisposableBean}, which raced with
* {@code CloudEurekaClient#shutdown()} during context shutdown and broke
* unregister-on-shutdown. That fix was reverted, and this supplier is now intentionally
* stateless (gh-4569): it never caches or closes an HTTP client itself. Lifecycle
* management of the shared client belongs to the owning
* {@link RestClientTransportClientFactory}, which is already scoped to a single Eureka
* client - see {@link RestClientTransportClientFactoryShutdownTests}.
*/
class DefaultEurekaClientHttpRequestFactorySupplierTests {
private final DefaultEurekaClientHttpRequestFactorySupplier supplier = new DefaultEurekaClientHttpRequestFactorySupplier(
new TimeoutProperties(), Collections.emptySet());
@Test
void shouldNotBeADisposableBean() {
// Guard against reintroducing gh-4275: this class must not be destroyed via an
// independent Spring bean-destroy callback.
assertThat(supplier).isNotInstanceOf(DisposableBean.class);
}
@Test
void getShouldReturnANonNullRequestFactory() {
ClientHttpRequestFactory requestFactory = supplier.get(null, null);
assertThat(requestFactory).isNotNull();
}
@Test
void getShouldBuildAFreshHttpClientOnEveryCall() {
// The supplier is stateless - caching and lifecycle management belong to the
// caller (RestClientTransportClientFactory), so every call must return an
// independent client rather than a shared one.
ClientHttpRequestFactory first = supplier.get(null, null);
ClientHttpRequestFactory second = supplier.get(null, null);
Object firstHttpClient = ((HttpComponentsClientHttpRequestFactory) first).getHttpClient();
Object secondHttpClient = ((HttpComponentsClientHttpRequestFactory) second).getHttpClient();
assertThat(firstHttpClient).isNotSameAs(secondHttpClient);
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2017-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.eureka.http;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import com.netflix.discovery.shared.resolver.DefaultEndpoint;
import com.netflix.discovery.shared.transport.TransportClientFactory;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
class RestClientTransportClientFactoriesTests {
@Test
void shouldUseSslContextAndHostnameVerifierProvidedByDiscoveryClient() throws Exception {
SSLContext sslContextFromArgs = SSLContext.getDefault();
SSLContext sslContextFromDiscoveryClient = SSLContext.getInstance("TLS");
sslContextFromDiscoveryClient.init(null, null, null);
HostnameVerifier hostnameVerifierFromArgs = (hostname, session) -> false;
HostnameVerifier hostnameVerifierFromDiscoveryClient = (hostname, session) -> true;
AtomicReference<SSLContext> capturedSslContext = new AtomicReference<>();
AtomicReference<HostnameVerifier> capturedHostnameVerifier = new AtomicReference<>();
EurekaClientHttpRequestFactorySupplier requestFactorySupplier = (sslContext, hostnameVerifier) -> {
capturedSslContext.set(sslContext);
capturedHostnameVerifier.set(hostnameVerifier);
return new SimpleClientHttpRequestFactory();
};
RestClientDiscoveryClientOptionalArgs args = new RestClientDiscoveryClientOptionalArgs(requestFactorySupplier,
RestClient::builder);
args.setSSLContext(sslContextFromArgs);
args.setHostnameVerifier(hostnameVerifierFromArgs);
RestClientTransportClientFactories factories = new RestClientTransportClientFactories(args);
TransportClientFactory transportClientFactory = factories.newTransportClientFactory(null, null, null,
Optional.of(sslContextFromDiscoveryClient), Optional.of(hostnameVerifierFromDiscoveryClient));
transportClientFactory.newClient(new DefaultEndpoint("http://localhost"));
assertThat(capturedSslContext.get()).isSameAs(sslContextFromDiscoveryClient);
assertThat(capturedHostnameVerifier.get()).isSameAs(hostnameVerifierFromDiscoveryClient);
}
}
@@ -0,0 +1,111 @@
/*
* Copyright 2013-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.netflix.eureka.http;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.configuration.TlsProperties;
import org.springframework.cloud.netflix.eureka.TimeoutProperties;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests that {@link RestClientTransportClientFactory} owns and deterministically closes
* the shared HTTP client it lazily builds via
* {@link EurekaClientHttpRequestFactorySupplier#get}, rather than delegating that
* lifecycle to a potentially shared supplier instance (gh-4569).
*
* <p>
* Since each {@code RestClientTransportClientFactory} is already scoped to a single
* Eureka client, caching the client here means
* {@link RestClientTransportClientFactory#shutdown()} only ever closes a client owned
* exclusively by this factory - there is no shared-state hazard between a refreshed
* client or a second, independently-created Eureka client, as there would be if the
* client were cached in the supplier itself.
*/
class RestClientTransportClientFactoryShutdownTests {
private final DefaultEurekaClientHttpRequestFactorySupplier supplier = new DefaultEurekaClientHttpRequestFactorySupplier(
new TimeoutProperties(), Collections.emptySet());
private final RestClientTransportClientFactory factory = new RestClientTransportClientFactory(new TlsProperties(),
supplier);
@Test
void shutdownBeforeAnyNewClientCallShouldNotThrow() {
// shutdown() before newClient() (e.g. context shut down before any request was
// ever made) must not throw.
assertThatCode(factory::shutdown).doesNotThrowAnyException();
}
@Test
void shutdownShouldBeIdempotent() {
factory.newClient(endpoint());
factory.shutdown();
// Idempotent - shutdown paths may call shutdown() more than once.
assertThatCode(factory::shutdown).doesNotThrowAnyException();
}
@Test
void repeatedNewClientCallsShouldReuseTheSameCachedRequestFactory() {
// newClient() delegates to the same cached request factory on every call, rather
// than asking the supplier for a fresh one each time.
factory.newClient(endpoint());
Object firstHttpClient = cachedHttpClient(factory);
factory.newClient(endpoint());
Object secondHttpClient = cachedHttpClient(factory);
assertThat(firstHttpClient).isSameAs(secondHttpClient);
}
private static com.netflix.discovery.shared.resolver.EurekaEndpoint endpoint() {
com.netflix.discovery.shared.resolver.EurekaEndpoint endpoint = mock(
com.netflix.discovery.shared.resolver.EurekaEndpoint.class);
when(endpoint.getServiceUrl()).thenReturn("http://localhost:8761/eureka/");
return endpoint;
}
private static Object cachedHttpClient(RestClientTransportClientFactory factory) {
Object requestFactory = org.springframework.test.util.ReflectionTestUtils.getField(factory,
"cachedRequestFactory");
return ((HttpComponentsClientHttpRequestFactory) requestFactory).getHttpClient();
}
@Test
void newClientAfterShutdownShouldCreateANewCachedRequestFactory() {
factory.newClient(endpoint());
Object httpClientBeforeShutdown = cachedHttpClient(factory);
factory.shutdown();
factory.newClient(endpoint());
Object httpClientAfterShutdown = cachedHttpClient(factory);
// shutdown() clears the cached request factory, so a subsequent newClient()
// call must lazily build a fresh one rather than reusing the client that was
// just closed.
assertThat(httpClientAfterShutdown).isNotSameAs(httpClientBeforeShutdown);
}
}
@@ -16,9 +16,21 @@
package org.springframework.cloud.netflix.eureka.http;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import com.netflix.discovery.shared.resolver.DefaultEndpoint;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,6 +40,8 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -37,6 +51,7 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -101,6 +116,123 @@ class WebClientTransportClientFactoryTest {
transportClientFatory.newClient(new DefaultEndpoint("http://test:test@localhost:8761"));
}
@Test
void testHostnameVerificationFailure() throws Exception {
SelfSignedCertificate certificate = new SelfSignedCertificate("localhost");
io.netty.handler.ssl.SslContext serverSslContext = SslContextBuilder
.forServer(certificate.certificate(), certificate.privateKey())
.build();
AtomicBoolean hostnameVerified = new AtomicBoolean();
DisposableServer server = HttpServer.create()
.port(0)
.secure(sslContextSpec -> sslContextSpec.sslContext(serverSslContext))
.handle((request, response) -> response.send())
.bindNow();
try {
SSLContext clientSslContext = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
} };
clientSslContext.init(null, trustManagers, new SecureRandom());
HostnameVerifier hostnameVerifier = (hostname, session) -> {
hostnameVerified.set(true);
return false;
};
WebClientTransportClientFactory factory = new WebClientTransportClientFactory(WebClient::builder,
Optional.of(clientSslContext), Optional.of(hostnameVerifier));
try {
WebClientEurekaHttpClient client = (WebClientEurekaHttpClient) factory
.newClient(new DefaultEndpoint("https://localhost:" + server.port()));
assertThatThrownBy(() -> client.getWebClient()
.get()
.retrieve()
.bodyToMono(Void.class)
.block(Duration.ofSeconds(10))).hasRootCauseInstanceOf(SSLPeerUnverifiedException.class);
assertThat(hostnameVerified).isTrue();
}
finally {
factory.shutdown();
}
}
finally {
server.disposeNow();
certificate.delete();
}
}
@Test
void testHostnameVerificationWithDefaultVerifier() throws Exception {
SelfSignedCertificate certificate = new SelfSignedCertificate("localhost");
io.netty.handler.ssl.SslContext serverSslContext = SslContextBuilder
.forServer(certificate.certificate(), certificate.privateKey())
.build();
DisposableServer server = HttpServer.create()
.port(0)
.secure(sslContextSpec -> sslContextSpec.sslContext(serverSslContext))
.handle((request, response) -> response.send())
.bindNow();
try {
SSLContext clientSslContext = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = { new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
} };
clientSslContext.init(null, trustManagers, new SecureRandom());
WebClientTransportClientFactory factory = new WebClientTransportClientFactory(WebClient::builder,
Optional.of(clientSslContext), Optional.empty());
try {
WebClientEurekaHttpClient client = (WebClientEurekaHttpClient) factory
.newClient(new DefaultEndpoint("https://localhost:" + server.port()));
client.getWebClient().get().retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10));
}
finally {
factory.shutdown();
}
}
finally {
server.disposeNow();
certificate.delete();
}
}
@AfterEach
void shutdown() {
transportClientFatory.shutdown();
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
@@ -4,7 +4,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<name>Spring Cloud Starter Netflix Eureka Client</name>
@@ -3,7 +3,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix</artifactId>
<version>5.0.2</version>
<version>5.1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<name>Spring Cloud Starter Netflix Eureka Server</name>