Skip to content

Commit

Permalink
feat: add possibility to configure proxy and redirects (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
bednar authored Sep 13, 2021
1 parent 79333a1 commit 4a05882
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## 0.7.0 [unreleased]

### Features
1. [#38](https://github.com/influxdata/influxdb-client-swift/pull/38): Add configuration option for _Proxy_ and _Redirects_

## 0.6.0 [2021-07-09]

### API
Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This repository contains the reference Swift client for the InfluxDB 2.0.
- [Management API](#management-api)
- [Advanced Usage](#advanced-usage)
- [Default Tags](#default-tags)
- [Proxy and redirects](#proxy-and-redirects)
- [Contributing](#contributing)
- [License](#license)

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

### Proxy and redirects

> :warning: The `connectionProxyDictionary` cannot be defined on **Linux**. You have to set `HTTPS_PROXY` or `HTTP_PROXY` system environment.
You can configure the client to tunnel requests through an HTTP proxy by `connectionProxyDictionary` option:

```swift
var connectionProxyDictionary = [AnyHashable: Any]()
connectionProxyDictionary[kCFNetworkProxiesHTTPEnable as String] = 1
connectionProxyDictionary[kCFNetworkProxiesHTTPProxy as String] = "localhost"
connectionProxyDictionary[kCFNetworkProxiesHTTPPort as String] = 3128

let options: InfluxDBClient.InfluxDBOptions = InfluxDBClient.InfluxDBOptions(
bucket: "my-bucket",
org: "my-org",
precision: .ns,
connectionProxyDictionary: connectionProxyDictionary)

client = InfluxDBClient(url: "http://localhost:8086", token: "my-token", options: options)
```
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/).

#### Redirects

Client automatically follows HTTP redirects. You can disable redirects by an `urlSessionDelegate` configuration:

```swift
class DisableRedirect: NSObject, URLSessionTaskDelegate {
func urlSession(_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void) {
completionHandler(nil)
}
}

let options = InfluxDBClient.InfluxDBOptions(
bucket: "my-bucket",
org: "my-org",
urlSessionDelegate: DisableRedirect())

client = InfluxDBClient(url: "http://localhost:8086", token: "my-token", options: options)
```

For more info see - [URLSessionDelegate](https://developer.apple.com/documentation/foundation/urlsessiondelegate).


## Contributing

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.
Expand Down
22 changes: 20 additions & 2 deletions Sources/InfluxDBSwift/InfluxDBClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,14 @@ public class InfluxDBClient {
configuration.httpAdditionalHeaders = headers
configuration.timeoutIntervalForRequest = self.options.timeoutIntervalForRequest
configuration.timeoutIntervalForResource = self.options.timeoutIntervalForResource
configuration.connectionProxyDictionary = self.options.connectionProxyDictionary
configuration.protocolClasses = protocolClasses

session = URLSession(configuration: configuration)
if let urlSessionDelegate = self.options.urlSessionDelegate {
session = URLSession(configuration: configuration, delegate: urlSessionDelegate, delegateQueue: nil)
} else {
session = URLSession(configuration: configuration)
}
}

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

/// Create a new options for client.
///
Expand All @@ -140,18 +152,24 @@ extension InfluxDBClient {
/// - timeoutIntervalForRequest: Timeout interval to use when waiting for additional data.
/// - timeoutIntervalForResource: Maximum amount of time that a resource request should be allowed to take.
/// - enableGzip: Enable Gzip compression for HTTP requests.
/// - connectionProxyDictionary: Enable Gzip compression for HTTP requests.
/// - urlSessionDelegate: A delegate to handle HTTP session-level events.
public init(bucket: String? = nil,
org: String? = nil,
precision: TimestampPrecision = defaultTimestampPrecision,
timeoutIntervalForRequest: TimeInterval = 60,
timeoutIntervalForResource: TimeInterval = 60 * 5,
enableGzip: Bool = false) {
enableGzip: Bool = false,
connectionProxyDictionary: [AnyHashable: Any]? = nil,
urlSessionDelegate: URLSessionDelegate? = nil) {
self.bucket = bucket
self.org = org
self.precision = precision
self.timeoutIntervalForRequest = timeoutIntervalForRequest
self.timeoutIntervalForResource = timeoutIntervalForResource
self.enableGzip = enableGzip
self.connectionProxyDictionary = connectionProxyDictionary
self.urlSessionDelegate = urlSessionDelegate
}
}
}
Expand Down
101 changes: 101 additions & 0 deletions Tests/InfluxDBSwiftTests/InfluxDBClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,107 @@ final class InfluxDBClientTests: XCTestCase {
XCTAssertEqual(Self.dbURL(), client.url)
client.close()
}

func testConfigureProxy() {
#if os(macOS)
var connectionProxyDictionary = [AnyHashable: Any]()
connectionProxyDictionary[kCFNetworkProxiesHTTPEnable as String] = 1
connectionProxyDictionary[kCFNetworkProxiesHTTPProxy as String] = "localhost"
connectionProxyDictionary[kCFNetworkProxiesHTTPPort as String] = 3128

let options: InfluxDBClient.InfluxDBOptions = InfluxDBClient.InfluxDBOptions(
bucket: "my-bucket",
org: "my-org",
precision: .ns,
connectionProxyDictionary: connectionProxyDictionary)

client = InfluxDBClient(url: "http://localhost:8086", token: "my-token", options: options)
#endif
}

func testFollowRedirect() {
client = InfluxDBClient(
url: Self.dbURL(),
token: "my-token",
options: InfluxDBClient.InfluxDBOptions(bucket: "my-bucket", org: "my-org"),
protocolClasses: [MockURLProtocol.self])

let expectation = self.expectation(description: "Success response from API doesn't arrive")
expectation.expectedFulfillmentCount = 3

MockURLProtocol.handler = { request, _ in
XCTAssertEqual("Token my-token", request.allHTTPHeaderFields!["Authorization"])

expectation.fulfill()

// success
if let port = request.url?.port, port == 8088 {
let response = HTTPURLResponse(statusCode: 200)
return (response, "".data(using: .utf8)!)
}

// redirect
let response = HTTPURLResponse(statusCode: 307, headers: ["location": "http://localhost:8088"])
return (response, Data())
}

client.queryAPI.query(query: "from ...") { _, error in
if let error = error {
XCTFail("Error occurs: \(error)")
}
expectation.fulfill()
}

waitForExpectations(timeout: 1, handler: nil)
}

func testDisableRedirect() {
let expectation = self.expectation(description: "Redirect response from API doesn't arrive")
expectation.expectedFulfillmentCount = 2

class DisableRedirect: NSObject, URLSessionTaskDelegate {
let expectation: XCTestExpectation

init(_ expectation: XCTestExpectation) {
self.expectation = expectation
}

func urlSession(_ session: URLSession,
task: URLSessionTask,
willPerformHTTPRedirection response: HTTPURLResponse,
newRequest request: URLRequest,
completionHandler: @escaping (URLRequest?) -> Void) {
expectation.fulfill()
completionHandler(nil)
}
}

let options = InfluxDBClient.InfluxDBOptions(
bucket: "my-bucket",
org: "my-org",
urlSessionDelegate: DisableRedirect(expectation))

client = InfluxDBClient(
url: Self.dbURL(),
token: "my-token",
options: options,
protocolClasses: [MockURLProtocol.self])

MockURLProtocol.handler = { request, _ in
XCTAssertEqual("Token my-token", request.allHTTPHeaderFields!["Authorization"])

expectation.fulfill()

// redirect
let response = HTTPURLResponse(statusCode: 307, headers: ["location": "http://localhost:8088"])
return (response, Data())
}

client.queryAPI.query(query: "from ...") { _, _ in
}

waitForExpectations(timeout: 1, handler: nil)
}
}

final class InfluxDBErrorTests: XCTestCase {
Expand Down
15 changes: 12 additions & 3 deletions Tests/InfluxDBSwiftTests/MockHTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ extension HTTPURLResponse {
convenience init(statusCode: Int) {
self.init(url: MockURLProtocol.url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: [:])!
}

convenience init(statusCode: Int, headers: [String: String]?) {
self.init(url: MockURLProtocol.url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: headers)!
}
}

class MockURLProtocol: URLProtocol {
Expand All @@ -31,9 +35,14 @@ class MockURLProtocol: URLProtocol {

do {
let (response, data) = try handler(request, request.bodyValue)
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
let locationHeader = response.allHeaderFields["Location"]
if let location = locationHeader as? String, let url = URL(string: location), response.statusCode == 307 {
client?.urlProtocol(self, wasRedirectedTo: URLRequest(url: url), redirectResponse: response)
} else {
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
}
} catch {
client?.urlProtocol(self, didFailWithError: error)
}
Expand Down

0 comments on commit 4a05882

Please sign in to comment.