Skip to content

[ISSUE #8426] Resolve actually bound server port for client registration when server.port=0 - #8453

Open
konlue wants to merge 3 commits into
apache:masterfrom
konlue:fix/8426-client-bound-port
Open

konlue wants to merge 3 commits into
apache:masterfrom
konlue:fix/8426-client-bound-port

Conversation

@konlue

@konlue konlue commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #8426.

PortUtils.findPort discovered the registration port by reflecting
getPort() off the web-server factory bean. A factory answers with its
configured port, so an embedded server started on server.port=0 — the
standard way to get a random free port — made every listener and the shared
register config register the literal port 0, and the gateway could not
route to the instance. The reflective branch always succeeded, so the
external-container fallback behind it was unreachable in embedded apps.

What changed

  • PortUtils.findPort(ApplicationContext) (new overload): asks the owned
    WebServer for the port it actually bound, walking up the context parent
    chain so classic child contexts resolve to their owning server context. A
    management context is never in that chain and is never selected, and a
    port is only accepted when positive — a factory configured for a random
    port without a running owned server no longer resolves to a usable-looking
    address. Both spring-boot types are resolved reflectively because
    shenyu-client-core deliberately has no compile-time spring-boot
    dependency. The legacy chain (Boot 2.x factory reflection, Boot 1.x
    factory reflection, JMX external-container probe) is preserved behind it
    for external deployments, and failure now raises a diagnostic that points
    at the explicit advertised-port property instead of a bare "can not find
    port automatically".
  • SpringMvcClientEventListener and SpringWebSocketClientEventListener
    use the context overload. A positive advertised port stays authoritative
    exactly as before.
  • ClientRegisterConfigImpl.getPort aligned with the same contract: a
    positive configured port is authoritative; an absent or non-positive one
    resolves the actually bound port instead of registering a configured 0
    verbatim (previously a presence check, so port=0 leaked into
    URIRegisterDTO/MetaDataRegisterDTO/api-doc payloads unchanged — no
    wire-format change, only the resolved value).

Registration, metadata and api-doc payloads all flow through these two
entry points, so the corrected port feeds every registration artifact;
heartbeat identities already read the bound server port directly
(WebServerInitializedEvent) and stay consistent with it. A follow-up
commit also makes the total-discovery failure carry the advertised-port
diagnostic on every failure path, covered by a regression test.

Tests

  • PortUtilsTest (6): a real embedded Tomcat started on port 0 must report
    its bound port (red on the old implementation — it reported the
    configured 0); a fixed-port server control; child-context → owning server
    context walk; a reactive embedded Tomcat (same WebServerApplicationContext
    contract, so Jetty/Undertow are covered by the interface); and
    loud-failure instead of a silent zero.
  • ClientRegisterConfigImplTest (4, new): positive advertised port is
    authoritative; configured 0 and absent port resolve the bound port (red
    on the old implementation — errors / silent 0); zero resolution without
    a running server fails loudly.
  • SpringMvcClientEventListenerTest: mocks updated to the context overload;
    the now-dead getAutowireCapableBeanFactory stubs removed.

Module results on the current master baseline: shenyu-client-core 111,
shenyu-client-springmvc 41, shenyu-client-spring-websocket 29 — all
green; checkstyle clean per module.

Follow-ups

try {
legacy = findPort(applicationContext.getAutowireCapableBeanFactory());
} catch (ShenyuException e) {
throw new ShenyuException("can not find port automatically: no embedded web server reported a bound port "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exception msg not too long?!

try {
webServerApplicationContextClass = Class.forName(WEB_SERVER_APPLICATION_CONTEXT_CLASS_NAME);
} catch (ClassNotFoundException e) {
//spring-boot web classes are not on the classpath: nothing to probe

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.catch exception maybe throw??
2.add log error

return null;
}
final Class<?> webServerClass;
try {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two try??

konlue and others added 3 commits November 13, 2026 22:44
…istration when server.port=0

PortUtils.findPort reflected getPort() off the web-server factory config
bean, so an embedded server started on server.port=0 reported its
configured value 0 as the registration port. Discovery now reads the
port the owned WebServer actually bound from the client's application
context (walking up the parent chain so classic child contexts resolve
to their owning server context), keeps the Boot 1.x/2.x factory and
JMX external-container fallbacks for external deployments, and fails
loudly instead of silently resolving a non-positive port. The
client-wide register config now treats a positive advertised port as
authoritative and otherwise resolves the bound port, instead of
registering a configured 0 verbatim.
…vertised-port hint

When no embedded server, no web-server factory bean and no external
container connector is available, the context overload surfaced the bare
legacy message. Rethrow it with the advertised-port diagnostic and the
original cause attached, so operators get the actionable hint on every
failure path, and cover it with a regression test.
@konlue
konlue force-pushed the fix/8426-client-bound-port branch from 94bb9bd to 4c3647b Compare November 13, 2026 14:47
@konlue

konlue commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

Review response

Thanks @moremind. All three comments are addressed in 4c3647bea, on top of a rebase to current master (71b7f37cc).

Long exception message (PortUtils.java:83). The two diagnostics now share one short constant, PORT_NOT_FOUND_MESSAGE — can not find port automatically, please configure the advertised port (shenyu.client.http.props.port) — and the non-positive branch only appends the value it resolved (..., resolved port: 0). The old text explained the cause in two extra clauses; the property key in parentheses is what a user actually needs to act on.

catch at PortUtils.java:184. I kept returning null instead of throwing, because that return value is the hand-off to the legacy chain — Boot 2.x factory reflection, Boot 1.x factory reflection, then the JMX external-container probe. Throwing here would make that chain unreachable in precisely the deployments it exists for (external Tomcat, Spring MVC apps without spring-boot web classes), which is how the reflective branch used to hide the fallback. The silence is what I removed: the absent-class case now logs at debug (normal for non-Boot deployments, so it must not be noise), and a reflective failure on a class that is present logs at warn with the stack trace, since that is the unexpected case. If nothing is found downstream, findPort still throws loudly with the hint above, so a misconfigured client never registers an unusable port.

Two try blocks (PortUtils.java:188). Merged into a single try: both Class.forName lookups, the parent-chain walk and the two reflective calls share one exception boundary, with ClassNotFoundException and ReflectiveOperationException handled separately so the two cases can be told apart in the log. The walk is now a plain for loop instead of the manual depth counter, same MAX_CONTEXT_DEPTH bound.

Verification after the rebase: shenyu-client-core 112/0, shenyu-client-springmvc 41/0, shenyu-client-spring-websocket 29/0, and checkstyle:check is clean on all three modules. The PR is now 8 files, +339/-15.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][Java Client] Automatic port detection returns 0 after an embedded server binds a random port

2 participants