Skip to content

feat: share OkHttp URL construction with core - #999

Closed
dpiet-oai wants to merge 1 commit into
mainfrom
dp/http-request-url-shared-builder
Closed

dpiet-oai wants to merge 1 commit into
mainfrom
dp/http-request-url-shared-builder

Conversation

@dpiet-oai

Copy link
Copy Markdown
Contributor

Summary

Alternative to draft PR #997 for issue #886. Starting from current main, this moves the default transport's OkHttp URL construction into HttpRequest.url() and has OkHttp use that method. Core now declares the existing OkHttp 4.12.0 dependency (and its Okio transitives). No HttpClient interface or wrapper changes are required.

This fixes the reported log mismatch and changes the public HttpRequest.url() output. It deliberately goes beyond #886's narrower suggestion: query spaces change from + to %20 too. Compare this tradeoff with #997, which keeps core independent of OkHttp and logs each transport's prepared URL, but leaves HttpRequest.url() unchanged and requires observer support from custom clients and wrappers.

Representative behavior with the default client

The initial native request target is unchanged; the public URL and SDK log now agree with the default transport. “Target” means HTTP path and query, excluding the fragment.

Input (https://example.com unless shown) Before: public URL / log This PR: public URL / log Sent target, both versions
Base /v1, path a b https://example.com/v1/a+b https://example.com/v1/a%20b /v1/a%20b
Base /v1, path a+b https://example.com/v1/a%2Bb https://example.com/v1/a+b /v1/a+b
Base /v1, query a b=c d https://example.com/v1?a+b=c+d https://example.com/v1?a%20b=c%20d /v1?a%20b=c%20d
Base /v1, paths a, .., b https://example.com/v1/a/../b https://example.com/v1/b /v1/b
Base /v1?x=1, path users https://example.com/v1?x=1/users https://example.com/v1/users?x=1 /v1/users?x=1
Base /v1#section, path users https://example.com/v1#section/users https://example.com/v1/users#section /v1/users
Bare https://example.com https://example.com https://example.com/ /
Base HTTPS://EXAMPLE.COM:443/v1 unchanged casing/port https://example.com/v1 /v1
Base https://example.com:65536/v1 url() returns that text; log prints it, then native construction rejects url() throws IllegalArgumentException; logging fails before dispatch No request

For newly added path segments, the full measured character-level difference set is space (+ versus %20) plus !, $, &, ', (, ), +, ,, :, ;, =, @, [, ], ~ (core previously escaped these 15; OkHttp leaves them literal). For newly added query names/values, only space differs; a literal query + is %2B in both. Existing base text has additional normalization of raw spaces, Unicode, controls, userinfo, host/IP/port, dot segments, and query/fragment placement. These are observable URL and validation changes, not only alternate spellings.

Custom clients and compatibility

A custom HttpClient still implements the same methods; no observer forwarding is needed. A core-only consumer gains OkHttp and Okio runtime dependencies. HttpRequest.url() can now throw on a base the custom transport would accept. Because SDK logging calls url() before dispatch, enabled INFO/ERROR/DEBUG logging can block that transport, while OFF logging still dispatches. A focused test uses custom://logical-endpoint to show both paths. If a custom client rewrites the destination internally, the SDK log still shows the shared core URL, which may differ from the actual destination. Later interceptors and redirects can also change it.

Review release classification and migration impact for callers comparing, storing, or signing public URL strings, and for core-only and custom-transport users. The change should not be described as universally backward compatible. Please include security-focused review of URL/path validation and transport behavior.

Verification

  • The measured 147-case fixture tests HttpRequest.url() directly: 117 accepted URLs and 30 rejected bases; all pass. It covers the full added-character set, dot segments, existing queries/fragments, base encoding and controls, normalization, and rejection. Existing URL tests and local sync/async WireMock checks compare the public URL, log, and sent target.
  • Core and OkHttp suites and Kotlin lint pass offline with SKIP_MOCK_TESTS=true (external OpenAPI mock-server cases skipped); the example Java compilation passes. The isolated core runtime probe passes on JDK 21 and the core runtime classpath contains OkHttp 4.12.0 and Okio 3.6.0. JDK 8 runtime execution was not checked locally because that toolchain is unavailable.
  • The trusted generated-code budget passes: 2,157 / 3,000 lines, unchanged from main. No budget or generation metadata changed.

@github-actions

Copy link
Copy Markdown
Contributor

Castiron custom code

✅ No new custom-code files detected.

70 mixed files remain; 0 existing customizations changed.

Compared 6e773e774e8ecced0727e616. Generated baselines verified.

70 existing customizations unchanged
  • openai-java-core/src/main/kotlin/com/openai/models/audio/AudioResponseFormat.kt
  • openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionCreateParams.kt
  • openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionMessageFunctionToolCall.kt
  • openai-java-core/src/main/kotlin/com/openai/models/chat/completions/ChatCompletionToolMessageParam.kt
  • openai-java-core/src/main/kotlin/com/openai/models/embeddings/Embedding.kt
  • openai-java-core/src/main/kotlin/com/openai/models/embeddings/EmbeddingCreateParams.kt
  • openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseCreateParams.kt
  • openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseFunctionToolCall.kt
  • openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseFunctionWebSearch.kt
  • openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseInputItem.kt
  • openai-java-core/src/main/kotlin/com/openai/models/responses/ResponseTextConfig.kt
  • openai-java-core/src/main/kotlin/com/openai/models/videos/Video.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/BetaServiceAsync.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/BetaServiceAsyncImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/ResponseServiceAsync.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/ResponseServiceAsyncImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/WebhookServiceAsync.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/WebhookServiceAsyncImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/audio/TranscriptionServiceAsyncImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/beta/agents/SessionServiceAsync.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/beta/agents/SessionServiceAsyncImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/chat/ChatCompletionServiceAsync.kt
  • openai-java-core/src/main/kotlin/com/openai/services/async/finetuning/checkpoints/PermissionServiceAsyncImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/BetaService.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/BetaServiceImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/ResponseService.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/ResponseServiceImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/WebhookService.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/WebhookServiceImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/audio/TranscriptionServiceImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/agents/SessionService.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/beta/agents/SessionServiceImpl.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/chat/ChatCompletionService.kt
  • openai-java-core/src/main/kotlin/com/openai/services/blocking/finetuning/checkpoints/PermissionServiceImpl.kt
  • openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchTest.kt
  • openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventTest.kt
  • openai-java-core/src/test/kotlin/com/openai/models/live/ClientEventTest.kt
  • openai-java-core/src/test/kotlin/com/openai/models/live/ServerEventTest.kt
  • openai-java-core/src/test/kotlin/com/openai/models/live/SessionClosedEventTest.kt
  • openai-java-core/src/test/kotlin/com/openai/models/live/SessionStartEventTest.kt

30 more in the full report.

A changed generated baseline means this report cannot reliably identify which handwritten lines changed.

Inspect the custom-code diff

Download the exact patch produced by this run (requires repository access):

gh run download 34648238979 --repo openai/openai-java \
  --name castiron-custom-code-34648238979-1 --dir /tmp/castiron-custom-code-34648238979-1
git apply --stat /tmp/castiron-custom-code-34648238979-1/custom-code.patch
cat /tmp/castiron-custom-code-34648238979-1/custom-code.patch

Or reproduce it from an SDK checkout containing the vendored reporter:

git fetch --no-tags origin 6e773e774e8e324ddfb94437f471ebd7d22035bf cced0727e6166aa9f2441acdede9316737bf4ff5
python3 scripts/castiron/custom_code_report.py report \
  --base 6e773e774e8e324ddfb94437f471ebd7d22035bf \
  --head cced0727e6166aa9f2441acdede9316737bf4ff5 --fetch --require-head-hash --public \
  --out /tmp/castiron-custom-code-cced0727e616
cat /tmp/castiron-custom-code-cced0727e616/custom-code.patch

This is the current full custom patch for mixed files, not an attribution of only the handwritten lines changed by this PR.

Full report and patch

@dpiet-oai dpiet-oai closed this Sep 12, 2026
@dpiet-oai
dpiet-oai deleted the dp/http-request-url-shared-builder branch September 12, 2026 15:54
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.

1 participant