Skip to content

Commit 99bd384

Browse files
authored
Refactor deconstructURL and scheme parsing (#504)
* make `Scheme` a type * introduce new Endpoint type * use endpoint as storage in `HTTPClient.Request` * fix merge conflicts * rename Endpoint to DeconstructedURL * swift-format * make `DeconstructedURL` properties `var`'s * move scheme into global namespace - rename `useTLS` to `usesTLS` where posible without breaking public API - only import Foundation.URL * fix review comments
1 parent 70826d0 commit 99bd384

File tree

6 files changed

+200
-165
lines changed

6 files changed

+200
-165
lines changed

Sources/AsyncHTTPClient/ConnectionPool.swift

+2-41
Original file line numberDiff line numberDiff line change
@@ -24,52 +24,13 @@ enum ConnectionPool {
2424
private var tlsConfiguration: BestEffortHashableTLSConfiguration?
2525

2626
init(_ request: HTTPClient.Request) {
27-
self.connectionTarget = request.connectionTarget
28-
switch request.scheme {
29-
case "http":
30-
self.scheme = .http
31-
case "https":
32-
self.scheme = .https
33-
case "unix":
34-
self.scheme = .unix
35-
case "http+unix":
36-
self.scheme = .http_unix
37-
case "https+unix":
38-
self.scheme = .https_unix
39-
default:
40-
fatalError("HTTPClient.Request scheme should already be a valid one")
41-
}
27+
self.scheme = request.deconstructedURL.scheme
28+
self.connectionTarget = request.deconstructedURL.connectionTarget
4229
if let tls = request.tlsConfiguration {
4330
self.tlsConfiguration = BestEffortHashableTLSConfiguration(wrapping: tls)
4431
}
4532
}
4633

47-
enum Scheme: Hashable {
48-
case http
49-
case https
50-
case unix
51-
case http_unix
52-
case https_unix
53-
54-
var requiresTLS: Bool {
55-
switch self {
56-
case .https, .https_unix:
57-
return true
58-
default:
59-
return false
60-
}
61-
}
62-
}
63-
64-
/// Returns a key-specific `HTTPClient.Configuration` by overriding the properties of `base`
65-
func config(overriding base: HTTPClient.Configuration) -> HTTPClient.Configuration {
66-
var config = base
67-
if let tlsConfiguration = self.tlsConfiguration {
68-
config.tlsConfiguration = tlsConfiguration.base
69-
}
70-
return config
71-
}
72-
7334
var description: String {
7435
var hasher = Hasher()
7536
self.tlsConfiguration?.hash(into: &hasher)

Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift

+7-7
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ extension HTTPConnectionPool.ConnectionFactory {
185185
logger: Logger
186186
) -> EventLoopFuture<NegotiatedProtocol> {
187187
switch self.key.scheme {
188-
case .http, .http_unix, .unix:
188+
case .http, .httpUnix, .unix:
189189
return self.makePlainChannel(deadline: deadline, eventLoop: eventLoop).map { .http1_1($0) }
190-
case .https, .https_unix:
190+
case .https, .httpsUnix:
191191
return self.makeTLSChannel(deadline: deadline, eventLoop: eventLoop, logger: logger).flatMapThrowing {
192192
channel, negotiated in
193193

@@ -197,7 +197,7 @@ extension HTTPConnectionPool.ConnectionFactory {
197197
}
198198

199199
private func makePlainChannel(deadline: NIODeadline, eventLoop: EventLoop) -> EventLoopFuture<Channel> {
200-
precondition(!self.key.scheme.requiresTLS, "Unexpected scheme")
200+
precondition(!self.key.scheme.usesTLS, "Unexpected scheme")
201201
return self.makePlainBootstrap(deadline: deadline, eventLoop: eventLoop).connect(target: self.key.connectionTarget)
202202
}
203203

@@ -283,7 +283,7 @@ extension HTTPConnectionPool.ConnectionFactory {
283283
logger: Logger
284284
) -> EventLoopFuture<NegotiatedProtocol> {
285285
switch self.key.scheme {
286-
case .unix, .http_unix, .https_unix:
286+
case .unix, .httpUnix, .httpsUnix:
287287
preconditionFailure("Unexpected scheme. Not supported for proxy!")
288288
case .http:
289289
return channel.eventLoop.makeSucceededFuture(.http1_1(channel))
@@ -356,7 +356,7 @@ extension HTTPConnectionPool.ConnectionFactory {
356356
}
357357

358358
private func makeTLSChannel(deadline: NIODeadline, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, String?)> {
359-
precondition(self.key.scheme.requiresTLS, "Unexpected scheme")
359+
precondition(self.key.scheme.usesTLS, "Unexpected scheme")
360360
let bootstrapFuture = self.makeTLSBootstrap(
361361
deadline: deadline,
362362
eventLoop: eventLoop,
@@ -470,12 +470,12 @@ extension HTTPConnectionPool.ConnectionFactory {
470470
}
471471
}
472472

473-
extension ConnectionPool.Key.Scheme {
473+
extension Scheme {
474474
var isProxyable: Bool {
475475
switch self {
476476
case .http, .https:
477477
return true
478-
case .unix, .http_unix, .https_unix:
478+
case .unix, .httpUnix, .httpsUnix:
479479
return false
480480
}
481481
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import struct Foundation.URL
16+
17+
struct DeconstructedURL {
18+
var scheme: Scheme
19+
var connectionTarget: ConnectionTarget
20+
var uri: String
21+
22+
init(
23+
scheme: Scheme,
24+
connectionTarget: ConnectionTarget,
25+
uri: String
26+
) {
27+
self.scheme = scheme
28+
self.connectionTarget = connectionTarget
29+
self.uri = uri
30+
}
31+
}
32+
33+
extension DeconstructedURL {
34+
init(url: URL) throws {
35+
guard let schemeString = url.scheme else {
36+
throw HTTPClientError.emptyScheme
37+
}
38+
guard let scheme = Scheme(rawValue: schemeString.lowercased()) else {
39+
throw HTTPClientError.unsupportedScheme(schemeString)
40+
}
41+
42+
switch scheme {
43+
case .http, .https:
44+
guard let host = url.host, !host.isEmpty else {
45+
throw HTTPClientError.emptyHost
46+
}
47+
self.init(
48+
scheme: scheme,
49+
connectionTarget: .init(remoteHost: host, port: url.port ?? scheme.defaultPort),
50+
uri: url.uri
51+
)
52+
53+
case .httpUnix, .httpsUnix:
54+
guard let socketPath = url.host, !socketPath.isEmpty else {
55+
throw HTTPClientError.missingSocketPath
56+
}
57+
self.init(
58+
scheme: scheme,
59+
connectionTarget: .unixSocket(path: socketPath),
60+
uri: url.uri
61+
)
62+
63+
case .unix:
64+
let socketPath = url.baseURL?.path ?? url.path
65+
let uri = url.baseURL != nil ? url.uri : "/"
66+
guard !socketPath.isEmpty else {
67+
throw HTTPClientError.missingSocketPath
68+
}
69+
self.init(
70+
scheme: scheme,
71+
connectionTarget: .unixSocket(path: socketPath),
72+
uri: uri
73+
)
74+
}
75+
}
76+
}

Sources/AsyncHTTPClient/HTTPHandler.swift

+19-84
Original file line numberDiff line numberDiff line change
@@ -92,82 +92,16 @@ extension HTTPClient {
9292

9393
/// Represent HTTP request.
9494
public struct Request {
95-
/// Represent kind of Request
96-
enum Kind: Equatable {
97-
enum UnixScheme: Equatable {
98-
case baseURL
99-
case http_unix
100-
case https_unix
101-
}
102-
103-
/// Remote host request.
104-
case host
105-
/// UNIX Domain Socket HTTP request.
106-
case unixSocket(_ scheme: UnixScheme)
107-
108-
private static var hostRestrictedSchemes: Set = ["http", "https"]
109-
private static var allSupportedSchemes: Set = ["http", "https", "unix", "http+unix", "https+unix"]
110-
111-
func supportsRedirects(to scheme: String?) -> Bool {
112-
guard let scheme = scheme?.lowercased() else { return false }
113-
114-
switch self {
115-
case .host:
116-
return Kind.hostRestrictedSchemes.contains(scheme)
117-
case .unixSocket:
118-
return Kind.allSupportedSchemes.contains(scheme)
119-
}
120-
}
121-
}
122-
123-
static func useTLS(_ scheme: String) -> Bool {
124-
return scheme == "https" || scheme == "https+unix"
125-
}
126-
127-
static func deconstructURL(
128-
_ url: URL
129-
) throws -> (kind: Kind, scheme: String, connectionTarget: ConnectionTarget, uri: String) {
130-
guard let scheme = url.scheme?.lowercased() else {
131-
throw HTTPClientError.emptyScheme
132-
}
133-
switch scheme {
134-
case "http", "https":
135-
guard let host = url.host, !host.isEmpty else {
136-
throw HTTPClientError.emptyHost
137-
}
138-
let defaultPort = self.useTLS(scheme) ? 443 : 80
139-
let hostTarget = ConnectionTarget(remoteHost: host, port: url.port ?? defaultPort)
140-
return (.host, scheme, hostTarget, url.uri)
141-
case "http+unix", "https+unix":
142-
guard let socketPath = url.host, !socketPath.isEmpty else {
143-
throw HTTPClientError.missingSocketPath
144-
}
145-
let socketTarget = ConnectionTarget.unixSocket(path: socketPath)
146-
let kind = self.useTLS(scheme) ? Kind.UnixScheme.https_unix : .http_unix
147-
return (.unixSocket(kind), scheme, socketTarget, url.uri)
148-
case "unix":
149-
let socketPath = url.baseURL?.path ?? url.path
150-
let uri = url.baseURL != nil ? url.uri : "/"
151-
guard !socketPath.isEmpty else {
152-
throw HTTPClientError.missingSocketPath
153-
}
154-
let socketTarget = ConnectionTarget.unixSocket(path: socketPath)
155-
return (.unixSocket(.baseURL), scheme, socketTarget, uri)
156-
default:
157-
throw HTTPClientError.unsupportedScheme(url.scheme!)
158-
}
159-
}
160-
16195
/// Request HTTP method, defaults to `GET`.
16296
public let method: HTTPMethod
16397
/// Remote URL.
16498
public let url: URL
99+
165100
/// Remote HTTP scheme, resolved from `URL`.
166-
public let scheme: String
167-
/// The connection target, resolved from `URL`.
168-
let connectionTarget: ConnectionTarget
169-
/// URI composed of the path and query, resolved from `URL`.
170-
let uri: String
101+
public var scheme: String {
102+
self.deconstructedURL.scheme.rawValue
103+
}
104+
171105
/// Request custom HTTP Headers, defaults to no headers.
172106
public var headers: HTTPHeaders
173107
/// Request body, defaults to no body.
@@ -180,8 +114,10 @@ extension HTTPClient {
180114
var visited: Set<URL>?
181115
}
182116

117+
/// Parsed, validated and deconstructed URL.
118+
let deconstructedURL: DeconstructedURL
119+
183120
var redirectState: RedirectState?
184-
let kind: Kind
185121

186122
/// Create HTTP request.
187123
///
@@ -252,7 +188,8 @@ extension HTTPClient {
252188
/// - `emptyHost` if URL does not contains a host.
253189
/// - `missingSocketPath` if URL does not contains a socketPath as an encoded host.
254190
public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration?) throws {
255-
(self.kind, self.scheme, self.connectionTarget, self.uri) = try Request.deconstructURL(url)
191+
self.deconstructedURL = try DeconstructedURL(url: url)
192+
256193
self.redirectState = nil
257194
self.url = url
258195
self.method = method
@@ -261,14 +198,9 @@ extension HTTPClient {
261198
self.tlsConfiguration = tlsConfiguration
262199
}
263200

264-
/// Whether request will be executed using secure socket.
265-
public var useTLS: Bool {
266-
return Request.useTLS(self.scheme)
267-
}
268-
269201
/// Remote host, resolved from `URL`.
270202
public var host: String {
271-
switch self.connectionTarget {
203+
switch self.deconstructedURL.connectionTarget {
272204
case .ipAddress(let serialization, _): return serialization
273205
case .domain(let name, _): return name
274206
case .unixSocket: return ""
@@ -277,25 +209,28 @@ extension HTTPClient {
277209

278210
/// Resolved port.
279211
public var port: Int {
280-
switch self.connectionTarget {
212+
switch self.deconstructedURL.connectionTarget {
281213
case .ipAddress(_, let address): return address.port!
282214
case .domain(_, let port): return port
283-
case .unixSocket: return Request.useTLS(self.scheme) ? 443 : 80
215+
case .unixSocket: return self.deconstructedURL.scheme.defaultPort
284216
}
285217
}
286218

219+
/// Whether request will be executed using secure socket.
220+
public var useTLS: Bool { self.deconstructedURL.scheme.usesTLS }
221+
287222
func createRequestHead() throws -> (HTTPRequestHead, RequestFramingMetadata) {
288223
var head = HTTPRequestHead(
289224
version: .http1_1,
290225
method: self.method,
291-
uri: self.uri,
226+
uri: self.deconstructedURL.uri,
292227
headers: self.headers
293228
)
294229

295230
if !head.headers.contains(name: "host") {
296231
let port = self.port
297232
var host = self.host
298-
if !(port == 80 && self.scheme == "http"), !(port == 443 && self.scheme == "https") {
233+
if !(port == 80 && self.deconstructedURL.scheme == .http), !(port == 443 && self.deconstructedURL.scheme == .https) {
299234
host += ":\(port)"
300235
}
301236
head.headers.add(name: "host", value: host)
@@ -740,7 +675,7 @@ internal struct RedirectHandler<ResponseType> {
740675
return nil
741676
}
742677

743-
guard self.request.kind.supportsRedirects(to: url.scheme) else {
678+
guard self.request.deconstructedURL.scheme.supportsRedirects(to: url.scheme) else {
744679
return nil
745680
}
746681

0 commit comments

Comments
 (0)