diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index a40ee051a97..086aa3b9859 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -914,7 +914,7 @@ protected SpannerOptions(Builder builder) { openTelemetry = builder.openTelemetry; enableApiTracing = builder.enableApiTracing; enableExtendedTracing = builder.enableExtendedTracing; - if (builder.isExperimentalHost) { + if (builder.experimentalHost != null) { enableBuiltInMetrics = false; } else { enableBuiltInMetrics = builder.enableBuiltInMetrics; @@ -1139,7 +1139,8 @@ public static class Builder private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); private String monitoringHost = SpannerOptions.environment.getMonitoringHost(); private SslContext mTLSContext = null; - private boolean isExperimentalHost = false; + private String experimentalHost = null; + private boolean usePlainText = false; private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance(); private static String createCustomClientLibToken(String token) { @@ -1645,10 +1646,18 @@ public Builder setHost(String host) { @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676") public Builder setExperimentalHost(String host) { + if (this.usePlainText) { + Preconditions.checkArgument( + !host.startsWith("https:"), + "Please remove the 'https:' protocol prefix from the host string when using plain text communication"); + if (!host.startsWith("http")) { + host = "http://" + host; + } + } super.setHost(host); super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID); setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build()); - this.isExperimentalHost = true; + this.experimentalHost = host; return this; } @@ -1759,6 +1768,23 @@ public Builder useClientCert(String clientCertificate, String clientCertificateK return this; } + /** + * {@code usePlainText} will configure the transport to use plaintext (no TLS) and will set + * credentials to {@link com.google.cloud.NoCredentials} to avoid sending authentication over an + * unsecured channel. + */ + @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/4264") + public Builder usePlainText() { + this.usePlainText = true; + this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext) + .setCredentials(NoCredentials.getInstance()); + if (this.experimentalHost != null) { + // Re-apply host settings to ensure http:// is prepended. + setExperimentalHost(this.experimentalHost); + } + return this; + } + /** * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will * be used as fallback if this options is not set. @@ -1924,7 +1950,7 @@ public Builder setDefaultTransactionOptions( @Override public SpannerOptions build() { // Set the host of emulator has been set. - if (emulatorHost != null) { + if (emulatorHost != null && experimentalHost == null) { if (!emulatorHost.startsWith("http")) { emulatorHost = "http://" + emulatorHost; } @@ -1934,7 +1960,7 @@ public SpannerOptions build() { this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); // As we are using plain text, we should never send any credentials. this.setCredentials(NoCredentials.getInstance()); - } else if (isExperimentalHost && credentials == null) { + } else if (experimentalHost != null && credentials == null) { credentials = environment.getDefaultExperimentalHostCredentials(); } if (this.numChannels == null) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java index b79622bce5a..f6387535e4d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java @@ -16,10 +16,8 @@ package com.google.cloud.spanner.testing; -import com.google.cloud.NoCredentials; import com.google.cloud.spanner.SpannerOptions; import com.google.common.base.Strings; -import io.grpc.ManagedChannelBuilder; public class ExperimentalHostHelper { private static final String EXPERIMENTAL_HOST = "spanner.experimental_host"; @@ -58,9 +56,7 @@ public static void setExperimentalHostSpannerOptions(SpannerOptions.Builder buil builder.setExperimentalHost(experimentalHost); builder.setBuiltInMetricsEnabled(false); if (usePlainText) { - builder - .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) - .setCredentials(NoCredentials.getInstance()); + builder.usePlainText(); } if (isMtlsSetup()) { String clientCertificate = System.getProperty(CLIENT_CERT_PATH, ""); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index c1d7fe09afa..759888a6673 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -1360,4 +1360,27 @@ public void testCreateDefaultDynamicChannelPoolOptions() { assertEquals( SpannerOptions.DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL, defaults.getCleanupInterval()); } + + @Test + public void testPlainTextOptions() { + SpannerOptions options = + SpannerOptions.newBuilder().setExperimentalHost("localhost:8080").usePlainText().build(); + assertEquals("http://localhost:8080", options.getHost()); + assertEquals(NoCredentials.getInstance(), options.getCredentials()); + options = + SpannerOptions.newBuilder() + .setExperimentalHost("http://localhost:8080") + .usePlainText() + .build(); + assertEquals("http://localhost:8080", options.getHost()); + options = + SpannerOptions.newBuilder().usePlainText().setExperimentalHost("localhost:8080").build(); + assertEquals("http://localhost:8080", options.getHost()); + options = + SpannerOptions.newBuilder() + .usePlainText() + .setExperimentalHost("http://localhost:8080") + .build(); + assertEquals("http://localhost:8080", options.getHost()); + } }