Skip to content

Commit 7aec5ed

Browse files
Merge pull request #831 from splitio/http-module
Http module
2 parents 3f39f91 + 0c07eb6 commit 7aec5ed

47 files changed

Lines changed: 1565 additions & 459 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Package.swift

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ let package = Package(
88
products: [
99
.library(name: "Split", targets: ["Split"]),
1010

11-
.library(name: "SplitCommons", targets: ["Logging", "BackoffCounter"]),],
11+
.library(name: "SplitCommons", targets: ["Logging", "Http", "BackoffCounter"]),],
1212
targets: [
1313
.target(
1414
name: "Split",
15-
dependencies: ["BackoffCounter", "Logging"],
15+
dependencies: ["Http", "BackoffCounter", "Logging"],
1616
path: "Split",
1717
exclude: [
1818
"Common/Yaml/LICENSE",
@@ -33,9 +33,22 @@ let package = Package(
3333
path: "Sources/Logging/Tests"
3434
),
3535

36+
.target(
37+
name: "Http",
38+
dependencies: ["Logging"],
39+
path: "Sources/Http",
40+
exclude: ["Tests", "README.md"]
41+
),
42+
.testTarget(
43+
name: "HttpTests",
44+
dependencies: ["Http"],
45+
path: "Sources/Http/Tests"
46+
),
47+
3648
.target(
3749
name: "BackoffCounter",
3850
dependencies: ["Logging"],
51+
path: "Sources/BackoffCounter",
3952
exclude: ["Tests", "README.md"]
4053
),
4154
.testTarget(
Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
//
22
// Endpoint.swift
3-
// Split
3+
// Http
44
//
55
// Created by Javier L. Avrudsky on 13/05/2020.
66
// Copyright (c) 2020 Split. All rights reserved.
77
//
88

99
import Foundation
1010

11-
class Endpoint {
11+
public class Endpoint {
1212

13-
private(set) var url: URL
14-
private(set) var method: HttpMethod
15-
private(set) var headers = [String: String]()
13+
public private(set) var url: URL
14+
public private(set) var method: HttpMethod
15+
public private(set) var headers = [String: String]()
1616

1717
private init(baseUrl: URL, path: String?, isPathEncoded: Bool = false, defaultQueryString: String? = nil) {
1818

@@ -41,41 +41,41 @@ class Endpoint {
4141
self.method = .get
4242
}
4343

44-
static func builder(baseUrl: URL, path: String? = nil, defaultQueryString: String? = nil) -> Builder {
45-
return Builder(baseUrl: baseUrl, path: path, isPathEncoded: false, defaultQueryString: defaultQueryString)
44+
public static func builder(baseUrl: URL, path: String? = nil, defaultQueryString: String? = nil) -> Builder {
45+
Builder(baseUrl: baseUrl, path: path, isPathEncoded: false, defaultQueryString: defaultQueryString)
4646
}
4747

48-
static func builder(baseUrl: URL, encodedPath: String, defaultQueryString: String? = nil) -> Builder {
49-
return Builder(baseUrl: baseUrl, path: encodedPath, isPathEncoded: true, defaultQueryString: defaultQueryString)
48+
public static func builder(baseUrl: URL, encodedPath: String, defaultQueryString: String? = nil) -> Builder {
49+
Builder(baseUrl: baseUrl, path: encodedPath, isPathEncoded: true, defaultQueryString: defaultQueryString)
5050
}
5151

52-
struct Builder {
52+
public struct Builder {
5353
private var endpoint: Endpoint
5454

55-
init(baseUrl: URL, path: String?, isPathEncoded: Bool, defaultQueryString: String? = nil) {
55+
public init(baseUrl: URL, path: String?, isPathEncoded: Bool, defaultQueryString: String? = nil) {
5656
endpoint = Endpoint(baseUrl: baseUrl, path: path,
5757
isPathEncoded: isPathEncoded, defaultQueryString: defaultQueryString)
5858
}
5959

60-
func add(header: String, withValue value: String) -> Self {
60+
public func add(header: String, withValue value: String) -> Self {
6161
endpoint.headers[header] = value
6262
return self
6363
}
6464

65-
func add(headers: [String: String]) -> Self {
65+
public func add(headers: [String: String]) -> Self {
6666
for (header, value) in headers {
6767
endpoint.headers[header] = value
6868
}
6969
return self
7070
}
7171

72-
func set(method: HttpMethod) -> Self {
72+
public func set(method: HttpMethod) -> Self {
7373
endpoint.method = method
7474
return self
7575
}
7676

77-
func build() -> Endpoint {
78-
return endpoint
77+
public func build() -> Endpoint {
78+
endpoint
7979
}
8080
}
8181
}

Split/Network/Security/HostDomainFilter.swift renamed to Sources/Http/HostDomainFilter.swift

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
//
22
// HostsFilter.swift
3-
// Split
3+
// Http
44
//
55
// Created by Javier Avrudsky on 30/07/2024.
66
// Copyright © 2024 Split. All rights reserved.
77
//
88

99
import Foundation
1010

11-
struct HostDomainFilter {
12-
static let endString = "$"
13-
static let mainRegex = "^(?:[a-zA-Z0-9_-]+\\.)"
14-
static let wildCards = [(prefix: "**.", pattern: "\(mainRegex)*"),
11+
private extension String {
12+
func matchRegex(_ pattern: String) -> Bool {
13+
guard let regex = try? NSRegularExpression(pattern: pattern) else { return false }
14+
return regex.firstMatch(in: self, range: NSRange(startIndex..<endIndex, in: self)) != nil
15+
}
16+
}
17+
18+
public struct HostDomainFilter {
19+
public static let endString = "$"
20+
public static let mainRegex = "^(?:[a-zA-Z0-9_-]+\\.)"
21+
public static let wildCards = [(prefix: "**.", pattern: "\(mainRegex)*"),
1522
(prefix: "*.", pattern: "\(mainRegex)?")]
1623

17-
static func pinsFor(host: String, pins: [CredentialPin]) -> [CredentialPin] {
24+
public static func pinsFor(host: String, pins: [CredentialPin]) -> [CredentialPin] {
1825
var foundPins = [CredentialPin]()
1926
for pin in pins {
2027
var hasWildcard = false
2128
for wildCard in wildCards {
2229
let count = wildCard.prefix.count
2330
if pin.host.starts(with: wildCard.prefix), pin.host.count > count {
24-
let pinHost = pin.host
25-
.suffix(starting: count)
26-
.asString()
31+
// Extract the host suffix after the wildcard prefix (e.g., "**.example.com" -> "example.com")
32+
let pinHost = String(pin.host.suffix(from: pin.host.index(pin.host.startIndex, offsetBy: count)))
2733
.replacingOccurrences(of: ".", with: "\\.")
2834
let regex = "\(wildCard.pattern)\(pinHost)\(endString)"
2935
if host.matchRegex(regex) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Protocol for handling HTTPS authentication challenges.
2+
3+
import Foundation
4+
5+
/// Implementations can provide custom certificate validation, client certificate authentication, or other challenge responses.
6+
public protocol HttpAuthenticator: Sendable {
7+
func authenticate(session: URLSession,
8+
challenge: URLAuthenticationChallenge,
9+
completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
10+
}
Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,45 @@
11
//
22
// HttpClient.swift
3-
// Split
3+
// Http
44
//
55
// Created by Javier L. Avrudsky on 5/23/18.
66

77
import Foundation
8+
#if !COCOAPODS
9+
import Logging
10+
#endif
11+
12+
private func += <K, V> ( left: inout [K: V], right: [K: V]) {
13+
for (key, value) in right {
14+
left[key] = value
15+
}
16+
}
817

918
// MARK: HttpSession
1019

1120
/// HttpClient is main wrapper component to handle HTTP activity
1221
/// This file also includes some complementary HTTP client components
1322
///
1423
struct HttpQueue {
15-
public static let `default`: String = "split-rest-queue"
24+
static let `default`: String = "split-rest-queue"
1625
}
1726

1827
// MARK: HTTP codes
19-
struct HttpCode {
28+
public struct HttpCode {
2029
static let requestOk = 200
2130
static let multipleChoice = 300
22-
static let badRequest = 400
31+
public static let badRequest = 400
2332
static let unauthorized = 401
2433
static let forbidden = 403
2534
static let notFound = 404
2635
static let requestTimeOut = 408
27-
static let uriTooLong = 414
28-
static let internalServerError = 500
29-
static let networkLost = -1005
36+
public static let uriTooLong = 414
37+
public static let internalServerError = 500
38+
public static let networkLost = -1005
3039
}
3140

3241
// MARK: HttpMethod
33-
enum HttpMethod: String, CustomStringConvertible {
42+
public enum HttpMethod: String, CustomStringConvertible {
3443
case get
3544
case post
3645
case patch
@@ -69,21 +78,23 @@ enum HttpMethod: String, CustomStringConvertible {
6978
}
7079

7180
// MARK: HttpSession Delegate
72-
typealias HttpHeaders = [String: String]
81+
public typealias HttpHeaders = [String: String]
7382

74-
class HttpSessionConfig: @unchecked Sendable {
83+
public class HttpSessionConfig: @unchecked Sendable {
7584
static let kDefaultConnectionTimeout: TimeInterval = 30
7685

77-
static let `default`: HttpSessionConfig = {
78-
return HttpSessionConfig()
86+
public static let `default`: HttpSessionConfig = {
87+
HttpSessionConfig()
7988
}()
80-
var connectionTimeOut: TimeInterval = kDefaultConnectionTimeout
81-
var httpsAuthenticator: SplitHttpsAuthenticator?
82-
var pinChecker: TlsPinChecker?
83-
var notificationHelper: NotificationHelper?
89+
public var connectionTimeOut: TimeInterval = kDefaultConnectionTimeout
90+
public var authenticator: HttpAuthenticator?
91+
public var pinChecker: TlsPinChecker?
92+
public var notificationHandler: HttpNotificationHandler?
93+
94+
public init() {}
8495
}
8596

86-
protocol HttpClient {
97+
public protocol HttpClient {
8798

8899
func sendRequest(endpoint: Endpoint, parameters: HttpParameters?,
89100
headers: [String: String]?, body: Data?) throws -> HttpDataRequest
@@ -93,18 +104,18 @@ protocol HttpClient {
93104
}
94105

95106
extension HttpClient {
96-
func sendRequest(endpoint: Endpoint, parameters: HttpParameters? = nil,
107+
public func sendRequest(endpoint: Endpoint, parameters: HttpParameters? = nil,
97108
headers: [String: String]? = nil) throws -> HttpDataRequest {
98-
return try sendRequest(endpoint: endpoint, parameters: parameters, headers: headers, body: nil)
109+
try sendRequest(endpoint: endpoint, parameters: parameters, headers: headers, body: nil)
99110
}
100111
}
101112

102-
class DefaultHttpClient: @unchecked Sendable {
113+
public class DefaultHttpClient: @unchecked Sendable {
103114

104115
#if swift(>=6.0)
105-
nonisolated(unsafe) static let shared: HttpClient = { DefaultHttpClient() }()
116+
nonisolated(unsafe) public static let shared: HttpClient = { DefaultHttpClient() }()
106117
#else
107-
static let shared: HttpClient = { DefaultHttpClient() }()
118+
public static let shared: HttpClient = { DefaultHttpClient() }()
108119
#endif
109120

110121
private var testSession: HttpSession?
@@ -114,9 +125,9 @@ class DefaultHttpClient: @unchecked Sendable {
114125
private var requestManager: HttpRequestManager!
115126
private var configuration: HttpSessionConfig
116127
private var isStarted = false
117-
private var startQueue = DispatchQueue(label: "http-client-start", target: DispatchQueue.general)
128+
private var startQueue = DispatchQueue(label: "http-client-start", attributes: .concurrent)
118129

119-
init(configuration: HttpSessionConfig = HttpSessionConfig.default,
130+
public init(configuration: HttpSessionConfig = HttpSessionConfig.default,
120131
session: HttpSession? = nil,
121132
requestManager: HttpRequestManager? = nil) {
122133

@@ -135,9 +146,9 @@ class DefaultHttpClient: @unchecked Sendable {
135146
if let requestManager = testRequestManager {
136147
self.requestManager = requestManager
137148
} else {
138-
self.requestManager = DefaultHttpRequestManager(authententicator: configuration.httpsAuthenticator,
149+
self.requestManager = DefaultHttpRequestManager(authenticator: configuration.authenticator,
139150
pinChecker: configuration.pinChecker,
140-
notificationHelper: configuration.notificationHelper)
151+
notificationHandler: configuration.notificationHandler)
141152
}
142153

143154
if let httpSession = testSession {
@@ -183,7 +194,7 @@ extension DefaultHttpClient {
183194
// MARK: DefaultHttpClient - HttpClient
184195
extension DefaultHttpClient: HttpClient {
185196

186-
func sendRequest(endpoint: Endpoint, parameters: HttpParameters?, headers: [String: String]?,
197+
public func sendRequest(endpoint: Endpoint, parameters: HttpParameters?, headers: [String: String]?,
187198
body: Data?) throws -> HttpDataRequest {
188199
var httpHeaders = endpoint.headers
189200
if let headers = headers {
@@ -197,7 +208,7 @@ extension DefaultHttpClient: HttpClient {
197208
return request
198209
}
199210

200-
func sendStreamRequest(endpoint: Endpoint, parameters: HttpParameters?,
211+
public func sendStreamRequest(endpoint: Endpoint, parameters: HttpParameters?,
201212
headers: [String: String]?) throws -> HttpStreamRequest {
202213
let request = try self.createStreamRequest(endpoint.url, parameters: parameters, headers: headers)
203214
request.send()

Split/Network/HttpClient/HttpDataRequest.swift renamed to Sources/Http/HttpDataRequest.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
//
22
// HttpDataRequest.swift
3-
// Split
3+
// Http
44
//
55
// Created by Javier L. Avrudsky on 12/05/2020.
66
// Copyright (c) 2020 Split. All rights reserved.
77
//
88

99
import Foundation
1010

11-
protocol HttpDataRequest: HttpRequest, HttpDataReceivingRequest {
11+
public protocol HttpDataRequest: HttpRequest, HttpDataReceivingRequest {
1212
var data: Data? { get }
1313
func notifyIncomingData(_ data: Data)
1414
func getResponse(completionHandler: @escaping RequestCompletionHandler,
1515
errorHandler: @escaping RequestErrorHandler) -> Self
1616
}
1717

1818
// MARK: HttpDataRequest
19-
class DefaultHttpDataRequest: BaseHttpRequest, HttpDataRequest, @unchecked Sendable {
19+
public class DefaultHttpDataRequest: BaseHttpRequest, HttpDataRequest, @unchecked Sendable {
2020

21-
private(set) var data: Data?
21+
public private(set) var data: Data?
2222

23-
override func notifyIncomingData(_ data: Data) {
23+
override public func notifyIncomingData(_ data: Data) {
2424
if self.data == nil {
2525
self.data = Data()
2626
}
2727
self.data?.append(data)
2828
}
2929

30-
func getResponse(completionHandler: @escaping RequestCompletionHandler,
30+
public func getResponse(completionHandler: @escaping RequestCompletionHandler,
3131
errorHandler: @escaping RequestErrorHandler) -> Self {
3232
requestQueue.sync {
3333
self.completionHandler = completionHandler
@@ -36,7 +36,7 @@ class DefaultHttpDataRequest: BaseHttpRequest, HttpDataRequest, @unchecked Senda
3636
return self
3737
}
3838

39-
override func complete(error: HttpError?) {
39+
override public func complete(error: HttpError?) {
4040
requestQueue.async(flags: .barrier) {
4141
var internalCode = InternalHttpErrorCode.noCode
4242
if self.pinnedCredentialFail {

0 commit comments

Comments
 (0)