WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,14 @@ public class ServiceStubsOptions {

protected final @Nullable Consumer<ManagedChannelBuilder<?>> channelInitializer;

/** Indicates whether basic HTTPS/SSL/TLS should be enabled * */
protected final boolean enableHttps;
/**
* Indicates whether basic HTTPS/SSL/TLS should be enabled. Null means not explicitly set by the
* user, allowing runtime derivation of the effective value.
*/
protected final Boolean enableHttps;

/** Indicates whether an API key was provided, used for automatic TLS enablement */
protected final boolean apiKeyProvided;

/** The user provided context for SSL/TLS over gRPC * */
protected final SslContext sslContext;
Expand Down Expand Up @@ -113,6 +119,7 @@ public class ServiceStubsOptions {
this.target = that.target;
this.channelInitializer = that.channelInitializer;
this.enableHttps = that.enableHttps;
this.apiKeyProvided = that.apiKeyProvided;
this.sslContext = that.sslContext;
this.healthCheckAttemptTimeout = that.healthCheckAttemptTimeout;
this.systemInfoTimeout = that.systemInfoTimeout;
Expand All @@ -134,7 +141,8 @@ public class ServiceStubsOptions {
ManagedChannel channel,
String target,
@Nullable Consumer<ManagedChannelBuilder<?>> channelInitializer,
boolean enableHttps,
Boolean enableHttps,
boolean apiKeyProvided,
SslContext sslContext,
Duration healthCheckAttemptTimeout,
Duration healthCheckTimeout,
Expand All @@ -154,6 +162,7 @@ public class ServiceStubsOptions {
this.target = target;
this.channelInitializer = channelInitializer;
this.enableHttps = enableHttps;
this.apiKeyProvided = apiKeyProvided;
this.sslContext = sslContext;
this.healthCheckAttemptTimeout = healthCheckAttemptTimeout;
this.healthCheckTimeout = healthCheckTimeout;
Expand Down Expand Up @@ -202,11 +211,24 @@ public Consumer<ManagedChannelBuilder<?>> getChannelInitializer() {
}

/**
* @return if gRPC should use SSL/TLS; Ignored and assumed {@code true} if {@link
* #getSslContext()} is set
* Returns whether gRPC should use SSL/TLS. This method computes the effective value based on:
*
* <ul>
* <li>If explicitly set via {@link Builder#setEnableHttps(boolean)}, returns that value
* <li>If an API key was provided and no custom SSL context or channel is set, returns {@code
* true} (auto-enabled)
* <li>Otherwise returns {@code false}
* </ul>
*
* <p>Note: When {@link #getSslContext()} is set, TLS is handled by the SSL context regardless of
* this value.
*
* @return if gRPC should use SSL/TLS
*/
public boolean getEnableHttps() {
return enableHttps;
return (enableHttps != null)
? enableHttps
: (apiKeyProvided && sslContext == null && channel == null);
}

/**
Expand Down Expand Up @@ -325,12 +347,13 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceStubsOptions that = (ServiceStubsOptions) o;
return enableHttps == that.enableHttps
return apiKeyProvided == that.apiKeyProvided
&& enableKeepAlive == that.enableKeepAlive
&& keepAlivePermitWithoutStream == that.keepAlivePermitWithoutStream
&& Objects.equals(channel, that.channel)
&& Objects.equals(target, that.target)
&& Objects.equals(channelInitializer, that.channelInitializer)
&& Objects.equals(enableHttps, that.enableHttps)
&& Objects.equals(sslContext, that.sslContext)
&& Objects.equals(healthCheckAttemptTimeout, that.healthCheckAttemptTimeout)
&& Objects.equals(healthCheckTimeout, that.healthCheckTimeout)
Expand All @@ -353,6 +376,7 @@ public int hashCode() {
target,
channelInitializer,
enableHttps,
apiKeyProvided,
sslContext,
healthCheckAttemptTimeout,
healthCheckTimeout,
Expand Down Expand Up @@ -418,7 +442,7 @@ public String toString() {
public static class Builder<T extends Builder<T>> {
private ManagedChannel channel;
private SslContext sslContext;
private boolean enableHttps;
private Boolean enableHttps;
Copy link
Member

Choose a reason for hiding this comment

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

I assume we don't want to support a user calling setEnableHttps(null) to restore to "default"? I don't mind, just want to confirm

Copy link
Member

Choose a reason for hiding this comment

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

Here's a contrived situation:

  • I create service stubs options with no API key and having set nothing about TLS whatsoever, thereby making enableHttps default to false on the service stub options
  • I want to create new service stub options build from the existing options, so now enableHttps is false even though I never set it
  • I do a addApiKey on this new service stub options I have never set any TLS setting on, but it still won't default to TLS because with the current code, it's impossible to create a builder from existing options and retain default TLS behavior

I don't mind if we break and/or don't-support this scenario, just wanted to mention it

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should at least document that scenario since it is confusing. Arguably if enableHttps wasn't explicitly set when the service stub options was created we should preserve that information when we go back to a builder

Copy link
Contributor Author

@THardy98 THardy98 Dec 4, 2025

Choose a reason for hiding this comment

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

Hmm, maybe should revert the change:
this.enableHttps != null ? this.enableHttps : false,

and make enableHttps a Boolean in ServiceStubOptions as well

The semantics might be clearer this way tbh

Copy link
Member

Choose a reason for hiding this comment

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

If we don't consider changing the return type of ServiceStubOptions.getEnableHttps() to Boolean instead of boolean to be too big of a compat break, I agree that is the better/clearer approach

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm I think changing the return type to a Boolean is a bit dangerous since if we return a null and users expect it to be a bool then they could get a null pointer exception. Can we just internally keep track of it as a Boolean, but keep the return type the same?

Copy link
Member

@cretz cretz Dec 5, 2025

Choose a reason for hiding this comment

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

I didn't figure it'd be that common, but meh. This works for me, but what should be the return value of getEnableHttps be if enableHttps is null? Would the logic be return (this.enableHttps != null && this.enableHttps) || (enableHttps == null && this.apiKeyProvided && this.sslContext == null && this.channel == null)? I also assume users may never want to differentiate whether it's explicitly set or not? Maybe a different field name/getter and getEnableHttps is that more advanced logic but the getter still exists for the wrapper?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I don't think it is that common, my main concern is that a user who does call it doesn't realize the type can now be null now

Copy link
Contributor

Choose a reason for hiding this comment

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

@maciejdudko do you have any thoughts on this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I don't think it is that common, my main concern is that a user who does call it doesn't realize the type can now be null now
I basically used the expression Chad suggested - I think that's pretty reasonable. I've updated the comment to reflect the return value.

private String target;
private Consumer<ManagedChannelBuilder<?>> channelInitializer;
private Duration healthCheckAttemptTimeout;
Expand All @@ -435,6 +459,7 @@ public static class Builder<T extends Builder<T>> {
private Collection<GrpcMetadataProvider> grpcMetadataProviders;
private Collection<ClientInterceptor> grpcClientInterceptors;
private Scope metricsScope;
private boolean apiKeyProvided;

protected Builder() {}

Expand All @@ -443,6 +468,7 @@ protected Builder(ServiceStubsOptions options) {
this.target = options.target;
this.channelInitializer = options.channelInitializer;
this.enableHttps = options.enableHttps;
this.apiKeyProvided = options.apiKeyProvided;
this.sslContext = options.sslContext;
this.healthCheckAttemptTimeout = options.healthCheckAttemptTimeout;
this.healthCheckTimeout = options.healthCheckTimeout;
Expand All @@ -455,8 +481,15 @@ protected Builder(ServiceStubsOptions options) {
this.connectionBackoffResetFrequency = options.connectionBackoffResetFrequency;
this.grpcReconnectFrequency = options.grpcReconnectFrequency;
this.headers = options.headers;
this.grpcMetadataProviders = options.grpcMetadataProviders;
this.grpcClientInterceptors = options.grpcClientInterceptors;
// Make mutable copies of collections to allow adding more items
this.grpcMetadataProviders =
options.grpcMetadataProviders != null && !options.grpcMetadataProviders.isEmpty()
? new ArrayList<>(options.grpcMetadataProviders)
: null;
this.grpcClientInterceptors =
options.grpcClientInterceptors != null && !options.grpcClientInterceptors.isEmpty()
? new ArrayList<>(options.grpcClientInterceptors)
: null;
this.metricsScope = options.metricsScope;
}

Expand Down Expand Up @@ -613,6 +646,7 @@ public T addGrpcMetadataProvider(GrpcMetadataProvider grpcMetadataProvider) {
* @return {@code this}
*/
public T addApiKey(AuthorizationTokenSupplier apiKey) {
this.apiKeyProvided = true;
addGrpcMetadataProvider(
new AuthorizationGrpcMetadataProvider(() -> "Bearer " + apiKey.supply()));
return self();
Expand Down Expand Up @@ -804,6 +838,7 @@ public ServiceStubsOptions build() {
this.target,
this.channelInitializer,
this.enableHttps,
this.apiKeyProvided,
this.sslContext,
this.healthCheckAttemptTimeout,
this.healthCheckTimeout,
Expand Down Expand Up @@ -837,7 +872,7 @@ public ServiceStubsOptions validateAndBuildWithDefaults() {
"Only one of the 'sslContext' or 'channel' options can be set at a time");
}

if (this.enableHttps && this.channel != null) {
if (Boolean.TRUE.equals(this.enableHttps) && this.channel != null) {
throw new IllegalStateException(
"Only one of the 'enableHttps' or 'channel' options can be set at a time");
}
Expand Down Expand Up @@ -866,6 +901,7 @@ public ServiceStubsOptions validateAndBuildWithDefaults() {
target,
this.channelInitializer,
this.enableHttps,
this.apiKeyProvided,
this.sslContext,
healthCheckAttemptTimeout,
healthCheckTimeout,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package io.temporal.serviceclient;

import static org.junit.Assert.*;

import org.junit.Test;

public class ServiceStubsOptionsTest {

@Test
public void testTLSEnabledByDefaultWhenAPIKeyProvided() {
ServiceStubsOptions options =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.addApiKey(() -> "test-api-key")
.validateAndBuildWithDefaults();

assertTrue(options.getEnableHttps());
}

@Test
public void testExplicitTLSDisableBeforeAPIKeyStillDisables() {
ServiceStubsOptions options =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.setEnableHttps(false)
.addApiKey(() -> "test-api-key")
.validateAndBuildWithDefaults();

// Explicit TLS=false should take precedence regardless of order
assertFalse(options.getEnableHttps());
}

@Test
public void testExplicitTLSDisableAfterAPIKeyStillDisables() {
ServiceStubsOptions options =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.addApiKey(() -> "test-api-key")
.setEnableHttps(false)
.validateAndBuildWithDefaults();

// Explicit TLS=false should take precedence regardless of order
assertFalse(options.getEnableHttps());
}

@Test
public void testTLSDisabledByDefaultWithoutAPIKey() {
ServiceStubsOptions options =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.validateAndBuildWithDefaults();

assertFalse(options.getEnableHttps());
}

@Test
public void testExplicitTLSEnableWithoutAPIKey() {
ServiceStubsOptions options =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.setEnableHttps(true)
.validateAndBuildWithDefaults();

assertTrue(options.getEnableHttps());
}

@Test
public void testBuilderFromOptionsPreservesDefaultTLSBehavior() {
ServiceStubsOptions options1 =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.validateAndBuildWithDefaults();

assertFalse(options1.getEnableHttps());

ServiceStubsOptions options2 =
WorkflowServiceStubsOptions.newBuilder(options1)
.addApiKey(() -> "test-api-key")
.validateAndBuildWithDefaults();

assertTrue(
"TLS should auto-enable when API key is added to builder from options that had default TLS behavior",
options2.getEnableHttps());
}

@Test
public void testBuilderFromOptionsWithExplicitTLSDisableStaysDisabled() {
ServiceStubsOptions options1 =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.setEnableHttps(false)
.validateAndBuildWithDefaults();

assertFalse(options1.getEnableHttps());

ServiceStubsOptions options2 =
WorkflowServiceStubsOptions.newBuilder(options1)
.addApiKey(() -> "test-api-key")
.validateAndBuildWithDefaults();

assertFalse(
"TLS should stay disabled when explicitly set to false, even with API key",
options2.getEnableHttps());
}

@Test
public void testBuilderFromOptionsWithExplicitTLSEnableStaysEnabled() {
ServiceStubsOptions options1 =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.setEnableHttps(true)
.validateAndBuildWithDefaults();

assertTrue(options1.getEnableHttps());

ServiceStubsOptions options2 =
WorkflowServiceStubsOptions.newBuilder(options1).validateAndBuildWithDefaults();

assertTrue("TLS should stay enabled when explicitly set to true", options2.getEnableHttps());
}

@Test
public void testSpringBootStyleAutoTLSWithApiKey() {
ServiceStubsOptions options1 =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("my-namespace.tmprl.cloud:7233")
.addApiKey(() -> "my-api-key")
.validateAndBuildWithDefaults();

assertTrue(
"TLS should auto-enable when API key is provided without explicit TLS setting",
options1.getEnableHttps());

ServiceStubsOptions options2 =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.setEnableHttps(false)
.addApiKey(() -> "my-api-key")
.validateAndBuildWithDefaults();

assertFalse(
"TLS should stay disabled when explicitly set to false, even with API key",
options2.getEnableHttps());

ServiceStubsOptions options3 =
WorkflowServiceStubsOptions.newBuilder()
.setTarget("localhost:7233")
.validateAndBuildWithDefaults();

assertFalse(
"TLS should be disabled when no API key and no explicit TLS setting",
options3.getEnableHttps());
}
}
Loading