Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ src/test/java/com/auth0/client/mgmt/OAuthTokenSupplierTest.java
src/test/java/com/auth0/client/mgmt/ManagementApiBuilderTest.java
src/test/java/com/auth0/client/mgmt/CustomDomainInterceptorTest.java
src/test/java/com/auth0/client/mgmt/CustomDomainHeaderIntegrationTest.java
src/test/java/com/auth0/net/client/DefaultHttpClientTest.java

# Configuration files from auth0-real
.codecov.yml
Expand Down
29 changes: 29 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,35 @@ If the `DefaultHttpClient` does not support your required networking client conf
your own client by implementing the `Auth0HttpClient` interface and providing it to the API client. This is an advanced
use case and should be used only when necessary.

If you already have a configured `OkHttpClient` (for example, one shared across your application with a custom connection
pool, dispatcher, timeouts, or interceptors), you can reuse it as the base for the `DefaultHttpClient` via `withClient`:

```java
OkHttpClient okHttpClient = new OkHttpClient.Builder()
// your shared transport configuration, e.g. timeouts, dispatcher, or a custom interceptor
.build();

Auth0HttpClient httpClient = DefaultHttpClient.newBuilder()
.withClient(okHttpClient)
.build();

AuthAPI auth = AuthAPI.newBuilder("{YOUR_DOMAIN}", "{YOUR_CLIENT_ID}", "{YOUR_CLIENT_SECRET}")
.withHttpClient(httpClient)
.build();
```

When you supply a client this way, **you own its transport configuration**: timeouts, dispatcher, connection pool, cache,
proxy, and any interceptors on it are used as-is. The corresponding `DefaultHttpClient.Builder` transport settings
(`withReadTimeout`, `withConnectTimeout`, `withMaxRequests`, `withMaxRequestsPerHost`, `withProxy`) are ignored in this
case, so your client is never silently overridden — configure those on your own `OkHttpClient` instead.

The SDK's own behavior is always layered on top and cannot be bypassed: the Auth0 telemetry, rate-limit handling, and
logging interceptors are added to your client. These remain configurable via `withTelemetry`, `telemetryEnabled`,
`withMaxRetries`, and `withLogging`.

> If you do not supply a client, nothing changes: the `DefaultHttpClient.Builder` builds and fully configures the
> transport as before.

### Management API

The Management API client uses `ManagementApi` as the main entry point.
Expand Down
67 changes: 59 additions & 8 deletions src/main/java/com/auth0/net/client/DefaultHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
* to the Auth0 APIs. Instances can be configured and created using the {@link Builder}.
* <p>
* To minimize resource usage, instances should be created once and used in both the
* {@link com.auth0.client.mgmt.ManagementAPI} and {@link com.auth0.client.auth.AuthAPI}

Check warning on line 24 in src/main/java/com/auth0/net/client/DefaultHttpClient.java

View workflow job for this annotation

GitHub Actions / gradle

Tag @link: reference not found: com.auth0.client.mgmt.ManagementAPI
* API clients.
* </p>
* <p>
* For most use cases, usage of this client is recommended. If you have more advanced use cases,
* such as the need to re-use an existing HTTP client, you may consider providing a custom
* implementation of {@link Auth0HttpClient}.
* For most use cases, usage of this client is recommended. To reuse an existing {@link OkHttpClient}
* and its transport configuration, use {@link Builder#withClient(OkHttpClient)}. Only for advanced use
* cases not covered by the {@link Builder} should you provide a custom implementation of
* {@link Auth0HttpClient}.
* </p>
*/
public class DefaultHttpClient implements Auth0HttpClient {
Expand Down Expand Up @@ -55,15 +56,30 @@
}

private DefaultHttpClient(Builder builder) {
okhttp3.OkHttpClient.Builder clientBuilder = new okhttp3.OkHttpClient.Builder();
clientBuilder.readTimeout(sanitizeTimeout(builder.readTimeout), TimeUnit.SECONDS);
clientBuilder.connectTimeout(sanitizeTimeout(builder.connectTimeout), TimeUnit.SECONDS);
okhttp3.OkHttpClient.Builder clientBuilder;
if (builder.baseClient != null) {
// A caller-supplied client owns all transport configuration: timeouts, dispatcher, connection
// pool, cache, proxy, and any interceptors already registered on it are used as-is. The transport
// settings on this builder (timeouts, dispatcher, proxy) are intentionally NOT applied here, so
// they never silently override the caller's client. The SDK's behavior interceptors are still
// layered on below.
clientBuilder = builder.baseClient.newBuilder();
} else {
// Default path (unchanged): the SDK builds and fully configures the transport.
clientBuilder = new okhttp3.OkHttpClient.Builder();
clientBuilder.readTimeout(sanitizeTimeout(builder.readTimeout), TimeUnit.SECONDS);
clientBuilder.connectTimeout(sanitizeTimeout(builder.connectTimeout), TimeUnit.SECONDS);
clientBuilder.dispatcher(getDispatcher(builder.maxRequests, builder.maxRequestsPerHost));
configureProxy(clientBuilder, builder.proxyOptions);
}

// SDK behavior is always applied regardless of the transport client, so Auth0 telemetry, rate-limit
// handling, and logging are guaranteed. These interceptors are parameterized by this builder
// (withTelemetry/withLogging/withMaxRetries), which remain effective even when a base client is used.
clientBuilder.addInterceptor(getLoggingInterceptor(builder.loggingOptions));
clientBuilder.addInterceptor(getTelemetryInterceptor(builder.telemetryEnabled, builder.telemetry));
clientBuilder.addInterceptor(getRateLimitInterceptor(builder.maxRetries));
clientBuilder.dispatcher(getDispatcher(builder.maxRequests, builder.maxRequestsPerHost));

configureProxy(clientBuilder, builder.proxyOptions);
this.client = clientBuilder.build();
}

Expand Down Expand Up @@ -288,13 +304,15 @@
private int maxRetries = 3;
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private OkHttpClient baseClient;

/**
* Sets the value of the read timeout, in seconds. Defaults to ten seconds. A value of zero results in no read timeout.
* Negative numbers will be treated as zero.
*
* @param readTimeout the value of the read timeout to use.
* @return this builder instance.
* @see #withClient(OkHttpClient) ignored when a base client is supplied.
*/
public Builder withReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
Expand All @@ -306,6 +324,7 @@
* Negative numbers will be treated as zero.
* @param connectTimeout the value of the connect timeout to use.
* @return this builder instance.
* @see #withClient(OkHttpClient) ignored when a base client is supplied.
*/
public Builder withConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
Expand All @@ -327,6 +346,7 @@
*
* @param proxyOptions the Proxy configuration options
* @return this builder instance.
* @see #withClient(OkHttpClient) ignored when a base client is supplied.
*/
public Builder withProxy(ProxyOptions proxyOptions) {
this.proxyOptions = proxyOptions;
Expand Down Expand Up @@ -381,6 +401,7 @@
*
* @param maxRequests the number of requests to execute concurrently. Must be equal to or greater than one.
* @return this builder instance.
* @see #withClient(OkHttpClient) ignored when a base client is supplied.
*/
public Builder withMaxRequests(int maxRequests) {
this.maxRequests = maxRequests;
Expand All @@ -392,12 +413,42 @@
*
* @param maxRequestsPerHost the maximum number of requests for each host to execute concurrently. Must be equal to or greater than one.
* @return this builder instance.
* @see #withClient(OkHttpClient) ignored when a base client is supplied.
*/
public Builder withMaxRequestsPerHost(int maxRequestsPerHost) {
this.maxRequestsPerHost = maxRequestsPerHost;
return this;
}

/**
* Use an existing {@link OkHttpClient} as the base for this client, reusing all of its
* <strong>transport</strong> configuration as-is: timeouts, dispatcher, connection pool, cache,
* proxy, and any interceptors already registered on it.
* <p>
* When a base client is provided, the transport-related settings on this builder
* ({@link #withReadTimeout(int)}, {@link #withConnectTimeout(int)}, {@link #withMaxRequests(int)},
* {@link #withMaxRequestsPerHost(int)}, and {@link #withProxy(ProxyOptions)}) are
* <strong>ignored</strong>. This is deliberate: your client's configuration is never silently
* overridden. Configuring those concerns is your responsibility, on the {@code OkHttpClient} you
* supply.
* </p>
* <p>
* The SDK's own behavior is always layered on top and cannot be bypassed: the Auth0 telemetry,
* rate-limit handling, and logging interceptors are added to your client. These remain configurable
* via {@link #withTelemetry(String, String)}, {@link #telemetryEnabled(boolean)},
* {@link #withMaxRetries(int)}, and {@link #withLogging(LoggingOptions)}, which stay effective even
* when a base client is used.
* </p>
*
* @param baseClient the {@link OkHttpClient} whose transport configuration should be reused.
* @return this builder instance.
*/
public Builder withClient(OkHttpClient baseClient) {
Asserts.assertNotNull(baseClient, "base client");
this.baseClient = baseClient;
return this;
}

/**
* Create a {@code DefaultHttpClient} from this configured builder.
* @return the created {@code DefaultHttpClient}.
Expand Down
26 changes: 26 additions & 0 deletions src/test/java/com/auth0/client/auth/AuthAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -299,6 +300,31 @@ public void shouldSendCustomTelemetryWhenConfigured() throws Exception {
assertThat(telemetry.get("version").asText(), is("1.2.3"));
}

@Test
public void shouldReuseBaseClientAndPreserveSdkBehavior() throws Exception {
OkHttpClient baseClient = new OkHttpClient.Builder()
.addInterceptor(chain -> chain.proceed(chain.request()
.newBuilder()
.addHeader("X-Custom-Base", "base-client")
.build()))
.build();

AuthAPI customApi = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID, CLIENT_SECRET)
.withHttpClient(
DefaultHttpClient.newBuilder().withClient(baseClient).build())
.build();

Request<UserInfo> request = customApi.userInfo("accessToken");
server.jsonResponse(AUTH_USER_INFO, 200);
request.execute();

RecordedRequest recordedRequest = server.takeRequest();
// The base client's interceptor is carried over via newBuilder(), so the user's config is preserved.
assertThat(recordedRequest.getHeader("X-Custom-Base"), is("base-client"));
// SDK behavior (telemetry) is still layered on top, so integrity is preserved.
assertThat(recordedRequest.getHeader("Auth0-Client"), is(notNullValue()));
}

@Test
public void shouldNestAuth0JavaInTelemetryEnv() throws Exception {
String value = new Telemetry("my-wrapper-sdk", "1.2.3", "auth0-java-9.9.9").getValue();
Expand Down
69 changes: 69 additions & 0 deletions src/test/java/com/auth0/net/client/DefaultHttpClientTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.auth0.net.client;

import static com.auth0.AssertsUtil.verifyThrows;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import org.junit.jupiter.api.Test;

public class DefaultHttpClientTest {

@Test
public void shouldReuseBaseClientTransportConfigAndIgnoreBuilderTransportSettings() {
OkHttpClient baseClient =
new OkHttpClient.Builder().readTimeout(42, TimeUnit.SECONDS).build();
baseClient.dispatcher().setMaxRequests(7);

DefaultHttpClient httpClient = DefaultHttpClient.newBuilder()
.withClient(baseClient)
// Transport settings below must be ignored when a base client is supplied.
.withReadTimeout(99)
.withMaxRequests(99)
.build();

OkHttpClient built = httpClient.getOkClient();
assertThat(built.readTimeoutMillis(), is(42_000));
assertThat(built.dispatcher().getMaxRequests(), is(7));
}

@Test
public void shouldApplyBuilderTransportSettingsWhenNoBaseClient() {
DefaultHttpClient httpClient = DefaultHttpClient.newBuilder()
.withReadTimeout(42)
.withMaxRequests(7)
.build();

OkHttpClient built = httpClient.getOkClient();
assertThat(built.readTimeoutMillis(), is(42_000));
assertThat(built.dispatcher().getMaxRequests(), is(7));
}

@Test
public void shouldLayerSdkInterceptorsOnTopOfBaseClientInterceptors() {
OkHttpClient baseClient = new OkHttpClient.Builder()
.addInterceptor(chain -> chain.proceed(chain.request()))
.build();
int baseInterceptorCount = baseClient.interceptors().size();

DefaultHttpClient httpClient =
DefaultHttpClient.newBuilder().withClient(baseClient).build();

// The base client's interceptor plus the three SDK interceptors (logging, telemetry, rate-limit).
assertThat(httpClient.getOkClient().interceptors().size(), is(baseInterceptorCount + 3));
// The original base client is left untouched.
assertThat(
baseClient.interceptors().size(),
is(not(httpClient.getOkClient().interceptors().size())));
}

@Test
public void shouldThrowWhenBaseClientIsNull() {
verifyThrows(
IllegalArgumentException.class,
() -> DefaultHttpClient.newBuilder().withClient(null),
"'base client' cannot be null!");
}
}
Loading