Skip to content

Commit

Permalink
RUMM-833 Add DD prefix to URLSessionInterceptor name and polish p…
Browse files Browse the repository at this point in the history
…ublic API comments
  • Loading branch information
ncreated committed Dec 15, 2020
1 parent c9a4104 commit 4e5071e
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 28 deletions.
8 changes: 4 additions & 4 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@
61B038542527246D00518F3C /* URLSessionSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E132B25233EC90098C6B0 /* URLSessionSwizzler.swift */; };
61B0385A2527247000518F3C /* DDURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E1365252383D80098C6B0 /* DDURLSessionDelegate.swift */; };
61B038602527247200518F3C /* URLSessionTracingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03810252656F500518F3C /* URLSessionTracingHandler.swift */; };
61B038662527247800518F3C /* URLSessionInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E1337252340810098C6B0 /* URLSessionInterceptor.swift */; };
61B038662527247800518F3C /* DDURLSessionInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618E1337252340810098C6B0 /* DDURLSessionInterceptor.swift */; };
61B0386C2527247B00518F3C /* TaskInterception.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61417DC52525CDDE00E2D55C /* TaskInterception.swift */; };
61B03879252724AB00518F3C /* URLSessionInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03874252724AB00518F3C /* URLSessionInterceptorTests.swift */; };
61B0387A252724AB00518F3C /* FirstPartyURLsFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B03875252724AB00518F3C /* FirstPartyURLsFilterTests.swift */; };
Expand Down Expand Up @@ -625,7 +625,7 @@
618DCFE024C766F500589570 /* SendRUMFixture2ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendRUMFixture2ViewController.swift; sourceTree = "<group>"; };
618DCFE224C766FB00589570 /* SendRUMFixture3ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendRUMFixture3ViewController.swift; sourceTree = "<group>"; };
618E132B25233EC90098C6B0 /* URLSessionSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionSwizzler.swift; sourceTree = "<group>"; };
618E1337252340810098C6B0 /* URLSessionInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionInterceptor.swift; sourceTree = "<group>"; };
618E1337252340810098C6B0 /* DDURLSessionInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionInterceptor.swift; sourceTree = "<group>"; };
618E1365252383D80098C6B0 /* DDURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDURLSessionDelegate.swift; sourceTree = "<group>"; };
618E136C252384D90098C6B0 /* URLSessionAutoInstrumentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionAutoInstrumentation.swift; sourceTree = "<group>"; };
618E13A92524B8700098C6B0 /* HTTPHeadersReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeadersReader.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1495,7 +1495,7 @@
isa = PBXGroup;
children = (
613F23DC252B05BD006CD2D7 /* URLFiltering */,
618E1337252340810098C6B0 /* URLSessionInterceptor.swift */,
618E1337252340810098C6B0 /* DDURLSessionInterceptor.swift */,
61940C7B25668EC600A20043 /* URLSessionInterceptionHandler.swift */,
61417DC52525CDDE00E2D55C /* TaskInterception.swift */,
);
Expand Down Expand Up @@ -2577,7 +2577,7 @@
61133BDF2423979B00786299 /* SwiftExtensions.swift in Sources */,
6149FB3A2529D17F00EE387A /* InternalURLsFilter.swift in Sources */,
618DCFD724C7265300589570 /* RUMUUID.swift in Sources */,
61B038662527247800518F3C /* URLSessionInterceptor.swift in Sources */,
61B038662527247800518F3C /* DDURLSessionInterceptor.swift in Sources */,
61B03898252724DE00518F3C /* TracingHTTPHeaders.swift in Sources */,
9E544A4F24753C6E00E83072 /* MethodSwizzler.swift in Sources */,
61133BEA2423979B00786299 /* LogConsoleOutput.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-2020 Datadog, Inc.
*/

import Foundation

internal protocol URLSessionInterceptorType: class {
func modify(request: URLRequest) -> URLRequest
func taskCreated(task: URLSessionTask)
func taskMetricsCollected(task: URLSessionTask, metrics: URLSessionTaskMetrics)
func taskCompleted(task: URLSessionTask, error: Error?)
}

/// An object performing interception of requests sent with `URLSession`.
public class DDURLSessionInterceptor: URLSessionInterceptorType {
public static var shared: DDURLSessionInterceptor? {
URLSessionAutoInstrumentation.instance?.interceptor
}

/// Filters first party `URLs` defined by the user.
private let firstPartyURLsFilter: FirstPartyURLsFilter
/// Filters internal `URLs` used by the SDK.
private let internalURLsFilter: InternalURLsFilter
/// Handles resources interception.
/// Depending on which instrumentation is enabled, this can be either RUM or Tracing handler sending respectively: RUM Resource or tracing Span.
internal let handler: URLSessionInterceptionHandler
/// Whether or not to inject tracing headers to intercepted 1st party requests.
/// Set to `true` if Tracing instrumentation is enabled (no matter o RUM state).
internal let injectTracingHeadersToFirstPartyRequests: Bool
/// Additional header injected to intercepted 1st party requests.
/// Set to `x-datadog-origin: rum` if both RUM and Tracing instrumentations are enabled and `nil` in all other cases.
internal let additionalHeadersForFirstPartyRequests: [String: String]?

// MARK: - Initialization

convenience init(
configuration: FeaturesConfiguration.URLSessionAutoInstrumentation,
dateProvider: DateProvider
) {
let handler: URLSessionInterceptionHandler

if configuration.instrumentRUM {
handler = URLSessionRUMResourcesHandler(dateProvider: dateProvider)
} else {
handler = URLSessionTracingHandler()
}

self.init(configuration: configuration, handler: handler)
}

init(
configuration: FeaturesConfiguration.URLSessionAutoInstrumentation,
handler: URLSessionInterceptionHandler
) {
self.firstPartyURLsFilter = FirstPartyURLsFilter(configuration: configuration)
self.internalURLsFilter = InternalURLsFilter(configuration: configuration)
self.handler = handler

if configuration.instrumentTracing {
self.injectTracingHeadersToFirstPartyRequests = true

if configuration.instrumentRUM {
// If RUM instrumentation is enabled, additional `x-datadog-origin: rum` header is injected to the user request,
// so that user's backend instrumentation can further process it and count on RUM quota.
self.additionalHeadersForFirstPartyRequests = [
TracingHTTPHeaders.originField: TracingHTTPHeaders.rumOriginValue
]
} else {
self.additionalHeadersForFirstPartyRequests = nil
}
} else {
self.injectTracingHeadersToFirstPartyRequests = false
self.additionalHeadersForFirstPartyRequests = nil
}
}

/// An internal queue for synchronising the access to `interceptionByTask`.
private let queue = DispatchQueue(label: "com.datadoghq.URLSessionInterceptor", target: .global(qos: .utility))
/// Maps `URLSessionTask` to its `TaskInterception` object.
private var interceptionByTask: [URLSessionTask: TaskInterception] = [:]

// MARK: - Public

/// Intercepts given `URLRequest` before it is sent.
/// If Tracing feature is enabled and first party hosts are configured in `Datadog.Configuration`, this method will
/// modify the `request` by adding Datadog trace propagation headers. This will enable end-to-end trace propagation
/// from the client application to backend services instrumented with Datadog agents.
/// - Parameter request: input request.
/// - Returns: modified input requests. The modified request may contain additional Datadog headers.
public func modify(request: URLRequest) -> URLRequest {
guard !internalURLsFilter.isInternal(url: request.url) else {
return request
}
if injectTracingHeadersToFirstPartyRequests,
firstPartyURLsFilter.isFirstParty(url: request.url) {
return injectSpanContext(into: request)
}
return request
}

/// Notifies the `URLSessionTask` creation.
/// This method should be called as soon as the task was created.
/// - Parameter task: the task object obtained from `URLSession`.
public func taskCreated(task: URLSessionTask) {
guard let request = task.originalRequest,
!internalURLsFilter.isInternal(url: request.url) else {
return
}

queue.async {
let interception = TaskInterception(
request: request,
isFirstParty: self.firstPartyURLsFilter.isFirstParty(url: request.url)
)
self.interceptionByTask[task] = interception

if let spanContext = self.extractSpanContext(from: request) {
interception.register(spanContext: spanContext)
}

self.handler.notify_taskInterceptionStarted(interception: interception)
}
}

/// Notifies the `URLSessionTask` metrics collection.
/// This method should be called as soon as the task metrics were received by `URLSessionDelegate`.
/// - Parameters:
/// - task: task receiving metrics.
/// - metrics: metrics object delivered to `URLSessionDelegate`.
public func taskMetricsCollected(task: URLSessionTask, metrics: URLSessionTaskMetrics) {
guard !internalURLsFilter.isInternal(url: task.originalRequest?.url) else {
return
}

queue.async {
guard let interception = self.interceptionByTask[task] else {
return
}

interception.register(
metrics: ResourceMetrics(taskMetrics: metrics)
)

if interception.isDone {
self.finishInterception(task: task, interception: interception)
}
}
}

/// Notifies the `URLSessionTask` completion.
/// This method should be called as soon as the task was completed.
/// - Parameter task: the task object obtained from `URLSession`.
/// - Parameter error: optional `Error` if the task completed with error.
public func taskCompleted(task: URLSessionTask, error: Error?) {
guard !internalURLsFilter.isInternal(url: task.originalRequest?.url) else {
return
}

queue.async {
guard let interception = self.interceptionByTask[task] else {
return
}

interception.register(
completion: ResourceCompletion(response: task.response, error: error)
)

if interception.isDone {
self.finishInterception(task: task, interception: interception)
}
}
}

// MARK: - Private

private func finishInterception(task: URLSessionTask, interception: TaskInterception) {
interceptionByTask[task] = nil
handler.notify_taskInterceptionCompleted(interception: interception)
}

// MARK: - SpanContext Injection & Extraction

private func injectSpanContext(into firstPartyRequest: URLRequest) -> URLRequest {
guard let tracer = Global.sharedTracer as? Tracer else {
return firstPartyRequest
}

let writer = HTTPHeadersWriter()
let spanContext = tracer.createSpanContext()

tracer.inject(spanContext: spanContext, writer: writer)

var newRequest = firstPartyRequest
writer.tracePropagationHTTPHeaders.forEach { field, value in
newRequest.setValue(value, forHTTPHeaderField: field)
}

additionalHeadersForFirstPartyRequests?.forEach { field, value in
newRequest.setValue(value, forHTTPHeaderField: field)
}

return newRequest
}

private func extractSpanContext(from request: URLRequest) -> DDSpanContext? {
guard let tracer = Global.sharedTracer as? Tracer,
let headers = request.allHTTPHeaderFields else {
return nil
}

let reader = HTTPHeadersReader(httpHeaderFields: headers)
return tracer.extract(reader: reader) as? DDSpanContext
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Foundation

/// An object performing interception of requests sent with `URLSession`.
public protocol URLSessionInterceptorType: class {
internal protocol URLSessionInterceptorType: class {
/// Modifies the `URLRequest` before the `URLSessionTask` is created.
func modify(request: URLRequest) -> URLRequest

Expand All @@ -21,8 +21,8 @@ public protocol URLSessionInterceptorType: class {
func taskCompleted(task: URLSessionTask, error: Error?)
}

public class URLSessionInterceptor: URLSessionInterceptorType {
public static var shared: URLSessionInterceptor? {
public class DDURLSessionInterceptor: URLSessionInterceptorType {
public static var shared: DDURLSessionInterceptor? {
URLSessionAutoInstrumentation.instance?.interceptor
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ internal class URLSessionAutoInstrumentation {
static var instance: URLSessionAutoInstrumentation?

let swizzler: URLSessionSwizzler
let interceptor: URLSessionInterceptor
let interceptor: DDURLSessionInterceptor

init?(
configuration: FeaturesConfiguration.URLSessionAutoInstrumentation,
dateProvider: DateProvider
) {
do {
self.interceptor = URLSessionInterceptor(configuration: configuration, dateProvider: dateProvider)
self.interceptor = DDURLSessionInterceptor(configuration: configuration, dateProvider: dateProvider)
self.swizzler = try URLSessionSwizzler()
} catch {
consolePrint(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class URLSessionInterceptorTests: XCTestCase {
let instrumentRUM = false

// When
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: .mockWith(instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM),
dateProvider: SystemDateProvider()
)
Expand All @@ -65,7 +65,7 @@ class URLSessionInterceptorTests: XCTestCase {
let instrumentRUM = true

// When
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: .mockWith(instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM),
dateProvider: SystemDateProvider()
)
Expand All @@ -88,7 +88,7 @@ class URLSessionInterceptorTests: XCTestCase {
let instrumentRUM = true

// When
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: .mockWith(instrumentTracing: instrumentTracing, instrumentRUM: instrumentRUM),
dateProvider: SystemDateProvider()
)
Expand Down Expand Up @@ -122,7 +122,7 @@ class URLSessionInterceptorTests: XCTestCase {

func testGivenTracingAndRUMInstrumentationEnabled_whenInterceptingRequests_itInjectsTracingContextToFirstPartyRequests() throws {
// Given
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: true),
handler: handler
)
Expand Down Expand Up @@ -152,7 +152,7 @@ class URLSessionInterceptorTests: XCTestCase {

func testGivenOnlyTracingInstrumentationEnabled_whenInterceptingRequests_itInjectsTracingContextToFirstPartyRequests() throws {
// Given
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: false),
handler: handler
)
Expand Down Expand Up @@ -181,7 +181,7 @@ class URLSessionInterceptorTests: XCTestCase {

func testGivenOnlyRUMInstrumentationEnabled_whenInterceptingRequests_itDoesNotModifyThem() throws {
// Given
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: false, rumInstrumentationEnabled: true),
handler: handler
)
Expand All @@ -201,7 +201,7 @@ class URLSessionInterceptorTests: XCTestCase {

func testGivenTracingInstrumentationEnabledButTracerNotRegistered_whenInterceptingRequests_itDoesNotInjectTracingContextToAnyRequest() throws {
// Given
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: .random()),
handler: handler
)
Expand Down Expand Up @@ -236,7 +236,7 @@ class URLSessionInterceptorTests: XCTestCase {
}

// Given
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: .random()),
handler: handler
)
Expand Down Expand Up @@ -313,7 +313,7 @@ class URLSessionInterceptorTests: XCTestCase {
}

// Given
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: false, rumInstrumentationEnabled: true),
handler: handler
)
Expand Down Expand Up @@ -375,7 +375,7 @@ class URLSessionInterceptorTests: XCTestCase {
// MARK: - Thread Safety

func testRandomlyCallingDifferentAPIsConcurrentlyDoesNotCrash() {
let interceptor = URLSessionInterceptor(
let interceptor = DDURLSessionInterceptor(
configuration: mockConfiguration(tracingInstrumentationEnabled: true, rumInstrumentationEnabled: true),
handler: handler
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class URLSessionAutoInstrumentationTests: XCTestCase {
}

func testWhenURLSessionAutoInstrumentationIsEnabled_thenSharedIntrceptorIsAvailable() {
XCTAssertNil(URLSessionInterceptor.shared)
XCTAssertNil(DDURLSessionInterceptor.shared)

// When
URLSessionAutoInstrumentation.instance = URLSessionAutoInstrumentation(
Expand All @@ -32,7 +32,7 @@ class URLSessionAutoInstrumentationTests: XCTestCase {
}

// Then
XCTAssertNotNil(URLSessionInterceptor.shared)
XCTAssertNotNil(DDURLSessionInterceptor.shared)
}

func testGivenURLSessionAutoInstrumentationEnabled_whenRUMMonitorIsRegistered_itSubscribesAsResourcesHandler() throws {
Expand Down
Loading

0 comments on commit 4e5071e

Please sign in to comment.