diff --git a/client/client-api/src/main/java/software/amazon/smithy/java/client/core/ClientTransportFactory.java b/client/client-api/src/main/java/software/amazon/smithy/java/client/core/ClientTransportFactory.java
index c019828ca..bd0cb0ac9 100644
--- a/client/client-api/src/main/java/software/amazon/smithy/java/client/core/ClientTransportFactory.java
+++ b/client/client-api/src/main/java/software/amazon/smithy/java/client/core/ClientTransportFactory.java
@@ -6,7 +6,6 @@
package software.amazon.smithy.java.client.core;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ServiceLoader;
@@ -52,15 +51,17 @@ default byte priority() {
*
Transports must be able to be instantiated without any arguments for use in dynamic clients.
*/
default ClientTransport createTransport() {
- return createTransport(Document.of(Collections.emptyMap()));
+ return createTransport(Document.EMPTY_MAP, Document.EMPTY_MAP);
}
/**
- * Create a {@link ClientTransport} with a user-provided configuration.
+ * Create a {@link ClientTransport} with transport-specific settings and the full plugin settings.
*
- * Configurations are typically specified in the configuration of the client-codegen plugin.
+ *
The {@code pluginSettings} document contains the full codegen plugin configuration, allowing
+ * transports to read shared settings (e.g., {@code httpConfig}) that are not transport-specific.
+ * Transport-specific settings should take precedence over shared settings.
*/
- ClientTransport createTransport(Document settings);
+ ClientTransport createTransport(Document settings, Document pluginSettings);
/**
* Loads all {@link ClientTransportFactory} implementations and sorts them by priority.
diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java
new file mode 100644
index 000000000..2fe1eb364
--- /dev/null
+++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/HttpTransportConfig.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.java.client.http;
+
+import java.time.Duration;
+import software.amazon.smithy.java.core.serde.document.Document;
+import software.amazon.smithy.java.http.api.HttpVersion;
+
+/**
+ * Common HTTP transport configuration shared across transport implementations.
+ *
+ * Subclass this to add transport-specific settings.
+ */
+public class HttpTransportConfig {
+
+ private Duration requestTimeout;
+ private Duration connectTimeout;
+ private HttpVersion httpVersion;
+
+ /**
+ * Per-request timeout. Null means no timeout (use client default).
+ *
+ *
Defined in the document data as milliseconds.
+ *
+ * @return request timeout.
+ */
+ public Duration requestTimeout() {
+ return requestTimeout;
+ }
+
+ public HttpTransportConfig requestTimeout(Duration requestTimeout) {
+ this.requestTimeout = requestTimeout;
+ return this;
+ }
+
+ /**
+ * TCP connect timeout. Null means use transport default.
+ */
+ public Duration connectTimeout() {
+ return connectTimeout;
+ }
+
+ public HttpTransportConfig connectTimeout(Duration connectTimeout) {
+ this.connectTimeout = connectTimeout;
+ return this;
+ }
+
+ /**
+ * HTTP version preference. Null means use transport default.
+ *
+ *
Defined in the document data as "http/1.0", "http/1.1", or "h2".
+ */
+ public HttpVersion httpVersion() {
+ return httpVersion;
+ }
+
+ public HttpTransportConfig httpVersion(HttpVersion httpVersion) {
+ this.httpVersion = httpVersion;
+ return this;
+ }
+
+ /**
+ * Populate fields from a Document config.
+ *
+ *
{@code
+ * {
+ * "requestTimeoutMs": 5000,
+ * "connectTimeoutMs": 3000,
+ * "httpVersion": "HTTP/2.0"
+ * }
+ * }
+ *
+ * @param doc configuration document
+ * @return this config for chaining
+ */
+ public HttpTransportConfig fromDocument(Document doc) {
+ var config = doc.asStringMap();
+
+ var timeout = config.get("requestTimeoutMs");
+ if (timeout != null) {
+ this.requestTimeout = Duration.ofMillis(timeout.asLong());
+ }
+
+ var connTimeout = config.get("connectTimeoutMs");
+ if (connTimeout != null) {
+ this.connectTimeout = Duration.ofMillis(connTimeout.asLong());
+ }
+
+ var version = config.get("httpVersion");
+ if (version != null) {
+ this.httpVersion = HttpVersion.from(version.asString());
+ }
+
+ return this;
+ }
+}
diff --git a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java
index 4713a7087..52be2b7af 100644
--- a/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java
+++ b/client/client-http/src/main/java/software/amazon/smithy/java/client/http/JavaHttpClientTransport.java
@@ -39,6 +39,7 @@ public class JavaHttpClientTransport implements ClientTransport runtimeTraits;
private final Selector runtimeTraitsSelector;
+ private final ObjectNode httpConfig;
private final Map> generatedSymbols = new HashMap<>();
private JavaCodegenSettings(Builder builder) {
@@ -103,6 +106,7 @@ private JavaCodegenSettings(Builder builder) {
this.useExternalTypes = builder.useExternalTypes;
this.runtimeTraits = Collections.unmodifiableList(builder.runtimeTraits);
this.runtimeTraitsSelector = builder.runtimeTraitsSelector;
+ this.httpConfig = builder.httpConfig;
}
/**
@@ -128,7 +132,8 @@ public static JavaCodegenSettings fromNode(ObjectNode settingsNode) {
.getStringMember(EDITION, builder::edition)
.getBooleanMember(USE_EXTERNAL_TYPES, builder::useExternalTypes)
.getArrayMember(RUNTIME_TRAITS, n -> n.expectStringNode().expectShapeId(), builder::runtimeTraits)
- .getStringMember(RUNTIME_TRAITS_SELECTOR, builder::runtimeTraitsSelector);
+ .getStringMember(RUNTIME_TRAITS_SELECTOR, builder::runtimeTraitsSelector)
+ .getObjectMember(HTTP_CONFIG, builder::httpConfig);
builder.sourceLocation(settingsNode.getSourceLocation().getFilename());
@@ -203,6 +208,10 @@ public Selector runtimeTraitsSelector() {
return runtimeTraitsSelector;
}
+ public ObjectNode httpConfig() {
+ return httpConfig;
+ }
+
@SmithyInternalApi
public void addSymbol(Symbol symbol) {
var symbols = generatedSymbols.computeIfAbsent(symbol.getNamespace(), k -> new HashSet<>());
@@ -262,6 +271,7 @@ public static final class Builder {
private final List runtimeTraits = new ArrayList<>();
private Selector runtimeTraitsSelector;
private boolean useExternalTypes;
+ private ObjectNode httpConfig;
public Builder service(String string) {
this.service = ShapeId.from(string);
@@ -366,6 +376,11 @@ public Builder runtimeTraitsSelector(String selector) {
return this;
}
+ public Builder httpConfig(ObjectNode httpConfig) {
+ this.httpConfig = httpConfig;
+ return this;
+ }
+
public JavaCodegenSettings build() {
return new JavaCodegenSettings(this);
}
diff --git a/codegen/codegen-plugin/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java b/codegen/codegen-plugin/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java
index 418f8ec91..a27f43bd2 100644
--- a/codegen/codegen-plugin/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java
+++ b/codegen/codegen-plugin/src/main/java/software/amazon/smithy/java/codegen/client/generators/ClientInterfaceGenerator.java
@@ -133,7 +133,8 @@ final class Builder extends ${client:T}.Builder<${interface:T}, Builder>${?setti
${?hasDefaults}${defaultPlugins:C|}
${/hasDefaults}${?hasDefaultProtocol}${defaultProtocol:C|}
${/hasDefaultProtocol}${?hasTransportSettings}${transportSettings:C|}
- ${/hasTransportSettings}${?defaultSchemes}
+ ${/hasTransportSettings}${?hasHttpConfig}${httpConfig:C|}
+ ${/hasHttpConfig}${?defaultSchemes}
${defaultAuth:C|}${/defaultSchemes}
private Builder() {
@@ -151,7 +152,7 @@ private Builder() {
configBuilder().protocol(${protocolFactory:C}.createProtocol(protocolSettings, protocolTrait));
}
${/hasDefaultProtocol}${?hasDefaultTransport}if (configBuilder().transport() == null) {
- configBuilder().transport(${transportFactory:C}.createTransport(${?hasTransportSettings}transportSettings${/hasTransportSettings}));
+ configBuilder().transport(${transportFactory:C}.createTransport(${?hasTransportSettings}transportSettings${/hasTransportSettings}${^hasTransportSettings}${document:T}.EMPTY_MAP${/hasTransportSettings}, ${?hasHttpConfig}httpConfig${/hasHttpConfig}${^hasHttpConfig}${document:T}.EMPTY_MAP${/hasHttpConfig}));
}${/hasDefaultTransport}
${?hasBdd}${loadBddInfo:C|}${/hasBdd}
return new ${impl:T}(this);
@@ -199,6 +200,10 @@ final class RequestOverrideBuilder extends ${requestOverride:T}.OverrideBuilder<
writer.putContext(
"transportSettings",
new TransportSettingsGenerator(writer, settings.transportSettings()));
+ var hasHttpConfig = settings.httpConfig() != null && !settings.httpConfig().isEmpty();
+ writer.putContext("hasHttpConfig", hasHttpConfig);
+ writer.putContext("httpConfig", new HttpConfigGenerator(writer, settings.httpConfig()));
+ writer.putContext("document", Document.class);
writer.putContext(
"operations",
new OperationMethodGenerator(
@@ -249,6 +254,20 @@ public void run() {
}
}
+ private record HttpConfigGenerator(JavaWriter writer, ObjectNode settings) implements Runnable {
+ @Override
+ public void run() {
+ if (settings == null) {
+ return;
+ }
+ writer.pushState();
+ writer.putContext("document", Document.class);
+ writer.putContext("nodeWriter", new NodeDocumentWriter(writer, settings));
+ writer.write("private static final ${document:T} httpConfig = ${nodeWriter:C};");
+ writer.popState();
+ }
+ }
+
private record NodeDocumentWriter(JavaWriter writer, ObjectNode node) implements NodeVisitor, Runnable {
@Override
diff --git a/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java b/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java
index 04b7eccd8..b0e04836e 100644
--- a/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java
+++ b/core/src/main/java/software/amazon/smithy/java/core/serde/document/Document.java
@@ -10,6 +10,7 @@
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -68,6 +69,11 @@
* itself was serialized or deserialized directly.
*/
public interface Document extends SerializableShape {
+ /**
+ * An empty map document.
+ */
+ Document EMPTY_MAP = of(Collections.emptyMap());
+
/**
* Get the Smithy data model type for the underlying contents of the document.
*