You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by "FrankChen021 (via GitHub)" <gi...@apache.org> on 2026/09/05 04:08:53 UTC
[PR] feat(server): add response identity headers (druid)
FrankChen021 opened a new pull request, #20268:
URL: https://github.com/apache/druid/pull/20268
### Description
This PR adds opt-in response headers that identify the Druid process which generated an HTTP response:
- `X-Druid-Response-Server`: advertised host and port
- `X-Druid-Response-Service`: configured `druid.service` name
The feature is disabled by default and can be enabled with:
```properties
druid.server.http.enableResponseIdentityHeaders=true
```
The identity handler wraps the complete Jetty handler chain, so locally generated responses, including early HTTP errors, receive the headers when enabled.
#### Router proxy behavior
For query and management requests proxied by a Router:
- If the upstream returns both identity headers, the Router forwards the upstream identity.
- If the upstream returns neither header or only one header, the Router returns neither identity header.
- If no upstream response is received and the Router generates the response, the headers identify the Router.
- A Router with the feature disabled suppresses upstream identity headers.
Treating the two headers as an all-or-nothing pair prevents clients from receiving an ambiguous partial identity during rolling upgrades or partial configuration.
#### Security
The server header can reveal internal hostnames, IP addresses, and ports. The setting remains disabled by default and should only be enabled when clients are authorized to receive cluster topology information.
#### Automated tests
- 45 focused configuration, handler, management-proxy, and query-proxy tests passed.
- `ResponseIdentityHeaderTest`: 6 embedded end-to-end tests passed.
- The embedded coverage includes Coordinator and Overlord APIs directly and through Router, Broker SQL and native queries directly and through Router, a native query directly to Historical, and Router-generated responses.
- `git diff --check origin/master...HEAD` passed.
#### Real nano cluster validation
The feature was also tested against a locally built `apache-druid-39.0.0-SNAPSHOT` nano-quickstart cluster. All 16 requests returned the expected status and process identity.
| URL | Request | Status | Response server | Response service |
|---|---|---:|---|---|
| `http://localhost:8888/status/health` | Router health | 200 | `localhost:8888` | `druid/router` |
| `http://localhost:8082/status/health` | Broker health | 200 | `localhost:8082` | `druid/broker` |
| `http://localhost:8081/status/health` | Coordinator/Overlord health | 200 | `localhost:8081` | `druid/coordinator` |
| `http://localhost:8083/status/health` | Historical health | 200 | `localhost:8083` | `druid/historical` |
| `http://localhost:8091/status/health` | Middle Manager health | 200 | `localhost:8091` | `druid/middleManager` |
| `http://localhost:8081/druid/coordinator/v1/isLeader` | Coordinator API direct | 200 | `localhost:8081` | `druid/coordinator` |
| `http://localhost:8888/druid/coordinator/v1/isLeader` | Coordinator API via Router | 200 | `localhost:8081` | `druid/coordinator` |
| `http://localhost:8081/druid/indexer/v1/isLeader` | Overlord API direct | 200 | `localhost:8081` | `druid/coordinator` |
| `http://localhost:8888/druid/indexer/v1/isLeader` | Overlord API via Router | 200 | `localhost:8081` | `druid/coordinator` |
| `http://localhost:8082/druid/v2/sql` | SQL direct to Broker | 200 | `localhost:8082` | `druid/broker` |
| `http://localhost:8888/druid/v2/sql` | SQL via Router | 200 | `localhost:8082` | `druid/broker` |
| `http://localhost:8082/druid/v2` | Native query direct to Broker | 200 | `localhost:8082` | `druid/broker` |
| `http://localhost:8888/druid/v2` | Native query via Router | 200 | `localhost:8082` | `druid/broker` |
| `http://localhost:8083/druid/v2` | Native query direct to Historical | 200 | `localhost:8083` | `druid/historical` |
| `http://localhost:8888/status/health` | Router-generated `PATCH` rejection | 405 | `localhost:8888` | `druid/router` |
| `http://localhost:8888/not-a-druid-endpoint` | Router-generated missing endpoint | 404 | `localhost:8888` | `druid/router` |
The nano configuration runs Coordinator and Overlord in one process on port `8081` with `druid.service=druid/coordinator`, so requests to Overlord endpoints correctly report that process identity.
#### Release note
Druid can optionally return `X-Druid-Response-Server` and `X-Druid-Response-Service` on HTTP responses, allowing operators to identify the process that served a direct or Router-proxied request. Enable the feature with `druid.server.http.enableResponseIdentityHeaders=true` only for clients permitted to receive cluster topology information.
<hr>
##### Key changed/added classes in this PR
- `ResponseIdentityHeaderHandler`
- `AsyncManagementForwardingServlet`
- `AsyncQueryForwardingServlet`
- `ResponseIdentityHeaderTest`
<hr>
This PR has:
- [x] been self-reviewed.
- [x] added documentation for new or modified features or behaviors.
- [x] a release note entry in the PR description.
- [x] added comments explaining the "why" and the intent of the code wherever it would not be obvious for an unfamiliar reader.
- [x] added unit tests or modified existing tests to cover new code paths.
- [x] added integration tests.
- [x] been tested in a test Druid cluster.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] feat(server): add response identity headers (druid)
Posted by "FrankChen021 (via GitHub)" <gi...@apache.org>.
FrankChen021 commented on code in PR #20268:
URL: https://github.com/apache/druid/pull/20268#discussion_r3940962558
##########
services/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java:
##########
@@ -634,6 +635,10 @@ protected void onServerResponseHeaders(
if (responseContext != null) {
proxyResponse.setHeader(responseContext.getName(), responseContext.getValue());
}
+ // When response identity headers are enabled, the outer response handler initially adds the Router identity.
+ // An upstream response must replace it with the upstream identity, or with no identity when the upstream does not
+ // provide a complete header triple.
+ ResponseIdentityHeaderHandler.clearRouterIdentity(proxyResponse);
Review Comment:
Fixed in cb191b827f. Each proxy now remembers its local identity before replacing it with upstream headers and restores that local triple in onProxyResponseFailure before Jetty generates the 502/504. The same handling is applied to query forwarding, management forwarding, and the legacy Coordinator-to-Overlord proxy. The shared remember/restore behavior has focused unit coverage.
##########
server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java:
##########
@@ -404,6 +404,9 @@ public void lifeCycleStopped(LifeCycle event)
JettyServerInitializer initializer = injector.getInstance(JettyServerInitializer.class);
try {
initializer.initialize(server, injector);
+ if (config.isEnableResponseIdentityHeaders()) {
+ server.setHandler(new ResponseIdentityHeaderHandler(node, server.getHandler()));
Review Comment:
Fixed in cb191b827f. Response identity headers are now also added through the Jetty server error-handler path, which covers request-line and header parsing failures that never enter the application handler chain. I added an embedded raw-socket test that sends an invalid HTTP header and verifies the resulting 400 response contains the Router server, service, and version identity.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] feat(server): add response identity headers (druid)
Posted by "FrankChen021 (via GitHub)" <gi...@apache.org>.
FrankChen021 commented on code in PR #20268:
URL: https://github.com/apache/druid/pull/20268#discussion_r3940633632
##########
server/src/main/java/org/apache/druid/server/initialization/jetty/ResponseIdentityHeaderHandler.java:
##########
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.server.initialization.jetty;
+
+import org.apache.druid.server.DruidNode;
+import org.eclipse.jetty.client.Response;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.rewrite.handler.HeaderPatternRule;
+import org.eclipse.jetty.rewrite.handler.RewriteHandler;
+import org.eclipse.jetty.server.Handler;
+
+import javax.servlet.http.HttpServletResponse;
+
+public class ResponseIdentityHeaderHandler extends RewriteHandler
+{
+ public static final String RESPONSE_SERVER_HEADER = "X-Druid-Server";
+ public static final String RESPONSE_SERVICE_HEADER = "X-Druid-Service";
+ public static final String RESPONSE_VERSION_HEADER = "X-Druid-Version";
+
+ public ResponseIdentityHeaderHandler(final DruidNode selfNode, final Handler handler)
+ {
+ super(handler);
+ addRule(new HeaderPatternRule("*", RESPONSE_SERVER_HEADER, selfNode.getHostAndPortToUse()));
Review Comment:
[P2] Response resets can discard the identity headers
HeaderPatternRule writes these fields before invoking the wrapped handler. A downstream handler can legally reset the response and Jetty clears its headers; Druid's PreResponseAuthorizationCheckFilter does this on its 403 error path (server/src/main/java/org/apache/druid/server/security/PreResponseAuthorizationCheckFilter.java:169-172). With this feature enabled, those generated responses therefore omit the identity triple despite the documented every-response guarantee. Inject the headers at final response commit (or reapply them after reset) and cover the reset path.
##########
server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java:
##########
@@ -404,6 +404,9 @@ public void lifeCycleStopped(LifeCycle event)
JettyServerInitializer initializer = injector.getInstance(JettyServerInitializer.class);
try {
initializer.initialize(server, injector);
+ if (config.isEnableResponseIdentityHeaders()) {
+ server.setHandler(new ResponseIdentityHeaderHandler(node, server.getHandler()));
Review Comment:
[P2] Legacy Overlord proxy bypasses identity filtering
This wraps the entire handler chain, including the Coordinator's /druid/indexer/* OverlordProxyServlet (services/src/main/java/org/apache/druid/cli/CoordinatorJettyServerInitializer.java:131-133), but that servlet still inherits Jetty's default response-header forwarding. When the Coordinator feature is disabled, an upstream-enabled Overlord's X-Druid-* headers are forwarded anyway; when the Coordinator feature is enabled and the upstream returns only a partial triple, the proxy overwrites only those fields while the outer handler leaves the Coordinator values for the rest. This bypasses the all-or-nothing identity contract implemented for Router proxies and can expose or emit an ambiguous identity. Apply the same clearing/filtering to OverlordProxyServlet or explicitly scope and document this behavior.
##########
server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java:
##########
@@ -206,6 +206,9 @@ public ServerConfig(@NotNull ErrorResponseTransformStrategy errorResponseTransfo
@JsonProperty
private boolean enableHSTS = false;
+ @JsonProperty
+ private boolean enableResponseIdentityHeaders = false;
Review Comment:
[P2] Preserve the flag in the CliIndexer config copy
CliIndexerServerModule.makeAdjustedServerConfig constructs the ServerConfig passed to JettyServerModule.makeAndInitializeServer, but its explicit constructor call (server/src/main/java/org/apache/druid/server/initialization/jetty/CliIndexerServerModule.java:142-166) does not copy enableResponseIdentityHeaders. Thus druid.server.http.enableResponseIdentityHeaders=true is lost for the CliIndexer HTTP server, so that service never installs the new handler even though the documentation says the setting applies to all Druid services. Preserve the flag in the adjusted config and cover this server type.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] feat(server): add response identity headers (druid)
Posted by "FrankChen021 (via GitHub)" <gi...@apache.org>.
FrankChen021 merged PR #20268:
URL: https://github.com/apache/druid/pull/20268
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] feat(server): add response identity headers (druid)
Posted by "FrankChen021 (via GitHub)" <gi...@apache.org>.
FrankChen021 commented on code in PR #20268:
URL: https://github.com/apache/druid/pull/20268#discussion_r3940813437
##########
server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java:
##########
@@ -404,6 +404,9 @@ public void lifeCycleStopped(LifeCycle event)
JettyServerInitializer initializer = injector.getInstance(JettyServerInitializer.class);
try {
initializer.initialize(server, injector);
+ if (config.isEnableResponseIdentityHeaders()) {
+ server.setHandler(new ResponseIdentityHeaderHandler(node, server.getHandler()));
Review Comment:
[P2] Cover Jetty-generated errors outside the handler
This wrapper only runs after Jetty has parsed the request and entered the application handler. Jetty creates a temporary ChannelResponse for request-line/header parse failures and uses its own error-response path, which clears response headers without passing through this wrapper. Malformed or oversized requests therefore return without the identity triple even though the docs promise headers on every response, including early errors. Install the identity at the connector/error-response path or add equivalent coverage there.
##########
services/src/main/java/org/apache/druid/server/AsyncQueryForwardingServlet.java:
##########
@@ -634,6 +635,10 @@ protected void onServerResponseHeaders(
if (responseContext != null) {
proxyResponse.setHeader(responseContext.getName(), responseContext.getValue());
}
+ // When response identity headers are enabled, the outer response handler initially adds the Router identity.
+ // An upstream response must replace it with the upstream identity, or with no identity when the upstream does not
+ // provide a complete header triple.
+ ResponseIdentityHeaderHandler.clearRouterIdentity(proxyResponse);
Review Comment:
[P2] Restore Router identity on proxy failure
After upstream headers arrive, this clears the Router triple. If the upstream then fails before the downstream response commits, Jetty's inherited onProxyResponseFailure calls resetBuffer(), which preserves headers, and sendError() to generate the Router's 502/504. The final Router-generated error consequently either carries the upstream identity or, for a partial upstream triple, no identity at all. Restore the Router triple in that failure path; apply the same handling to the management and Coordinator-to-Overlord proxies.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org
Re: [PR] feat(server): add response identity headers (druid)
Posted by "FrankChen021 (via GitHub)" <gi...@apache.org>.
FrankChen021 commented on code in PR #20268:
URL: https://github.com/apache/druid/pull/20268#discussion_r3940723330
##########
server/src/main/java/org/apache/druid/server/initialization/jetty/ResponseIdentityHeaderHandler.java:
##########
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+package org.apache.druid.server.initialization.jetty;
+
+import org.apache.druid.server.DruidNode;
+import org.eclipse.jetty.client.Response;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.rewrite.handler.HeaderPatternRule;
+import org.eclipse.jetty.rewrite.handler.RewriteHandler;
+import org.eclipse.jetty.server.Handler;
+
+import javax.servlet.http.HttpServletResponse;
+
+public class ResponseIdentityHeaderHandler extends RewriteHandler
+{
+ public static final String RESPONSE_SERVER_HEADER = "X-Druid-Server";
+ public static final String RESPONSE_SERVICE_HEADER = "X-Druid-Service";
+ public static final String RESPONSE_VERSION_HEADER = "X-Druid-Version";
+
+ public ResponseIdentityHeaderHandler(final DruidNode selfNode, final Handler handler)
+ {
+ super(handler);
+ addRule(new HeaderPatternRule("*", RESPONSE_SERVER_HEADER, selfNode.getHostAndPortToUse()));
Review Comment:
Fixed in 180c4466c6. ResponseIdentityHeaderHandler now wraps the mutable header collection so servlet-level header clears re-add the complete identity triple, while retaining core Response.reset() handling. ResponseIdentityHeaderHandlerTest exercises both getHeaders().clear() and reset().
##########
server/src/main/java/org/apache/druid/server/initialization/ServerConfig.java:
##########
@@ -206,6 +206,9 @@ public ServerConfig(@NotNull ErrorResponseTransformStrategy errorResponseTransfo
@JsonProperty
private boolean enableHSTS = false;
+ @JsonProperty
+ private boolean enableResponseIdentityHeaders = false;
Review Comment:
Fixed in 180c4466c6. The flag-aware ServerConfig constructor is now used by CliIndexerServerModule.makeAdjustedServerConfig, which copies enableResponseIdentityHeaders; the prior public constructor signature remains available for compatibility. CliIndexerServerModuleTest covers preservation of the enabled flag.
##########
server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java:
##########
@@ -404,6 +404,9 @@ public void lifeCycleStopped(LifeCycle event)
JettyServerInitializer initializer = injector.getInstance(JettyServerInitializer.class);
try {
initializer.initialize(server, injector);
+ if (config.isEnableResponseIdentityHeaders()) {
+ server.setHandler(new ResponseIdentityHeaderHandler(node, server.getHandler()));
Review Comment:
Fixed in 180c4466c6. OverlordProxyServlet now clears the Coordinator identity before upstream response handling and applies the same enabled-and-complete-triple filter as the Router proxies. Unit tests cover complete, partial, and disabled cases, and ResponseIdentityHeaderTest exercises a real separate Coordinator-to-Overlord proxy request.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org