Skip to content

Commit e1e8c2e

Browse files
authored
misc: add advanced endpoint config guide (#878)
1 parent 13833d9 commit e1e8c2e

File tree

3 files changed

+170
-17
lines changed

3 files changed

+170
-17
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "40de1cb5-015f-4108-b3ef-419629a615ff",
3+
"type": "misc",
4+
"description": "add clarifying docs for endpointUrl"
5+
}

codegen/smithy-aws-kotlin-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsServiceConfigIntegration.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import software.amazon.smithy.kotlin.codegen.core.*
88
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
99
import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding
1010
import software.amazon.smithy.kotlin.codegen.lang.KotlinTypes
11+
import software.amazon.smithy.kotlin.codegen.model.asNullable
1112
import software.amazon.smithy.kotlin.codegen.model.boxed
1213
import software.amazon.smithy.kotlin.codegen.rendering.*
1314
import software.amazon.smithy.kotlin.codegen.rendering.util.ConfigProperty
@@ -76,9 +77,15 @@ class AwsServiceConfigIntegration : KotlinIntegration {
7677

7778
val EndpointUrlProp = ConfigProperty {
7879
name = "endpointUrl"
79-
symbol = RuntimeTypes.Core.Net.Url.toBuilder().boxed().build()
80+
symbol = RuntimeTypes.Core.Net.Url.asNullable()
8081
documentation = """
81-
A custom endpoint to use when making requests.
82+
A custom endpoint to route requests to. The endpoint set here is passed to the configured
83+
[endpointProvider], which may inspect and modify it as needed.
84+
85+
Setting a custom endpointUrl should generally be preferred to overriding the [endpointProvider] and is
86+
the recommended way to route requests to development or preview instances of a service.
87+
88+
**This is an advanced config option.**
8289
""".trimIndent()
8390
propertyType = ConfigPropertyType.SymbolDefault
8491
}
Lines changed: 156 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,167 @@
11
# Configuring Client Endpoints
22

3-
The AWS SDK for Kotlin exposes the ability to provide a custom endpoint to be used for a service. In most cases
4-
you can just use the default `EndpointProvider` provided with each service client. There are some reasons to provide
5-
a custom endpoint though such as working with a pre-release version of a service or access to specific service
6-
features not yet modeled in the SDK.
3+
AWS services are deployed across a matrix of hostname configurations. One of the first steps in making a request to an
4+
AWS service is determining where to route that request. This process is known in the SDK as endpoint resolution.
75

8-
An [Endpoint Provider](https://github.com/awslabs/smithy-kotlin/blob/main/runtime/protocol/http/common/src/aws/smithy/kotlin/runtime/http/endpoints/EndpointProvider.kt)
9-
can be configured to provide custom endpoint resolution logic for service clients. Every service client config is
10-
generated with an endpoint provider that can be overridden, where the template argument is typealiased to a set of parameters
11-
unique to each service. Each service client package has an exported `ServiceId` constant that can be used to determine
12-
which service is invoking your endpoint resolver.
6+
The AWS SDK for Kotlin exposes the ability to modify this behavior within client config. In most cases, the default
7+
configuration will suffice. However, there exist several reasons for which you may wish to modify this behavior, such as:
8+
* making requests against a pre-release version or local deployment of a service
9+
* access to specific service features not yet modeled in the SDK
1310

14-
## Examples
11+
**Disclaimer: Endpoint resolution is an advanced SDK topic. By changing these settings you risk breaking your code. The
12+
default settings should be applicable to most users in production environments.**
13+
14+
## Customization
15+
16+
There are two primary means through which a user can control the behavior of endpoint resolution within the SDK, both of which
17+
are exposed as config settings on each service client:
18+
19+
1. `endpointUrl: Url`
20+
1. `endpointProvider: EndpointProvider`
21+
22+
## `endpointUrl`
23+
24+
Users can set a value for `endpointUrl` to indicate a "base" hostname for the instance of your service. The value set
25+
here is not final-- it is ultimately passed as a parameter to the client's `EndpointProvider` when final resolution
26+
occurs. The provider implementation then has the opportunity to inspect and potentially modify that value to determine
27+
the final endpoint.
28+
29+
For example, if you perform an S3 `GetObject` request against a given bucket with a client where you've specified
30+
an `endpointUrl`, the default provider implementation will inject the bucket into the hostname if it is virtual-host
31+
compatible (assuming you haven't disabled virtual-hosting in client config).
1532

16-
The following code snippet shows how a service endpoint provider can be overridden for S3:
33+
In practice, this will most likely be used to point your client at a development or preview instance of a service.
34+
35+
## `EndpointProvider`
36+
37+
EndpointProvider is the definitive mechanism through which endpoint resolution occurs.
1738

1839
```kotlin
19-
import aws.sdk.kotlin.services.s3.endpoints.EndpointProvider
40+
public fun interface EndpointProvider<T> {
41+
public suspend fun resolveEndpoint(params: T): Endpoint
42+
}
43+
```
2044

21-
val client = S3Client.fromEnvironment {
22-
endpointProvider = EndpointProvider { // this example doesn't use the passed EndpointParameters
23-
Endpoint("https://mybucket.s3.us-west-2.amazonaws.com")
45+
The provider's `resolveEndpoint` method is invoked as part of the workflow for every request you make in the SDK. The
46+
`Endpoint` value returned by the provider is used **as-is** when making the request.
47+
48+
### `EndpointProvider` parameters
49+
50+
Each service takes a specific set of inputs which are passed to its resolution function, defined in each service client
51+
package as `EndpointParameters`.
52+
53+
Every service includes the following base parameters, which are used to facilitate general endpoint resolution within
54+
AWS:
55+
56+
| name | type | description |
57+
|----------------|-----------|------------------------------------------------------------|
58+
| `region` | `String` | The client's AWS region |
59+
| `endpoint` | `String` | A string representation of the value set for `endpointUrl` |
60+
| `useFips` | `Boolean` | Whether FIPS endpoints are enabled in client config |
61+
| `useDualStack` | `Boolean` | Whether dual-stack endpoints are enabled in client config |
62+
63+
Services can specify additional parameters required for resolution. For example, S3's `EndpointParameters` include the
64+
bucket name, as well as several S3-specific feature settings such as whether virtual host addressing.
65+
66+
If you are implementing your own provider, you should never need to construct your own instance of `EndpointParameters`.
67+
The SDK will source the values per-request and pass them to your implementation of `resolveEndpoint`.
68+
69+
## `endpointUrl` vs. `EndpointProvider`
70+
71+
It is important to understand that the following two statements do **NOT** produce clients with equivalent endpoint
72+
resolution behavior:
73+
74+
```kotlin
75+
// using endpointUrl
76+
S3Client.fromEnvironment { endpointUrl = Url.parse("https://endpoint.example") }
77+
78+
// using endpointProvider
79+
S3Client.fromEnvironment {
80+
endpointProvider = object : EndpointProvider {
81+
override suspend fun resolveEndpoint(params: EndpointParameters): Endpoint = Endpoint("https://endpoint.example")
2482
}
2583
}
2684
```
85+
86+
In the former (using `endpointUrl`) statement, you are specifying a base URL to be passed to the (default) provider,
87+
which may be modified as part of endpoint resolution.
88+
89+
In the latter (using `endpointProvider`), you are specifying in absolute terms that requests should be made against the
90+
given example endpoint.
91+
92+
**While these two settings are not mutually exclusive, in general, you can expect to only need to modify one of them
93+
depending on your use case. As a general SDK user, you will most often be making endpoint customizations
94+
through `endpointUrl`.**
95+
96+
## A note about S3
97+
98+
S3 is a complex service with many of its features modeled through endpoint-related customizations, such as bucket
99+
virtual hosting, where the bucket name is inserted into the hostname for requests.
100+
101+
Because of this, it is generally not recommended to replace the `EndpointProvider` implementation in S3. If you need to
102+
extend its resolution behavior (such as sending requests to a local development stack with additional endpoint
103+
considerations), you will most likely need to wrap the default implementation. An example of this is shown below.
104+
105+
## Examples
106+
107+
### `endpointUrl`
108+
109+
The following code snippet shows how the general service endpoint can be overridden for S3:
110+
111+
```kotlin
112+
val client = S3Client.fromEnvironment {
113+
endpointUrl = Url.parse("https://custom-s3-endpoint.local")
114+
// endpointProvider is left as the default
115+
}
116+
```
117+
118+
### `EndpointProvider`
119+
120+
The following code snippet shows how one might wrap S3's default provider implementation:
121+
122+
```kotlin
123+
import aws.sdk.kotlin.services.s3.endpoints.DefaultEndpointProvider as DefaultEndpointProvider
124+
import aws.sdk.kotlin.services.s3.endpoints.EndpointParams as S3EndpointParams
125+
import aws.sdk.kotlin.services.s3.endpoints.EndpointProvider as S3EndpointProvider
126+
import aws.smithy.kotlin.runtime.client.endpoints.Endpoint
127+
128+
public class CustomS3EndpointProvider : S3EndpointProvider {
129+
override suspend fun resolveEndpoint(params: S3EndpointParams) =
130+
if (/* input params indicate we must route another endpoint for whatever reason */) {
131+
Endpoint(/* ... */)
132+
} else {
133+
DefaultS3EndpointProvider().resolveEndpoint(params)
134+
}
135+
}
136+
```
137+
138+
As demonstrated above, it is recommended to fall back to the default implementation in your own provider.
139+
140+
### `endpointUrl` + `endpointProvider`
141+
142+
The following example program demonstrates the interaction between the `endpointUrl` and `endpointProvider` settings.
143+
**This is an advanced use case:**
144+
145+
```kotlin
146+
import aws.sdk.kotlin.services.s3.S3Client
147+
import aws.sdk.kotlin.services.s3.endpoints.DefaultEndpointProvider as DefaultEndpointProvider
148+
import aws.sdk.kotlin.services.s3.endpoints.EndpointParams as S3EndpointParams
149+
import aws.sdk.kotlin.services.s3.endpoints.EndpointProvider as S3EndpointProvider
150+
import aws.smithy.kotlin.runtime.client.endpoints.Endpoint
151+
152+
fun main() = runBlocking {
153+
S3Client.fromEnvironment {
154+
endpointUrl = Url.parse("https://example.endpoint")
155+
endpointProvider = CustomS3EndpointProvider()
156+
}.use { s3 ->
157+
// ...
158+
}
159+
}
160+
161+
class CustomS3EndpointProvider : S3EndpointProvider {
162+
override suspend fun resolveEndpoint(params: S3EndpointParams) {
163+
println(params.endpoint) // with the above client, the value set for endpointUrl is available here
164+
// ...
165+
}
166+
}
167+
```

0 commit comments

Comments
 (0)