Skip to content

Commit 4a05882

Browse files
authored
feat: add possibility to configure proxy and redirects (#38)
1 parent 79333a1 commit 4a05882

File tree

5 files changed

+185
-5
lines changed

5 files changed

+185
-5
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 0.7.0 [unreleased]
22

3+
### Features
4+
1. [#38](https://github.com/influxdata/influxdb-client-swift/pull/38): Add configuration option for _Proxy_ and _Redirects_
5+
36
## 0.6.0 [2021-07-09]
47

58
### API

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ This repository contains the reference Swift client for the InfluxDB 2.0.
2525
- [Management API](#management-api)
2626
- [Advanced Usage](#advanced-usage)
2727
- [Default Tags](#default-tags)
28+
- [Proxy and redirects](#proxy-and-redirects)
2829
- [Contributing](#contributing)
2930
- [License](#license)
3031

@@ -660,6 +661,54 @@ mining,customer=California\ Miner,sensor_id=123-456-789,sensor_state=normal dept
660661
mining,customer=California\ Miner,sensor_id=123-456-789,sensor_state=normal pressure=3i
661662
```
662663

664+
### Proxy and redirects
665+
666+
> :warning: The `connectionProxyDictionary` cannot be defined on **Linux**. You have to set `HTTPS_PROXY` or `HTTP_PROXY` system environment.
667+
668+
You can configure the client to tunnel requests through an HTTP proxy by `connectionProxyDictionary` option:
669+
670+
```swift
671+
var connectionProxyDictionary = [AnyHashable: Any]()
672+
connectionProxyDictionary[kCFNetworkProxiesHTTPEnable as String] = 1
673+
connectionProxyDictionary[kCFNetworkProxiesHTTPProxy as String] = "localhost"
674+
connectionProxyDictionary[kCFNetworkProxiesHTTPPort as String] = 3128
675+
676+
let options: InfluxDBClient.InfluxDBOptions = InfluxDBClient.InfluxDBOptions(
677+
bucket: "my-bucket",
678+
org: "my-org",
679+
precision: .ns,
680+
connectionProxyDictionary: connectionProxyDictionary)
681+
682+
client = InfluxDBClient(url: "http://localhost:8086", token: "my-token", options: options)
683+
```
684+
For more info see - [URLSessionConfiguration.connectionProxyDictionary](https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1411499-connectionproxydictionary), [Global Proxy Settings Constants](https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants/).
685+
686+
#### Redirects
687+
688+
Client automatically follows HTTP redirects. You can disable redirects by an `urlSessionDelegate` configuration:
689+
690+
```swift
691+
class DisableRedirect: NSObject, URLSessionTaskDelegate {
692+
func urlSession(_ session: URLSession,
693+
task: URLSessionTask,
694+
willPerformHTTPRedirection response: HTTPURLResponse,
695+
newRequest request: URLRequest,
696+
completionHandler: @escaping (URLRequest?) -> Void) {
697+
completionHandler(nil)
698+
}
699+
}
700+
701+
let options = InfluxDBClient.InfluxDBOptions(
702+
bucket: "my-bucket",
703+
org: "my-org",
704+
urlSessionDelegate: DisableRedirect())
705+
706+
client = InfluxDBClient(url: "http://localhost:8086", token: "my-token", options: options)
707+
```
708+
709+
For more info see - [URLSessionDelegate](https://developer.apple.com/documentation/foundation/urlsessiondelegate).
710+
711+
663712
## Contributing
664713

665714
If you would like to contribute code you can do through GitHub by forking the repository and sending a pull request into the `master` branch.

Sources/InfluxDBSwift/InfluxDBClient.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,14 @@ public class InfluxDBClient {
6363
configuration.httpAdditionalHeaders = headers
6464
configuration.timeoutIntervalForRequest = self.options.timeoutIntervalForRequest
6565
configuration.timeoutIntervalForResource = self.options.timeoutIntervalForResource
66+
configuration.connectionProxyDictionary = self.options.connectionProxyDictionary
6667
configuration.protocolClasses = protocolClasses
6768

68-
session = URLSession(configuration: configuration)
69+
if let urlSessionDelegate = self.options.urlSessionDelegate {
70+
session = URLSession(configuration: configuration, delegate: urlSessionDelegate, delegateQueue: nil)
71+
} else {
72+
session = URLSession(configuration: configuration)
73+
}
6974
}
7075

7176
/// Create a new client for InfluxDB 1.8 compatibility API.
@@ -130,6 +135,13 @@ extension InfluxDBClient {
130135
/// - SeeAlso: https://docs.influxdata.com/influxdb/v2.0/api/#operation/PostWrite
131136
/// - SeeAlso: https://docs.influxdata.com/influxdb/v2.0/api/#operation/PostQuery
132137
public let enableGzip: Bool
138+
/// A dictionary containing information about the proxy to use within the HTTP client.
139+
/// - SeeAlso: https://developer.apple.com/documentation/foundation/urlsessionconfiguration/
140+
/// - SeeAlso: https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants/
141+
public let connectionProxyDictionary: [AnyHashable: Any]?
142+
/// A delegate to handle HTTP session-level events. Useful for disable redirects or custom auth handling.
143+
/// - SeeAlso: https://developer.apple.com/documentation/foundation/urlsessiondelegate
144+
public weak var urlSessionDelegate: URLSessionDelegate?
133145

134146
/// Create a new options for client.
135147
///
@@ -140,18 +152,24 @@ extension InfluxDBClient {
140152
/// - timeoutIntervalForRequest: Timeout interval to use when waiting for additional data.
141153
/// - timeoutIntervalForResource: Maximum amount of time that a resource request should be allowed to take.
142154
/// - enableGzip: Enable Gzip compression for HTTP requests.
155+
/// - connectionProxyDictionary: Enable Gzip compression for HTTP requests.
156+
/// - urlSessionDelegate: A delegate to handle HTTP session-level events.
143157
public init(bucket: String? = nil,
144158
org: String? = nil,
145159
precision: TimestampPrecision = defaultTimestampPrecision,
146160
timeoutIntervalForRequest: TimeInterval = 60,
147161
timeoutIntervalForResource: TimeInterval = 60 * 5,
148-
enableGzip: Bool = false) {
162+
enableGzip: Bool = false,
163+
connectionProxyDictionary: [AnyHashable: Any]? = nil,
164+
urlSessionDelegate: URLSessionDelegate? = nil) {
149165
self.bucket = bucket
150166
self.org = org
151167
self.precision = precision
152168
self.timeoutIntervalForRequest = timeoutIntervalForRequest
153169
self.timeoutIntervalForResource = timeoutIntervalForResource
154170
self.enableGzip = enableGzip
171+
self.connectionProxyDictionary = connectionProxyDictionary
172+
self.urlSessionDelegate = urlSessionDelegate
155173
}
156174
}
157175
}

Tests/InfluxDBSwiftTests/InfluxDBClientTests.swift

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,107 @@ final class InfluxDBClientTests: XCTestCase {
7474
XCTAssertEqual(Self.dbURL(), client.url)
7575
client.close()
7676
}
77+
78+
func testConfigureProxy() {
79+
#if os(macOS)
80+
var connectionProxyDictionary = [AnyHashable: Any]()
81+
connectionProxyDictionary[kCFNetworkProxiesHTTPEnable as String] = 1
82+
connectionProxyDictionary[kCFNetworkProxiesHTTPProxy as String] = "localhost"
83+
connectionProxyDictionary[kCFNetworkProxiesHTTPPort as String] = 3128
84+
85+
let options: InfluxDBClient.InfluxDBOptions = InfluxDBClient.InfluxDBOptions(
86+
bucket: "my-bucket",
87+
org: "my-org",
88+
precision: .ns,
89+
connectionProxyDictionary: connectionProxyDictionary)
90+
91+
client = InfluxDBClient(url: "http://localhost:8086", token: "my-token", options: options)
92+
#endif
93+
}
94+
95+
func testFollowRedirect() {
96+
client = InfluxDBClient(
97+
url: Self.dbURL(),
98+
token: "my-token",
99+
options: InfluxDBClient.InfluxDBOptions(bucket: "my-bucket", org: "my-org"),
100+
protocolClasses: [MockURLProtocol.self])
101+
102+
let expectation = self.expectation(description: "Success response from API doesn't arrive")
103+
expectation.expectedFulfillmentCount = 3
104+
105+
MockURLProtocol.handler = { request, _ in
106+
XCTAssertEqual("Token my-token", request.allHTTPHeaderFields!["Authorization"])
107+
108+
expectation.fulfill()
109+
110+
// success
111+
if let port = request.url?.port, port == 8088 {
112+
let response = HTTPURLResponse(statusCode: 200)
113+
return (response, "".data(using: .utf8)!)
114+
}
115+
116+
// redirect
117+
let response = HTTPURLResponse(statusCode: 307, headers: ["location": "http://localhost:8088"])
118+
return (response, Data())
119+
}
120+
121+
client.queryAPI.query(query: "from ...") { _, error in
122+
if let error = error {
123+
XCTFail("Error occurs: \(error)")
124+
}
125+
expectation.fulfill()
126+
}
127+
128+
waitForExpectations(timeout: 1, handler: nil)
129+
}
130+
131+
func testDisableRedirect() {
132+
let expectation = self.expectation(description: "Redirect response from API doesn't arrive")
133+
expectation.expectedFulfillmentCount = 2
134+
135+
class DisableRedirect: NSObject, URLSessionTaskDelegate {
136+
let expectation: XCTestExpectation
137+
138+
init(_ expectation: XCTestExpectation) {
139+
self.expectation = expectation
140+
}
141+
142+
func urlSession(_ session: URLSession,
143+
task: URLSessionTask,
144+
willPerformHTTPRedirection response: HTTPURLResponse,
145+
newRequest request: URLRequest,
146+
completionHandler: @escaping (URLRequest?) -> Void) {
147+
expectation.fulfill()
148+
completionHandler(nil)
149+
}
150+
}
151+
152+
let options = InfluxDBClient.InfluxDBOptions(
153+
bucket: "my-bucket",
154+
org: "my-org",
155+
urlSessionDelegate: DisableRedirect(expectation))
156+
157+
client = InfluxDBClient(
158+
url: Self.dbURL(),
159+
token: "my-token",
160+
options: options,
161+
protocolClasses: [MockURLProtocol.self])
162+
163+
MockURLProtocol.handler = { request, _ in
164+
XCTAssertEqual("Token my-token", request.allHTTPHeaderFields!["Authorization"])
165+
166+
expectation.fulfill()
167+
168+
// redirect
169+
let response = HTTPURLResponse(statusCode: 307, headers: ["location": "http://localhost:8088"])
170+
return (response, Data())
171+
}
172+
173+
client.queryAPI.query(query: "from ...") { _, _ in
174+
}
175+
176+
waitForExpectations(timeout: 1, handler: nil)
177+
}
77178
}
78179

79180
final class InfluxDBErrorTests: XCTestCase {

Tests/InfluxDBSwiftTests/MockHTTP.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ extension HTTPURLResponse {
88
convenience init(statusCode: Int) {
99
self.init(url: MockURLProtocol.url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: [:])!
1010
}
11+
12+
convenience init(statusCode: Int, headers: [String: String]?) {
13+
self.init(url: MockURLProtocol.url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: headers)!
14+
}
1115
}
1216

1317
class MockURLProtocol: URLProtocol {
@@ -31,9 +35,14 @@ class MockURLProtocol: URLProtocol {
3135

3236
do {
3337
let (response, data) = try handler(request, request.bodyValue)
34-
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
35-
client?.urlProtocol(self, didLoad: data)
36-
client?.urlProtocolDidFinishLoading(self)
38+
let locationHeader = response.allHeaderFields["Location"]
39+
if let location = locationHeader as? String, let url = URL(string: location), response.statusCode == 307 {
40+
client?.urlProtocol(self, wasRedirectedTo: URLRequest(url: url), redirectResponse: response)
41+
} else {
42+
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
43+
client?.urlProtocol(self, didLoad: data)
44+
client?.urlProtocolDidFinishLoading(self)
45+
}
3746
} catch {
3847
client?.urlProtocol(self, didFailWithError: error)
3948
}

0 commit comments

Comments
 (0)