Skip to content

feat: support SOCKS proxy support for HTTP client#2635

Merged
ThisaruGuruge merged 9 commits into
masterfrom
feat/socks-proxy-support
Jul 8, 2026
Merged

feat: support SOCKS proxy support for HTTP client#2635
ThisaruGuruge merged 9 commits into
masterfrom
feat/socks-proxy-support

Conversation

@ThisaruGuruge

@ThisaruGuruge ThisaruGuruge commented Jun 23, 2026

Copy link
Copy Markdown
Member

Purpose

$Subject

Fixes: ballerina-platform/ballerina-library#8810

Examples

Checklist

  • Linked to an issue
  • Updated the changelog
  • Added tests
  • Updated the spec
  • Checked native-image compatibility
  • Checked the impact on OpenAPI generation

Added native SOCKS proxy support to the Ballerina HTTP client. This introduces new proxy configuration APIs (ProxyProtocol and an extended ProxyConfig with protocol selection) and updates the HTTP transport implementation to correctly route requests for HTTP, SOCKS4, and SOCKS5 proxies, including handling of proxy credentials (SOCKS4 password settings are ignored with a warning). The change also updates the HTTP client spec and documentation (including a new SOCKS proxy example) and adds/extends automated coverage using embedded and Docker-based SOCKS proxy servers, covering both end-to-end request behavior and SOCKS-based connection pooling. Supporting test/build infrastructure was updated to manage SOCKS proxy lifecycles and align dependency/version pins, along with an “Unreleased” changelog entry for the feature.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9863ae55-4366-4df1-b6dd-4b99001ae580

📥 Commits

Reviewing files that changed from the base of the PR and between 7041201 and a18309b.

📒 Files selected for processing (9)
  • ballerina-tests/http-advanced-tests/Dependencies.toml
  • ballerina-tests/http-client-tests/Dependencies.toml
  • ballerina-tests/http-dispatching-tests/Dependencies.toml
  • ballerina-tests/http-interceptor-tests/Dependencies.toml
  • ballerina-tests/http-misc-tests/Dependencies.toml
  • ballerina-tests/http-misc-tests/tests/socks_proxy_enabled_client_test.bal
  • ballerina-tests/http-test-common/Dependencies.toml
  • ballerina-tests/http2-tests/Dependencies.toml
  • ballerina/Dependencies.toml
✅ Files skipped from review due to trivial changes (8)
  • ballerina-tests/http2-tests/Dependencies.toml
  • ballerina/Dependencies.toml
  • ballerina-tests/http-dispatching-tests/Dependencies.toml
  • ballerina-tests/http-interceptor-tests/Dependencies.toml
  • ballerina-tests/http-advanced-tests/Dependencies.toml
  • ballerina-tests/http-test-common/Dependencies.toml
  • ballerina-tests/http-misc-tests/Dependencies.toml
  • ballerina-tests/http-client-tests/Dependencies.toml

📝 Walkthrough

Walkthrough

This PR adds SOCKS4/SOCKS5 proxy support to the HTTP client, including public config changes, native proxy routing, embedded and Docker-based tests, documentation/examples, and supporting dependency updates. It also changes one resiliency test to use a local unreachable port.

Changes

SOCKS Proxy Support

Layer / File(s) Summary
Public API and proxy config
ballerina/http_client_config.bal, native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java, native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java, native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ProxyServerConfiguration.java
Adds ProxyProtocol, a protocol field on ProxyConfig, proxy protocol constants, and runtime protocol resolution and storage.
Native proxy transport
native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/DefaultHttpClientConnector.java, native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java, native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/channel/pool/PoolableTargetChannelFactory.java, native/src/main/java/module-info.java, native/build.gradle
Adds protocol-aware proxy routing for HTTP, SOCKS4, and SOCKS5, plus Netty resolver/codec wiring.
Native SOCKS proxy tests
native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/*, native/src/test/java/io/ballerina/stdlib/http/transport/contract/config/ProxyServerConfigurationTest.java, native/src/test/resources/testng.xml
Adds embedded SOCKS server support, proxy-setup helpers, SOCKS4/SOCKS5 and pooling tests, and suite registration.
Ballerina and integration SOCKS tests
ballerina-tests/build.gradle, ballerina-tests/resources/socks/*, ballerina-tests/http-misc-tests/tests/socks_proxy_enabled_client_test.bal, ballerina-tests/http-misc-tests/tests/test_service_ports.bal, integration-tests/build.gradle, integration-tests/tests/resources/socks/*, integration-tests/tests/socks_proxy_test.bal, integration-tests/Ballerina.toml
Adds SOCKS proxy lifecycle tasks, Docker compose/config files, and Ballerina tests for SOCKS5 success and SOCKS4 password handling.
Docs and example
ballerina/README.md, docs/spec/spec.md, changelog.md, examples/socks-proxy/*
Adds proxy documentation and a runnable SOCKS5 example project.
Cookie store constant rename
ballerina/cookie_cookieStore.bal
Renames internal HTTP/HTTPS protocol constants and updates cookie-store checks.

Dependency Bumps and Test Stability

Layer / File(s) Summary
Root dependency updates
ballerina/Ballerina.toml, ballerina/Dependencies.toml, gradle.properties
Updates mime-native, io, mime, task, and url versions.
Test module dependency updates
ballerina-tests/*/Dependencies.toml, integration-tests/Dependencies.toml, native/build.gradle
Updates auth, crypto, file, http, io, mime, task, time, and url versions across test modules, plus the Netty SOCKS codec test dependency.
Resiliency test stability fix
ballerina-tests/http-resiliency-tests/tests/resiliency_http_circuit_breaker_without_status_codes_test.bal, ballerina-tests/http-resiliency-tests/tests/test_service_ports.bal
Replaces an external unreachable domain with a local unreachable port.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HttpUtil
  participant HttpClientChannelInitializer
  participant PoolableTargetChannelFactory
  participant Proxy
  participant Backend

  Client->>HttpUtil: proxy settings
  HttpUtil->>HttpUtil: resolve ProxyProtocol
  HttpUtil->>HttpClientChannelInitializer: configure proxy pipeline
  HttpClientChannelInitializer->>HttpClientChannelInitializer: add SOCKS4/SOCKS5/HTTP handler
  PoolableTargetChannelFactory->>Proxy: connect using proxy host/port or unresolved target
  Proxy->>Backend: relay request
  Backend-->>Proxy: response
  Proxy-->>Client: response
Loading

Suggested reviewers: MohamedSabthar, shafreenAnfar, lnash94

Poem

A rabbit hopped through proxy streams so neat,
With SOCKS4, SOCKS5, and a little springy beat. 🐇
The docs stood tall, the tests ran free,
The backend bounced back happily.
Hop, hop—through tunnels wide and true,
This bundle of changes just zooms on through.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The template sections are present, but Purpose is a placeholder and Examples are empty, so the description is largely incomplete. Replace $Subject with a real purpose summary and add concrete examples or state why none are needed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the main change: adding SOCKS proxy support for the HTTP client.
Linked Issues check ✅ Passed The changes implement the requested ProxyProtocol/ProxyConfig support and add native SOCKS4/SOCKS5 client handling and tests.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes stand out; the added docs, tests, build wiring, and version bumps support the SOCKS proxy feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/socks-proxy-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ThisaruGuruge ThisaruGuruge changed the title fix: update invalid link with a local URL feat: support SOCKS proxy support for HTTP client Jun 25, 2026
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.82192% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.43%. Comparing base (8d364d4) to head (a18309b).

Files with missing lines Patch % Lines
...tractimpl/sender/HttpClientChannelInitializer.java 69.69% 7 Missing and 3 partials ⚠️
...in/java/io/ballerina/stdlib/http/api/HttpUtil.java 78.57% 0 Missing and 3 partials ⚠️
...der/channel/pool/PoolableTargetChannelFactory.java 90.00% 0 Missing and 1 partial ⚠️

❌ Your project check has failed because the head coverage (79.43%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2635      +/-   ##
============================================
+ Coverage     79.22%   79.43%   +0.21%     
- Complexity        0      915     +915     
============================================
  Files           376      441      +65     
  Lines         21636    24610    +2974     
  Branches       3382     3939     +557     
============================================
+ Hits          17142    19550    +2408     
- Misses         3546     3858     +312     
- Partials        948     1202     +254     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ThisaruGuruge ThisaruGuruge force-pushed the feat/socks-proxy-support branch from f709571 to dfa395c Compare July 2, 2026 04:28
@ThisaruGuruge ThisaruGuruge changed the base branch from socks-support to master July 3, 2026 07:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (9)
ballerina-tests/resources/socks/sockd.conf (1)

8-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale comment: backend is a compose service, not reached via host.docker.internal.

The header states the proxy "relays to the backend Ballerina services running on the host (reached via host.docker.internal — see compose.yml)", but compose.yml's backend is an in-compose http-echo container reached by SOCKS5 remote DNS resolving the backend service name (confirmed by the test comment in socks_proxy_enabled_client_test.bal lines 20-26 and the SOCKS_BACKEND_URL = "http://backend:5678" target). This description is misleading for future maintainers.

Proposed fix
-# Listens on the published proxy port and relays to the backend Ballerina services running on
-# the host (reached via host.docker.internal — see compose.yml).
+# Listens on the published proxy port and relays to the `backend` compose service via SOCKS5
+# remote DNS resolution on the proxy side (see compose.yml).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ballerina-tests/resources/socks/sockd.conf` around lines 8 - 9, The header
comment in sockd.conf is outdated and misdescribes how the SOCKS proxy reaches
the backend. Update the comment near the top of the file to match the actual
setup used by the SOCKS tests, where the backend is the in-compose service
reached via the backend service name and remote DNS, not host.docker.internal.
Keep the wording aligned with the behavior reflected in
socks_proxy_enabled_client_test.bal and the SOCKS_BACKEND_URL target.
ballerina-tests/build.gradle (1)

303-351: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a shared Docker helper to avoid duplicating the LDAP task pattern.

startSocksServer/stopSocksServer duplicate the exact structure of startLdapServer/stopLdapServer (container name check, docker ps --filter, compose up/down, sleep). A parameterized helper (container name, compose file path, wait times) would reduce this duplication now that there are two near-identical Docker lifecycle task pairs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ballerina-tests/build.gradle` around lines 303 - 351, The startSocksServer
and stopSocksServer tasks duplicate the same Docker lifecycle flow already used
by the LDAP tasks, so factor the shared logic into a reusable helper instead of
repeating the container check, compose up/down, and sleep steps. Use a
parameterized method or task helper that takes the container name, compose file,
and wait duration, then have startSocksServer/stopSocksServer delegate to it
alongside startLdapServer/stopLdapServer to keep the build script consistent and
easier to maintain.
integration-tests/tests/resources/socks/sockd.conf (1)

16-24: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Overly permissive ACL creates an open relay while the container runs.

Both client pass and socks pass allow unauthenticated relaying to: 0.0.0.0/0, not just to the backend service the comment describes. Combined with the published host port (9913:9913 in compose.yml), this is an open, unauthenticated SOCKS relay for the container's lifetime — a concern on shared/self-hosted CI runners.

🔒 Suggested scoped ACL
 socks pass {
-    from: 0.0.0.0/0 to: 0.0.0.0/0
+    from: 0.0.0.0/0 to: backend
     log: error connect disconnect
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integration-tests/tests/resources/socks/sockd.conf` around lines 16 - 24, The
ACL in sockd.conf is too broad and effectively exposes the SOCKS service as an
open relay when the container is running. Tighten both client pass and socks
pass rules so they only allow traffic to the backend service referenced by the
test setup, using the existing ACL blocks rather than 0.0.0.0/0. Keep the change
scoped to the sockd.conf rules so the SOCKS relay is not publicly reachable
through the published port.
integration-tests/tests/resources/socks/compose.yml (1)

5-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin Docker image tags for reproducibility.

hashicorp/http-echo and vimagick/dante are unpinned (defaulting to latest), so CI runs aren't reproducible if upstream images change.

♻️ Suggested version pinning
     backend:
-        image: hashicorp/http-echo
+        image: hashicorp/http-echo:0.2.3
         container_name: socks-it-backend-server
         command: ["-text=Response from backend through SOCKS proxy", "-listen=:5678"]

     socks_proxy:
-        image: vimagick/dante
+        image: vimagick/dante:latest-stable-tag
         container_name: socks-it-proxy-server
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integration-tests/tests/resources/socks/compose.yml` around lines 5 - 11, The
Docker Compose services for the SOCKS integration test are using unpinned image
references, which makes CI behavior non-reproducible. Update the image fields
for the backend and proxy services in the compose definition to use fixed
version tags (or immutable digests) instead of the implicit latest default. Keep
the existing service names and configuration in compose.yml, and only adjust the
image references for hashicorp/http-echo and vimagick/dante.
integration-tests/build.gradle (1)

127-163: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Fixed sleep + unowned teardown can be flaky under concurrent runs.

startSocksServer waits a hardcoded 10s instead of polling for the SOCKS port to be ready, and if the container was already running (skip-start branch), stopSocksServer still unconditionally runs docker compose down — tearing down a container this build didn't start. Combined with the fixed container name socks-it-proxy-server (no per-build/job suffix), concurrent CI runs on a shared docker host could interfere with each other's tests.

♻️ Suggested readiness check instead of fixed sleep
-                println "Waiting 10s until the SOCKS proxy server gets initiated."
-                sleep(10 * 1000)
+                println "Waiting until the SOCKS proxy server is ready to accept connections."
+                def ready = false
+                for (int i = 0; i < 30 && !ready; i++) {
+                    def probe = exec {
+                        commandLine 'sh', '-c', "nc -z localhost 9913"
+                        ignoreExitValue = true
+                    }
+                    ready = probe.exitValue == 0
+                    if (!ready) sleep(1000)
+                }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integration-tests/build.gradle` around lines 127 - 163, The SOCKS server
lifecycle in startSocksServer and stopSocksServer is flaky under concurrent runs
because it uses a fixed 10s sleep, a shared container name, and unconditional
teardown even when this build did not start the container. Replace the hardcoded
wait with a readiness poll that checks the SOCKS port/container until it is
actually available, and make stopSocksServer only tear down containers owned by
the current build or job. Also scope the Docker Compose service/container naming
used by the socks-it-proxy-server setup so parallel CI runs do not collide.
native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java (1)

184-225: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce cognitive complexity of configureProxyServer to satisfy the failing SonarCloud gate.

The nested per-protocol credential branches push complexity to 17 (limit 15). Extracting each protocol's handler creation into small private helpers (e.g. addSocks5Handler, addSocks4Handler, addHttpProxyHandler) keeps the switch flat and clears the gate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java`
around lines 184 - 225, The configureProxyServer method is too complex because
it mixes protocol selection with nested credential checks, pushing SonarCloud
over the limit. Refactor HttpClientChannelInitializer by extracting the SOCKS5,
SOCKS4, and HTTP proxy handler creation into small private helper methods such
as addSocks5Handler, addSocks4Handler, and addHttpProxyHandler, then keep
configureProxyServer focused on the switch and pipeline delegation only.
Preserve the existing username/password and sslConfig behavior while moving the
branching logic into those helpers.

Source: Linters/SAST tools

native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks4ProxyServerTestCase.java (1)

55-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a negative test for SOCKS4 userId mismatch.

Only the matching-userId and no-auth paths are tested. EmbeddedSocksServer implements an IDENTD_AUTH_FAILURE rejection when the userId doesn't match, but that path isn't exercised here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks4ProxyServerTestCase.java`
around lines 55 - 88, Add a negative test in Socks4ProxyServerTestCase for the
SOCKS4 userId mismatch path. Use the existing runScenario helper and
EmbeddedSocksServer behavior to configure a proxy userId that does not match the
client-sent value, then assert the connection/request fails with the
IDENTD_AUTH_FAILURE rejection. Keep the new test alongside the existing
testSocks4ProxyOverHttpWithUserId and related methods so the mismatch case is
covered.
native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/EmbeddedSocksServer.java (1)

161-176: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Guard against null expectedPassword before comparing.

expectedPassword.equals(msg.password()) NPEs if a server is constructed with a non-null username but a null password. authRequired() only checks expectedUsername != null. Not exercised today (tests always pass both or neither), but worth defending against future misuse of this test double.

🛡️ Suggested guard
-            boolean ok = expectedUsername.equals(msg.username()) && expectedPassword.equals(msg.password());
+            boolean ok = expectedUsername.equals(msg.username())
+                    && java.util.Objects.equals(expectedPassword, msg.password());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/EmbeddedSocksServer.java`
around lines 161 - 176, The null check in Socks5PasswordAuthHandler.channelRead0
only guards expectedUsername, so comparing expectedPassword can still throw if
the server is created with a null password. Update the auth validation in
EmbeddedSocksServer’s Socks5PasswordAuthHandler to safely handle a null
expectedPassword before calling equals on it, using the existing
expectedUsername/expectedPassword fields to keep the check consistent with
authRequired().
native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks5ProxyServerTestCase.java (1)

56-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a negative test for SOCKS5 auth failure (wrong credentials).

Only the successful auth and no-auth paths are covered. The embedded server's FAILURE rejection path for mismatched username/password is never exercised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks5ProxyServerTestCase.java`
around lines 56 - 90, The SOCKS5 proxy tests in Socks5ProxyServerTestCase only
cover successful and no-auth cases, so add a negative test that exercises the
embedded server’s FAILURE path for invalid credentials. Create a new test method
alongside the existing
testSocks5ProxyOverHttpWithAuth/testSocks5ProxyOverHttpsWithAuth cases that
calls runScenario with incorrect username/password values and asserts the
connection fails as expected, so the auth rejection branch is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ballerina/http_client_config.bal`:
- Around line 145-157: The ProxyConfig documentation currently says SOCKS4
passwords are ignored, but HttpUtil.getProxyProtocol rejects any non-empty
SOCKS4 password. Update the password field doc in ProxyConfig to state that
SOCKS4 does not support password authentication and non-empty passwords are
unsupported/rejected, so callers understand the actual contract.

---

Nitpick comments:
In `@ballerina-tests/build.gradle`:
- Around line 303-351: The startSocksServer and stopSocksServer tasks duplicate
the same Docker lifecycle flow already used by the LDAP tasks, so factor the
shared logic into a reusable helper instead of repeating the container check,
compose up/down, and sleep steps. Use a parameterized method or task helper that
takes the container name, compose file, and wait duration, then have
startSocksServer/stopSocksServer delegate to it alongside
startLdapServer/stopLdapServer to keep the build script consistent and easier to
maintain.

In `@ballerina-tests/resources/socks/sockd.conf`:
- Around line 8-9: The header comment in sockd.conf is outdated and misdescribes
how the SOCKS proxy reaches the backend. Update the comment near the top of the
file to match the actual setup used by the SOCKS tests, where the backend is the
in-compose service reached via the backend service name and remote DNS, not
host.docker.internal. Keep the wording aligned with the behavior reflected in
socks_proxy_enabled_client_test.bal and the SOCKS_BACKEND_URL target.

In `@integration-tests/build.gradle`:
- Around line 127-163: The SOCKS server lifecycle in startSocksServer and
stopSocksServer is flaky under concurrent runs because it uses a fixed 10s
sleep, a shared container name, and unconditional teardown even when this build
did not start the container. Replace the hardcoded wait with a readiness poll
that checks the SOCKS port/container until it is actually available, and make
stopSocksServer only tear down containers owned by the current build or job.
Also scope the Docker Compose service/container naming used by the
socks-it-proxy-server setup so parallel CI runs do not collide.

In `@integration-tests/tests/resources/socks/compose.yml`:
- Around line 5-11: The Docker Compose services for the SOCKS integration test
are using unpinned image references, which makes CI behavior non-reproducible.
Update the image fields for the backend and proxy services in the compose
definition to use fixed version tags (or immutable digests) instead of the
implicit latest default. Keep the existing service names and configuration in
compose.yml, and only adjust the image references for hashicorp/http-echo and
vimagick/dante.

In `@integration-tests/tests/resources/socks/sockd.conf`:
- Around line 16-24: The ACL in sockd.conf is too broad and effectively exposes
the SOCKS service as an open relay when the container is running. Tighten both
client pass and socks pass rules so they only allow traffic to the backend
service referenced by the test setup, using the existing ACL blocks rather than
0.0.0.0/0. Keep the change scoped to the sockd.conf rules so the SOCKS relay is
not publicly reachable through the published port.

In
`@native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java`:
- Around line 184-225: The configureProxyServer method is too complex because it
mixes protocol selection with nested credential checks, pushing SonarCloud over
the limit. Refactor HttpClientChannelInitializer by extracting the SOCKS5,
SOCKS4, and HTTP proxy handler creation into small private helper methods such
as addSocks5Handler, addSocks4Handler, and addHttpProxyHandler, then keep
configureProxyServer focused on the switch and pipeline delegation only.
Preserve the existing username/password and sslConfig behavior while moving the
branching logic into those helpers.

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/EmbeddedSocksServer.java`:
- Around line 161-176: The null check in Socks5PasswordAuthHandler.channelRead0
only guards expectedUsername, so comparing expectedPassword can still throw if
the server is created with a null password. Update the auth validation in
EmbeddedSocksServer’s Socks5PasswordAuthHandler to safely handle a null
expectedPassword before calling equals on it, using the existing
expectedUsername/expectedPassword fields to keep the check consistent with
authRequired().

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks4ProxyServerTestCase.java`:
- Around line 55-88: Add a negative test in Socks4ProxyServerTestCase for the
SOCKS4 userId mismatch path. Use the existing runScenario helper and
EmbeddedSocksServer behavior to configure a proxy userId that does not match the
client-sent value, then assert the connection/request fails with the
IDENTD_AUTH_FAILURE rejection. Keep the new test alongside the existing
testSocks4ProxyOverHttpWithUserId and related methods so the mismatch case is
covered.

In
`@native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks5ProxyServerTestCase.java`:
- Around line 56-90: The SOCKS5 proxy tests in Socks5ProxyServerTestCase only
cover successful and no-auth cases, so add a negative test that exercises the
embedded server’s FAILURE path for invalid credentials. Create a new test method
alongside the existing
testSocks5ProxyOverHttpWithAuth/testSocks5ProxyOverHttpsWithAuth cases that
calls runScenario with incorrect username/password values and asserts the
connection fails as expected, so the auth rejection branch is covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d39a694-382b-4f28-8713-2882256731ae

📥 Commits

Reviewing files that changed from the base of the PR and between 8d364d4 and 55b835a.

📒 Files selected for processing (49)
  • ballerina-tests/build.gradle
  • ballerina-tests/http-advanced-tests/Dependencies.toml
  • ballerina-tests/http-client-tests/Dependencies.toml
  • ballerina-tests/http-dispatching-tests/Dependencies.toml
  • ballerina-tests/http-interceptor-tests/Dependencies.toml
  • ballerina-tests/http-misc-tests/Dependencies.toml
  • ballerina-tests/http-misc-tests/tests/socks_proxy_enabled_client_test.bal
  • ballerina-tests/http-misc-tests/tests/test_service_ports.bal
  • ballerina-tests/http-resiliency-tests/Dependencies.toml
  • ballerina-tests/http-resiliency-tests/tests/resiliency_http_circuit_breaker_without_status_codes_test.bal
  • ballerina-tests/http-resiliency-tests/tests/test_service_ports.bal
  • ballerina-tests/http-security-tests/Dependencies.toml
  • ballerina-tests/http-service-tests/Dependencies.toml
  • ballerina-tests/http-test-common/Dependencies.toml
  • ballerina-tests/http2-tests/Dependencies.toml
  • ballerina-tests/resources/socks/compose.yml
  • ballerina-tests/resources/socks/sockd.conf
  • ballerina/Ballerina.toml
  • ballerina/Dependencies.toml
  • ballerina/README.md
  • ballerina/cookie_cookieStore.bal
  • ballerina/http_client_config.bal
  • changelog.md
  • docs/spec/spec.md
  • examples/socks-proxy/Ballerina.toml
  • examples/socks-proxy/README.md
  • examples/socks-proxy/main.bal
  • gradle.properties
  • integration-tests/Ballerina.toml
  • integration-tests/Dependencies.toml
  • integration-tests/build.gradle
  • integration-tests/tests/resources/socks/compose.yml
  • integration-tests/tests/resources/socks/sockd.conf
  • integration-tests/tests/socks_proxy_test.bal
  • native/build.gradle
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contract/config/ProxyServerConfiguration.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/DefaultHttpClientConnector.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/channel/pool/PoolableTargetChannelFactory.java
  • native/src/main/java/module-info.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/contract/config/ProxyServerConfigurationTest.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/EmbeddedSocksServer.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/ProxyServerUtil.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks4ProxyServerTestCase.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/Socks5ProxyServerTestCase.java
  • native/src/test/java/io/ballerina/stdlib/http/transport/proxyserver/SocksConnectionPoolTestCase.java
  • native/src/test/resources/testng.xml

Comment thread ballerina/http_client_config.bal
The Dante SOCKS proxy container is not started on Windows (host
networking is unsupported there), so tag the tests that depend on it
with the disabledOnWindows group and pass --disable-groups
disabledOnWindows to the integration-tests run on Windows.
Comment thread examples/socks-proxy/README.md Outdated
Comment thread examples/socks-proxy/README.md
Comment thread ballerina-tests/resources/socks/sockd.conf Outdated
Comment thread ballerina-tests/build.gradle Outdated
- Refactor configureProxyServer into private helpers to reduce cognitive
  complexity flagged by SonarCloud
- Change SOCKS4 password handling from error to warn-and-ignore, since
  SOCKS4 simply does not support passwords rather than it being an
  invalid configuration
- Update spec.md and example README to reflect warn-and-ignore behaviour
- Fix inaccurate sockd.conf comment (backend is an in-compose service,
  not a host service reached via host.docker.internal)
- Fix inaccurate build.gradle comment on the Windows guard (Docker
  unavailability, not host networking limitation)
- Remove hard line-wrapping from example README

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java (1)

1294-1345: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Password is still stored for SOCKS4 despite the "warn-and-ignore" intent.

getProxyProtocol logs a warning that the SOCKS4 password will be ignored, but populateSenderConfigurations unconditionally calls setProxyPassword(proxyPassword) afterward whenever the field is non-empty (irrespective of the resolved protocol). The password is therefore still stored on ProxyServerConfiguration; "ignoring" it relies entirely on downstream native transport code (not in this file) never reading it for SOCKS4. Consider explicitly skipping setProxyPassword when the protocol is SOCKS4, so the intent is enforced at this layer rather than depending on an assumption about consumer code.

♻️ Proposed fix
             if (!proxyUserName.isEmpty()) {
                 proxyServerConfiguration.setProxyUsername(proxyUserName);
             }
-            if (!proxyPassword.isEmpty()) {
+            if (!proxyPassword.isEmpty()
+                    && proxyProtocolEnum != ProxyServerConfiguration.ProxyProtocol.SOCKS4) {
                 proxyServerConfiguration.setProxyPassword(proxyPassword);
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java` around lines
1294 - 1345, The SOCKS4 “warn-and-ignore” handling is incomplete in
populateSenderConfigurations/getProxyProtocol because setProxyPassword is still
called for any non-empty password, so the password remains stored on
ProxyServerConfiguration. Update populateSenderConfigurations to only call
setProxyPassword when the resolved protocol from getProxyProtocol is not SOCKS4,
and keep the existing warning in getProxyProtocol for SOCKS4 passwords.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java`:
- Around line 1294-1345: The SOCKS4 “warn-and-ignore” handling is incomplete in
populateSenderConfigurations/getProxyProtocol because setProxyPassword is still
called for any non-empty password, so the password remains stored on
ProxyServerConfiguration. Update populateSenderConfigurations to only call
setProxyPassword when the resolved protocol from getProxyProtocol is not SOCKS4,
and keep the existing warning in getProxyProtocol for SOCKS4 passwords.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 809a7332-65ad-4ae7-a9e9-29caf5ce87c9

📥 Commits

Reviewing files that changed from the base of the PR and between f79991c and 7041201.

📒 Files selected for processing (7)
  • ballerina-tests/build.gradle
  • ballerina-tests/http-dispatching-tests/tests/service_dispatching_default_data_binding.bal
  • ballerina-tests/resources/socks/sockd.conf
  • docs/spec/spec.md
  • examples/socks-proxy/README.md
  • native/src/main/java/io/ballerina/stdlib/http/api/HttpUtil.java
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java
✅ Files skipped from review due to trivial changes (3)
  • ballerina-tests/resources/socks/sockd.conf
  • examples/socks-proxy/README.md
  • docs/spec/spec.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • ballerina-tests/build.gradle
  • native/src/main/java/io/ballerina/stdlib/http/transport/contractimpl/sender/HttpClientChannelInitializer.java

The test previously asserted that client creation fails when a password
is set on a SOCKS4 proxy config. Since the behaviour was changed to
log a warning and ignore the password instead of returning an error,
update the test to verify that client creation succeeds.
@sonarqubecloud

sonarqubecloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

@TharmiganK TharmiganK left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@ThisaruGuruge ThisaruGuruge merged commit 8619141 into master Jul 8, 2026
8 of 9 checks passed
@ThisaruGuruge ThisaruGuruge deleted the feat/socks-proxy-support branch July 8, 2026 05:46
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.

Add support for SOCKS protocol based proxies in the HTTP client

2 participants