Expiration of CachedURLResponse not working - cache-control

I was trying to set custom headers for 'Cache-Control' to achieve a cache at client side(server side has 'Cache-Control: no-cache'). Trying to achieve following two major things.
Response of some of the end points should be cached in memory and
should have an expiration(user defined)
Once expiration time is over, then app should ignore the cache and should fetch data from server.
I followed this link and was able to achieve first target but somehow even after expiry app is still using cache and not triggering any API calls. Not sure if 'max-age' set in header is ignored by the app. Please guide me if I am missing something here.
Here are the code snippets.
Session Configuration:
let sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad
sessionConfiguration.urlCache = .shared
self.currentURLSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
Request:
if let urlPath = URL(string: <WEB_API_END_POINT>){
var aRequest = URLRequest(url: urlPath, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60)
aRequest.addValue("private", forHTTPHeaderField: "Cache-Control")
let aTask = self.currentURLSession.dataTask(with: aRequest)
aTask.resume()
}
Caching logic:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, willCacheResponse proposedResponse: CachedURLResponse, completionHandler: #escaping (CachedURLResponse?) -> Void) {
if proposedResponse.response.url?.path.contains("/employees") == true {
let updatedResponse = proposedResponse.response(withExpirationDuration: 60)
completionHandler(updatedResponse)
} else {
completionHandler(proposedResponse)
}
}
CachedURLResponse Extension:
extension CachedURLResponse {
func response(withExpirationDuration duration: Int) -> CachedURLResponse {
var cachedResponse = self
if let httpResponse = cachedResponse.response as? HTTPURLResponse, var headers = httpResponse.allHeaderFields as? [String : String], let url = httpResponse.url{
headers["Cache-Control"] = "max-age=\(duration)"
headers.removeValue(forKey: "Expires")
headers.removeValue(forKey: "s-maxage")
if let newResponse = HTTPURLResponse(url: url, statusCode: httpResponse.statusCode, httpVersion: "HTTP/1.1", headerFields: headers) {
cachedResponse = CachedURLResponse(response: newResponse, data: cachedResponse.data, userInfo: headers, storagePolicy: .allowedInMemoryOnly)
}
}
return cachedResponse
}
}

Was able to fix it at my own. Still sharing the answer in case if helps someone else in need.
Added 'Cache-Control' response header in server response and there we have 'max-age: 60', which indicates that response can be valid till 60 seconds only. So till 60 seconds, app will cache that data and after 60 seconds if making another request, this will fetch fresh data from server.
With that, At client side other than defining your cache policy, nothing else is required.
You can do this either for entire URL Session:
let sessionConfiguration: URLSessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .useProtocolCachePolicy
sessionConfiguration.urlCache = .shared
self.currentURLSession = URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
Or can do it for specific request.
if let urlPath = URL(string: <WEB_API_END_POINT>) {
var aRequest = URLRequest(url: urlPath, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 60)
let aTask = self.currentURLSession.dataTask(with: aRequest)
aTask.resume()
}

Related

Could not parse the ephemeral key response following protocol

I am trying to create an ephemeral key in my IOS app. I can successfully create a stripe customer that saves in my firebase console and on my stripe dashboard. However, when I try to create the ephemeral key, I am receiving the error in my ios console after trying to view the checkout controller.
'Could not parse the ephemeral key response following protocol STPCustomerEphemeralKeyProvider. Make sure your backend is sending the unmodified JSON of the ephemeral key to your app.
and on my firebase function logs I am seeing,
createEphemeralKey
Request has incorrect Content-Type.
createEphemeralKey
Invalid request, unable to process.
in my index.js file, the code that I am using is
exports.createEphemeralKey = functions.https.onCall(async(data, context) => {
var stripeVersion = data.api_version;
const customerId = data.customer_id;
return stripe.ephemeralKeys.create(
{customer: customerId},
{stripe_version: stripeVersion}
).then((key) => {
return key
}).catch((err) => {
console.log(err)
})
})
Below is how I create my stripe customer.
exports.createStripeCustomer = functions.auth.user().onCreate((user) => {
return stripe.customers.create({
email: user.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${user.uid}/customer_id`).set(customer.id);
});
});
and then myAPIClient looks like.
enum APIError: Error {
case unknown
var localizedDescription: String {
switch self {
case .unknown:
return "Unknown error"
}
}
}
static let sharedClient = MyAPIClient()
var baseURLString: String? = "https://myProject.cloudfunctions.net/"
var baseURL: URL {
if let urlString = self.baseURLString, let url = URL(string: urlString) {
return url
} else {
fatalError()
}
}
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
Alamofire.request(url, method: .post, parameters: [
"api_version": apiVersion,
])
.validate(statusCode: 200..<300)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
completion(nil, error)
}
}
}
On my checkOutVC, I have
var stripePublishableKey = "pk_test_testProjectKey"
var backendBaseURL: String? = "https://myProject.cloudfunctions.net"
let customerContext = STPCustomerContext(keyProvider: MyAPIClient())
init(price: Int, settings: Settings) {
if let stripePublishableKey = UserDefaults.standard.string(forKey: "StripePublishableKey") {
self.stripePublishableKey = stripePublishableKey
}
if let backendBaseURL = UserDefaults.standard.string(forKey: "StripeBackendBaseURL") {
self.backendBaseURL = backendBaseURL
}
let stripePublishableKey = self.stripePublishableKey
let backendBaseURL = self.backendBaseURL
assert(stripePublishableKey.hasPrefix("pk_"), "You must set your Stripe publishable key at the top of acceptWorker.swift to run this app.")
assert(backendBaseURL != nil, "You must set your backend base url at the top of acceptWorker.swift to run this app.")
Stripe.setDefaultPublishableKey(self.stripePublishableKey)
let config = STPPaymentConfiguration.shared()
config.appleMerchantIdentifier = self.appleMerchantID
config.companyName = self.companyName
config.requiredBillingAddressFields = settings.requiredBillingAddressFields
config.requiredShippingAddressFields = settings.requiredShippingAddressFields
config.shippingType = settings.shippingType
config.additionalPaymentOptions = settings.additionalPaymentOptions
config.cardScanningEnabled = true
self.country = settings.country
self.paymentCurrency = settings.currency
self.theme = settings.theme
MyAPIClient.sharedClient.baseURLString = self.backendBaseURL
let paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, theme: settings.theme)
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
I apologize for the long lines of code but I am really running into a brick wall. Why isnt the backend creating the ephemeralKey for customers?
Two things are jumping out at me:
You’ve written a callable type function (using onCall) but you’re
trying to call it with a normal HTTP request. These functions need to
be called with Firebase’s client library
(https://firebase.google.com/docs/functions/callable#call_the_function).
This stack overflow answer provides some great links about this:
Firebase Cloud Function to delete user.
Your firebase function is parsing stripe_version and customer_id from
data, but your request is only sending api_version. Where in your
code are you sending stripe_version and customer_id?

Swift POST Request not working

I have this server set up with NodeJS that can receive a name and an email and let me know if they match.It can also let me know if the received data is not valid.
This is my code in swift:
func post() {
let parameters = [
"email": "windvaan#live.nl",
"password": "Dittoenbram1234!"
]
let url = NSURL(string: "http://localhost:3000/login")
var request = URLRequest(url: url! as URL)
request.httpMethod = "POST"
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions.prettyPrinted) else {
return
}
request.httpBody = httpBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error.localizedDescription)
}
}
}.resume()
}
When I call this function my server says that the data is not valid, but the data I put as the parameters is. I believe it has something to do with this function and the parameters because my server is working fine. Thx in advance!
I figured it out, I had to add in this in my swift file:
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
As per as your parameters value, it's not in valid JSON format.
When you print your parameters value it will print - ["password": "Dittoenbram1234!", "email": "windvaan#live.nl"] , which is invalid.
You need to have this format - { email = "windvaan#live.nl"; password = "Dittoenbram1234!";}
For this
Replace this -
let parameters = [
"email": "windvaan#live.nl",
"password": "Dittoenbram1234!"
]
With this -
let parameters: NSDictionary = [
"email": "windvaan#live.nl",
"password": "Dittoenbram1234!"
]
Hope this will help you. :)

In Swift, URLSession.shared.dataTask sometimes work and sometimes not

client: swift, xcode 8.3
server: nodejs, express, mongodb
In client side, it first send login request (username & password) to server, upon receive a valid token, it proceeds to the next screen to send another request to get a list of books.
The strange point is that, for the second request for books, sometimes it works - if with debug mode and a number of breakpoints proceeding "slowly", while sometimes it does not work - if proceed the breakpoints not such slow or without them. I repeated a number of times and figured out this pattern.
Here is the piece of code which send request - millions of thanks!!!
let url = URL(string: "http://" + SERVER_IP + ":" + PORT + "/books")
print("Query:>> url: ")
print(url)
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
print("Query>> response from loadBooks")
if let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) {
var books = [Book]()
if let array = json as? [Any] {
for i in 0...array.count-1 {
var bookJson = array[i] as? [String: Any]
let b = Book(json: bookJson!)
books.append(b!)
}
}
print ("Query>> \(books.count)" + " books loaded, callback completion")
completion(books)
}
}
task.resume()
Console output when the request is not successful:
Query:>> url: Optional(localhost:3001/books)
2017-09-07 15:31:45.173596+0800 SimplyRead[76273:2379182] [] nw_endpoint_flow_service_writes [1.1 ::1.3001 ready socket-flow (satisfied)] Write request has 4294967295 frame count, 0 byte count Query>> response from loadBooks
2017-09-07 15:51:41.210732+0800 SimplyRead[76273:2390856] [] tcp_connection_write_eof_block_invoke Write close callback received error: [89] Operation canceled

How to generate iOS Application Certificate for Fairplay implementation

We are developing a iOS music app. For content protection we are going to use Apples fairplay DRM system. I am following apple's HDLCatalog example for reference. While implementing i noticed there are two methods in AssetLoaderDelegate class that need to be implemented. I will appreciate if any one can help me out how to implemented below two methods. Thanks in advance.
1.)
public func fetchApplicationCertificate() -> Data? {
// MARK: ADAPT: YOU MUST IMPLEMENT THIS METHOD.
let applicationCertificate: Data? = nil
if applicationCertificate == nil {
fatalError("No certificate being returned by \(#function)!")
}
return applicationCertificate
}
2.)
public func contentKeyFromKeyServerModuleWithSPCData(spcData: Data, assetIDString: String) -> Data? {
// MARK: ADAPT: YOU MUST IMPLEMENT THIS METHOD.
let ckcData: Data? = nil
if ckcData == nil {
fatalError("No CKC being returned by \(#function)!")
}
return ckcData
}
I am updating here that we managed to implement fetchApplicationCertificate() method. Now we are facing problems for generating ckc data
Application Certificate
The application certificate is the DER formated public certificate created when you registered for Fairplay with Apple. This should be placed on a web server (AWS S3 is ideal) and retrieved once per application session.
CKC Data
This is specific to whomever you're using for Fairplay License Services. They will have specified an interface for sending the SPC data from your client to their license server. This could be JSON over REST, SOAP, MQ or anything they chose. You will have to ask them for the API spec.
Step 1 :
let queue = DispatchQueue(label: "fairplay.resourcerequests", attributes: [])
let url = URL(string: videoUrl)! // Streaming the video from this URL
let videoAsset = AVURLAsset(url: url, options: nil)
videoAsset.resourceLoader.setDelegate(self, queue: queue)
Step 2:
extension PlayerViewController : AVAssetResourceLoaderDelegate{
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
let url = loadingRequest.request.url
var error: NSError?
print("Player Delegate Method")
print("URL Schema Is \(url?.scheme! ?? "")")
if((url?.scheme ?? "") == "skd"),
let assetString = url!.host, let assetID = assetString.data(using: String.Encoding.utf8) // Get the URI for the content key.{guard let fetchedCertificate = self.fetchedCertificate else { return false} // Get the application certificate from the server.
let requestedBytes = try loadingRequest.streamingContentKeyRequestData(forApp: fetchedCertificate, contentIdentifier: assetID, options: nil)
do{
print("Online Video Streaming Going On")
let responseData = try contentKeyFromKeyServerModuleWithRequestData(requestedBytes, assetString: assetString, expiryDuration: expiryDuration)
guard let dataRequest = loadingRequest.dataRequest else {
// print("Failed to get instance of AVAssetResourceLoadingDataRequest (loadingRequest.dataRequest).")
return false
}
dataRequest.respond(with: responseData)
if let infoRequest = loadingRequest.contentInformationRequest,
expiryDuration != 0.0
{
infoRequest.renewalDate = Date(timeIntervalSinceNow: expiryDuration)
infoRequest.contentType = "application/octet-stream"
infoRequest.contentLength = Int64(responseData.count)
infoRequest.isByteRangeAccessSupported = false
}
// Treat the processing of the requested resource as complete.
loadingRequest.finishLoading()
// The resource request has been handled regardless of whether the server returned an error.
return true
}catch let e as NSError
{
error = e
// print("content key error\(error)")
}
}catch let e as NSError {
error = e
// Resource loading failed with an error.
// print("streamingContentKeyRequestDataForApp failure: \(error.localizedDescription)")
}}}
Step 3:
func contentKeyFromKeyServerModuleWithRequestData(_ requestBytes: Data, assetString: String, expiryDuration: TimeInterval?=0.0, persitent:Bool?=true) throws -> Data {
// If the key server provided a CKC, return it.
// if let ckcData = ckcData {
// return ckcData
// }
// else
// {
let base64Decoded = requestBytes.base64EncodedString(options: NSData.Base64EncodingOptions())
//MARK: Get Fairplay license for the current user
NSLog("using ticket: %#", streamTicket )
if let returnData:Data = mediaMakerDRMLicenseCall(base64Decoded, ticket: streamTicket)
{
if returnData.count <= 0
{
// Otherwise, the CKC was not provided by key server. Fail with bogus error.
// Generate an error describing the failure.
throw NSError(domain: "com.example.apple-samplecode", code: 0, userInfo: [
NSLocalizedDescriptionKey: NSLocalizedString("Item cannot be played.", comment: "Item cannot be played."),
NSLocalizedFailureReasonErrorKey: NSLocalizedString("Could not get the content key from the Key Server.", comment: "Failure to successfully send the SPC to the Key Server and get the content key from the returned Content Key Context (CKC) message.")
])
}
else
{
return returnData
}
}
//}
}
Step 4:
func mediaMakerDRMLicenseCall(_ playerSPC : String, ticket : String) -> Data{// API Call to fetch license from client server}

Swift WKWebView

how to handle NTLM authentication using WKWebView, the aim is to load a secured URL with credentials (Sharepoint Hosted application) similarly I wanted to add a key value pair to the http request
any code sample using swift2.0 that handles my case will be appreciated .
I went through the documentation and I have found the solutions below is the equivalent of the method shouldSatratLoading etc..
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
print(" decidePolicyForNavigationAction.......")
let headerArr = navigationAction.request.allHTTPHeaderFields?.keys.array
let headerIsPresent = headerArr?.contains(APP_HEADER_ID)
if headerIsPresent! {
decisionHandler(.Allow)
}else{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
dispatch_async(dispatch_get_main_queue(), {
let newRequest: NSMutableURLRequest = navigationAction.request as! NSMutableURLRequest
// set new header
newRequest.addValue(APP_HEADER_VALUE, forHTTPHeaderField:APP_HEADER_ID)
// reload the request
webView.loadRequest(newRequest)
})
})
decisionHandler(.Cancel)
}
}
If anyone needs more info please let me know

Resources