

# Configure Client Endpoints
<a name="configure-endpoints"></a>

**Warning**  
 Endpoint resolution is an advanced SDK topic. By changing these settings you risk breaking your code. The default settings should be applicable to most users in production environments. 

 The AWS SDK for Go provides the ability to configure a custom endpoint to be used for a service. In most cases, the default configuration will suffice. Configuring custom endpoints allows for additional behavior, such as working with pre-release versions of a service. 

## Customization
<a name="customization"></a>

 There are two "versions" of endpoint resolution config within the SDK. 
+  v2, released in Q3 of 2023, configured via: 
  +  `EndpointResolverV2` 
  +  `BaseEndpoint` 
+  v1, released alongside the SDK, configured via: 
  +  `EndpointResolver` 

 We recommend users of v1 endpoint resolution migrate to v2 to obtain access to newer endpoint-related service features. 

## V2: `EndpointResolverV2` \$1 `BaseEndpoint`
<a name="v2-endpointresolverv2--baseendpoint"></a>

 In resolution v2, `EndpointResolverV2` is the definitive mechanism through which endpoint resolution occurs. The resolver's `ResolveEndpoint` method is invoked as part of the workflow for every request you make in the SDK. The hostname of the `Endpoint` returned by the resolver is used **as-is** when making the request (operation serializers can still append to the HTTP path, however). 

 Resolution v2 includes an additional client-level config, `BaseEndpoint`, which is used to specify a "base" hostname for the instance of your service. The value set here is not definitive-- it is ultimately passed as a parameter to the client's `EndpointResolverV2` when final resolution occurs (read on for more information about `EndpointResolverV2` parameters). The resolver implementation then has the opportunity to inspect and potentially modify that value to determine the final endpoint. 

 For example, if you perform an S3 `GetObject` request against a given bucket with a client where you've specified a `BaseEndpoint`, the default resolver will inject the bucket into the hostname if it is virtual-host compatible (assuming you haven't disabled virtual-hosting in client config). 

 In practice, `BaseEndpoint` will most likely be used to point your client at a development or preview instance of a service. 

### `EndpointResolverV2` parameters
<a name="endpointresolverv2-parameters"></a>

 Each service takes a specific set of inputs which are passed to its resolution function, defined in each service package as `EndpointParameters`. 

 Every service includes the following base parameters, which are used to facilitate general endpoint resolution within AWS: 


|  name  |  type  |  description  | 
| --- | --- | --- | 
|  Region  |  string  |  The client's AWS region  | 
|  Endpoint  |  string  |  The value set for BaseEndpoint in client config  | 
|  UseFips  |  bool  |  Whether FIPS endpoints are enabled in client config  | 
|  UseDualStack  |  bool  |  Whether dual-stack endpoints are enabled in client config  | 

 Services can specify additional parameters required for resolution. For example, S3's `EndpointParameters` include the bucket name, as well as several S3-specific feature settings such as whether virtual host addressing is enabled. 

 If you are implementing your own `EndpointResolverV2`, you should never need to construct your own instance of `EndpointParameters`. The SDK will source the values per-request and pass them to your implementation. 

### A note about Amazon S3
<a name="a-note-about-amazon-s3"></a>

 Amazon S3 is a complex service with many of its features modeled through complex endpoint customizations, such as bucket virtual hosting, S3 MRAP, and more. 

 Because of this, we recommend that you don't replace the `EndpointResolverV2` implementation in your S3 client. If you need to extend its resolution behavior, perhaps by sending requests to a local development stack with additional endpoint considerations, we recommend wrapping the default implementation such that it delegates back to the default as a fallback (shown in examples below). 

### Examples
<a name="examples"></a>

#### With `BaseEndpoint`
<a name="with-baseendpoint"></a>

 The following code snippet shows how to point your S3 client at a local instance of a service, which in this example is hosted on the loopback device at port 8080. 

```
client := s3.NewFromConfig(cfg, func (o *svc.Options) {
    o.BaseEndpoint = aws.String("https://localhost:8080/")
})
```

#### With `EndpointResolverV2`
<a name="with-endpointresolverv2"></a>

 The following code snippet shows how to inject custom behavior into S3's endpoint resolution using `EndpointResolverV2`. 

```
import (
    "context"
    "net/url"

    "github.com/aws/aws-sdk-go-v2/service/s3"
    smithyendpoints "github.com/aws/smithy-go/endpoints"
)

type resolverV2 struct {
    // you could inject additional application context here as well
}

func (*resolverV2) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (
        smithyendpoints.Endpoint, error,
    ) {
    if /* input params or caller context indicate we must route somewhere */ {
        u, err := url.Parse("https://custom.service.endpoint/")
        if err != nil {
            return smithyendpoints.Endpoint{}, err
        }
        return smithyendpoints.Endpoint{
            URI: *u,
        }, nil
    }

    // delegate back to the default v2 resolver otherwise
    return s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}

func main() {
    // load config...

    client := s3.NewFromConfig(cfg, func (o *s3.Options) {
        o.EndpointResolverV2 = &resolverV2{
            // ...
        }
    })
}
```

#### With both
<a name="with-both"></a>

 The following sample program demonstrates the interaction between `BaseEndpoint` and `EndpointResolverV2`. **This is an advanced use case:** 

```
import (
    "context"
    "fmt"
    "log"
    "net/url"

    "github.com/aws/aws-sdk-go-v2"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    smithyendpoints "github.com/aws/smithy-go/endpoints"
)

type resolverV2 struct {}

func (*resolverV2) ResolveEndpoint(ctx context.Context, params s3.EndpointParameters) (
        smithyendpoints.Endpoint, error,
    ) {
    // s3.Options.BaseEndpoint is accessible here:
    fmt.Printf("The endpoint provided in config is %s\n", *params.Endpoint)

    // fallback to default
    return s3.NewDefaultEndpointResolverV2().ResolveEndpoint(ctx, params)
}

func main() {
    cfg, err := config.LoadDefaultConfig(context.Background())
    if (err != nil) {
        log.Fatal(err)
    }

    client := s3.NewFromConfig(cfg, func (o *s3.Options) {
        o.BaseEndpoint = aws.String("https://endpoint.dev/")
        o.EndpointResolverV2 = &resolverV2{}
    })

    // ignore the output, this is just for demonstration
    client.ListBuckets(context.Background(), nil)
}
```

 When run, the above program outputs the following: 

```
The endpoint provided in config is https://endpoint.dev/
```

## V1: `EndpointResolver`
<a name="v1-endpointresolver"></a>

**Warning**  
 Endpoint resolution v1 is retained for backwards compatibility and is isolated from the modern behavior in endpoint resolution v2. It will only be used if the `EndpointResolver` field is set by the caller.   
 Use of v1 will most likely prevent you from accessing endpoint-related service features introduced with or after the release of v2 resolution. See "Migration" for instructions on how to upgrade. 

 A [EndpointResolver](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#EndpointResolver) can be configured to provide custom endpoint resolution logic for service clients. You can use a custom endpoint resolver to override a service's endpoint resolution logic for all endpoints, or a just specific regional endpoint. Custom endpoint resolver can trigger the service's endpoint resolution logic to fallback if a custom resolver does not wish to resolve a requested endpoint. [EndpointResolverWithOptionsFunc](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#EndpointResolverWithOptionsFunc) can be used to easily wrap functions to satisfy the `EndpointResolverWithOptions` interface. 

 A `EndpointResolver` can be easily configured by passing the resolver wrapped with [WithEndpointResolverWithOptions](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config#WithEndpointResolverWithOptions) to [LoadDefaultConfig](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/config#LoadDefaultConfig) , allowing for the ability to override endpoints when loading credentials, as well as configuring the resulting `aws.Config` with your custom endpoint resolver. 

 The endpoint resolver is given the service and region as a string, allowing for the resolver to dynamically drive its behavior. Each service client package has an exported `ServiceID` constant which can be used to determine which service client is invoking your endpoint resolver. 

 An endpoint resolver can use the [EndpointNotFoundError](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#EndpointNotFoundError) sentinel error value to trigger fallback resolution to the service clients default resolution logic. This allows you to selectively override one or more endpoints seamlessly without having to handle fallback logic. 

 If your endpoint resolver implementation returns an error other than `EndpointNotFoundError`, endpoint resolution will stop and the service operation returns an error to your application. 

### Examples
<a name="examples-1"></a>

#### With fallback
<a name="with-fallback"></a>

 The following code snippet shows how a single service endpoint can be overridden for DynamoDB with fallback behavior for other endpoints: 

```
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
    if service == dynamodb.ServiceID && region == "us-west-2" {
        return aws.Endpoint{
            PartitionID:   "aws",
            URL:           "https://test.us-west-2.amazonaws.com",
            SigningRegion: "us-west-2",
        }, nil
    }
    // returning EndpointNotFoundError will allow the service to fallback to it's default resolution
    return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})

cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithEndpointResolverWithOptions(customResolver))
```

#### Without fallback
<a name="without-fallback"></a>

 The following code snippet shows how a single service endpoint can be overridden for DynamoDB without fallback behavior for other endpoints: 

```
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
    if service == dynamodb.ServiceID && region == "us-west-2" {
        return aws.Endpoint{
            PartitionID:   "aws",
            URL:           "https://test.us-west-2.amazonaws.com",
            SigningRegion: "us-west-2",
        }, nil
    }
    return aws.Endpoint{}, fmt.Errorf("unknown endpoint requested")
})

cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithEndpointResolverWithOptions(customResolver))
```

### Immutable endpoints
<a name="immutable-endpoints"></a>

**Warning**  
 Setting an endpoint as immutable may prevent some service client features from functioning correctly, and could result in undefined behavior. Caution should be taken when defining an endpoint as immutable. 

 Some service clients, such as Amazon S3, may modify the endpoint returned by the resolver for certain service operations. For example, Amazon S3 will automatically handle [Virtual Bucket Addressing](https://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html) by mutating the resolved endpoint. You can prevent the SDK from mutating your custom endpoints by setting [HostnameImmutable](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#Endpoint.HostnameImmutable) to `true`. For example: 

```
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
    if service == dynamodb.ServiceID && region == "us-west-2" {
        return aws.Endpoint{
            PartitionID:   "aws",
            URL:           "https://test.us-west-2.amazonaws.com",
            SigningRegion: "us-west-2",
            HostnameImmutable: true,
        }, nil
    }
    return aws.Endpoint{}, fmt.Errorf("unknown endpoint requested")
})

cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithEndpointResolverWithOptions(customResolver))
```

## Migration
<a name="migration"></a>

 When migrating from v1 to v2 of endpoint resolution, the following general principles apply: 
+  Returning an [Endpoint](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#Endpoint) with [HostnameImmutable](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#Endpoint.HostnameImmutable) set to `false` is roughly equivalent to setting `BaseEndpoint` to the originally returned URL from v1 and leaving `EndpointResolverV2` as the default. 
+  Returning an Endpoint with HostnameImmutable set to `true` is roughly equivalent to implementing an `EndpointResolverV2` which returns the originally returned URL from v1. 
  +  The primary exception is for operations with modeled endpoint prefixes. A note on this is given further down. 

 Examples for these cases are provided below. 

**Warning**  
 V1 immutable endpoints and V2 resolution are not equivalent in behavior. For example, signing overrides for custom features like S3 Object Lambda would still be set for immutable endpoints returned via v1 code, but the same will not be done for v2. 

### Note on host prefixes
<a name="note-on-host-prefixes"></a>

 Some operations are modeled with host prefixes to be prepended to the resolved endpoint. This behavior must work in tandem with the output of ResolveEndpointV2 and therefore the host prefix will still be applied to that result. 

 You can manually disable endpoint host prefixing by applying a middleware, see the examples section. 

### Examples
<a name="examples-2"></a>

#### Mutable endpoint
<a name="mutable-endpoint"></a>

 The following code sample demonstrates how to migrate a basic v1 endpoint resolver that returns a modifiable endpoint: 

```
// v1
client := svc.NewFromConfig(cfg, func (o *svc.Options) {
    o.EndpointResolver = svc.EndpointResolverFromURL("https://custom.endpoint.api/")
})

// v2
client := svc.NewFromConfig(cfg, func (o *svc.Options) {
    // the value of BaseEndpoint is passed to the default EndpointResolverV2
    // implementation, which will handle routing for features such as S3 accelerate,
    // MRAP, etc.
    o.BaseEndpoint = aws.String("https://custom.endpoint.api/")
})
```

#### Immutable endpoint
<a name="immutable-endpoint"></a>

```
// v1
client := svc.NewFromConfig(cfg, func (o *svc.Options) {
    o.EndpointResolver = svc.EndpointResolverFromURL("https://custom.endpoint.api/", func (e *aws.Endpoint) {
        e.HostnameImmutable = true
    })
})

// v2
import (
    smithyendpoints "github.com/aws/smithy-go/endpoints"
)

type staticResolver struct {}

func (*staticResolver) ResolveEndpoint(ctx context.Context, params svc.EndpointParameters) (
        smithyendpoints.Endpoint, error,
    ) {
    // This value will be used as-is when making the request.
    u, err := url.Parse("https://custom.endpoint.api/")
    if err != nil {
        return smithyendpoints.Endpoint{}, err
    }
    return smithyendpoints.Endpoint{
        URI: *u,
    }, nil
}

client := svc.NewFromConfig(cfg, func (o *svc.Options) {
    o.EndpointResolverV2 = &staticResolver{}
})
```

#### Disable host prefix
<a name="disable-host-prefix"></a>

```
import (
    "context"
    "fmt"
    "net/url"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/<service>"
    smithyendpoints "github.com/aws/smithy-go/endpoints"
    "github.com/aws/smithy-go/middleware"
    smithyhttp "github.com/aws/smithy-go/transport/http"
)

// disableEndpointPrefix applies the flag that will prevent any
// operation-specific host prefix from being applied
type disableEndpointPrefix struct{}

func (disableEndpointPrefix) ID() string { return "disableEndpointPrefix" }

func (disableEndpointPrefix) HandleInitialize(
    ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (middleware.InitializeOutput, middleware.Metadata, error) {
    ctx = smithyhttp.SetHostnameImmutable(ctx, true)
    return next.HandleInitialize(ctx, in)
}

func addDisableEndpointPrefix(o *<service>.Options) {
    o.APIOptions = append(o.APIOptions, (func(stack *middleware.Stack) error {
        return stack.Initialize.Add(disableEndpointPrefix{}, middleware.After)
    }))
}

type staticResolver struct{}

func (staticResolver) ResolveEndpoint(ctx context.Context, params <service>.EndpointParameters) (
    smithyendpoints.Endpoint, error,
) {
    u, err := url.Parse("https://custom.endpoint.api/")
    if err != nil {
        return smithyendpoints.Endpoint{}, err
    }

    return smithyendpoints.Endpoint{URI: *u}, nil
}


func main() {
    cfg, err := config.LoadDefaultConfig(context.Background())
    if err != nil {
        panic(err)
    }

    svc := <service>.NewFromConfig(cfg, func(o *<service>.Options) {
        o.EndpointResolverV2 = staticResolver{}
    })

    _, err = svc.<Operation>(context.Background(), &<service>.<OperationInput>{ /* ... */ },
        addDisableEndpointPrefix)
    if err != nil {
        panic(err)
    }
}
```