Skip to content

Commit b617339

Browse files
committed
added different strategy
1 parent 9257611 commit b617339

File tree

5 files changed

+200
-40
lines changed

5 files changed

+200
-40
lines changed

README.md

+20-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Network layer for running requests like GET, POST, PUT, DELETE etc customizable
88
## Features
99
- [x] Multiplatform
1010
- [x] Stand alone package without any dependencies using just Apple's facilities
11-
- [x] Set up amount of attempts(retry) with **"Exponential backoff"** strategy if request fails. Exponential backoff is a strategy in which you increase the delays between retries.
11+
- [x] Set up amount of attempts(retry) with **"Exponential backoff"** or **"Constant backoff"** strategy if request fails. Exponential backoff is a strategy in which you increase the delays between retries. Constant backoff is a strategy when delay between retries is a constant value
1212
- [x] Customizable for different requests schemes from classic **CRUD Rest** to what suits to you
1313
- [x] Customizable in term of URLSession
1414
- [x] Customizable in terms of URLSessionTaskDelegate, URLSessionDelegate
@@ -62,19 +62,29 @@ Network layer for running requests like GET, POST, PUT, DELETE etc customizable
6262
### Custom request
6363

6464
```swift
65-
/// - Parameters:
66-
/// - request: A URL load request that is independent of protocol or URL scheme
67-
/// - retry: Amount of attempts Default value is 1
68-
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
69-
func send(
70-
with request : URLRequest,
71-
retry : UInt = 1,
72-
_ taskDelegate: ITaskDelegate? = nil
65+
/// Send custom request based on the specific request instance
66+
/// - Parameters:
67+
/// - request: A URL load request that is independent of protocol or URL scheme
68+
/// - retry: ``RetryService.Strategy`` strategy Default value .exponential with 5 retry and duration 2.0
69+
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
70+
public func send<T>(
71+
with request : URLRequest,
72+
retry strategy : RetryService.Strategy = .exponential(),
73+
_ taskDelegate: ITaskDelegate? = nil
74+
) async throws -> Http.Response<T> where T : Decodable
7375
```
7476

77+
## Retry strategy
78+
79+
| type | description |
80+
| --- | --- |
81+
| constant | The strategy implements constant backoff |
82+
| exponential | The strategy implements exponential backoff |
83+
84+
7585
# The concept
7686

77-
* Proxy is defining a communication layer and responsible for exchanging data with data source. There might be Http proxy, File proxy etc or some flavours REST proxy, LongFile proxy.
87+
* Proxy is defining a communication layer and responsible for exchanging data with data source. There might be Http proxy, File proxy etc or some flavors REST proxy, LongFile proxy.
7888
* Reader and Writer are used to interpret data.
7989

8090
![The concept](https://github.com/The-Igor/async-http-client/blob/main/img/concept.png)

Sources/async-http-client/proxy/http/Proxy.swift

+37-30
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public extension Http{
3434
/// - path: Path
3535
/// - query: An array of name-value pairs
3636
/// - headers: A dictionary containing all of the HTTP header fields for a request
37-
/// - retry: Amount of attempts Default value is 1
37+
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
3838
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
3939
public func get<T>(
4040
path: String,
@@ -47,8 +47,9 @@ public extension Http{
4747
{
4848

4949
let request = try buildURLRequest(for: path, query: query, headers: headers)
50+
let strategy = RetryService.Strategy.exponential(retry: retry, duration: 2)
5051

51-
return try await send(with: request, retry: retry, taskDelegate)
52+
return try await send(with: request, retry: strategy, taskDelegate)
5253
}
5354

5455
/// POST request
@@ -57,7 +58,7 @@ public extension Http{
5758
/// - body: The data sent as the message body of a request, such as for an HTTP POST or PUT requests
5859
/// - query: An array of name-value pairs
5960
/// - headers: A dictionary containing all of the HTTP header fields for a request
60-
/// - retry: Amount of attempts Default value is 1
61+
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
6162
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
6263
public func post<T>(
6364
path: String,
@@ -70,8 +71,9 @@ public extension Http{
7071
-> Http.Response<T> where T: Decodable
7172
{
7273
let request = try buildURLRequest(for: path, method: .post, query: query, body: body, headers: headers)
74+
let strategy = RetryService.Strategy.exponential(retry: retry)
7375

74-
return try await send(with: request, retry: retry, taskDelegate)
76+
return try await send(with: request, retry: strategy, taskDelegate)
7577
}
7678

7779

@@ -81,7 +83,7 @@ public extension Http{
8183
/// - body: The data sent as the message body of a request, such as for an HTTP POST or PUT requests
8284
/// - query: An array of name-value pairs
8385
/// - headers: A dictionary containing all of the HTTP header fields for a request
84-
/// - retry: Amount of attempts Default value is 1
86+
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
8587
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
8688
public func put<T>(
8789
path: String,
@@ -94,16 +96,17 @@ public extension Http{
9496
-> Http.Response<T> where T: Decodable
9597
{
9698
let request = try buildURLRequest(for: path, method: .put, query: query, body: body, headers: headers)
99+
let strategy = RetryService.Strategy.exponential(retry: retry)
97100

98-
return try await send(with: request, retry: retry, taskDelegate)
101+
return try await send(with: request, retry: strategy, taskDelegate)
99102
}
100103

101104
/// DELETE request
102105
/// - Parameters:
103106
/// - path: Path
104107
/// - query: An array of name-value pairs
105108
/// - headers: A dictionary containing all of the HTTP header fields for a request
106-
/// - retry: Amount of attempts Default value is 1
109+
/// - retry: Amount of attempts Default value .exponential with 5 retry and duration 2.0
107110
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
108111
public func delete<T>(
109112
path: String,
@@ -115,26 +118,30 @@ public extension Http{
115118
-> Http.Response<T> where T: Decodable
116119
{
117120
let request = try buildURLRequest(for: path, method: .delete, query: query, headers: headers)
118-
119-
return try await send(with: request, retry: retry, taskDelegate)
121+
let strategy = RetryService.Strategy.exponential(retry: retry)
122+
return try await send(with: request, retry: strategy, taskDelegate)
120123
}
121124

122125

123126
/// Send custom request based on the specific request instance
124127
/// - Parameters:
125128
/// - request: A URL load request that is independent of protocol or URL scheme
126-
/// - retry: Amount of attempts Default value is 1
129+
/// - retry: ``RetryService.Strategy`` strategy Default value .exponential with 5 retry and duration 2.0
127130
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
128131
public func send<T>(
129132
with request : URLRequest,
130-
retry : UInt = 1,
133+
retry strategy : RetryService.Strategy = .exponential(),
131134
_ taskDelegate: ITaskDelegate? = nil
132135
) async throws -> Http.Response<T> where T : Decodable
133136
{
134137

135138
let reader = config.reader
136139

137-
let (data,response) = try await sendRetry(with: request, retry: retry, taskDelegate)
140+
let (data,response) = try await sendRetry(
141+
with: request,
142+
retry: strategy,
143+
taskDelegate
144+
)
138145

139146
let value: T = try reader.read(data: data)
140147

@@ -150,35 +157,35 @@ private extension Http.Proxy{
150157

151158
/// - Parameters:
152159
/// - request: A URL load request that is independent of protocol or URL scheme
153-
/// - retry: Amount of attempts Default value is 1
160+
/// - retry: ``RetryService`` strategy
154161
/// - taskDelegate: A protocol that defines methods that URL session instances call on their delegates to handle task-level events
155162
func sendRetry(
156163
with request : URLRequest,
157-
retry : UInt = 1,
164+
retry strategy : RetryService.Strategy,
158165
_ taskDelegate: ITaskDelegate? = nil
159166
) async throws -> (Data, URLResponse)
160167
{
161168

162169
let session = config.getSession
163-
var nextDelay: UInt64 = 1
170+
let service = RetryService(strategy: strategy)
164171

165-
if retry > 1{
166-
let up = retry - 1
167-
for i in 1...up{
168-
do{
169-
return try await session.data(for: request, delegate: taskDelegate)
170-
}catch{
171-
#if DEBUG
172-
print("retry \(i)")
173-
#endif
174-
}
175-
// nanoseconds the only choice for iOS15
176-
try? await Task.sleep(nanoseconds: 1_000_000_000 * nextDelay)
177-
178-
nextDelay *= 2
172+
for delay in service{
173+
do{
174+
print(delay)
175+
return try await session.data(for: request, delegate: taskDelegate)
176+
}catch{
177+
#if DEBUG
178+
print("retry send \(delay)")
179+
#endif
179180
}
181+
182+
try? await Task.sleep(nanoseconds: 1_000_000_000 * UInt64(delay))
180183
}
181-
184+
185+
#if DEBUG
186+
print("retry send last")
187+
#endif
188+
182189
/// one more time to let the error to propagate if it fails the last time
183190
return try await session.data(for: request, delegate: taskDelegate)
184191
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// RetryIterator.swift
3+
//
4+
//
5+
// Created by Igor on 06.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
11+
public extension RetryService{
12+
13+
/// Retry iterator
14+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
15+
struct RetryIterator: IteratorProtocol {
16+
17+
/// Current amount of retries
18+
public private(set) var retries: UInt = 1
19+
20+
/// Retry service
21+
public let service: RetryService
22+
23+
/// - Parameter service: Retry service ``RetryService``
24+
init(service: RetryService){
25+
self.service = service
26+
}
27+
28+
/// Returns the next delay amount, or `nil`.
29+
public mutating func next() -> TimeInterval? {
30+
31+
let max = service.maximumRetries
32+
let time = service.duration
33+
34+
guard time > 0 else{ return nil }
35+
36+
guard max > retries && max != 0 else { return nil }
37+
38+
defer { retries += 1 }
39+
40+
let delay: TimeInterval
41+
42+
switch service.strategy {
43+
case .constant(_, let duration):
44+
delay = duration
45+
case .exponential(_, let duration):
46+
delay = duration * Double(retries)
47+
}
48+
49+
return delay
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// RetryService.swift
3+
//
4+
//
5+
// Created by Igor on 06.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
11+
/// Generate sequence of time delays between retries depending on retry strategy
12+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
13+
public struct RetryService: Equatable, Sequence{
14+
15+
/// Retry strategy
16+
public let strategy: Strategy
17+
18+
19+
/// Default service
20+
static let `default` = RetryService(strategy: .exponential())
21+
22+
23+
/// - Parameter strategy: Retry strategy ``RetryService.Strategy``
24+
public init(strategy: Strategy){
25+
self.strategy = strategy
26+
}
27+
28+
/// Max amount of retires
29+
var maximumRetries: UInt{
30+
strategy.maximumRetries
31+
}
32+
33+
/// Duration between retries For .exponential multiply on the amount of the current retries
34+
var duration: TimeInterval{
35+
strategy.duration
36+
}
37+
38+
/// - Returns: Retry delays iterator
39+
public func makeIterator() -> RetryIterator {
40+
return RetryIterator(service: self)
41+
}
42+
}
43+
44+
45+
46+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// Strategy.swift
3+
//
4+
//
5+
// Created by Igor on 06.03.2023.
6+
//
7+
8+
import Foundation
9+
10+
public extension RetryService{
11+
12+
/// Retry strategy
13+
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
14+
enum Strategy: Hashable {
15+
16+
/// constant delay between reties
17+
case constant(
18+
retry : UInt = 5,
19+
duration: TimeInterval = 2.0
20+
)
21+
22+
/// Exponential backoff is a strategy in which you increase the delays between retries.
23+
case exponential(
24+
retry : UInt = 5,
25+
duration: TimeInterval = 2.0
26+
)
27+
28+
29+
/// Max amount of retries
30+
var maximumRetries: UInt{
31+
switch self{
32+
case .constant(let retry, _) : return retry
33+
case .exponential(let retry, _) : return retry
34+
}
35+
}
36+
37+
/// Duration between retries For .exponential multiply on the amount of the current retries
38+
var duration: TimeInterval{
39+
switch self{
40+
case .constant(_, let duration) : return duration
41+
case .exponential(_, let duration) : return duration
42+
}
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)