URL File Size With NSURLConnection - Swift - ios

i am trying to get a file size from url before downloading
here is the obj-c code
NSURL *URL = [NSURL URLWithString:"ExampleURL"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: nil];
long long size = [response expectedContentLength];
and here is Swift Code
var url:NSURL = NSURL(string: "ExmapleURL")
var request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.HTTPMethod = "HEAD"
var response = NSHTTPURLResponse()
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response , error: nil)
but i have error here
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response , error: nil)
'NSHTTPURLResponse' is not identical to 'NSURLResponse?'
did i miss something in swift here ?

The response parameter has the type
AutoreleasingUnsafeMutablePointer<NSURLResponse?>
which means that you can pass the address of an optional NSURLResponse as argument:
var response : NSURLResponse?
NSURLConnection.sendSynchronousRequest(request, returningResponse: &response , error: nil)
You can then conditionally cast the returned response to a NSHTTPURLResponse:
if let httpResponse = response as? NSHTTPURLResponse {
println(httpResponse.expectedContentLength)
}
Note that you should check the return value of sendSynchronousRequest(), which
is nil if no connection could be made.
It is also recommended to call this
method only from a separate thread (or use sendAsynchronousRequest() instead)
because it can take a while to make a connection
– in particular when using a cellular network – and the main thread would be
blocked otherwise.

Swift 4 solution:
func fetchContentLength(for url: URL, completionHandler: #escaping (_ contentLength: Int64?) -> ()) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil, let response = response as? HTTPURLResponse, let contentLength = response.allHeaderFields["Content-Length"] as? String else {
completionHandler(nil)
return
}
completionHandler(Int64(contentLength))
}
task.resume()
}
// Usage:
let url = URL(string: "https://s3.amazonaws.com/x265.org/video/Tears_400_x265.mp4")!
fetchContentLength(for: url, completionHandler: { contentLength in
print(contentLength ?? 0)
})

Related

Basic Authentication in Swift 3 does't work

I am struggling with basic authentication in Swift.
I have a Rest back end service over SSL and with basic authentication. My objective-c client code works well but the corresponding Swift one doesn't work because the authentication fails.
This is the Swift code:
let sUrl = "HTTPS://localhost:8443/Test_1/rest/Service/returnInfo"
let url: URL = URL(string: sUrl)!
let request: URLRequest = URLRequest(url: url);
let session: URLSession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue())
let task: URLSessionDataTask = session.dataTask(with: request) { (data, response, inError) in {
...
let httpResponse = response as! HTTPURLResponse
if (httpResponse.statusCode != 200) {
let details = [NSLocalizedDescriptionKey: "HTTP Error"]
let error = NSError(domain:"WS", code:httpResponse.statusCode, userInfo:details)
completionHandler(nil, error);
return
}
...
}
task.resume()
The delegate method is quite similar to the corresponding method in Objective-c:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.previousFailureCount == 0 else {
challenge.sender?.cancel(challenge)
// Inform the user that the user name and password are incorrect
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let proposedCredential = URLCredential(user: user!, password: password!, persistence: .none)
completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, proposedCredential)
}
The httpResponse.statusCode is always 401.
The delegate method is called only once, instead the corresponding method in Objective-c is called two times.
Where am I wrong?
UPDATE
The corresponding Objective-c code:
NSString *sUrl = [NSString stringWithFormat:#"HTTPS://localhost:8443/Test_1/rest/Service/returnInfo"];
NSURL *url = [NSURL URLWithString:sUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *inError) {
if (inError != nil) {
completionHandler(0, inError);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode != 200) {
NSDictionary *details = #{NSLocalizedDescriptionKey:#"HTTP Error"};
NSError *error = [NSError errorWithDomain:#"WS" code:httpResponse.statusCode userInfo:details];
completionHandler(0, error);
return;
}
NSError *jsonError;
NSDictionary *valueAsDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if (jsonError != nil) {
completionHandler(0, jsonError);
return;
}
if (![valueAsDictionary[#"ret"] boolValue]) {
NSInteger code = [valueAsDictionary[#"code"] integerValue];
NSDictionary *details = #{NSLocalizedDescriptionKey:(valueAsDictionary[#"message"]!=nil) ? valueAsDictionary[#"message"] : #""};
NSError *error = [NSError errorWithDomain:#"WS" code:code userInfo:details];
completionHandler(0, error);
return;
}
completionHandler(valueAsDictionary[#"value"], nil);
}];
[task resume];
This is the delegate function:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:_user password:_password persistence:NSURLCredentialPersistenceNone];
completionHandler(NSURLSessionAuthChallengeUseCredential, newCredential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
I eventually managed to make it working in Swift, even if I don't know because it was not working before.
Apparently, user and password have to be explicitly added to the HTTP headers.
let sUrl = "HTTPS://localhost:8443/Test_1/rest/Service/returnInfo"
let url: URL = URL(string: sUrl)!
let request: URLRequest = URLRequest(url: url);
// Changes from here ...
let config = URLSessionConfiguration.default
let userPasswordData = "\(user!):\(password!)".data(using: .utf8)
let base64EncodedCredential = userPasswordData!.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0))
let authString = "Basic \(base64EncodedCredential)"
config.httpAdditionalHeaders = ["Authorization" : authString]
let session: URLSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
// ... to here
let task: URLSessionDataTask = session.dataTask(with: request) { (data, response, inError) in {
...
let httpResponse = response as! HTTPURLResponse
if (httpResponse.statusCode != 200) {
let details = [NSLocalizedDescriptionKey: "HTTP Error"]
let error = NSError(domain:"WS", code:httpResponse.statusCode, userInfo:details)
completionHandler(nil, error);
return
}
...
}
task.resume()
This code is worked for me in Swift 3.0.1:
let login = "username"
let password = "password"
let sUrl = NSURL(string: (urlString as NSString) as String)
let request: URLRequest = URLRequest(url: sUrl as! URL);
let config = URLSessionConfiguration.default
let userPasswordData = "\(login):\(password)".data(using: .utf8)
let base64EncodedCredential = userPasswordData!.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0))
let authString = "Basic \(base64EncodedCredential)"
config.httpAdditionalHeaders = ["Authorization" : authString]
let session: URLSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
let task = session.dataTask(with: request as URLRequest) { (data, response, error) -> Void in
print("response \(data)")
let httpResponse = response as! HTTPURLResponse
if (httpResponse.statusCode != 200) {
print(error?.localizedDescription as Any)
print("Handle Error")
}
else{
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
print("Synchronous\(jsonResult)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
task.resume()
}
According to your question, this is your request (line) instance.
let request: URLRequest = URLRequest(url: url);
You have not set any header parameters for your request instance, here. Please compare request header and body parameters with you objective C client.
Header params may include - content type as well as other useful confidential param like API keys also.
Check your objective C client request and set same params here in your swift code

Getting Header Response in Swift

I am following this answer for making HTTP calls in my swift project. How to make an HTTP request in Swift?
and following is the code I am using to make a synchronous call
let urlPath: String = "http://apiserver.com/api/login/?username=asdf&password=asdf"
var url: NSURL = NSURL(string: urlPath)!
var request1: NSURLRequest = NSURLRequest(URL: url)
var response: AutoreleasingUnsafeMutablePointer<NSURLResponse?> = nil
var error: NSErrorPointer = nil
var dataVal: NSData = NSURLConnection.sendSynchronousRequest(request1, returningResponse: response, error:nil)!
var err: NSError
println("response -- \(response)")
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataVal, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
println("Synchronous \(jsonResult)")
is here anyone who can help me to get HTTP Header Response or status code by using this code? please
Try this:
func getData(url: NSURL) {
let config: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session: NSURLSession = NSURLSession(configuration: config)
let dataTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {(data: NSData!, urlResponse: NSURLResponse!, error: NSError!) -> Void in
if let httpUrlResponse = urlResponse as? NSHTTPURLResponse
{
if error {
println("Error Occurred: \(error.localizedDescription)")
} else {
println("\(httpUrlResponse.allHeaderFields)") // Error
}
}
})
dataTask.resume()
}
Per your code in the original question, have you tried this?
let urlPath: String = "http://apiserver.com/api/login/?username=asdf&password=asdf"
var url: NSURL = NSURL(string: urlPath)!
var request1: NSURLRequest = NSURLRequest(URL: url)
var response: NSURLResponse? = nil
var error: NSError? = nil
var dataVal: NSData = NSURLConnection.sendSynchronousRequest(request1, returningResponse: &response, error:&error)!
var err: NSError
println("response -- \(response)")
if let response = response as? NSHTTPURLResponse {
if response.statusCode == 200 {
print("Success")
}
}
var jsonResult: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataVal, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
println("Synchronous \(jsonResult)")
Extension to retrive header field
extension URLResponse {
func headerField(forKey key: String) -> String? {
(self as? HTTPURLResponse)?.allHeaderFields[key] as? String
}
}
Usage
var urlResponse: URLResponse = ...
let headerField = urlResponse.headerField(forKey: "retry-after")

iOS get file size before downloading

I couldn't find a solution that worked for me. But I need to get the file size of a video I am downloading so that I can make sure the user has enough space on his phone for it.
My thoughts are to check the size of the video, then if the user has space for it, I would download it. Any recommendations?
NSURL *url = [NSURL URLWithString:stringURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {...}];
Here's a variation of the other answers that uses a function (in Swift 4) to call a closure when the size is retrieved:
func getDownloadSize(url: URL, completion: #escaping (Int64, Error?) -> Void) {
let timeoutInterval = 5.0
var request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: timeoutInterval)
request.httpMethod = "HEAD"
URLSession.shared.dataTask(with: request) { (data, response, error) in
let contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
completion(contentLength, error)
}.resume()
}
Here's how this function could be used:
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png")!
getDownloadSize(url: url, completion: { (size, error) in
if error != nil {
print("An error occurred when retrieving the download size: \(error.localizedDescription)")
} else {
print("The download size is \(size).")
}
})
Swift 3:
Since you are calling dataTask you can't use the value outside of the block so use it this way.
var contentLength: Int64 = NSURLSessionTransferSizeUnknown
let request = NSMutableURLRequest(url: url as URL, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30.0);
request.httpMethod = "HEAD";
request.timeoutInterval = 5;
let group = DispatchGroup()
group.enter()
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
//Here you should use the value
print("contentLength",contentLength)
group.leave()
}).resume()
Use this function to get remote size of URL. Please note this function is synchronous and will block thread, so call it from a thread different from main thread:
extension NSURL {
var remoteSize: Int64 {
var contentLength: Int64 = NSURLSessionTransferSizeUnknown
let request = NSMutableURLRequest(URL: self, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30.0);
request.HTTPMethod = "HEAD";
request.timeoutInterval = 5;
let group = dispatch_group_create()
dispatch_group_enter(group)
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
dispatch_group_leave(group)
}).resume()
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, Int64(5 * NSEC_PER_SEC)))
return contentLength
}
}
then call remoteSize variable everywhere you need, on a thread different from main thread:
let size = url.remoteSize
SWIFT 3:
extension NSURL {
var remoteSize: Int64 {
var contentLength: Int64 = NSURLSessionTransferSizeUnknown
let request = NSMutableURLRequest(url: self as URL, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30.0);
request.httpMethod = "HEAD";
request.timeoutInterval = 5;
let group = DispatchGroup()
group.enter()
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
group.leave()
}).resume()
return contentLength
}
}

How do you add headers to dataTaskWithUrl?

I have a dataTaskWithUrl:
var headers: NSDictionary = ["X-Mashape-Key": "my-secret-key" , "Accept" : "application/json"]
var stringUrl = "https://restcountries-v1.p.mashape.com/all"
stringUrl = stringUrl.stringByReplacingOccurrencesOfString(" ", withString: "+")
let url = NSURL(string: stringUrl)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary{
println(jsonResult)
}else{
println("error")
}
})
task.resume()
I want to add headers to my task.
In other words, I would like to convert this code to swift:
NSDictionary *headers = #{#"X-Mashape-Key": #"my-secret-key", #"Accept": #"application/json"};
UNIUrlConnection *asyncConnection = [[UNIRest get:^(UNISimpleRequest *request) {
[request setUrl:#"https://restcountries-v1.p.mashape.com/all"];
[request setHeaders:headers];
}] asJsonAsync:^(UNIHTTPJsonResponse *response, NSError *error) {
NSInteger code = response.code;
NSDictionary *responseHeaders = response.headers;
UNIJsonNode *body = response.body;
NSData *rawBody = response.rawBody;
}];
I am new to dataRequests. I do not understand Objective C code but I made a guess when I looked at that code. I need to use headers because I if I just try going to
https://restcountries-v1.p.mashape.com/all directly, I get an error. I had received that Objective C code from this website: https://www.mashape.com/fayder/rest-countries-v1. Any help in the right direction would be very much appreciated.
Thanks
Update for Swift 4+:
let httpUrl = "http://...."
guard let url = URL(string: httpUrl) else {
return
}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("my-secret-key", forHTTPHeaderField: "X-Mashape-Key")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
}
task.resume()
Old Post:
If you want to use dataTask
var stringUrl = "https://restcountries-v1.p.mashape.com/all"
stringUrl = stringUrl.stringByReplacingOccurrencesOfString(" ", withString: "+")
let url = NSURL(string: stringUrl)
let session = NSURLSession.sharedSession()
var muableRequest = NSMutableURLRequest(URL: url!)
muableRequest.setValue("application/json", forHTTPHeaderField: "Accept")
muableRequest.setValue("my-secret-key", forHTTPHeaderField: "X-Mashape-Key")
let task = session.dataTaskWithRequest(muableRequest, completionHandler: { (data, response, error) -> Void in
if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil){
println(jsonResult)
}
})
task.resume()
It's the same answer as #Leo's answer but the syntax for Swift changed a little which is why I think it's good to "update the answer a little". So this should work with Swift 3.
func get(_ url: String) {
if let url = URL(string: url) {
var request = URLRequest(url: url)
// Set headers
request.setValue("headerValue", forHTTPHeaderField: "headerField")
request.setValue("anotherHeaderValue", forHTTPHeaderField: "anotherHeaderField")
let completionHandler = {(data: Data?, response: URLResponse?, error: Error?) -> Void in
// Do something
}
URLSession.shared.dataTask(with: request, completionHandler: completionHandler).resume()
} else {
// Something went wrong
}

Can't get allHeaderFields from NSURLConnection.sendSynchronousRequest with swift

As the response required for NSURLConnection.sendSynchronousRequest is now requiring NSURLResponse not NSHTTPURLResponse I can't get allHeaderFields.. Is there something I can do here?
var newRequest: NSMutableURLRequest = NSMutableURLRequest(URL: request.URL)
newRequest.HTTPMethod = "HEAD"
var response: NSURLResponse
NSURLConnection.sendSynchronousRequest(newRequest, returningResponse: &response, error: nil)
if response.respondsToSelector(Selector(allHeaderFields)) {
let allHeaders = response.allHeaderFields
}
In the old Objective-C version I was doing this which no longer works in swift..
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:[request URL]];
[newRequest setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:newRequest returningResponse:&response error: NULL];
if ([response respondsToSelector:#selector(allHeaderFields)]) {
NSDictionary *dictionary = [response allHeaderFields];
}
Rather than respondsToSelector, you should use optional binding, casting it to a NSHTTPURLResponse:
let newRequest: NSMutableURLRequest = NSMutableURLRequest(URL: url)
newRequest.HTTPMethod = "HEAD"
var response: NSURLResponse?
NSURLConnection.sendSynchronousRequest(newRequest, returningResponse: &response, error: nil)
if let httpResponse = response as? NSHTTPURLResponse {
// use `httpResponse.allHeaderFields`
}

Resources