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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,15 +51,17 @@ default byte priority() {
* <p>Transports must be able to be instantiated without any arguments for use in dynamic clients.
*/
default ClientTransport<RequestT, ResponseT> 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.
*
* <p>Configurations are typically specified in the configuration of the client-codegen plugin.
* <p>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<RequestT, ResponseT> createTransport(Document settings);
ClientTransport<RequestT, ResponseT> createTransport(Document settings, Document pluginSettings);

/**
* Loads all {@link ClientTransportFactory} implementations and sorts them by priority.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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).
*
* <p>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.
*
* <p>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.
*
* <pre>{@code
* {
* "requestTimeoutMs": 5000,
* "connectTimeoutMs": 3000,
* "httpVersion": "HTTP/2.0"
* }
* }</pre>
*
* @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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class JavaHttpClientTransport implements ClientTransport<HttpRequest, Htt

private static final InternalLogger LOGGER = InternalLogger.getLogger(JavaHttpClientTransport.class);
private final HttpClient client;
private final Duration defaultRequestTimeout;

static {
// For some reason, this can't just be done in the constructor to always take effect.
Expand Down Expand Up @@ -67,14 +68,24 @@ private static void setHostProperties() {
}

public JavaHttpClientTransport() {
this(HttpClient.newHttpClient());
this(HttpClient.newHttpClient(), null);
}

/**
* @param client Java client to use.
*/
public JavaHttpClientTransport(HttpClient client) {
this(client, null);
}

/**
* @param client Java client to use.
* @param defaultRequestTimeout Default per-request timeout. Used when {@link HttpContext#HTTP_REQUEST_TIMEOUT}
* is not set in the request context. Null means no default.
*/
public JavaHttpClientTransport(HttpClient client, Duration defaultRequestTimeout) {
this.client = client;
this.defaultRequestTimeout = defaultRequestTimeout;
setHostProperties();
}

Expand Down Expand Up @@ -128,7 +139,9 @@ private java.net.http.HttpRequest createJavaRequest(Context context, HttpRequest
.uri(request.uri());

Duration requestTimeout = context.get(HttpContext.HTTP_REQUEST_TIMEOUT);

if (requestTimeout == null) {
requestTimeout = defaultRequestTimeout;
}
if (requestTimeout != null) {
httpRequestBuilder.timeout(requestTimeout);
}
Expand Down Expand Up @@ -214,17 +227,20 @@ public String name() {
}

@Override
public JavaHttpClientTransport createTransport(Document node) {
public JavaHttpClientTransport createTransport(Document node, Document pluginSettings) {
setHostProperties();
var versionNode = node.asStringMap().get("version");
HttpClient httpClient;
if (versionNode != null) {
var version = HttpVersion.from(versionNode.asString());
httpClient = HttpClient.newBuilder().version(smithyToHttpVersion(version)).build();
} else {
httpClient = HttpClient.newHttpClient();
// Start with httpConfig from plugin settings as baseline, then apply transport-specific settings on top.
var config = new HttpTransportConfig().fromDocument(pluginSettings.asStringMap()
.getOrDefault("httpConfig", Document.EMPTY_MAP));
config.fromDocument(node);
var builder = HttpClient.newBuilder();
if (config.httpVersion() != null) {
builder.version(smithyToHttpVersion(config.httpVersion()));
}
if (config.connectTimeout() != null) {
builder.connectTimeout(config.connectTimeout());
}
return new JavaHttpClientTransport(httpClient);
return new JavaHttpClientTransport(builder.build(), config.requestTimeout());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class JavaCodegenSettings {
private static final String TRANSPORT = "transport";
private static final String DEFAULT_PLUGINS = "defaultPlugins";
private static final String DEFAULT_SETTINGS = "defaultSettings";
private static final String HTTP_CONFIG = "httpConfig";
private static final String RELATIVE_DATE = "relativeDate";
private static final String RELATIVE_VERSION = "relativeVersion";
private static final String EDITION = "edition";
Expand All @@ -60,6 +61,7 @@ public final class JavaCodegenSettings {
TRANSPORT,
DEFAULT_PLUGINS,
DEFAULT_SETTINGS,
HTTP_CONFIG,
RELATIVE_DATE,
RELATIVE_VERSION,
EDITION,
Expand All @@ -84,6 +86,7 @@ public final class JavaCodegenSettings {
private final boolean useExternalTypes;
private final List<ShapeId> runtimeTraits;
private final Selector runtimeTraitsSelector;
private final ObjectNode httpConfig;
private final Map<String, Set<Symbol>> generatedSymbols = new HashMap<>();

private JavaCodegenSettings(Builder builder) {
Expand All @@ -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;
}

/**
Expand All @@ -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());

Expand Down Expand Up @@ -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<>());
Expand Down Expand Up @@ -262,6 +271,7 @@ public static final class Builder {
private final List<ShapeId> runtimeTraits = new ArrayList<>();
private Selector runtimeTraitsSelector;
private boolean useExternalTypes;
private ObjectNode httpConfig;

public Builder service(String string) {
this.service = ShapeId.from(string);
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<Void>, Runnable {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down
Loading