Networking

SAPURLSession


SAPURLSession is a HTTP networking library. The SAPURLSession component provides an API around the native URLSession which is similar to the URLSession data task convenience methods. The SAPURLSession component can be used to communicate with a HTTP server. Besides, it provides support for modifying the HTTP request before it is sent, and the HTTP response prior to being returned to the caller. One can register multiple request and response observers to implement for various scenarios like e.g. authentication.

The primary usage scenario and workflow for this component is that the application:

  1. Creates a SAPURLSession instance
  2. Uses other SDK components to register observers, for example to handle authentication.
  3. Passes the configured SAPURLSession to SDK components that require HTTP communication, but do not handle aspects such as authentication. In addition, the application can also exchange HTTP messages with this component if required.

Note: The SAPURLSession component does not replace URLSession, but adds additional functionality to it. To use it efficiently, first familiarize yourself with the URLSession API.

Note: The streamTask APIs are not supported through SAPURLSession.

For additional information, see: Using URL Session

Usage

To use the SAPURLSession component:

  1. Create a SAPURLSession instance
  2. Exchange HTTP messages with a HTTP server
  3. Use observers to modify the HTTP request or the response

Create an SAPURLSession

You have different options to create a SAPURLSession instance. The simplest way is to initialize it using defaults.

let sapSession = SAPURLSession()

Note: When using the default initializer, the SAPURLSession initilizes the underlying URLSession with the following parameters:

  • configuration: URLSessionConfiguration.default
  • delegate: nil
  • delegateQueue: nil

You can provide your own URLSessionConfiguration object to configure the SAPURLSession for your needs. This allows tweaking connectivity of your app on the networking layer, including network timeouts, supported TLS version, etc. You can also use this to indicate the desired quality of service for your app (or parts of the network traffic of your app) in your enterprise WiFi network and prioritize network traffic across your apps and networks in a Fastlane enabled infrastructure.

If you need to be informed about URLSession events, implement the SAPURLSessionDelegate APIs, and pass this delegate as a parameter. You can register this delegate also to obtain the backing URLSessionTask once its initialized.

let sapSession = SAPURLSession(delegate: self)

Exchange HTTP messages

Once you have created an SAPURLSession you can use it to exchange messages. Depending on your use case you can either directly use this session or pass it to other SDK components that require an SAPURLSession instance for communication.

The SAPURLSession class exposes methods for sending short requests to the server, and it support downloading and uploading of large payloads as well

Sending short requests

To send short requests to the server, use the func dataTask(with:completionHandler:) method to instantiate a SAPURLSessionTask instance. You must start the task by calling its resume() method.

let sapSession = SAPURLSession()
let url = URL(string: "<#URL String#>")!

let task = sapSession.dataTask(with: url) { data, response, error in
  if let httpResponse = response as? HTTPURLResponse {
    // process response, inspect error message
  }
}
task.resume()

Downloading

You can download large amounts of data using the func dataTask(with:receivedResponseHandler:receivedDataHandler:completionHandler:). The downloaded data is retrieved in batches via the receivedDataHandler handler.

let sapSession = SAPURLSession()
let url = URL(string: "<#URL String#>")!
let request = URLRequest(url: url)

let dataTask = sapSession.dataTask(with: request, receivedResponseHandler: { response in
  // process response
}, receivedDataHandler: { data in
  // process data batches
}) { error in
  // error handling
}
dataTask.resume()

Uploading

To upload larger amounts of data to a server, use the func uploadTask(withStreamedRequest:needNewBodyStream:completionHandler:) as follows:

let sapSession = SAPURLSession()

let url = URL(string: "<#URL String#>")!
var request = URLRequest(url: url)
request.httpMethod = SAPURLSession.HTTPMethod.put

let uploadTask = sapSession.uploadTask(withStreamedRequest: request, needNewBodyStream: {
  return InputStream(fileAtPath: path)
}) { data, response, error in
  // error handling, response processing
  if let httpResponse = response as? HTTPURLResponse {
    // process response
  }
}
uploadTask.resume()

Using Observers

You can use observers to modify the HTTP requests before they are sent, and to modify the HTTP responses after they have been received. For that you can register the SAPURLSessionObserving implementations on the SAPURLSession, so that the registered observers are called each time this session is used to exchange HTTP messages:

class MySAPURLSessionObserver: SAPURLSessionObserving {
  func sapURLSession(_ session: SAPURLSession, task: SAPURLSessionTask, willSend request: URLRequest, completionHandler: @escaping (SAPURLSession.RequestDisposition) -> Void) -> Void {
    var updatedRequest = request
    updatedRequest.addValue("MyHeaderValue", forHTTPHeaderField: "MyHeaderKey")
    completionHandler(.allow(updatedRequest))
  }

  func func sapURLSession(_ session: SAPURLSession, dataTask: SAPURLSessionTask, didReceive response: URLResponse, completionHandler: @escaping (SAPURLSession.ResponseDisposition) -> Void) {
    completionHandler(.allow(response))
  }
}

let sapSession = SAPURLSession()
let observer = MySAPURLSessionObserver()
sapSession.register(observer)

Note: When implementing the SAPURLSessionObserving protocol you have the option to implement only the methods you want to use. This is possible because all of them have default implementation via protocol extension. The compliler will not signal error if you have a typo in the method implementation! Because there is a default implementation, it will call the one that matches. The best way to implement these methods are with the help of the Xcode’s code completion.

The observers have all of the delegate methods (except URLSessionStreamDelegate) on them, with completion handler. See the API documentation for further information.

The registered observers are called in the registration order. They contain asynchronous methods, which means their completionHandler must be called. The SAPURLSessionDelegate is called after the observers.

When a delegate with completionHandler is called, the SAPURLSession will start to call the observers in the registration order. This iteration continues until one of the observer responds with a non-default value (e.g. error response or authentication challenge response). After a non-default response from an observer the iteration is stopped, and the rest of the observers including the delegate (SAPURLSessionDelegate) will not be called.

The registered observer can be called multiple times during a single request. This is because of the possibility of one observer starts an internal request. Use the SAPURLSession.context to mark your requests or find a different way to identify whether or not your observer has to run for the given iteration. (The SAPURLSession.context is a simple [String: Any] dictionary.)

Calling the completionHandler

There are different dispositions for different observer methods, with which the completionHandler must be called. See the API doc. for more detail.

  • SAPURLSession.RequestDisposition
  • SAPURLSession.ResponseDisposition
  • SAPURLSession.CancellableDisposition
  • SAPURLSession.DataDisposition
  • SAPURLSession.AuthChallengeDisposition
  • SAPURLSession.HTTPRedirectDisposition
  • SAPURLSession.InputStreamDisposition
  • SAPURLSession.CacheResponseDisposition
  • SAPURLSession.DelayedRequestDisposition

Authentication challenge

The existing SAPURLSession implementation augments URLSession. URLSession provides the means for basic authentication, therefore the SDK does not expose any new functionality (API, observers, etc.).

To receive authentication challenges, clients must pass in a SAPURLSessionDelegate when initializing the SAPURLSession.

let sapSession = SAPURLSession(delegate: self)

The provided delegate implements the func sapURLSession(session:dataTask:didReceive:completionHandler:) delegate method, and handles the challenge accordingly.

For more information about this delegate, please see the Apple Reference: https://developer.apple.com/reference/foundation/urlsessiontaskdelegate/1411595-urlsession

Basic Authentication

The credential is based on username/password in the func sapURLSession(session:dataTask:didReceive:completionHandler:) delegate method.

This is an example how to create a URLCredential from username and password:

let urlCredential = URLCredential(user: "<#Username#>", password: "<#Password#>", persistence: .none)

Certificate Authentication

The credential is based on a certificate in the func sapURLSession(session:dataTask:didReceive:completionHandler:) delegate method.

This is an example how to create a URLCredential from a PKCS#12 file:

let path = Bundle.main.path(forResource: <#Filename#>, ofType: "p12")!
let data = NSData(contentsOfFile:path)! as Data
var items: CFArray?
let certOptions:NSDictionary = [kSecImportExportPassphrase as NSString:<#password#> as NSString]
var urlCredential: URLCredential!
var securityError = errSecSuccess
securityError = SecPKCS12Import(data as NSData, certOptions, &items)
switch securityError {
case errSecDecode:
  // Either the PKCS#12 formatted blob can't be read or it is malformed.
case errSecAuthFailed:
  // An incorrect password was passed, or data in the container got damaged.
default:
  let certItems = (items! as Array)
  let dict = certItems.first! as! Dictionary<String, AnyObject>
  let certChain = dict[kSecImportItemCertChain as String] as? Array<SecTrust>
  let identity = dict[kSecImportItemIdentity as String] as! SecIdentity?
  urlCredential = URLCredential(identity: identity!, certificates: certChain!, persistence: URLCredential.Persistence.forSession)
}
return urlCredential

Communicating with SAPcpms services

SAPcpms services need a X-SMP-APPID header for the requests which sets up the session to identify the App that is connecting to SAPcpms. In addition, an optional device ID is can also be sent in a X-SMP-DEVICEID header. These headers can be set using SAPcpmsObserver.

The code sample below demonstrates how to register the observer to an SAPURLSession instance.

let applicationID = <#Application ID#>
let deviceID = <#Device ID#>
let applicationVersion = <#Application version#>

let sapcpmsObserver = SAPcpmsObserver(applicationID: applicationID, deviceID: deviceID, applicationVersion: applicationVersion)

sapSession.register(sapcpmsObserver)

CSRF protection of SAPcpms services

Note: If you want to use CSRF protection for your application, first you must enable this feature in the SAP Cloud Platform Mobile Services cockpit. See: Defining Applications

If the CSRF protection is enabled on SAPcpms, you need to send a X-CSRF-Token header for the modifying HTTP requests. The CSRFTokenObserver can automatically handle this for every request.

Basic usage

The code sample below demonstrates how to register the observer to an SAPURLSession instance if you have one root URL for every CSRF token request. The rootUrl parameter is the URL from which the CSRF token will be requested.

let urlSession: SAPURLSession = <#SAPURLSession instance#>
let rootUrl: URL = <#your root URL#>

let csrfTokenObserver = CSRFTokenObserver(rootUrl: rootUrl)
urlSession.register(csrfTokenObserver)

Advanced usage

If you have multiple CSRF protected backends and these backends accept different CSRF tokens you must implement CSRFTokenURLProviding protocol to provide CSRF URL for different request URL’s. For token storing the CSRFTokenStoring protocol is used.

class myCSRFTokenURLProvider: CSRFTokenURLProviding {
  func csrfTokenURL(for: URL) -> URL {
    // TODO: implement custom logic to create token URL from the request URL
    return <#token URL#>
  }
}

public class myCSRFTokenStore: CSRFTokenStoring {
  /// Stores the CSRF token for the given URL.
  public func store(token: String, for url: URL) {
    // TODO: implement
  }

  /// Retrieves the CSRF token for the given URL.
  public func token(for url: URL) -> String? {
    // TODO: implement
    return <#token URL#>
  }

  /// Deletes the CSRF token for the given URL.
  public func deleteToken(for url: URL) {
    // TODO: implement
  }
}

let csrfTokenObserver = CSRFTokenObserver(tokenUrlProvider: myCSRFTokenURLProvider, tokenStore: myCSRFTokenStore)
urlSession.register(csrfTokenObserver)

If you would like to read more about how this protection works, see: Using Custom Header Protection

NetworkActivityIndicatorController

Controls whether the Network Activity Indicator should be showed or hidden. When you use NetworkActivityIndicatorObserver never modify the NetworkActivityIndicatorController directly but use the observer class instead. The component uses a counter internally. When this counter has a positive value the indicator is visible.

NetworkActivityIndicatorObserver

Shows and hides the Network Activity Indicator automatically based on the states of requests in the URLSession. It presents the indicator when a request starts and hides it when the request finished. The state of the ActivityIndicator is shared among all the URLSession and observer instances. The observer uses the NetworkActivityIndicatorController to control the presentation of the indicator. When you use this observer never modify the Network Activity Indicator directly but use the NetworkActivityIndicatorController.

The code snippet below demonstrates how to use it:

let indicatorObserver = NetworkActivityIndicatorObserver()
sapURLSession.register(indicatorObserver)

Add correlation ID to requests

Correlation ID is used to identify and track requests through on server side. Adding CorrelationObserver to a SAPURLSession will automatically generate a unique correlation ID and add to each request. In case the SAPURLSessionTask resent the same correlation ID will be used. The code sample below demonstrates how to register the observer to an SAPURLSession instance.

let correlationObserver = CorrelationObserver()
sapURLSession.register(correlationObserver)

Detect blocked users

You can block users through the SAP Cloud Platform Mobile Services if you don’t want them to have further access to your resources. To be able to detect such action in your application, use the SAPcpmsUserBlockedObserver.

let blockDetectionObserver = SAPcpmsUserBlockedObserver { error in
  // This block of code is invoked when a blocked response is detected.
  // The error is a `SAPcpmsUserBlockedError` describing the specific case.
  <#Notify the user about the blocking and run some clean-up code.#>
}
sapURLSession.register(blockDetectionObserver)

Add Accept-Language header to requests

Adding LanguageObserver to a SAPURLSession will automatically set the Accept-Language headers to each HTTP request. By default it uses the device language, but you can also force a language by adding the language parameter to the initializer.

let urlSession: SAPURLSession = <#SAPURLSession instance#>

let languageObserver = LanguageObserver()
urlSession.register(languageObserver)

Clear all authentication credentials

To remove all authentication credentials there are several steps to do

After onboarding remove cookies from HTTPCookieStorage and remove all cached responses from URLCache

sapURLSession.configuration.urlCache?.removeAllCachedResponses()
sapURLSession.configuration.httpCookieStorage?.removeCookies(since: .distantPast)

Credential store

And at last remove all key with the prefix of the authentication process

Additional Information

SAPURLSession component Logger ID

This component uses the following name prefix for logging: ‘SAP.Foundation.SAPURLSession’

  • This class represents a client that can be used to communicate with an HTTP server. It wraps the native URLSession.

    You have different options to create a SAPURLSession instance. The simplest way is to initialize it using defaults.

    let urlSession = SAPURLSession()
    

    Note: When using the default initializer, the SAPURLSession initilizes the underlying URLSession with the following parameters:

    • configuration: URLSessionConfiguration.default
    • delegate: nil
    • delegateQueue: nil

    If you need to be informed about URLSession events, implement the SAPURLSessionDelegate APIs, and pass this delegate as a parameter. You can register this delegate also to obtain the backing URLSessionTask once its initialized.

    let urlSession = SAPURLSession(delegate: self)
    

    Once you have created an SAPURLSession you can use it to exchange messages. Depending on your use case you can either directly use this session or pass it to other SDK components that require an SAPURLSession instance for communication.

    The SAPURLSession class exposes methods for sending short requests to the server, and it supports downloading and uploading of large payloads as well.

    See more

    Declaration

    Swift

    public class SAPURLSession
  • This class is a wrapper class on top of URLSessionTask.

    See more

    Declaration

    Swift

    public class SAPURLSessionTask
  • CorrelationObserver

    Generates and adds a unique identifier to the requests of a SAPURLSessionTask. In case the task will resend the same correlationID will be used. The correlationID will be logged out to the logs with Logger under the component SAP.Foundation.SAPURLSession.CorrelationObserver‘ The code snippet below demonstrates how to use it:

    let correlationObserver = CorrelationObserver()
    sapURLSession.register(correlationObserver)
    
    See more

    Declaration

    Swift

    open class CorrelationObserver : SAPURLSessionObserving
  • This observer is responsible to set the required X-CSRF-Token header for communication with services exposed by SAPcpms. To enable this functionality you need to register this observer to an SAPURLSession instance. For token storing the CSRFTokenStoring protocol is used.

    This is an example for a basic usage of this observer if you have one root URL for every CSRF token request: The rootUrl parameter is the URL from which the CSRF token will be requested.

    let urlSession: SAPURLSession = <#SAPURLSession instance#>
    let rootUrl: URL = <#your root URL#>
    
    let csrfTokenObserver = CSRFTokenObserver(rootUrl: rootUrl)
    urlSession.register(csrfTokenObserver)
    
    

    If you have multiple CSRF protected backends and these backends accept different CSRF tokens you must implement CSRFTokenURLProviding protocol to provide CSRF URL for different request URL’s. For token storing the CSRFTokenStoring protocol is used.

    class myCSRFTokenURLProvider: CSRFTokenURLProviding {        
        func csrfTokenURL(for: URL) -> URL {
            // implement custom logic to create token URL from the request URL
            // ...
            return <#token URL#>
        }
    }
    
    let csrfTokenObserver = CSRFTokenObserver(tokenUrlProvider: myCSRFTokenURLProvider, tokenStore: myCSRFTokenStore)
    urlSession.register(csrfTokenObserver)
    
    
    See more

    Declaration

    Swift

    public class CSRFTokenObserver : SAPURLSessionObserving
  • This observer is responsible to set the Accept-Language headers to each HTTP request of the SAPURLSession.

    To enable this functionality you need to register this observer to an SAPURLSession instance.

    This is an example for a typical usage of the observer:

    let urlSession: SAPURLSession = <#SAPURLSession instance#>
    
    let languageObserver = LanguageObserver()
    urlSession.register(languageObserver)
    
    See more

    Declaration

    Swift

    open class LanguageObserver : SAPURLSessionObserving
  • NetworkActivityIndicatorObserver

    Shows and hides the Network Activity Indicator automatically, based on the states of requests in the SAPURLSession. It presents the indicator when a request starts and hides it when the request finished. The state of the ActivityIndicator is shared among all the SAPURLSession and SAPURLSessionObserving instances. The observer uses the NetworkActivityIndicatorController to control the presentation of the indicator. When you use this observer never modify the Network Activity Indicator directly but use the NetworkActivityIndicatorController.

    The code snippet below demonstrates how to use it:

    let indicatorObserver = NetworkActivityIndicatorObserver()
    sapURLSession.register(indicatorObserver)
    
    See more

    Declaration

    Swift

    open class NetworkActivityIndicatorObserver
  • This observer is responsible to set required headers for communication with services exposed by SAPcpms. This includes the X-SMP-APPID and X-SMP-DEVICEID header fields, which are added to each HTTP request of the SAPURLSession.

    To enable this functionality you need to register this observer to an SAPURLSession instance.

    This is an example for a typical usage of the observer:

    let urlSession: SAPURLSession = <#SAPURLSession instance#>
    let applicationID: String = <#application_ID#>
    let deviceID: String? = <#device_ID#>
    let applicationVersion: String? = <#application_Version#>
    
    let observer = SAPcpmsObserver(applicationID: applicationID, deviceID: deviceID, applicationVersion: applicationVersion)
    urlSession.register(observer)
    
    See more

    Declaration

    Swift

    public class SAPcpmsObserver : SAPURLSessionObserving
  • Use this observer to detect user blocked errors from SAPcpms. When a blocked response is detected, the provided handler is called with the specific blocking case - see SAPcpmsUserBlockedError for more information. The observer is in silent mode after this detection and will remain in this mode until a non-blocking response is detected. The observer will not call the provided handler while in silent mode. All requests that are detected as blocking ones will result in the appropriate SAPcpmsUserBlockedError error.

    See more

    Declaration

    Swift

    open class SAPcpmsUserBlockedObserver : SAPURLSessionObserving
  • Set of errors to signal the blocked user.

    • traffic: traffic is blocked
    • trafficAndRegistration: traffic and registration both blocked
    • registration: registration blocked
    See more

    Declaration

    Swift

    public enum SAPcpmsUserBlockedError : Error
  • NetworkActivityIndicatorController

    Controls whether the Network Activity Indicator should be showed or hidden. When you use NetworkActivityIndicatorObserver, never modify the Network Activity Indicator directly - use this class instead. The component uses a counter internally. When this counter has a positive value the indicator is visible.

    See more

    Declaration

    Swift

    public class NetworkActivityIndicatorController
  • The SAP implemented HTTPCookieStorage which can be used with instances of SAPURLSession. The store can be initialized with a CodableStoring instance which makes it capable to persist cookies between application runs. This implementation won’t share the non-session cookies among the other SAPHTTPCookieStorage instances.

    Example:

    let sessionConfiguration = URLSessionConfiguration.default
    sessionConfiguration.httpCookieStorage = sapCookieStorage
    let sapUrlSession = SAPURLSession(configuration: sessionConfiguration)
    
    See more

    Declaration

    Swift

    public class SAPHTTPCookieStorage : HTTPCookieStorage
  • The SAPURLSessionDelegate protocol describes the methods that SAPURLSession objects call on their delegates to handle session- and task-level events.

    See more

    Declaration

    Swift

    public protocol SAPURLSessionDelegate : AnyObject
  • Defines a protocol which is capable of storing CSRF tokens.

    See more

    Declaration

    Swift

    public protocol CSRFTokenStoring
  • Defines a protocol which is capable of providing CSRF URL for every request URL.

    See more

    Declaration

    Swift

    public protocol CSRFTokenURLProviding
  • Declares the methods which allow callers to modify the network request and the response.

    See more

    Declaration

    Swift

    public protocol SAPURLSessionObserving : AnyObject