I am working on a project and trying to figure out if my download destination may fail.
As an example, if the destination URL or path fails to be clear because it already exists, I would like to handle this and prevent the request to be made.
Is there a simple way to stop a download request while in the destination closure ?
Alamofire
.download(
.GET,
url,
destination:{
(temporaryURL, response) in
let manager = NSFileManager.defaultManager()
let directoryURL = manager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
let pathComponent = response.suggestedFilename
let localPath = directoryURL.URLByAppendingPathComponent(NSBundle.mainBundle().bundleIdentifier ?? "Default", isDirectory:true).URLByAppendingPathComponent(pathComponent!)
if (manager.fileExistsAtPath(localPath!.path!)) {
do {
try manager.removeItemAtURL(localPath!)
} catch let error {
print(error)
==>
HOW CAN I GET THIS TO STOP THE DOWNLOAD
AND CALL RESPONSE WITH AN ERROR
<==
}
}
return localPath!
}
)
.validate(statusCode: 200..<300)
.response {
...
};
As a bonus, I'd love to be able to send this to the response closure as an error, which would make error handling way easier.
Related
I'm new to Swift and Xcode, and have started porting a small app from Android.
It's not to big so making nested api-calls (like a chain) has worked.
Now I try doing the same in XCode but get error: Failed to construct URL.
I will put the code for the first function that works:
func apiTest(){
/*Setting up for HTTP requests to https://example.com */
let session = URLSession.shared
let url = URL(string: APIBaseUrl+"get")!
let task = session.dataTask(with: url) { data, response, error in
if error != nil || data == nil {
print("Client error!")
return
}
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
print("Server error!")
return
}
guard let mime = response.mimeType, mime == "application/json" else {
print("Wrong MIME type!")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print("OK?")
print(json)
let dict = json as! [String:Any]
print(dict)
print(dict["message"] ?? "Could not read response")
let expected:[String:Any] = ["message": "success"]
print(NSDictionary(dictionary: dict).isEqual(to: expected))
if(NSDictionary(dictionary: dict).isEqual(to: expected)){
//it's working
self.apiTestOk = true
/*Here: continue to a similar function*/
self.apiGetUserPrivateInfo(_id: self.id, _key: self.key)
}
//let message = json as! SimpleMessage //fel i runtime
//print (message.message) //fel i runtime
//let decoder = JSONDecoder()
//let message = try decoder.decode(SimpleMessage.self)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}
task.resume()
/*end http requests*/
}
So I call a similar function with
self.apiGetUserPrivateInfo(_id: self.id, _key: self.key)
That function start like this:
func apiGetUserPrivateInfo(_id: Int, _key: String){
//"get/userp/{id}/{key}"
let session = URLSession.shared
let u:String = APIBaseUrl+"get/userp/\(_id)/\(_key)"
print (u)
var components = URLComponents()
components.scheme = "https"
components.host = "rapport.se/api"
components.path = u
guard let url = components.url else {
preconditionFailure("Failed to construct URL") // here it fails
}
I wonder if it could be the "session" that cant be reused. Would be grateful for an answer.
I also used:
let url = URL(string: u)!
with same result.
The problem is not the session (and, by the way, you generally do want to “reuse” sessions, to avoid overhead), but rather how you’re building URLs, specifically how you’re using the URLComponents.
If you're manually specifying the path for URLComponents, it needs to start with the / character. But URL has methods for building a URL with a path:
guard let baseURL = URL(string: "https://rapport.se/api") else { ... }
let url = baseURL.appendingPathComponent(u)
That’s simpler and more robust way to build up a URL than URLComponents. By the way, this appendingPathComponent is generally a preferred way of composing a URL in general, e.g., instead of:
let url = URL(string: APIBaseUrl+"get")!
You might do:
let url = URL(string: APIBaseUrl)!.appendingPathComponent("get")
This gets you out of the world of worrying about “gee, did my APIBaseUrl end in a / or not”, especially if there’s any risk of some future programmer changing the APIBaseUrl in such a way that the trailing / is removed, suddenly breaking your code. Using URL methods like appendingPathComponent is a robust way of doing this.
If you're wondering when you would use URLComponents, it is most useful if you already have a URL with its path, but just need to add query items to a URL. For example, space characters or & need to be percent escaped when included in a URL, and URLComponents does that for us. Consider:
guard var components = URLComponents(string: "https://example.com") else { ... }
components.queryItems = [
URLQueryItem(name: "q", value: "War & Peace")
]
guard let url = components.url else { ... }
That would result in a URL where the query components would be percent escaped (i.e. the space replaced with %20 and the & replaced with %26):
https://example.com?q=War%20%26%20Peace
So I get the error 'self.init' isn't called on all paths before return from initializer. Though I am confused to what that may be because I have added self.init(url: url) after initializing URLComponents for the url to be passed in. Then use URLRequest properties to make a request. But why is it still giving me this error? Is there something I missed? Would be helpful to know the reason why. Thank you.
import Foundation
extension URLRequest {
init(service: ServiceProtocol) {
guard let urlComponents = URLComponents(service: service),
let url = urlComponents.url else {
return
}
self.init(url: url)
httpMethod = service.method.rawValue
service.headers?.forEach { key, value in
addValue(value, forHTTPHeaderField: key)
}
guard case let .requestParameters(parameters) = service.task, service.parametersEncoding == .json else {
return
}
httpBody = try? JSONSerialization.data(withJSONObject: parameters)
}
}
It means if the url object is never successfully created, the required initializer is never called — which is forbidden.
You could make your init throw an error in the else statement or the guard.
I'm trying to download a zip file and save it from server using JWT token authentication thanks to Alamofire. The download works well without token authentication, the file is saved with success. When I activate the server-side authentication (using Passport.js with NodeJS), I always received 401. I attach the token to the header with the sessionManager adapter function. Others request (post, get using sessionManager.request(..) ) works well with this authentication mechanism.
Question is : Can we modify the header of Alamofire download function ? If yes how ?
Any advices appreciated
func getZip(){
let sessionManager = Alamofire.SessionManager.default
let authHandler = JWTAccessTokenAdapter(accessToken: Auth.getAccessToken())
sessionManager.retrier = authHandler
sessionManager.adapter = authHandler
let downloadUrl: String = Auth.getApiEndpoint() + "get_zip"
let destinationPath: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
let fileURL = documentsURL.appendingPathComponent("myZip.zip")
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
sessionManager.download(downloadUrl, method: .get, encoding: URLEncoding.httpBody, to: destinationPath)
.downloadProgress { progress in
print(">> Zip Download Progress: \(progress.fractionCompleted)")
}
.responseData { response in
switch response.result{
case .success:
if response.destinationURL != nil, let filePath = response.destinationURL?.absoluteString {
print("success & filepath : \(filePath)")
completionHandler(filePath, true)
}
break
case .failure:
print("faillure")
completionHandler("", false)
break
}
}
}
}
JWT Adapter :
class JWTAccessTokenAdapter: RequestAdapter {
typealias JWT = String
private var accessToken: JWT
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(Auth.getApiEndpoint()) {
/// Set the Authorization header value using the access token.
urlRequest.setValue(accessToken, forHTTPHeaderField: "Authorization")
}
return urlRequest
}
}
Output :
response: SUCCESS: 12 bytes // (Unauthorized) -> Corrupted zip file
Without a validation step in your request chain, all responses will be considered successful. So check your response code (or just add .validate() before responseData) and see if your request is actually successful. Also, you may want to double check your parameter encoding, though you don't seem to be sending any parameters.
I'm trying to use GCDWebServer to read/write to a file in the documents directory. Reading the file seems fairly straight-forward and I've pretty much got that how I need.
I want to write to the file using a POST request. So I tried adding a handler for method "POST", but in the process block I can't seem to read the body of the request at all.
If I do something like:
webServer?.addHandler(forMethod: "POST", path: "/post", request: GCDWebServerDataRequest.self, processBlock: {request in
dump(request)
return GCDWebServerDataResponse(html: "An error occurred.")
})
That prints all the headers to the console (in an NSObject). But how do I read in the body to a variable?
(iOS 10.3, Swift 3, XCode 8.3.2)
self.webServer?.addHandler(forMethod: "POST", path: "/uploadfile", request: GCDWebServerMultiPartFormRequest.classForCoder(), processBlock: { (request) -> GCDWebServerResponse? in
print("ádasdnaskdnaskjd=====<")
let get = request as! GCDWebServerMultiPartFormRequest
do {
if let files = get.files {
try files.forEach({ (file) in
if let multipartFile = file as? GCDWebServerMultiPartFile {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let urlOfFile = documentsPath.appending("/\(multipartFile.fileName!)");
let url = URL(fileURLWithPath: urlOfFile)
let fileManager = FileManager.default
try fileManager.moveItem(at: URL(fileURLWithPath: multipartFile.temporaryPath), to: url);
print("URL of file: \(url)")
}
})
}
return GCDWebServerDataResponse.init(html: "")
} catch {
print(error.localizedDescription)
}
return GCDWebServerDataResponse.init(html: "")
})
You can use postman to test it with body (form-data)
Will it be possible to show progress for
Alamofire.request(.POST, URL, parameters: parameter, encoding: .JSON)
.responseJSON { response in
// Do your stuff
}
I get my images/documents as a base64 string then I convert it as files in mobile
Can I show a progress bar with percentage?
I am using Alamofire, Swift 2
The way you monitor progress in Alamofire is using the progress closure on a Request. More details on usage can be found in the README. While that example in the README demonstrates usage on a download request, it still works on a data request as well.
The one important note is that you do not always get perfect reporting back from the server for a data request. The reason is that the server does not always report an accurate content length before streaming the data. If the content length is unknown, the progress closure will be called, but the totalExpectedBytesToRead will always be -1.
In this situation, you can only report accurate progress if you know the approximate size of the data being downloaded. You could then use your approximate number in conjunction with the totalBytesRead value to compute an estimated download progress value.
Alamofire 4.0 and Swift 4.x
func startDownload(audioUrl:String) -> Void {
let fileUrl = self.getSaveFileUrl(fileName: audioUrl)
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
return (fileUrl, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(audioUrl, to:destination)
.downloadProgress { (progress) in
self.surahNameKana.text = (String)(progress.fractionCompleted)
}
.responseData { (data) in
// at this stage , the downloaded data are already saved in fileUrl
self.surahNameKana.text = "Completed!"
}
}
To make a download with progress for Swift 2.x users with Alamofire >= 3.0:
let url = "https://httpbin.org/stream/100" // this is for example..
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, url, parameters: params, encoding: ParameterEncoding.URL,destination:destination)
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
// Here you can update your progress object
print("Total bytes read on main queue: \(totalBytesRead)")
print("Progress on main queue: \(Float(totalBytesRead) / Float(totalBytesExpectedToRead))")
}
}
.response { request, _, _, error in
print("\(request?.URL)") // original URL request
if let error = error {
let httpError: NSError = error
let statusCode = httpError.code
} else { //no errors
let filePath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
print("File downloaded successfully: \(filePath)")
}
}