smithy-lang/smithy-swift

`SdkHttpRequest().headers` is immutable

Closed this issue · 6 comments

Describe the bug

The headers property of SdkHttpRequest is immutable since 0.20.0.
This unnecessarily prevents the callers from injecting headers using a custom HttpClientEngine.

Expected Behavior

Ideally, expose an easier way to inject headers on a request level.
But in the absence of that, please make headers mutable.

Current Behavior

SdkHttpRequest().headers is immutable.

Reproduction Steps

class MyHTTPClientEngineProxy: HttpClientEngine {
    let base: HttpClientEngine

    init(base: HttpClientEngine) {
        self.base = base
    }

    func execute(request: SdkHttpRequest) async throws -> HttpResponse {
        // ❌ Cannot use mutating member on immutable value: 'headers' is a get-only property
        request.headers.add(name: "foo", value: "bar")
        return try await base.execute(request: request)
    }
}

Possible Solution

Add a setter to headers.

Additional Information/Context

No response

AWS SWIFT SDK version used

0.24.0

Compiler and Version used

n/a

Operating System and version

Darwin

Addressed in PR: #588

Where a convenience method toBuilder() is added to SdkHttpRequest to allow seamless update of a built SdkHttpRequest object.

Let's say oldRequest is a built SdkHttpRequest object and you want to add a new header x-amz: example to it.
To do so, you can do:

let newRequest = oldRequest.toBuilder().withHeader(name: "x-amz", value: "example").build()

Closed.

Re-opened; will be closed with the next minor version release of Swift SDK.

Thanks for taking this on @sichanyoo.
If I'm not mistaken, this doesn't resolve our issue though. The new toBuilder().with...().build() API is creating a new instance of SdkHttpRequest, not mutating the existing one.

In other words, this

func execute(request: SdkHttpRequest) async throws -> HttpResponse {
    let newRequest = request.toBuilder().withHeader(name: "foo", value: "bar").build()
    return try await base.execute(request: newRequest)
}

is the same as this, which we can already do today

func execute(request: SdkHttpRequest) async throws -> HttpResponse {
    var headers = request.headers
    headers.add(name: "foo", value: "bar")
    let newRequest = SdkHttpRequest(
        method: request.method,
        endpoint: request.endpoint,
        headers: headers,
        queryItems: request.queryItems,
        body: request.body
    )
    return try await base.execute(request: newRequest)
}

However, this doesn't work because the instance of SdkHttpRequest passed to execute(request:) is referenced somewhere in ClientRuntime and/or CRT, and the implementation depends on that same instance being used when firing the request.

I see - as discussed offline, I'll look into whether there's a standard way in SDKs to allow adding headers and get back to you.

Released in AWS SDK for Swift 0.26.0