Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTPClientRequest: allow custom TLS config #709

Merged
merged 1 commit into from
Oct 16, 2023

Conversation

weissi
Copy link
Contributor

@weissi weissi commented Aug 15, 2023

In the transition from HTTPClient.Request to HTTPClientRequest we unfortunately lost some important functionality: Custom TLS configuration.

The can be important to override on a per-request basis but more importantly it was the most significant step which allows us to unlock the 3-tier architecture (#392). For the 3-tier architecture it's very important to get rid of any top-level configuration.

This PR brings back the lost functionality.

Copy link
Member

@fabianfett fabianfett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it! I should have done this way earlier! I especially like that we add this property to the request itself and don't pass it as a function parameter!

@fabianfett fabianfett requested a review from Lukasa August 15, 2023 12:25
Copy link
Collaborator

@dnadoba dnadoba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't belongs to the HTTPRequest. It is extra configuration and belongs into a separate type.

This belongs into tier 2 of #392.

@weissi
Copy link
Contributor Author

weissi commented Aug 15, 2023

This doesn't belongs to the HTTPRequest. It is extra configuration and belongs into a separate type.

This belongs into tier 2 of #392.

Hang on, tier 3 can override anything from tier 2.

The idea is:

  • tier 1 (the connection pool/TLS context cache) has (next to) no configuration
  • tier 2 is a holder of configuration you need a lot
  • tier 3 is the request and may override tier 2 for the odd property

For example (hypothetical API):

// HTTPService is tier 1

var client = HTTPClient(service: HTTPService.shared) // HTTPClient is tier-2
client.redirects = .max(5)
client.tlsConfiguration = mySuperSpecialTLSConfig

var apiRequest = HTTPClientRequest() // HTTPClientRequest is tier-3
apiRequest.redirects = .disallow // API request doesn't need redirects
apiRequest.url = "https://..."
try await client.execute(apiRequest)

var webRequest = HTTPClientRequest()
webRequest.url = "https://..."
try await client.execute(webRequest)

@weissi weissi requested a review from dnadoba August 15, 2023 15:45
@dnadoba
Copy link
Collaborator

dnadoba commented Aug 15, 2023

As discussed in #392, there seems to be no point in tier 3 if tier 2 is ~free to create.

@weissi
Copy link
Contributor Author

weissi commented Aug 15, 2023

As discussed in #392, there seems to be no point in tier 3 if tier 2 is ~free to create.

Convenience and you don't need to repeat everything.

For example, I might want to ~always set a certain header in my tier 2 but in tier 3 I definitely want to add headers.

Like

actor FooClient {
    private let client: HTTPClient = {
        var client = HTTPClient(...)
        client.headers["user-agent"] = "my super lib/123"
        [...]
        return client
    }

    func upgradeFoo() {
        var request = HTTPClientRequest()
        request.url = "..."
        request.headers["foo-level"] = "max"
        try await self.client.execute(request)
    }
}

@dnadoba
Copy link
Collaborator

dnadoba commented Aug 15, 2023

I agree with that, default headers on tier 2 are great. What I don't agree with is that tier 3 has override for everything in tier 2. Tier 3 should just be (HTTPRequest, Body) where HTTPRequest is the new type form swift-http-types and Body is some kind of AsyncSequence. Nothing else.

@weissi
Copy link
Contributor Author

weissi commented Aug 15, 2023

I agree with that, default headers on tier 2 are great. What I don't agree with is that tier 3 has override for everything in tier 2. Tier 3 should just be (HTTPRequest, Body) where HTTPRequest is the new type form swift-http-types and Body is some kind of AsyncSequence. Nothing else.

I think that's important. But also irrelevant for this PR. This PR brings back accidentally lost functionality when we added HTTPClientRequest a close friend of HTTPClient.Request (even documented that way). HTTPClient.Request has tlsConfiguration and so should HTTPClientRequest.

In the future when we pick up swift-http-types the whole nomenclature of what a "request" even is will change. Then we can discuss where the various configuration options can go. But this is the wrong place IMHO, we're just adding stuff back that we forgot before.

Copy link
Collaborator

@dnadoba dnadoba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about this again I came to the conclusion that it doesn't hurt to add this before we implement the 3-tier architecture as we will likely deprecate HTTPClientRequest anyway.

Minor nits, afterwards we are good to go from my side.

Sources/AsyncHTTPClient/AsyncAwait/HTTPClientRequest.swift Outdated Show resolved Hide resolved
Tests/AsyncHTTPClientTests/HTTPClientTests.swift Outdated Show resolved Hide resolved

// For now, let's allow bad TLS certs
request.tlsConfiguration = TLSConfiguration.clientDefault
request.tlsConfiguration!.certificateVerification = .none
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets get ride of the force unwrap here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that would hide issues. Force unwraps here are good because they'd catch that going wrong at the right place here

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, nil is the default configuration and it would be reasonable to normalise the input value to become nil after setting it to the default value.

Note that this shouldn't be an Optional property to begin with but that's the current API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that would not be reasonable, that would be user hostile. If this breaks, AHC is broken, let's not hide this

// For the second request, we reset the TLS config
request.tlsConfiguration = nil
do {
let response2 = try await client.execute(request, timeout: .hours(99))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a bit long

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disagree. Tests with timeouts that aren't ~forever really suck because at some point CI is slow and then you'll raise the timeout again. So you never quite know why it failed: Was CI slow or did it actually time out. If we set it to ~never (hours(99)) then if it times out, it'll be killed by CI after the long CI timeout and we know that this is a true failure that needs to be fixed.

That means you'll have one timeout (the CI timeout) that is actually meaningful if it's breached. If everybody puts like .seconds(1) or .seconds(10) all over their tests then it requires constant thinking which leads to everybody just ignoring and restarting failed tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to use .hours(.max) to make this clear but apple/swift-nio#2532

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this doesn't really help with unreasonably slow CIs as there are more timeouts with a default value. Also tests don't only run only CI but also locally and in compatibly suites. A reasonable time limit of 30 seconds is appropriate to be a good citizen if run in a compatibility suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any system that runs tests needs to already deal with tests taking forever. For example if you hit a deadlock. We shouldn't introduce another category of failures.

@weissi
Copy link
Contributor Author

weissi commented Oct 9, 2023

@dnadoba can we get this over the line please?

@dnadoba
Copy link
Collaborator

dnadoba commented Oct 16, 2023

@swift-server-bot test this please

@weissi weissi enabled auto-merge (squash) October 16, 2023 13:56
@weissi weissi merged commit d766674 into swift-server:main Oct 16, 2023
6 of 8 checks passed
@weissi weissi deleted the jw-tls-config branch October 16, 2023 14:10
@gjcairo gjcairo added the semver/minor For PRs that when merged cause a bump of the minor version, ie. 1.x.0 -> 1.(x+1).0 label Dec 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver/minor For PRs that when merged cause a bump of the minor version, ie. 1.x.0 -> 1.(x+1).0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants