Skip to content

[πŸ› Bug]: WebSocket headers not applied in JdkHttpClient.openSocket – breaks DevTools behind authenticated proxyΒ #15645

Open
@pranaynailwal

Description

@pranaynailwal

Description


Title
WebSocket headers not applied in JdkHttpClient.openSocket – breaks DevTools behind authenticated proxy


Description

Hi Selenium team,

This might be a bug report (though please correct me if there's a better way to approach it!).

🧩 Problem Statement

When using DevTools with a Selenium Grid that sits behind an authentication proxy, the connection to DevTools via WebSocket fails because headers from the original request are not applied in the openSocket method of JdkHttpClient.


πŸ” Our Setup

  • We're running Selenium Grid behind an authentication proxy that supports OIDC + AppToAppAuth.
  • The grid requires an Authorization header for all requests, including WebSocket connections used by DevTools.
  • We’ve successfully handled normal HTTP requests using:
    • A custom org.openqa.selenium.remote.http.Filter to inject authentication headers.
    • A custom HttpClient.Factory provided via -Dwebdriver.http.factory.

πŸ§ͺ Behavior Observed

All standard WebDriver HTTP requests work fine with the injected headers, but WebSocket connections from SeleniumCdpConnection fail.

Upon inspection of the openSocket method in JdkHttpClient, we found:

WebSocket.Builder builder = client.newWebSocketBuilder()
                                  .connectTimeout(connectTimeout);
// no headers are applied here!
builder.buildAsync(uri, listener);

Ref. JdkHttpClient.java#L170

While the HttpRequest passed to openSocket has the proper headers, those headers are not applied to the Java WebSocket builder via .header(String, String).


πŸ› οΈ Impact

This breaks the DevTools usage entirely when running behind an authenticated proxy.


πŸ™ What We're Looking For

  • Could the WebSocket headers from the HttpRequest be applied to the WebSocket.Builder before buildAsync()?
  • Alternatively, is there a recommended workaround to allow injecting headers into WebSocket connections?

Thanks so much for your time and for maintaining Selenium πŸ™Œ
Happy to provide more details or run test cases if needed!


Reproducible Code

mport org.openqa.selenium.remote.http.*;

public class AuthenticatedHttpClient implements HttpClient {

    private final HttpClient delegate;

    public AuthenticatedHttpClient(HttpClient delegate) {
        this.delegate = delegate;
    }

    @Override
    public HttpResponse execute(HttpRequest request) {
        injectHeaders(request);
        return delegate.execute(request);
    }

    @Override
    public WebSocket openSocket(HttpRequest request, WebSocket.Listener listener) {
        injectHeaders(request);
        return delegate.openSocket(request, listener);
    }

    private void injectHeaders(HttpRequest request) {
        if (request.getHeader("Authorization") == null) {
            request.addHeader("Authorization", "Bearer " + SeleniumA3AuthenticationConnection.getA3BearerToken());
            request.addHeader("X-A3-Other-App", SeleniumA3AuthenticationConnection.SELENIUM_AUTOMATION_APPLICATION_ID);
            request.addHeader("X-A3-Context", SeleniumA3AuthenticationConnection.CONTEXT);
        }
    }

    @Override
    public void close() {
        delegate.close();
    }
}


import org.openqa.selenium.remote.http.*;
import org.openqa.selenium.remote.http.HttpClientName;
import org.openqa.selenium.remote.http.jdk.JdkHttpClient;

@HttpClientName("a3-auth-factory")
public class AuthenticatedHttpClientFactory implements HttpClient.Factory {


    private final HttpClient.Factory baseFactory;

    public AuthenticatedHttpClientFactory() {
        this.baseFactory = new JdkHttpClient.Factory();
//        this.baseFactory = new CustomClient.Factory();
    }

    @Override
    public HttpClient createClient(ClientConfig config) {
        return new AuthenticatedHttpClient(baseFactory.createClient(config));
    }

    @Override
    public void cleanupIdleClients() {
        baseFactory.cleanupIdleClients();
    }

}

Debugging Logs

>>> Injecting Headers for Selenium Request to: wss://<grid-ul>/MY-AUTH-GRID/session/29806f3bba4e70387b90ceef9a9b9a90/se/cdp
authorization -> Bearer XXX+XXX=:XXX:1745124943319:1
x-a3-context -> XXX
x-a3-other-app -> XXX
Exception in thread "main" org.openqa.selenium.remote.http.ConnectionFailedException: JdkWebSocket initial request execution error
Build info: version: '4.31.0', revision: '1ef9f18787*'
System info: os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '15.4.1', java.version: '21.0.1'
Driver info: driver.version: unknown
  at org.openqa.selenium.remote.http.jdk.JdkHttpClient.openSocket(JdkHttpClient.java:250)
  at com.org.testapp.selenium.AuthenticatedHttpClient.openSocket(AuthenticatedHttpClient.java:22)
  at org.openqa.selenium.devtools.Connection.<init>(Connection.java:89)
  at org.openqa.selenium.devtools.SeleniumCdpConnection.<init>(SeleniumCdpConnection.java:36)
  at org.openqa.selenium.devtools.SeleniumCdpConnection.lambda$create$2(SeleniumCdpConnection.java:103)
  at java.base/java.util.Optional.map(Optional.java:260)
  at org.openqa.selenium.devtools.SeleniumCdpConnection.create(SeleniumCdpConnection.java:103)
  at org.openqa.selenium.devtools.SeleniumCdpConnection.create(SeleniumCdpConnection.java:49)
  at org.openqa.selenium.devtools.DevToolsProvider.getImplementation(DevToolsProvider.java:50)
  at org.openqa.selenium.devtools.DevToolsProvider.getImplementation(DevToolsProvider.java:29)
  at org.openqa.selenium.remote.Augmenter.augment(Augmenter.java:207)
  at org.openqa.selenium.remote.Augmenter.augment(Augmenter.java:178)
  at com.org.testapp.Playground.useDevTools(Playground.java:31)
  at com.org.testapp.Playground.main(Playground.java:26)
Caused by: java.net.http.WebSocketHandshakeException
  at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:224)
  at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150)
  at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
  at java.base/java.util.concurrent.CompletableFuture.postFire(CompletableFuture.java:614)
  at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:844)
  at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483)
  at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
  at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
  at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
  at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
  at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
Caused by: jdk.internal.net.http.websocket.CheckFailedException: Unexpected HTTP response status code 302
  at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.checkFailed(OpeningHandshake.java:341)
  at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.handleResponse(OpeningHandshake.java:250)
  at java.net.http/jdk.internal.net.http.websocket.OpeningHandshake.resultFrom(OpeningHandshake.java:220)
  ... 10 more

What version of Selenium are you currently using?

4.25.0 and 4.31

The following statements are true

  • This applies to the most recent version of Selenium (we can't fix old versions)
  • This hasn't already been reported (I searched and didn't find it)
  • All information necessary to reproduce the issue has been provided above

Did this work for you before?

Not sure, this is my first time trying it

If yes, what version of Selenium did it work with?

No response

Operating System

--

Selenium Language Binding

Java

Which browsers are you experiencing the issue with?

No response

Are you using Selenium Grid?

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    B-gridEverything grid and server relatedC-javaJava BindingsI-defectSomething is not working as intendedJ-awaiting answerQuestion asked of user; a reply moves it to triage again

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions