mirror of
https://github.com/swift-server/async-http-client.git
synced 2026-06-02 07:37:34 +00:00
Users should be able to use swift-configuration to create the http client configuration object Changes - duplicate package.swift to create a separate version for 6.0 and 6.1, since Configuration is 6.2+ - add helper to create http client configuration using ConfigReader
280 lines
11 KiB
Swift
280 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the AsyncHTTPClient open source project
|
|
//
|
|
// Copyright (c) 2026 Apple Inc. and the AsyncHTTPClient project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if compiler(>=6.2)
|
|
import Configuration
|
|
import Foundation
|
|
import NIOCore
|
|
import Testing
|
|
|
|
@testable import AsyncHTTPClient
|
|
|
|
struct HTTPClientConfigurationPropsTests {
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func allPropertiesAreSetFromConfig() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": .init(.stringArray(["localhost:127.0.0.1", "example.com:192.168.1.1"]), isSecret: false),
|
|
"redirect.mode": "follow",
|
|
"redirect.maxRedirects": 10,
|
|
"redirect.allowCycles": true,
|
|
|
|
"timeout.connectionMs": 5000,
|
|
"timeout.readMs": 30000,
|
|
"timeout.writeMs": 15000,
|
|
|
|
"connectionPool.idleTimeoutMs": 120_000,
|
|
"connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit": 16,
|
|
"connectionPool.retryConnectionEstablishment": false,
|
|
"connectionPool.preWarmedHTTP1ConnectionCount": 5,
|
|
|
|
"httpVersion": "http1Only",
|
|
"maximumUsesPerConnection": 100,
|
|
])
|
|
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.dnsOverride["localhost"] == "127.0.0.1")
|
|
#expect(config.dnsOverride["example.com"] == "192.168.1.1")
|
|
|
|
switch config.redirectConfiguration.mode {
|
|
case .follow(let max, let allowCycles):
|
|
#expect(max == 10)
|
|
#expect(allowCycles)
|
|
case .disallow:
|
|
Issue.record("Unexpected value")
|
|
}
|
|
|
|
#expect(config.timeout.connect == .milliseconds(5000))
|
|
#expect(config.timeout.read == .milliseconds(30000))
|
|
#expect(config.timeout.write == .milliseconds(15000))
|
|
|
|
#expect(config.connectionPool.idleTimeout == .milliseconds(120000))
|
|
#expect(config.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit == 16)
|
|
#expect(config.connectionPool.retryConnectionEstablishment == false)
|
|
#expect(config.connectionPool.preWarmedHTTP1ConnectionCount == 5)
|
|
|
|
#expect(config.httpVersion == .http1Only)
|
|
|
|
#expect(config.maximumUsesPerConnection == 100)
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func defaultsAreUsedWhenConfigIsEmpty() throws {
|
|
let testProvider = InMemoryProvider(values: [:])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.dnsOverride.isEmpty)
|
|
|
|
#expect(config.timeout.connect == nil)
|
|
#expect(config.timeout.read == nil)
|
|
#expect(config.timeout.write == nil)
|
|
|
|
#expect(config.connectionPool.idleTimeout == .seconds(60))
|
|
#expect(config.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit == 8)
|
|
#expect(config.connectionPool.retryConnectionEstablishment == true)
|
|
#expect(config.connectionPool.preWarmedHTTP1ConnectionCount == 0)
|
|
|
|
#expect(config.httpVersion == .automatic)
|
|
|
|
#expect(config.maximumUsesPerConnection == nil)
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func redirectConfigurationDisallow() throws {
|
|
let testProvider = InMemoryProvider(values: ["redirect.mode": "disallow"])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
switch config.redirectConfiguration.mode {
|
|
case .disallow:
|
|
break
|
|
case .follow:
|
|
Issue.record("Unexpected value")
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func redirectConfigurationInvalidModeThrowsError() throws {
|
|
let testProvider = InMemoryProvider(values: ["redirect.mode": "invalid"])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
#expect(throws: HTTPClientError.invalidRedirectConfiguration) {
|
|
_ = try HTTPClient.Configuration(configReader: configReader)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func httpVersionAutomatic() throws {
|
|
let testProvider = InMemoryProvider(values: ["httpVersion": "automatic"])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.httpVersion == .automatic)
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func httpVersionInvalidThrowsError() throws {
|
|
let testProvider = InMemoryProvider(values: ["httpVersion": "http3"])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
#expect(throws: HTTPClientError.invalidHTTPVersionConfiguration) {
|
|
try HTTPClient.Configuration(configReader: configReader)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func dnsOverridesWithIPv6() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": .init(.stringArray(["localhost:::1", "example.com:2001:db8::1"]), isSecret: false)
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.dnsOverride["localhost"] == "::1")
|
|
#expect(config.dnsOverride["example.com"] == "2001:db8::1")
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func dnsOverridesWithInvalidFormat() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": .init(.stringArray(["invalidentry", "localhost:127.0.0.1"]), isSecret: false)
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
#expect(throws: HTTPClientError.invalidDNSOverridesConfiguration) {
|
|
_ = try HTTPClient.Configuration(configReader: configReader)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func dnsOverridesWithBlankValue() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": .init(.stringArray(["localhost:"]), isSecret: false)
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
#expect(throws: HTTPClientError.invalidDNSOverridesConfiguration) {
|
|
_ = try HTTPClient.Configuration(configReader: configReader)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func dnsOverridesWithBlankKey() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": .init(.stringArray([":127.0.0.1"]), isSecret: false)
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
#expect(throws: HTTPClientError.invalidDNSOverridesConfiguration) {
|
|
_ = try HTTPClient.Configuration(configReader: configReader)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func dnsOverridesWithSpaces() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": .init(.stringArray(["test.com: 127.0.0.1"]), isSecret: false)
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
#expect(config.dnsOverride == ["test.com": "127.0.0.1"])
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func timeoutConfigurationPartial() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"timeout.connectionMs": 1000,
|
|
"timeout.readMs": 2000,
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.timeout.connect == .milliseconds(1000))
|
|
#expect(config.timeout.read == .milliseconds(2000))
|
|
#expect(config.timeout.write == nil)
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func connectionPoolConfigurationPartial() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"connectionPool.idleTimeoutMs": 90000,
|
|
"connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit": 12,
|
|
])
|
|
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.connectionPool.idleTimeout == .milliseconds(90000))
|
|
#expect(config.connectionPool.concurrentHTTP1ConnectionsPerHostSoftLimit == 12)
|
|
// These should use defaults
|
|
#expect(config.connectionPool.retryConnectionEstablishment)
|
|
#expect(config.connectionPool.preWarmedHTTP1ConnectionCount == 0)
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func redirectConfigurationWithDefaults() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"redirect.mode": "follow"
|
|
])
|
|
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
#expect(config.redirectConfiguration.mode == .follow(max: 5, allowCycles: false))
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func redirectConfigurationCustomValues() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"redirect.mode": "follow",
|
|
"redirect.maxRedirects": 3,
|
|
"redirect.allowCycles": true,
|
|
])
|
|
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.redirectConfiguration.mode == .follow(max: 3, allowCycles: true))
|
|
}
|
|
|
|
@Test
|
|
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
|
|
func emptyDnsOverridesArray() throws {
|
|
let testProvider = InMemoryProvider(values: [
|
|
"dnsOverrides": "[]"
|
|
])
|
|
let configReader = ConfigReader(provider: testProvider)
|
|
let config = try HTTPClient.Configuration(configReader: configReader)
|
|
|
|
#expect(config.dnsOverride.isEmpty)
|
|
}
|
|
}
|
|
#endif
|