Skip to content

Commit

Permalink
feat(auto-retry): added possibility to retry more requests
Browse files Browse the repository at this point in the history
  • Loading branch information
jguz-pubnub committed Dec 12, 2023
1 parent b47b478 commit a994d2f
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 98 deletions.
7 changes: 6 additions & 1 deletion PubNubMembership/Sources/Membership+PubNub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import PubNubUser
public protocol PubNubMembershipInterface {
/// A copy of the configuration object used for this session
var configuration: PubNubConfiguration { get }

/// Session used for performing request/response REST calls
var networkSession: SessionReplaceable { get }

Expand Down Expand Up @@ -268,6 +267,7 @@ public extension PubNubMembershipInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.fetchMemberships],
responseDecoder: FetchMultipleValueResponseDecoder<PubNubMembership.PartialSpace>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -320,6 +320,7 @@ public extension PubNubMembershipInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.fetchMemberships],
responseDecoder: FetchMultipleValueResponseDecoder<PubNubMembership.PartialUser>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -365,6 +366,7 @@ public extension PubNubMembershipInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.addMemberships],
responseDecoder: FetchStatusResponseDecoder(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -401,6 +403,7 @@ public extension PubNubMembershipInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.addMemberships],
responseDecoder: FetchStatusResponseDecoder(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -463,6 +466,7 @@ public extension PubNubMembershipInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.removeMemberships],
responseDecoder: FetchStatusResponseDecoder(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -499,6 +503,7 @@ public extension PubNubMembershipInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.removeMemberships],
responseDecoder: FetchStatusResponseDecoder(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down
5 changes: 4 additions & 1 deletion PubNubSpace/Sources/Space+PubNub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import PubNub
public protocol PubNubSpaceInterface {
/// A copy of the configuration object used for this session
var configuration: PubNubConfiguration { get }

/// Session used for performing request/response REST calls
var networkSession: SessionReplaceable { get }

Expand Down Expand Up @@ -213,6 +212,7 @@ public extension PubNubSpaceInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.fetchSpaces],
responseDecoder: FetchMultipleValueResponseDecoder<PubNubSpace>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand All @@ -237,6 +237,7 @@ public extension PubNubSpaceInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.fetchSpaces],
responseDecoder: FetchSingleValueResponseDecoder<PubNubSpace>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -273,6 +274,7 @@ public extension PubNubSpaceInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.createSpace],
responseDecoder: FetchSingleValueResponseDecoder<PubNubSpace>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -317,6 +319,7 @@ public extension PubNubSpaceInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.removeSpace],
responseDecoder: FetchStatusResponseDecoder(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down
6 changes: 4 additions & 2 deletions PubNubUser/Sources/User+PubNub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
//

import Foundation

import PubNub

/// Protocol interface to manage `PubNubUser` entities using closures
public protocol PubNubUserInterface {
/// A copy of the configuration object used for this session
var configuration: PubNubConfiguration { get }

/// Session used for performing request/response REST calls
var networkSession: SessionReplaceable { get }

Expand Down Expand Up @@ -221,6 +219,7 @@ public extension PubNubUserInterface {
(requestConfig.customSession ?? networkSession)?
.route(
router,
requestOperator: configuration.automaticRetry?[.fetchUsers],
responseDecoder: FetchMultipleValueResponseDecoder<PubNubUser>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -248,6 +247,7 @@ public extension PubNubUserInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.fetchUsers],
responseDecoder: FetchSingleValueResponseDecoder<PubNubUser>(),
responseQueue: requestConfig.responseQueue
) {
Expand Down Expand Up @@ -288,6 +288,7 @@ public extension PubNubUserInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.createUser],
responseDecoder: FetchSingleValueResponseDecoder<PubNubUser>(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down Expand Up @@ -336,6 +337,7 @@ public extension PubNubUserInterface {
(requestConfig.customSession ?? networkSession)
.route(
router,
requestOperator: configuration.automaticRetry?[.removeUser],
responseDecoder: FetchStatusResponseDecoder(),
responseQueue: requestConfig.responseQueue
) { result in
Expand Down
12 changes: 9 additions & 3 deletions Sources/PubNub/APIs/File+PubNub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public extension PubNub {
) {
route(
FileManagementRouter(.list(channel: channel, limit: limit, next: next), configuration: configuration),
requestOperator: configuration.automaticRetry?[.listFiles],
responseDecoder: FileListResponseDecoder(),
custom: requestConfig
) { result in
Expand Down Expand Up @@ -60,6 +61,7 @@ public extension PubNub {
) {
route(
FileManagementRouter(.delete(channel: channel, fileId: fileId, filename: filename), configuration: configuration),
requestOperator: configuration.automaticRetry?[.removeFile],
responseDecoder: FileGeneralSuccessResponseDecoder(),
custom: requestConfig
) { result in
Expand Down Expand Up @@ -137,6 +139,7 @@ public extension PubNub {
.generateURL(channel: channel, body: .init(name: remoteFilename)),
configuration: configuration
),
requestOperator: configuration.automaticRetry?[.generateFileUploadURL],
responseDecoder: FileGenerateResponseDecoder(),
custom: requestConfig
) { [configuration] result in
Expand Down Expand Up @@ -225,9 +228,12 @@ public extension PubNub {
configuration: configuration
)

route(router,
responseDecoder: PublishResponseDecoder(),
custom: request.customRequestConfig) { result in
route(
router,
requestOperator: configuration.automaticRetry?[.publishFile],
responseDecoder: PublishResponseDecoder(),
custom: request.customRequestConfig
) { result in
completion?(result.map { $0.payload.timetoken })
}
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/PubNub/Networking/Replaceables+PubNub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public protocol SessionReplaceable {

func route<Decoder>(
_ router: HTTPRouter,
requestOperator: RequestOperator?,
responseDecoder: Decoder,
responseQueue: DispatchQueue,
completion: @escaping (Result<EndpointResponse<Decoder.Payload>, Error>) -> Void
Expand All @@ -135,11 +136,12 @@ public protocol SessionReplaceable {
public extension SessionReplaceable {
func route<Decoder>(
_ router: HTTPRouter,
requestOperator: RequestOperator? = nil,
responseDecoder: Decoder,
responseQueue: DispatchQueue = .main,
completion: @escaping (Result<EndpointResponse<Decoder.Payload>, Error>) -> Void
) where Decoder: ResponseDecoder {
request(with: router, requestOperator: nil)
request(with: router, requestOperator: requestOperator)
.validate()
.response(
on: responseQueue,
Expand Down
143 changes: 134 additions & 9 deletions Sources/PubNub/Networking/Request/Operators/AutomaticRetry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ public struct AutomaticRetry: RequestOperator, Hashable {
/// No retry will be performed
public static var none = AutomaticRetry(retryLimit: 1)
/// Retry immediately twice on lost network connection
public static var connectionLost = AutomaticRetry(policy: .immediately,
retryableURLErrorCodes: [.networkConnectionLost])
public static var connectionLost = AutomaticRetry(
policy: .immediately,
retryableURLErrorCodes: [.networkConnectionLost]
)
/// Exponential backoff twice when no internet connection is detected
public static var noInternet = AutomaticRetry(policy: .defaultExponential,
retryableURLErrorCodes: [.notConnectedToInternet])
public static var noInternet = AutomaticRetry(
policy: .defaultExponential,
retryableURLErrorCodes: [.notConnectedToInternet]
)

/// Provides the action taken when a retry is to be performed
public enum ReconnectionPolicy: Hashable {
/// Exponential backoff with base/scale factor of 2, and a 300s max delay
public static let defaultExponential: ReconnectionPolicy = .exponential(base: 2, scale: 2, maxDelay: 300)

/// Linear reconnect every 3 seconds
public static let defaultLinear: ReconnectionPolicy = .linear(delay: 3)

Expand Down Expand Up @@ -80,12 +83,52 @@ public struct AutomaticRetry: RequestOperator, Hashable {
public let retryableHTTPStatusCodes: Set<Int>
/// Collection of returned `URLError.Code` objects that will trigger a retry
public let retryableURLErrorCodes: Set<URLError.Code>
/// The list of endpoints excluded from retrying
public let excluded: [AutomaticRetry.Endpoint]

public init(
retryLimit: UInt = 2,
policy: ReconnectionPolicy = .defaultExponential,
retryableHTTPStatusCodes: Set<Int> = [500],
retryableURLErrorCodes: Set<URLError.Code> = AutomaticRetry.defaultRetryableURLErrorCodes
retryableURLErrorCodes: Set<URLError.Code> = AutomaticRetry.defaultRetryableURLErrorCodes,
excluded endpoints: [AutomaticRetry.Endpoint] = [
.addChannelsToGroup,
.removeChannelsFromGroup,
.listChannelsForGroup,
.listChannelGroups,
.removeChannelGroup,
.publish,
.fire,
.signal,
.time,
.whereNow,
.hereNow,
.setPresence,
.getPresence,
.fetchMessageActions,
.addMessageAction,
.removeMessageAction,
.fetchMessageHistory,
.deleteMessageHistory,
.messageCounts,
.fetchMemberships,
.addMemberships,
.removeMemberships,
.fetchUsers,
.createUser,
.removeUser,
.fetchSpaces,
.createSpace,
.removeSpace,
.listPushChannels,
.managePushChannels,
.listAPNSPushChannels,
.manageAPNSDevices,
.listFiles,
.generateFileUploadURL,
.publishFile,
.removeFile
]
) {
switch policy {
case let .exponential(base, scale, max):
Expand Down Expand Up @@ -117,6 +160,7 @@ public struct AutomaticRetry: RequestOperator, Hashable {
self.retryLimit = retryLimit
self.retryableHTTPStatusCodes = retryableHTTPStatusCodes
self.retryableURLErrorCodes = retryableURLErrorCodes
self.excluded = endpoints
}

public func retry(
Expand All @@ -138,11 +182,92 @@ public struct AutomaticRetry: RequestOperator, Hashable {
return true
} else if let errorCode = error.urlError?.code, retryableURLErrorCodes.contains(errorCode) {
return true
} else if let errorCode = error.pubNubError?.underlying?.urlError?.code,
retryableURLErrorCodes.contains(errorCode) {
} else if let errorCode = error.pubNubError?.underlying?.urlError?.code, retryableURLErrorCodes.contains(errorCode) {
return true
}

return false
}

public subscript(endpoint: AutomaticRetry.Endpoint) -> RequestOperator? {
excluded.contains(endpoint) ? nil : self
}

public enum Endpoint {
/// Adding a channel to the channel group
case addChannelsToGroup
/// Removing a channel from the channel group
case removeChannelsFromGroup
/// Listing all the channels of the channel group
case listChannelsForGroup
/// Listing all the channel groups
case listChannelGroups
/// Removing the channel group
case removeChannelGroup
/// Publishing a message to the channel
case publish
/// Publishing a message to PubNub Functions Event Handlers
case fire
/// Publish a message to PubNub Functions Event Handlers
case signal
/// Getting current `Timetoken` from System
case time
/// Subscribing to channels and/or channel groups
case subscribe
/// Informing Presence that a user is still active
case heartbeat
/// Obtaining information about the current list of channels a UUID is subscribed to
case whereNow
/// Obtaining information about the current state of a channel
case hereNow
/// Setting state dictionary pairs specific to a subscriber UUID
case setPresence
/// Getting state dictionary pairs from a specific subscriber uuid
case getPresence
/// Fetching a list of Message Actions for a channel
case fetchMessageActions
/// Add an Action to a Message
case addMessageAction
/// Removes a Message Action from a published Message
case removeMessageAction
/// Fetching historical messages of a channel
case fetchMessageHistory
/// Removing the messages from the history of a specific channel
case deleteMessageHistory
/// Returning the number of messages published for one or more channels
case messageCounts
/// Fetching all `PubNubMembership` linked to a specific `PubNubUser.id`
case fetchMemberships
/// Adding a `PubNubMembership` relationship between a `PubNubSpace` and one or more `PubNubUser`
case addMemberships
/// Removing the `PubNubMembership` relationship
case removeMemberships
/// Fetching one or all `PubNubUser` that exist on a keyset
case fetchUsers
/// Creating a new `PubNubUser`
case createUser
/// Removing a previously created `PubNubUser` (if it existed)
case removeUser
/// Fetching one or all `PubNubSpace` that exist on a keyset
case fetchSpaces
/// Creating a new `PubNubSpace`
case createSpace
/// Updating an existing`PubNubSpace`
case removeSpace
/// Getting channels on which push notification has been enabled using specified push token
case listPushChannels
/// Getting channels on which APNS push notification has been enabled using specified device token and topic
case listAPNSPushChannels
/// Adding/removing push notification functionality on provided set of channels
case managePushChannels
/// Adding/removing APNS push notification functionality on provided set of channels for a given topic
case manageAPNSDevices
/// Retrieve list of files uploaded to a channel
case listFiles
/// Generating a File Upload URL
case generateFileUploadURL
/// Publishing the `PubNubFile` representing the uploaded File
case publishFile
/// Removing file from specified `Channel`
case removeFile
}
}
Loading

0 comments on commit a994d2f

Please sign in to comment.