Skip to content

Commit

Permalink
[ch91505] Dynamic header configuration (#128)
Browse files Browse the repository at this point in the history
Allow dynamic configuration of http headers through LDConfig.headerDelegate
  • Loading branch information
apache-hb authored Nov 6, 2020
1 parent 3963585 commit cc9cd49
Show file tree
Hide file tree
Showing 52 changed files with 88 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github "launchdarkly/swift-eventsource" ~> 1.1.0
github "launchdarkly/swift-eventsource" ~> 1.2.0
2 changes: 1 addition & 1 deletion LaunchDarkly.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ Pod::Spec.new do |ld|
ld.swift_version = '5.0'

ld.subspec 'Core' do |es|
es.dependency 'LDSwiftEventSource', '1.1.0'
es.dependency 'LDSwiftEventSource', '1.2.0'
end
end
6 changes: 3 additions & 3 deletions LaunchDarkly.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@
833631CA221B5DFA00BA53EE /* ErrorNotifierSpec.swift */,
8354AC75224316C700CDE602 /* Cache */,
);
path = "Service Objects";
path = "ServiceObjects";
sourceTree = "<group>";
};
83396BC71F7C3688000E256E /* Networking */ = {
Expand Down Expand Up @@ -885,7 +885,7 @@
C443A4092315AA4D00145710 /* NetworkReporter.swift */,
831AAE2B20A9E4F600B46DBA /* Throttler.swift */,
);
path = "Service Objects";
path = "ServiceObjects";
sourceTree = "<group>";
};
B467790E24D8AECA00897F00 /* Frameworks */ = {
Expand Down Expand Up @@ -1970,7 +1970,7 @@
repositoryURL = "https://github.com/LaunchDarkly/swift-eventsource.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 1.1.0;
minimumVersion = 1.2.0;
};
};
B4903D9624BD61B200F087C4 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/LaunchDarkly/swift-eventsource.git",
"state": {
"branch": null,
"revision": "4f817e1a6ce5f8bc90e06b4f19dcd999db099210",
"version": "1.1.0"
"revision": "9e224016ec5dfda7d66e0f825152f97cbe44ccb8",
"version": "1.2.0"
}
}
]
Expand Down
2 changes: 2 additions & 0 deletions LaunchDarkly/LaunchDarkly/Models/DiagnosticEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ struct DiagnosticConfig: Codable {
let maxCachedUsers: Int
let mobileKeyCount: Int
let diagnosticRecordingIntervalMillis: Int
let customHeaders: Bool

init(config: LDConfig) {
customBaseURI = config.baseUrl != LDConfig.Defaults.baseUrl
Expand All @@ -135,5 +136,6 @@ struct DiagnosticConfig: Codable {
maxCachedUsers = config.maxCachedUsers >= 0 ? config.maxCachedUsers : -1
mobileKeyCount = 1 + (config.getSecondaryMobileKeys().count)
diagnosticRecordingIntervalMillis = Int(round(config.diagnosticRecordingInterval * 1_000))
customHeaders = !config.additionalHeaders.isEmpty || config.headerDelegate != nil
}
}
19 changes: 19 additions & 0 deletions LaunchDarkly/LaunchDarkly/Models/LDConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ public enum LDStreamingMode {

typealias MobileKey = String


/**
A callback for dynamically setting http headers when connection & reconnecting to a stream
or on every poll request. This function should return a copy of the headers recieved with
any modifications or additions needed. Removing headers is discouraged as it may cause
requests to fail.

- parameter url: The endpoint that is being connected to
- parameter headers: The default headers that would be used
- returns: The headers that will be used in the request
*/
public typealias RequestHeaderTransform = (_ url: URL, _ headers: [String: String]) -> [String: String]

/**
Use LDConfig to configure the LDClient. When initialized, a LDConfig contains the default values which can be changed as needed.

Expand Down Expand Up @@ -89,6 +102,9 @@ public struct LDConfig {

/// The default additional headers that should be added to all HTTP requests from SDK components to LaunchDarkly services
static let additionalHeaders: [String: String] = [:]

/// a closure to allow dynamic changes of headers on connect & reconnect
static let headerDelegate: RequestHeaderTransform? = nil
}

/// Constants relevant to setting up an `LDConfig`
Expand Down Expand Up @@ -238,6 +254,9 @@ public struct LDConfig {
/// Additional headers that should be added to all HTTP requests from SDK components to LaunchDarkly services
public var additionalHeaders: [String: String] = [:]

/// a closure to allow dynamic changes of headers on connect & reconnect
public var headerDelegate: RequestHeaderTransform?

/// LaunchDarkly defined minima for selected configurable items
public let minima: Minima

Expand Down
27 changes: 20 additions & 7 deletions LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ final class DarklyService: DarklyServiceProvider {

// MARK: Feature Flags

private func requestTask(with: URLRequest,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
) -> URLSessionDataTask {
// copying the request is needed because swift passes by const reference without any real way of changing that
var req = with
if let headerDelegate = config.headerDelegate {
req.allHTTPHeaderFields = headerDelegate(with.url!, req.allHTTPHeaderFields ?? [:])
}
return self.session.dataTask(with: req, completionHandler: completionHandler)
}

func getFeatureFlags(useReport: Bool, completion: ServiceCompletionHandler?) {
guard !config.mobileKey.isEmpty,
let flagRequest = flagRequest(useReport: useReport)
Expand All @@ -91,7 +102,7 @@ final class DarklyService: DarklyServiceProvider {
}
return
}
let dataTask = self.session.dataTask(with: flagRequest) { [weak self] data, response, error in
let dataTask = requestTask(with: flagRequest) { [weak self] data, response, error in
DispatchQueue.main.async {
self?.processEtag(from: (data, response, error))
completion?((data, response, error))
Expand Down Expand Up @@ -166,7 +177,9 @@ final class DarklyService: DarklyServiceProvider {

// MARK: Streaming

func createEventSource(useReport: Bool, handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
func createEventSource(useReport: Bool,
handler: EventHandler,
errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
if useReport {
return serviceFactory.makeStreamingProvider(url: reportStreamRequestUrl,
httpHeaders: httpHeaders.eventSourceHeaders,
Expand All @@ -175,11 +188,13 @@ final class DarklyService: DarklyServiceProvider {
.dictionaryValue(includePrivateAttributes: true, config: config)
.jsonData,
handler: handler,
delegate: config.headerDelegate,
errorHandler: errorHandler)
}
return serviceFactory.makeStreamingProvider(url: getStreamRequestUrl,
httpHeaders: httpHeaders.eventSourceHeaders,
handler: handler,
delegate: config.headerDelegate,
errorHandler: errorHandler)
}

Expand All @@ -206,7 +221,7 @@ final class DarklyService: DarklyServiceProvider {
}
return
}
let dataTask = self.session.dataTask(with: eventRequest(eventDictionaries: eventDictionaries, payloadId: payloadId)) { (data, response, error) in
let dataTask = requestTask(with: eventRequest(eventDictionaries: eventDictionaries, payloadId: payloadId)) { (data, response, error) in
completion?((data, response, error))
}
dataTask.resume()
Expand All @@ -232,7 +247,7 @@ final class DarklyService: DarklyServiceProvider {
Log.debug(typeName(and: #function, appending: ": ") + "Aborting. No mobile key.")
return
}
let dataTask = self.session.dataTask(with: diagnosticRequest(diagnosticEvent: diagnosticEvent)) { data, response, error in
let dataTask = requestTask(with: diagnosticRequest(diagnosticEvent: diagnosticEvent)) { data, response, error in
completion?((data, response, error))
}
dataTask.resume()
Expand All @@ -256,9 +271,7 @@ extension DarklyService: TypeIdentifying { }
extension URLRequest {
mutating func appendHeaders(_ newHeaders: [String: String]) {
var headers = self.allHTTPHeaderFields ?? [:]
headers.merge(newHeaders) { _, newValue in
newValue
}
headers.merge(newHeaders) { $1 }
self.allHTTPHeaderFields = headers
}
}
2 changes: 1 addition & 1 deletion LaunchDarkly/LaunchDarkly/Networking/HTTPHeaders.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@ struct HTTPHeaders {
}

private func withAdditionalHeaders(_ headers: [String: String]) -> [String: String] {
headers.merging(additionalHeaders, uniquingKeysWith: { $1 })
headers.merging(additionalHeaders) { $1 }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ protocol ClientServiceCreating {
func makeFlagChangeNotifier() -> FlagChangeNotifying
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider) -> EventReporting
func makeEventReporter(config: LDConfig, service: DarklyServiceProvider, onSyncComplete: EventSyncCompleteClosure?) -> EventReporting
func makeStreamingProvider(url: URL, httpHeaders: [String: String], handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider
func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?, handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider
func makeStreamingProvider(url: URL, httpHeaders: [String: String], handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider
func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?, handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider
func makeEnvironmentReporter() -> EnvironmentReporting
func makeThrottler(maxDelay: TimeInterval, environmentReporter: EnvironmentReporting) -> Throttling
func makeErrorNotifier() -> ErrorNotifying
Expand Down Expand Up @@ -83,17 +83,29 @@ final class ClientServiceFactory: ClientServiceCreating {
EventReporter(config: config, service: service, onSyncComplete: onSyncComplete)
}

func makeStreamingProvider(url: URL, httpHeaders: [String: String], handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
func makeStreamingProvider(url: URL,
httpHeaders: [String: String],
handler: EventHandler,
delegate: RequestHeaderTransform?,
errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
var config: EventSource.Config = EventSource.Config(handler: handler, url: url)
config.headers = httpHeaders
config.headerTransform = { delegate?(url, $0) ?? $0 }
if let errorHandler = errorHandler {
config.connectionErrorHandler = errorHandler
}
return EventSource(config: config)
}

func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?, handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
func makeStreamingProvider(url: URL,
httpHeaders: [String: String],
connectMethod: String?,
connectBody: Data?,
handler: EventHandler,
delegate: RequestHeaderTransform?,
errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
var config: EventSource.Config = EventSource.Config(handler: handler, url: url)
config.headerTransform = { delegate?(url, $0) ?? $0 }
config.headers = httpHeaders
if let errorHandler = errorHandler {
config.connectionErrorHandler = errorHandler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ class FlagSynchronizer: LDFlagSynchronizing, EventHandler {
private var syncQueue = DispatchQueue(label: Constants.queueName, qos: .utility)
private var eventSourceStarted: Date?

init(streamingMode: LDStreamingMode, pollingInterval: TimeInterval, useReport: Bool, service: DarklyServiceProvider, onSyncComplete: FlagSyncCompleteClosure?) {
init(streamingMode: LDStreamingMode,
pollingInterval: TimeInterval,
useReport: Bool,
service: DarklyServiceProvider,
onSyncComplete: FlagSyncCompleteClosure?) {
Log.debug(FlagSynchronizer.typeName(and: #function) + "streamingMode: \(streamingMode), " + "pollingInterval: \(pollingInterval), " + "useReport: \(useReport)")
self.streamingMode = streamingMode
self.pollingInterval = pollingInterval
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,22 @@ final class ClientServiceMockFactory: ClientServiceCreating {
}

var makeStreamingProviderCallCount = 0
var makeStreamingProviderReceivedArguments: (url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?, handler: EventHandler, errorHandler: ConnectionErrorHandler?)?
func makeStreamingProvider(url: URL, httpHeaders: [String: String], handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
var makeStreamingProviderReceivedArguments: (url: URL,
httpHeaders: [String: String],
connectMethod: String?,
connectBody: Data?,
handler: EventHandler,
delegate: RequestHeaderTransform?,
errorHandler: ConnectionErrorHandler?)?
func makeStreamingProvider(url: URL, httpHeaders: [String: String], handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
makeStreamingProviderCallCount += 1
makeStreamingProviderReceivedArguments = (url, httpHeaders, nil, nil, handler, errorHandler)
makeStreamingProviderReceivedArguments = (url, httpHeaders, nil, nil, handler, delegate, errorHandler)
return DarklyStreamingProviderMock()
}

func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?, handler: EventHandler, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String?, connectBody: Data?, handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider {
makeStreamingProviderCallCount += 1
makeStreamingProviderReceivedArguments = (url, httpHeaders, connectMethod, connectBody, handler, errorHandler)
makeStreamingProviderReceivedArguments = (url, httpHeaders, connectMethod, connectBody, handler, delegate, errorHandler)
return DarklyStreamingProviderMock()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ final class DiagnosticEventSpec: QuickSpec {
expect(diagnosticConfig.maxCachedUsers) == 5
expect(diagnosticConfig.mobileKeyCount) == 1
expect(diagnosticConfig.diagnosticRecordingIntervalMillis) == 900_000
expect(diagnosticConfig.customHeaders) == false
}
}
context("init with custom config") {
Expand All @@ -261,6 +262,7 @@ final class DiagnosticEventSpec: QuickSpec {
expect(diagnosticConfig.maxCachedUsers) == -1
expect(diagnosticConfig.mobileKeyCount) == 3
expect(diagnosticConfig.diagnosticRecordingIntervalMillis) == 600_000
expect(diagnosticConfig.customHeaders) == false
}
}
var diagnosticConfig: DiagnosticConfig!
Expand All @@ -273,7 +275,7 @@ final class DiagnosticEventSpec: QuickSpec {
context("using \(desc) encoding") {
it("encodes correct values to keys") {
let decoded = self.loadAndRestoreRaw(scheme, diagnosticConfig)
expect(decoded.count) == 17
expect(decoded.count) == 18
expect((decoded["customBaseURI"] as! Bool)) == diagnosticConfig.customBaseURI
expect((decoded["customEventsURI"] as! Bool)) == diagnosticConfig.customEventsURI
expect((decoded["customStreamURI"] as! Bool)) == diagnosticConfig.customStreamURI
Expand Down
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/LaunchDarkly/swift-eventsource.git",
"state": {
"branch": null,
"revision": "4f817e1a6ce5f8bc90e06b4f19dcd999db099210",
"version": "1.1.0"
"revision": "9e224016ec5dfda7d66e0f825152f97cbe44ccb8",
"version": "1.2.0"
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let package = Package(
.package(url: "https://github.com/AliSoftware/OHHTTPStubs.git", .upToNextMinor(from: "9.0.0")),
.package(url: "https://github.com/Quick/Quick.git", .upToNextMinor(from: "3.0.0")),
.package(url: "https://github.com/Quick/Nimble.git", .upToNextMinor(from: "9.0.0")),
.package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", .upToNextMinor(from: "1.1.0"))
.package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", .upToNextMinor(from: "1.2.0"))
],
targets: [
.target(
Expand All @@ -32,6 +32,6 @@ let package = Package(
dependencies: ["LaunchDarkly", "OHHTTPStubsSwift", "Quick", "Nimble"],
path: "LaunchDarkly",
exclude: ["LaunchDarklyTests/Info.plist", "LaunchDarklyTests/.swiftlint.yml"],
sources: ["GeneratedCode", "LaunchDarklyTests"])
sources: ["GeneratedCode", "LaunchDarklyTests"]),
],
swiftLanguageVersions: [.v5])

0 comments on commit cc9cd49

Please sign in to comment.