Swift, Check if particular website reachable - ios

How to check reachability of particular website?
I am connected to wifi network for internet access, which have blocked some sites. How to check if I have access to those sites or not?
I have checked with Reachability class, but I can not check for particular website.
Currently I am using Reachability.swift

I don't know what is the best practice, but I use HTTP request to do so.
func checkWebsite(completion: #escaping (Bool) -> Void ) {
guard let url = URL(string: "yourURL.com") else { return }
var request = URLRequest(url: url)
request.timeoutInterval = 1.0
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("\(error.localizedDescription)")
completion(false)
}
if let httpResponse = response as? HTTPURLResponse {
print("statusCode: \(httpResponse.statusCode)")
// do your logic here
// if statusCode == 200 ...
completion(true)
}
}
task.resume()
}

func pageExists(at url: URL) async -> Bool {
var headRequest = URLRequest(url: url)
headRequest.httpMethod = "HEAD"
headRequest.timeoutInterval = 3
let headRequestResult = try? await URLSession.shared.data(for: headRequest)
guard let httpURLResponse = headRequestResult?.1 as? HTTPURLResponse
else { return false }
return (200...299).contains(httpURLResponse.statusCode)
}

The initializer you want to use is listed on that page.
You pass the hostname as a parameter:
init?(hostname: String)
// example
Reachability(hostname: "www.mydomain.com")

Related

How to Decode Apple App Attestation Statement?

I'm trying to perform the Firebase App Check validation using REST APIs since it's the only way when developing App Clips as they dont' support sockets. I'm trying to follow Firebase docs. All I'm having trouble with is the decoding of the App Attestation Statement.
So far I've been able to extract the device keyId, make Firebase send me a challenge to be sent to Apple so they can provide me an App Attest Statement using DCAppAttestService.shared.attestKey method.
Swift:
private let dcAppAttestService = DCAppAttestService.shared
private var deviceKeyId = ""
private func generateAppAttestKey() {
// The generateKey method returns an ID associated with the key. The key itself is stored in the Secure Enclave
dcAppAttestService.generateKey(completionHandler: { [self] keyId, error in
guard let keyId = keyId else {
print("key generate failed: \(String(describing: error))")
return
}
deviceKeyId = keyId
print("Key ID: \(deviceKeyId.toBase64())")
})
}
func requestAppAttestChallenge() {
guard let url = URL(string: "\(PROJECT_PREFIX_BETA):generateAppAttestChallenge?key=\(FIREBASE_API_KEY)") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { [self] data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
return
}
guard let data = data, error == nil else {
return
}
let response = try? JSONDecoder().decode(AppAttestChallenge.self, from: data)
if let response = response {
let challenge = response.challenge
print("Response app check challenge: \(challenge)")
print("Response app check keyID: \(deviceKeyId)")
let hash = Data(SHA256.hash(data: Data(base64Encoded: challenge)!))
dcAppAttestService.attestKey(deviceKeyId, clientDataHash: hash, completionHandler: {attestationObj, errorAttest in
let string = String(decoding: attestationObj!, as: UTF8.self)
print("Attestation Object: \(string)")
})
}
}
task.resume()
}
I tried to send the attestation object to Firebase after converting it in a String, although I wasn't able to properly format the String. I see from Apple docs here the format of the attestation, but it isn't really a JSON so I don't know how to handle it. I was trying to send it to Firebase like this:
func exchangeAppAttestAttestation(appAttestation : String, challenge : String) {
guard let url = URL(string: "\(PROJECT_PREFIX_BETA):exchangeAppAttestAttestation?key=\(FIREBASE_API_KEY)") else {
return
}
let requestBody = ExchangeAttestChallenge(attestationStatement: appAttestation.toBase64(), challenge: challenge, keyID: deviceKeyId)
let jsonData = try! JSONEncoder().encode(requestBody)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("Exchange App Attestation \(String(describing: response))")
return
}
guard let data = data, error == nil else {
return
}
print("Exchange App Attestation: \(data)")
}
task.resume()
}

Swift completion handlers - using escaped closure?

Hi i am a beginner studying swift and would like to know what to use when making an api request. What is the modern and professional way?
is it using an escaping closure like so:
func getTrendingMovies(completion: #escaping (Result< [Movie], Error >) -> Void) {
guard let url = URL(string: "\(Constants.baseUrl)/trending/all/day?api_key=\.(Constants.API_KEY)") else {return}
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _,
error in
guard let data = data, error == nil else {
return
}
do {
let results = try JSONDecoder().decode(TrendingMoviesResponse.self, from:
data)
completion(.success(results.results))
} catch {
completion(.failure(error))
}
}
task.resume()
}
or should i make an api request without escaping closure while using a sort of delegate like so:
func performRequest(with urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
delegate?.didFailWithError(error: error!)
return
}
if let safeData = data{
// created parseJson func
if let weather = parseJSON(safeData){
delegate?.didUpdateWeather(self,weather: weather)
}
}
}
task.resume()
} else {
print("url is nil")
}
}
I agree with matt, the modern and professional way is async/await
func getTrendingMovies() async throws -> [Movie] {
let url = URL(string: "\(Constants.baseUrl)/trending/all/day?api_key=\(Constants.API_KEY)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(TrendingMoviesResponse.self, from: data).results
}

How to define a fallback case if a remote GET request fails?

I recently started with iOS development, and I'm currently working on adding new functionality to an existing app. For this feature I need to obtain a JSON file from a web server. However, if the server is unreachable (no internet/server unavailable/etc), a local JSON needs to be used instead.
In my current implementation I tried using a do catch block, but if there's no internet connection, the app just hangs instead of going to the catch block. JSON parsing and local data reading seem to work fine, the problem is likely in the GET method, as I tried to define a callback to return the JSON data as a separate variable, but I'm not sure if that's the correct way.
What is the best way to handle this scenario?
let url = URL(string: "https://jsontestlocation.com") // test JSON
do {
// make a get request, get the result as a callback
let _: () = getRemoteJson(requestUrl: url!, requestType: "GET") {
remoteJson in
performOnMainThread {
self.delegate.value?.didReceiveJson(.success(self.parseJson(jsonData: remoteJson!)!))
}
}
}
catch {
let localFile = readLocalFile(forName: "local_json_file")
let localJson = parseJson(jsonData: localFile!)
if let localJson = localJson {
self.delegate.value?.didReceiveJson(.success(localJson))
}
}
getRemoteJson() implementation:
private func getRemoteJson(requestUrl: URL, requestType: String, completion: #escaping (Data?) -> Void) {
// Method which returns a JSON questionnaire from a remote API
var request = URLRequest(url: requestUrl) // create the request
request.httpMethod = requestType
// make the request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// check if there is any error
if let error = error {
print("GET request error: \(error)")
}
// print the HTTP response
if let response = response as? HTTPURLResponse {
print("GET request status code: \(response.statusCode)")
}
guard let data = data else {return} // return nil if no data
completion(data) // return
}
task.resume() // resumes the task, if suspended
}
parseJson() implementation:
private func parseJson(jsonData: Data) -> JsonType? {
// Method definition
do {
let decodedData = try JSONDecoder().decode(JsonType.self, from: jsonData)
return decodedData
} catch {
print(error)
}
return nil
}
If you don't have to use complex logic with reachability, error handling, request retry etc. just return nil in your completion in case of data task, HTTP and No data errors:
func getRemoteJson(requestUrl: URL, requestType: String, completion: #escaping (Data?) -> Void) {
var request = URLRequest(url: requestUrl)
request.httpMethod = requestType
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Task error
guard error == nil else {
print("GET request error: \(error!)")
completion(nil)
return
}
// HTTP error
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
print("GET request failed: \(response!.description)")
completion(nil)
return
}
// No data
guard let data = data else {
completion(nil)
return
}
completion(data)
}
task.resume()
}
let url = URL(string: "https://jsontestlocation.com")!
getRemoteJson(requestUrl: url, requestType: "GET") { remoteJson in
if let json = remoteJson {
print(json)
...
}
else {
print("Request failed")
...
}
}
func NetworkCheck() -> Bool {
var isReachable = false
let reachability = Reachability()
print(reachability.status)
if reachability.isOnline {
isReachable = true
// True, when on wifi or on cellular network.
}
else
{
// "Sorry! Internet Connection appears to be offline
}
return isReachable
}
Call NetworkCheck() before your API request. If It returns false, read your local json file. if true do remote API call.
Incase after remote API call, any failure check with HTTP header response code.
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
}
I think you need to stop the request from hanging when it’s waiting for a response. The app might be running on a poor connection and be able to get some but not all the data in which case you likely want to failover to the local JSON.
I think you can roughly use what you have but add a timeout configuration on the URLSession as described here: https://stackoverflow.com/a/23428960/312910

How to check if the jpg / pdf file exists under the selected url?

In case I want to check if the file exists on my iPhone, just use the following code:
  let filePath = fileName.path
         let fileManager = FileManager.default
         if fileManager.fileExists (atPath: filePath) {
}
How can I check if there is a pdf / jpg / png file at the URL:
www.myname.com/files/file1.jpg or www.myname.com/files/file2.pdf etc.?
Could I ask for an example of such a function - but for files on internet web servers?
UPDATE
func remoteFileExistsAt(url: URL, completion: #escaping (Bool) -> Void) {
let checkSession = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request) { (data, response, error) -> Void in
if let httpResp = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
}
}
task.resume()
}
Is it possible to check in this function whether the file is of JPG or PNG type? If so - then we also return true, and if not, false?
Updated:
After the discussion on the comments section, the code is updated to work in more correct way.
You should check for the mimeType of the URLResponse object rather than checking whether the image could be represented as UIImageJPEGRepresentation/UIImagePNGRepresentation or not. Because it doesn't guarantee that the resource is actually a jpg/jpeg or png.
So the mimeType should be the most reliable parameter that needs to considered here.
enum MimeType: String {
case jpeg = "image/jpeg"
case png = "image/png"
}
func remoteResource(at url: URL, isOneOf types: [MimeType], completion: #escaping ((Bool) -> Void)) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let response = response as? HTTPURLResponse, response.statusCode == 200, let mimeType = response.mimeType else {
completion(false)
return
}
if types.map({ $0.rawValue }).contains(mimeType) {
completion(true)
} else {
completion(false)
}
}
task.resume()
}
Verify with this:
let jpegImageURL = URL(string: "https://vignette.wikia.nocookie.net/wingsoffire/images/5/54/Panda.jpeg/revision/latest?cb=20170205005103")!
remoteResource(at: jpegImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) // true
}
let pngImageURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/6/69/Giant_panda_drawing.png")!
remoteResource(at: pngImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) //true
}
let gifImageURL = URL(string: "https://media1.tenor.com/images/f88f6514b1a800bae53a8e95b7b99172/tenor.gif?itemid=4616586")!
remoteResource(at: gifImageURL, isOneOf: [.jpeg, .png]) { (result) in
print(result) //false
}
Previous Answer:
You can check if the remote data can be represented as UIImageJPEGRepresentation or UIImagePNGRepresentation. If yes, you can say that remote file is either JPEG or PNG.
Try this:
func remoteResource(at url: URL, isImage: #escaping ((Bool) -> Void)) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data, let image = UIImage(data: data) {
if let _ = UIImageJPEGRepresentation(image, 1.0) {
isImage(true)
} else if let _ = UIImagePNGRepresentation(image) {
isImage(true)
} else {
isImage(false)
}
} else {
isImage(false)
}
}
task.resume()
}
Usage:
let imageURL = URL(string: "http://domaind.com/index.php?action=GET_PHOTO&name=102537.jpg&resolution=FHD&lang=PL&region=1")!
remoteResource(at: imageURL) { (isImage) in
print(isImage) // prints true for your given link
}
It’s about getting data from an URL. If the data is nil the file don’t exist.
Getting data from an URL in Swift
If you want to know the file exists on a server, then it requires sending a HTTP request and receiving the response.
Please try with following code,
func remoteFileExistsAt(url: URL, completion: #escaping (Bool) -> Void) {
let checkSession = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request) { (data, response, error) -> Void in
if let httpResp = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
} else {
completion(false)
}
}
task.resume()
}
UPDATE
remoteFileExistsAt(url: URL(string: "http://domaind.com/index.php?action=GET_PHOTO&name=102537.jpg&resolution=FHD&lang=PL&region=1")!) { (success) in
print(success)
}

How to make a NSURLSesssion GET request with cookies

I'm using the Pinterest SDK to download a Pinterest Pin's link, (sample link that I get back from the server: https://www.pinterest.com/r/pin/186195765822871832/4801566892554728205/77314e40aeb26c0dc412e9cfa82f8dccc401fdb2b9806a3fe17ba8bafdb50510).
About 5 days ago I started getting 404 errors in my NSURLSesssion when trying to access similar links that I'd pulled down from Pinterest.
A friend said that he believes Pinterest must now require cookies to access that link.
How can I configure my session so that I can use cookies and get a 200 response back from Pinterest?
UPDATED CODE:
import UIKit
import PlaygroundSupport
var url = URL(string: "https://www.pinterest.com/r/pin/186195765822871832/4801566892554728205/77314e40aeb26c0dc412e9cfa82f8dccc401fdb2b9806a3fe17ba8bafdb50510")
var getSourceURLFromPinterest: URLSessionDataTask? = nil
let sessionConfig: URLSessionConfiguration = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = 30.0
sessionConfig.timeoutIntervalForResource = 30.0
let cookieJar = HTTPCookieStorage.shared
let cookieHeaderField = ["Set-Cookie": "key=value, key2=value2"]
let cookies = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField, for: url!)
HTTPCookieStorage.shared.setCookies(cookies, for: url, mainDocumentURL: url)
let session = URLSession(configuration: sessionConfig)
getSourceURLFromPinterest = session.dataTask(with: url! as URL) { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if error != nil {
print("error is \(error)")
}
if response == nil {
print("no response")
} else if let _ = data {
//Config Request
let request = NSMutableURLRequest(
url: (response?.url)!,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
request.httpMethod = "HEAD"
var statusCode = Int()
let session = URLSession.shared
let checkURLForResponse = session.dataTask(with: request as URLRequest) {urlData, myResponse, responseError in
if let httpResponse = myResponse as? HTTPURLResponse {
statusCode = httpResponse.statusCode
switch statusCode {
case _ where statusCode < 500 && statusCode > 299 && statusCode != 405: //whitelisted 405 to exclude Amazon.com false errors
print("status code \(statusCode) for \(url)")
default:
break;
}
} else { print("***NO httpResponse for \(url)") }
}
checkURLForResponse.resume()
}
}
getSourceURLFromPinterest!.resume()
PlaygroundPage.current.needsIndefiniteExecution = true
The other answers may work generally, but specifically for me this is how I coded the request in order to get a response from Pinterest's server. Note that specifically what I am doing I think is related to a possible bug in Pinterest's server, see: https://github.com/pinterest/ios-pdk/issues/124
I commented out my personal Pinterest session ID
var cookieSession = String()
var cookieCSRFToken = String()
var myWebServiceUrl = URL(string: "https://www.pinterest.com/r/pin/186195765821344905/4801566892554728205/a9bb098fcbd6b73c4f38a127caca17491dafc57135e9bbf6a0fdd61eab4ba885")
let requestOne = URLRequest(url: myWebServiceUrl!)
let sessionOne = URLSession.shared
let taskOne = sessionOne.dataTask(with: requestOne, completionHandler: { (data, response, error) in
if let error = error {
print("ERROR: \(error)")
}
else {
print("RESPONSE: \(response)")
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("DATA: " + dataString)
} // end: if
var cookies:[HTTPCookie] = HTTPCookieStorage.shared.cookies! as [HTTPCookie]
print("Cookies Count = \(cookies.count)")
for cookie:HTTPCookie in cookies as [HTTPCookie] {
// Get the _pinterest_sess ID
if cookie.name as String == "_pinterest_sess" {
//var cookieValue : String = "CookieName=" + cookie.value as String
cookieSession = cookie.value as String
print(cookieSession)
}
// Get the csrftoken
if cookie.name as String == "csrftoken" {
cookieCSRFToken = cookie.value
print(cookieCSRFToken)
}
} // end: for
} // end: if
})
taskOne.resume()
var requestTwo = URLRequest(url: myWebServiceUrl!)
cookieSession = "XXXXXXXX"
cookieCSRFToken = "JHDylCCKKNToE4VXgofq1ad3hg06uKKl"
var cookieRequest = "_auth=1; _b=\"AR4XTkMmqo9JKradOZyuMoSWcMdsBMuBHHIM21wj2RPInwdkbl2yuy56yQR4iqxJ+N4=\"; _pinterest_pfob=disabled; _pinterest_sess=\"" + cookieSession + "\"; csrftoken=" + cookieCSRFToken as String
requestTwo.setValue(cookieRequest as String, forHTTPHeaderField: "Cookie")
let taskTwo = sessionOne.dataTask(with: requestTwo, completionHandler: { (data, response, error) in
if let error = error {
print("ERROR: \(error)")
}
else {
print("RESPONSE: \(response)")
} // end: if
})
taskTwo.resume()
PlaygroundPage.current.needsIndefiniteExecution = true
You can configure a cookie based session in the following way. Please let me know if you need any help. The below is just an example
let session: URLSession = URLSession.shared
session.dataTask(with: myUrlRequest { urlData, response, responseError in
let httpRes: HTTPURLResponse = (response as? HTTPURLResponse)!
let cookies:[HTTPCookie] = HTTPCookie.cookies(withResponseHeaderFields: httpRes.allHeaderFields as! [String : String], for: httpRes.url!)
HTTPCookieStorage.shared.setCookies(cookies, for: response?.url!, mainDocumentURL: nil)
if responseError == nil {
}else {
}
}.resume()
Feel free to suggest edits to make it better. Please let me know if the below doesn't work.
When you do an authentication with the server, the server gives out the cookies, which you receives in the header of the response. You can extract that and set as a cookie in the shared storage of cookies. So everytime you make a call, for those domain, the cookies will be shared and checked, and if valid, it will let you in.
let resp: HTTPURLResponse = (response as? HTTPURLResponse)!
let cookies:[HTTPCookie] = HTTPCookie.cookies(withResponseHeaderFields: resp.allHeaderFields as! [String : String], for: resp.url!)
HTTPCookieStorage.shared.setCookies(cookies, for: response?.url!, mainDocumentURL: nil)
In this, the cookies will be an array, which contain cookies in an array. An response may contain more than one cookies.
In case, if you are using third party framework to manage the HTTP requests like Alamofire then it will take cares of the cookie management itself.

Resources