|
1 | 1 | # Configuring Client Endpoints |
2 | 2 |
|
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. |
7 | 5 |
|
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 |
13 | 10 |
|
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). |
15 | 32 |
|
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. |
17 | 38 |
|
18 | 39 | ```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 | +``` |
20 | 44 |
|
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") |
24 | 82 | } |
25 | 83 | } |
26 | 84 | ``` |
| 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