-
Notifications
You must be signed in to change notification settings - Fork 125
Refactor deconstructURL
and scheme
parsing
#504
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
Merged
Merged
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
fae26db
make `Scheme` a type
dnadoba ef5d726
introduce new Endpoint type
dnadoba e4d5351
use endpoint as storage in `HTTPClient.Request`
dnadoba 10c556b
Merge branch 'main'
dnadoba aec9ecb
fix merge conflicts
dnadoba c63d8fc
rename Endpoint to DeconstructedURL
dnadoba a2c9cbb
swift-format
dnadoba 43a0bb0
make `DeconstructedURL` properties `var`'s
dnadoba d428d68
move scheme into global namespace
dnadoba 0e4f36f
Merge branch 'main' into dn-scheme
dnadoba b3b2cfc
fix review comments
dnadoba File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the AsyncHTTPClient open source project | ||
// | ||
// Copyright (c) 2021 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import struct Foundation.URL | ||
|
||
struct DeconstructedURL { | ||
var scheme: Scheme | ||
var connectionTarget: ConnectionTarget | ||
var uri: String | ||
|
||
init( | ||
scheme: Scheme, | ||
connectionTarget: ConnectionTarget, | ||
uri: String | ||
) { | ||
self.scheme = scheme | ||
self.connectionTarget = connectionTarget | ||
self.uri = uri | ||
} | ||
} | ||
|
||
extension DeconstructedURL { | ||
init(url: URL) throws { | ||
guard let schemeString = url.scheme else { | ||
throw HTTPClientError.emptyScheme | ||
} | ||
guard let scheme = Scheme(rawValue: schemeString.lowercased()) else { | ||
throw HTTPClientError.unsupportedScheme(schemeString) | ||
} | ||
|
||
switch scheme { | ||
case .http, .https: | ||
guard let host = url.host, !host.isEmpty else { | ||
throw HTTPClientError.emptyHost | ||
} | ||
self.init( | ||
scheme: scheme, | ||
connectionTarget: .init(remoteHost: host, port: url.port ?? scheme.defaultPort), | ||
uri: url.uri | ||
) | ||
|
||
case .httpUnix, .httpsUnix: | ||
guard let socketPath = url.host, !socketPath.isEmpty else { | ||
throw HTTPClientError.missingSocketPath | ||
} | ||
self.init( | ||
scheme: scheme, | ||
connectionTarget: .unixSocket(path: socketPath), | ||
uri: url.uri | ||
) | ||
|
||
case .unix: | ||
let socketPath = url.baseURL?.path ?? url.path | ||
let uri = url.baseURL != nil ? url.uri : "/" | ||
guard !socketPath.isEmpty else { | ||
throw HTTPClientError.missingSocketPath | ||
} | ||
self.init( | ||
scheme: scheme, | ||
connectionTarget: .unixSocket(path: socketPath), | ||
uri: uri | ||
) | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,82 +92,19 @@ extension HTTPClient { | |
|
||
/// Represent HTTP request. | ||
public struct Request { | ||
/// Represent kind of Request | ||
enum Kind: Equatable { | ||
enum UnixScheme: Equatable { | ||
case baseURL | ||
case http_unix | ||
case https_unix | ||
} | ||
|
||
/// Remote host request. | ||
case host | ||
/// UNIX Domain Socket HTTP request. | ||
case unixSocket(_ scheme: UnixScheme) | ||
|
||
private static var hostRestrictedSchemes: Set = ["http", "https"] | ||
private static var allSupportedSchemes: Set = ["http", "https", "unix", "http+unix", "https+unix"] | ||
|
||
func supportsRedirects(to scheme: String?) -> Bool { | ||
guard let scheme = scheme?.lowercased() else { return false } | ||
|
||
switch self { | ||
case .host: | ||
return Kind.hostRestrictedSchemes.contains(scheme) | ||
case .unixSocket: | ||
return Kind.allSupportedSchemes.contains(scheme) | ||
} | ||
} | ||
} | ||
|
||
static func useTLS(_ scheme: String) -> Bool { | ||
return scheme == "https" || scheme == "https+unix" | ||
} | ||
|
||
static func deconstructURL( | ||
_ url: URL | ||
) throws -> (kind: Kind, scheme: String, connectionTarget: ConnectionTarget, uri: String) { | ||
guard let scheme = url.scheme?.lowercased() else { | ||
throw HTTPClientError.emptyScheme | ||
} | ||
switch scheme { | ||
case "http", "https": | ||
guard let host = url.host, !host.isEmpty else { | ||
throw HTTPClientError.emptyHost | ||
} | ||
let defaultPort = self.useTLS(scheme) ? 443 : 80 | ||
let hostTarget = ConnectionTarget(remoteHost: host, port: url.port ?? defaultPort) | ||
return (.host, scheme, hostTarget, url.uri) | ||
case "http+unix", "https+unix": | ||
guard let socketPath = url.host, !socketPath.isEmpty else { | ||
throw HTTPClientError.missingSocketPath | ||
} | ||
let socketTarget = ConnectionTarget.unixSocket(path: socketPath) | ||
let kind = self.useTLS(scheme) ? Kind.UnixScheme.https_unix : .http_unix | ||
return (.unixSocket(kind), scheme, socketTarget, url.uri) | ||
case "unix": | ||
let socketPath = url.baseURL?.path ?? url.path | ||
let uri = url.baseURL != nil ? url.uri : "/" | ||
guard !socketPath.isEmpty else { | ||
throw HTTPClientError.missingSocketPath | ||
} | ||
let socketTarget = ConnectionTarget.unixSocket(path: socketPath) | ||
return (.unixSocket(.baseURL), scheme, socketTarget, uri) | ||
default: | ||
throw HTTPClientError.unsupportedScheme(url.scheme!) | ||
} | ||
} | ||
|
||
/// Request HTTP method, defaults to `GET`. | ||
public let method: HTTPMethod | ||
/// Remote URL. | ||
public let url: URL | ||
|
||
/// Parsed, validated and deconstructed URL. | ||
internal let deconstructedURL: DeconstructedURL | ||
|
||
/// Remote HTTP scheme, resolved from `URL`. | ||
public let scheme: String | ||
/// The connection target, resolved from `URL`. | ||
let connectionTarget: ConnectionTarget | ||
/// URI composed of the path and query, resolved from `URL`. | ||
let uri: String | ||
public var scheme: String { | ||
self.deconstructedURL.scheme.rawValue | ||
} | ||
|
||
/// Request custom HTTP Headers, defaults to no headers. | ||
public var headers: HTTPHeaders | ||
/// Request body, defaults to no body. | ||
|
@@ -181,7 +118,6 @@ extension HTTPClient { | |
} | ||
|
||
var redirectState: RedirectState? | ||
let kind: Kind | ||
|
||
/// Create HTTP request. | ||
/// | ||
|
@@ -252,7 +188,8 @@ extension HTTPClient { | |
/// - `emptyHost` if URL does not contains a host. | ||
/// - `missingSocketPath` if URL does not contains a socketPath as an encoded host. | ||
public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration?) throws { | ||
(self.kind, self.scheme, self.connectionTarget, self.uri) = try Request.deconstructURL(url) | ||
self.deconstructedURL = try DeconstructedURL(url: url) | ||
|
||
self.redirectState = nil | ||
self.url = url | ||
self.method = method | ||
|
@@ -261,14 +198,9 @@ extension HTTPClient { | |
self.tlsConfiguration = tlsConfiguration | ||
} | ||
|
||
/// Whether request will be executed using secure socket. | ||
public var useTLS: Bool { | ||
return Request.useTLS(self.scheme) | ||
} | ||
|
||
/// Remote host, resolved from `URL`. | ||
public var host: String { | ||
switch self.connectionTarget { | ||
switch self.deconstructedURL.connectionTarget { | ||
case .ipAddress(let serialization, _): return serialization | ||
case .domain(let name, _): return name | ||
case .unixSocket: return "" | ||
|
@@ -277,25 +209,28 @@ extension HTTPClient { | |
|
||
/// Resolved port. | ||
public var port: Int { | ||
switch self.connectionTarget { | ||
switch self.deconstructedURL.connectionTarget { | ||
case .ipAddress(_, let address): return address.port! | ||
case .domain(_, let port): return port | ||
case .unixSocket: return Request.useTLS(self.scheme) ? 443 : 80 | ||
case .unixSocket: return self.deconstructedURL.scheme.defaultPort | ||
} | ||
} | ||
|
||
/// Whether request will be executed using secure socket. | ||
public var useTLS: Bool { self.deconstructedURL.scheme.usesTLS } | ||
|
||
func createRequestHead() throws -> (HTTPRequestHead, RequestFramingMetadata) { | ||
var head = HTTPRequestHead( | ||
version: .http1_1, | ||
method: self.method, | ||
uri: self.uri, | ||
uri: self.deconstructedURL.uri, | ||
headers: self.headers | ||
) | ||
|
||
if !head.headers.contains(name: "host") { | ||
let port = self.port | ||
var host = self.host | ||
if !(port == 80 && self.scheme == "http"), !(port == 443 && self.scheme == "https") { | ||
if !(port == 80 && self.deconstructedURL.scheme == .http), !(port == 443 && self.deconstructedURL.scheme == .https) { | ||
host += ":\(port)" | ||
} | ||
head.headers.add(name: "host", value: host) | ||
|
@@ -740,7 +675,7 @@ internal struct RedirectHandler<ResponseType> { | |
return nil | ||
} | ||
|
||
guard self.request.kind.supportsRedirects(to: url.scheme) else { | ||
guard self.request.deconstructedURL.scheme.supportsRedirects(to: url.scheme) else { | ||
return nil | ||
} | ||
|
||
|
@@ -813,6 +748,32 @@ internal struct RedirectHandler<ResponseType> { | |
} | ||
} | ||
|
||
extension Scheme { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should live in the Scheme file. |
||
func supportsRedirects(to destinationScheme: String?) -> Bool { | ||
guard | ||
let destinationSchemeString = destinationScheme?.lowercased(), | ||
let destinationScheme = Self(rawValue: destinationSchemeString) | ||
else { | ||
return false | ||
} | ||
return self.supportsRedirects(to: destinationScheme) | ||
} | ||
|
||
func supportsRedirects(to destinationScheme: Self) -> Bool { | ||
switch self { | ||
case .http, .https: | ||
switch destinationScheme { | ||
case .http, .https: | ||
return true | ||
case .unix, .httpUnix, .httpsUnix: | ||
return false | ||
} | ||
case .unix, .httpUnix, .httpsUnix: | ||
return true | ||
} | ||
} | ||
} | ||
|
||
extension RequestBodyLength { | ||
init(_ body: HTTPClient.Body?) { | ||
guard let body = body else { | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: Move down to other internal var like redirect state. Mixing internal and public makes this hard to read.