-
Notifications
You must be signed in to change notification settings - Fork 230
/
FlushRequest.swift
106 lines (89 loc) · 3.95 KB
/
FlushRequest.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//
// FlushRequest.swift
// Mixpanel
//
// Created by Yarden Eitan on 7/8/16.
// Copyright © 2016 Mixpanel. All rights reserved.
//
import Foundation
enum FlushType: String {
case events = "/track/"
case people = "/engage/"
case groups = "/groups/"
}
class FlushRequest: Network {
var networkRequestsAllowedAfterTime = 0.0
var networkConsecutiveFailures = 0
func sendRequest(_ requestData: String,
type: FlushType,
useIP: Bool,
headers: [String: String],
queryItems: [URLQueryItem] = []) -> Bool {
let responseParser: (Data) -> Int? = { data in
let response = String(data: data, encoding: String.Encoding.utf8)
if let response = response {
return Int(response) ?? 0
}
return nil
}
let resourceHeaders: [String: String] = ["Content-Type": "application/json"].merging(headers) {(_,new) in new }
let ipString = useIP ? "1" : "0"
var resourceQueryItems: [URLQueryItem] = [URLQueryItem(name: "ip", value: ipString)]
resourceQueryItems.append(contentsOf: queryItems)
let resource = Network.buildResource(path: type.rawValue,
method: .post,
requestBody: requestData.data(using: .utf8),
queryItems: resourceQueryItems,
headers: resourceHeaders,
parse: responseParser)
var result = false
let semaphore = DispatchSemaphore(value: 0)
flushRequestHandler(serverURL,
resource: resource,
completion: { success in
result = success
semaphore.signal()
})
_ = semaphore.wait(timeout: .now() + 120.0)
return result
}
private func flushRequestHandler(_ base: String,
resource: Resource<Int>,
completion: @escaping (Bool) -> Void) {
Network.apiRequest(base: base, resource: resource,
failure: { (reason, _, response) in
self.networkConsecutiveFailures += 1
self.updateRetryDelay(response)
Logger.warn(message: "API request to \(resource.path) has failed with reason \(reason)")
completion(false)
}, success: { (result, response) in
self.networkConsecutiveFailures = 0
self.updateRetryDelay(response)
if result == 0 {
Logger.info(message: "\(base) api rejected some items")
}
completion(true)
})
}
private func updateRetryDelay(_ response: URLResponse?) {
var retryTime = 0.0
let retryHeader = (response as? HTTPURLResponse)?.allHeaderFields["Retry-After"] as? String
if let retryHeader = retryHeader, let retryHeaderParsed = (Double(retryHeader)) {
retryTime = retryHeaderParsed
}
if networkConsecutiveFailures >= APIConstants.failuresTillBackoff {
retryTime = max(retryTime,
retryBackOffTimeWithConsecutiveFailures(networkConsecutiveFailures))
}
let retryDate = Date(timeIntervalSinceNow: retryTime)
networkRequestsAllowedAfterTime = retryDate.timeIntervalSince1970
}
private func retryBackOffTimeWithConsecutiveFailures(_ failureCount: Int) -> TimeInterval {
let time = pow(2.0, Double(failureCount) - 1) * 60 + Double(arc4random_uniform(30))
return min(max(APIConstants.minRetryBackoff, time),
APIConstants.maxRetryBackoff)
}
func requestNotAllowed() -> Bool {
return Date().timeIntervalSince1970 < networkRequestsAllowedAfterTime
}
}