Prevent redirect response with Alamofire in Swift - swift

I'm looking for example code how to prevent redirect response (status code 3xx) when request web api. I'm using Swift with Alamofire 1.2.
I have tried:
delegate.taskWillPerformHTTPRedirection = { (session: NSURLSession!, task: NSURLSessionTask!, response: NSHTTPURLResponse!, request: NSURLRequest!) in
return nil
}
but not work
I've also tried: https://github.com/Alamofire/Alamofire/pull/350/files and have changed my own code to:
var acc = self.txtAccount.text
var pwd = self.txtPassword.text
var url : String = "http://10.1.0.2:8081/wordpress/wp-json/users/me"
let delegate = Alamofire.Manager.sharedInstance.delegate
delegate.taskWillPerformHTTPRedirection = { (session: NSURLSession!, task: NSURLSessionTask!, response: NSHTTPURLResponse!, request: NSURLRequest!) in
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
request.HTTPMethod = "GET"
var credential = "\(acc):\(pwd)"
var authData = credential.dataUsingEncoding(NSUTF8StringEncoding)
var encodedAuthData = authData?.base64EncodedStringWithOptions(nil)
var authValue = "Basic \(encodedAuthData!)"
request.setValue(authValue, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
return request
}
//I've implemented URLRequestConvertible 'Router'. it also have call the same above url
Alamofire.request(Router.Authorize(acc, pwd))
.response({(request, response, data, error) in
println(request)
})
But it's not worked and seem like turned to infinite loop. I tested on Charles.

Alternative (code snippet) solution using AlamoFire 2.4 (Xcode7). In my case, I always expect a redirect. (I am unpacking a shortened link.) If the completion in the request.response call runs, that is an error to me.
func printRedirectUrl() {
// taskWillPerformHTTPRedirectionWithCompletion: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest, NSURLRequest? -> Void) -> Void)?
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirectionWithCompletion = { session, task, response, request, completion in
// request.URL has the redirected URL inside of it, no need to parse the body
print("REDIRECT Request: \(request)")
if let url = request.URL {
print("Extracted URL: \(url)")
}
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirection = nil // Restore redirect abilities
return
}
// We expect a redirect, so the completion of this call should never execute
let url = NSURL(string: "https://google.com")
let request = Alamofire.request(.GET, url!)
request.response { request, response, data, error in
print("Logic Error, response should NOT have been called for request: \(request)")
Alamofire.Manager.sharedInstance.delegate.taskWillPerformHTTPRedirection = nil // Restore redirect abilities - just in case
}
}
REDIRECT Request: { URL: https://www.google.com/ }
Extracted URL: https://www.google.com/

In Swift 4,
let delegate = Alamofire.SessionManager.default.delegate
delegate.taskWillPerformHTTPRedirection = { (session, task, response, request) -> URLRequest? in
// print("REDIRECT Request: \(request)")
return nil
}

Hello its actually pretty simple
Alamofire has a redirector that will
Example
let request = AF.request("https://google.com",method: .post,parameters: parameters)
.cURLDescription { description in
debugPrint(description)
}
let redirector = Redirector(behavior: .doNotFollow)
request.redirect(using: redirector)
with that it wont redirect
its also in the docs in the advanced usage section

It looks like returning nil can possibly cause a deadlock. Instead, try to create a new NSURLRequest with the same original URL. See #jhersh's notes in a previous Alamofire PR along with the comments and implementation in his tests.
How to Stop a Redirect
func disallowRedirect() {
let URL = "http://google.com/"
let delegate = Alamofire.Manager.sharedInstance.delegate
delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
return NSURLRequest(URL: NSURL(string: URL)!)
}
let request = Alamofire.request(.GET, URL)
request.response { request, response, data, error in
println("Request: \(request)")
println("Response: \(response)")
println("Data: \(NSString(data: data as! NSData, encoding: NSUTF8StringEncoding))")
println("Error: \(error)")
}
}
disallowRedirect()
The fact that you cannot pass nil into the NSURLSessionTaskDelegate method's completionHandler looks like a bug. I'm going to file a radar for this and I'll post a link to the bug report once I'm finished.

I don't know if your version of Alamofire has a support for public delegate. Last time I checked delegate was private. I am using the changes made by #jhersh. You can check his additions and how to use delegate by followin github pr. https://github.com/Alamofire/Alamofire/issues/314

Related

Swift: Mailgun 401 response code--No valid API key provided

So I have my send email function as seen below:
func email() {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: "https://api.mailgun.net/v3/sandbox################################/messages")! as URL)
request.httpMethod = "POST"
let credentials = "api:key-################################-########-########"
request.setValue("Basic \(credentials.toBase64())", forHTTPHeaderField: "Authorization")
let data = "from: Swift Email <(test#test.com)>&to: [myemail#gmail.com,(myemail#gmail.com)]&subject:Hello&text:Testing_some_Mailgun_awesomness"
request.httpBody = data.data(using: String.Encoding.ascii)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
So I guess either my API key is wrong or my request URL is wrong. To find my request URL, I went to https://app.mailgun.com/app/dashboard and then scrolled down to the Sending Domains section and copied that. To get my API key I went to https://app.mailgun.com/app/account/security/api_keys and just copied the Private API Key. I'm really not sure why I'm getting this invalid code--thank you in advance if you figure it out!
Side note: Not really sure if the data constant is set up right (in terms of missing or having too many parens), so if you could check that too that would be phenomenal.
I have a software called "paw" it helps forming REST API call for Xcode, curl, php, etc.
I don't know if this could help you
class MyRequestController {
func sendRequest(somevar: String, completion: #escaping (Books) -> Void) {
/* Configure session, choose between:
* defaultSessionConfiguration
* ephemeralSessionConfiguration
* backgroundSessionConfigurationWithIdentifier:
And set session-wide properties, such as: HTTPAdditionalHeaders,
HTTPCookieAcceptPolicy, requestCachePolicy or timeoutIntervalForRequest.
*/
let sessionConfig = URLSessionConfiguration.default
/* Create session, and optionally set a URLSessionDelegate. */
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
/* Create the Request:
(POST https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages)
*/
guard var URL = URL(string: "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages") else {return}
var request = URLRequest(url: URL)
request.httpMethod = "POST"
// Headers
request.addValue("Basic YXBpOllPVVJfQVBJX0tFWQ==", forHTTPHeaderField: "Authorization")
request.addValue("multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__", forHTTPHeaderField: "Content-Type")
// Body
let bodyString = "--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"from\"\r\n\r\n'Excited User <mailgun#YOUR_DOMAIN_NAME>'\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"to\"\r\n\r\nYOU#YOUR_DOMAIN_NAME\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"to\"\r\n\r\nbar#example.com\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"subject\"\r\n\r\n'Hello'\r\n--__X_PAW_BOUNDARY__\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\n'Testing some Mailgun awesomeness!'\r\n--__X_PAW_BOUNDARY__--\r\n"
request.httpBody = bodyString.data(using: .utf8, allowLossyConversion: true)
/* Start a new Task */
let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if (error == nil) {
// Success
let statusCode = (response as! HTTPURLResponse).statusCode
print("URL Session Task Succeeded: HTTP \(statusCode)")
}
else {
// Failure
print("URL Session Task Failed: %#", error!.localizedDescription);
}
})
task.resume()
session.finishTasksAndInvalidate()
}
}
**** and you CALL this function a bit like this
MyRequestController().sendRequest(somevar: "something")
take a look at https://www.youtube.com/watch?v=44APgBnapag for more details
this tutorial show how to do REST API calls with Xcode, this example it scan a barcode, send the scanned code via a function that calls the API and return infos...

cURL get data doesn't update when data on server has changed

I have a function which gets the data from an website api.
func getData() {
print("getData is called")
//create authentication ... omitted
//create authentication url and request
let urlPath = "https://...";
let url = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
//some configuration here...
let task = session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let json = JSON(data: data!)
}
task.resume()
}
I am able to get data from API, and I have an observer so that each time when the app goes to foreground the getData() function can be called again to update the data.
override func viewDidLoad() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "willEnterForeground", name: UIApplicationWillEnterForegroundNotification, object: nil)
}
func willEnterForeground() {
print("will enter foreground")
getData()
}
I am pretty sure that when my app goes to foreground, the getData() is called again, however, my data doesn't get updated even when data on the server API has changed. I tried to close the app and open it again, but it still doesn't update the data. So I was wondering if someone can give me some suggestions. Any help will be appreciated!
You need a to make it a closure with a completion. This will enable it to make the request and then call reload "Table View" once you have actually gotten the data back from the request. This is what you need to change on your getData() function
func getData(completion: (json) -> Void)) {
print("getData is called")
//create authentication url and request
let urlPath = "https://...";
let url = NSURL(string: urlPath)!
let request: NSMutableURLRequest = NSMutableURLRequest(URL: url)
request.setValue("Basic \(base64EncodedCredential)", forHTTPHeaderField: "Authorization")
request.HTTPMethod = "GET"
//some configuration here...
let task = session.dataTaskWithURL(url) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
let json = JSON(data: data!)
}
completion(json)
}
Then you will need to adjust where you call this function because you have changed the parameters it should look more like this.
func willEnterForeground() {
print("will enter foreground")
getData(completion:{
self.data = json
self.tableView.reloadData()
})
}
that will wait till the data is back before it reloads the view updating all your content to reflect what is on the server.
Let me know if you have more questions.
I finally found out it is because I didn't remove the cache. Change the cache policy works then.

Swift - proper way to wait for login request to complete before resuming execution

I have the following code which performs a login to a web server, retrieves an API token, and then allows the app to continue. After this code executes, the the calling function uses the login_result to report to the user whether they successfully logged in or need to try again.
Right now, I just have the app sleep for 3 seconds to make sure I have response before moving on. Otherwise, the login results handler starts executing without an answer (because the server hasn't had time to respond).
Obviously, this is a horrible way to do things. What is the correct / swift idiomatic way to handle this?
func remote_login (email: String, password: String) -> Bool {
login_result = false
var jsonResults: NSDictionary = [:]
let account = Account()
let url = NSURL(string: "\(base_api_url())/login?email=\(email)&password=\(password)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
account.setCredentials(jsonResults) // we store an API token returned from a successful login; we don't store the users credentials locally
self.login_result = true
}
catch {
print("\n\n\n Login Error \n\n\n")
}
}) // task
task.resume()
sleep(3) //need time for the response to come back...s/b far more elegant way to do this.
return self.login_result
}
Use a closure as your function argument, like so:
func remote_login(email: String, password: String, completion((Bool) -> Void)) {
Then, call the closure when finished (after JSON parsing) like this:
self.login_result = true
completion(login_result)
So, when you use the function, it will look like this:
remote_login("email", "password", {
success in
//Logged in successfully
}
The closure will be called when the HTTP request finishes.
As best I can see:
func remote_login (email: String, password: String, myFunctionCompletionHandler: ()->()) -> Bool {
login_result = false
var jsonResults: NSDictionary = [:]
let account = Account()
let url = NSURL(string: "\(base_api_url())/login?email=\(email)&password=\(password)")
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
do {
jsonResults = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
// we store an API token returned from a successful login; we don't store the users credentials locally
account.setCredentials(jsonResults)
myFunctionCompletionHandler()
// is this needed as it's handled by the handler?
self.login_result = true
}
catch {
myFunctionCompletionHandler()
print("\n\n\n Login Error \n\n\n")
}
}) // task
task.resume()
return self.login_result
}
You might have to refactor things to handle the completionHandler a little better as I've just literally "plonked it in" but that should do the trick :)..
Just handle and pass the "failed and passed" correctly across to it :)

Swift Send Email with MailGun

Problem
I would like to use the MailGun service to send emails from a pure Swift app.
Research So Far
As I understand it, there are two methods to send an email via MailGun. One is to email MailGun with the emails, and MailGun will redirect it (See Send via SMTP). That will, as I understand it, not work, as iOS cannot programatically automatically send mail, and must use methods that require user intervention. As such, I should use the API directly. As I understand it, I need to open a URL to do this, and so I should use some form of NSURLSession, as per this SO answer
Code
MailGun provides documentation for Python, which is as follows:
def send_simple_message():
return requests.post(
"https://api.mailgun.net/v3/sandbox(Personal info).mailgun.org/messages",
auth=("api", "key-(Personal info)"),
data={"from": "Excited User <(Personal info)>",
"to": ["bar#example.com", "(Personal info)"],
"subject": "Hello",
"text": "Testing some Mailgun awesomness!"})
with (Personal info) being substituted for keys/information/emails.
Question
How do I do that in Swift?
Thanks!
In python, the auth is being passed in the header.
You have to do a http post request, passing both the header and the body.
This is a working code:
func test() {
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.mailgun.net/v3/sandbox(Personal info).mailgun.org/messages")!)
request.HTTPMethod = "POST"
let data = "from: Excited User <(Personal info)>&to: [bar#example.com,(Personal info)]&subject:Hello&text:Testinggsome Mailgun awesomness!"
request.HTTPBody = data.dataUsingEncoding(NSASCIIStringEncoding)
request.setValue("key-(Personal info)", forHTTPHeaderField: "api")
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.URL!)")
print("response = \(response)")
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
people are getting 400 or 401 errors because none of the other answers construct the url correctly. here is some code that works in swift 5 and iOS15:
func sendEmail() {
// variablize our https path with API key, recipient and message text
let mailgunAPIPath = "https://api:YOUR_API_KEY#api.mailgun.net/v3/YOUR_DOMAIN/messages?"
let emailRecipient = "RECIPIENT#EMAIL.COM"
let emailMessage = "Testing%20email%20sender%20variables"
// Create a session and fill it with our request
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: mailgunAPIPath + "from=USER#YOUR_DOMAIN&to=\(emailRecipient)&subject=A%20New%20Test%21&text=\(emailMessage)")! as URL)
// POST and report back with any errors and response codes
request.httpMethod = "POST"
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
requests.post sends an HTTP POST request, encoding key/value pairs as application/x-www-form-urlencoded. You need to do the same.
convert the set of key-value pairs into application/x-www-form-urlencoded as per How to escape the HTTP params in Swift
compose the request using the resulting string for data & send it as per iOS : http Post using swift
I spent hours trying to get the selected answer working, but to no avail.
Although I was finally able to get this working properly with a large HTTP response. I put the full path into Keys.plist so that I can upload my code to github and broke out some of the arguments into variables so I can have them programmatically set later down the road.
// Email the FBO with desired information
// Parse our Keys.plist so we can use our path
var keys: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
if let dict = keys {
// variablize our https path with API key, recipient and message text
let mailgunAPIPath = dict["mailgunAPIPath"] as? String
let emailRecipient = "bar#foo.com"
let emailMessage = "Testing%20email%20sender%20variables"
// Create a session and fill it with our request
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: mailgunAPIPath! + "from=FBOGo%20Reservation%20%3Cscheduler#<my domain>.com%3E&to=reservations#<my domain>.com&to=\(emailRecipient)&subject=A%20New%20Reservation%21&text=\(emailMessage)")!)
// POST and report back with any errors and response codes
request.HTTPMethod = "POST"
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.URL!)")
print("response = \(response)")
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
The Mailgun Path is in Keys.plist as a string called mailgunAPIPath with the value:
https://API:key-<my key>#api.mailgun.net/v3/<my domain>.com/messages?
Hope this offers a solution to anyone else having issues with MailGun and wanting to avoid a 3rd party solution!
Swift 3 answer:
func test() {
let session = URLSession.shared
var request = URLRequest(url: URL(string: "https://api.mailgun.net/v3/sandbox(Personal info).mailgun.org/messages")!)
request.httpMethod = "POST"
let data = "from: Excited User <(Personal info)>&to: [bar#example.com,(Personal info)]&subject:Hello&text:Testinggsome Mailgun awesomness!"
request.httpBody = data.data(using: .ascii)
request.setValue("key-(Personal info)", forHTTPHeaderField: "api")
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.url!)")
print("response = \(response)")
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}

Simpliest solution to check if File exists on a webserver. (Swift)

There are a lot of discussion about this and I understand the solution to use the delegate method and check the response "404":
var request : NSURLRequest = NSURLRequest(URL: url)
var connection : NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: false)!
connection.start()
func connection(didReceiveResponse: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {
//...
}
But I would like to have a simple solution like:
var exists:Bool=fileexists(sURL);
Because I will have a lot of request in the same class with the delegate and I only want to check the response with my function fileexists().
Any hints ?
UPDATE
I guess I'll have to do a synchronious request like the following, but I get always 0x0000000000000000 as a response::
let urlPath: String = sURL;
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)
Swift 3.0 version of Martin R's answer written asynchronously (the main thread isn't blocked):
func fileExistsAt(url : URL, completion: #escaping (Bool) -> Void) {
let checkSession = Foundation.URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let task = checkSession.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if let httpResp: HTTPURLResponse = response as? HTTPURLResponse {
completion(httpResp.statusCode == 200)
}
})
task.resume()
}
Checking if a resource exists on a server requires sending a HTTP
request and receiving the response. TCP communication can take some
amount of time, e.g. if the server is busy, some router between the
client and the server does not work
correctly, the network is down etc.
That's why asynchronous requests are always preferred. Even if you think
that the request should take only milliseconds, it might sometimes be
seconds due to some network problems. And – as we all know – blocking
the main thread for some seconds is a big no-no.
All that being said, here is a possible implementation for a
fileExists() method. You should not use it on the main thread,
you have been warned!
The HTTP request method is set to "HEAD", so that the server sends
only the response header, but no data.
func fileExists(url : NSURL!) -> Bool {
let req = NSMutableURLRequest(URL: url)
req.HTTPMethod = "HEAD"
req.timeoutInterval = 1.0 // Adjust to your needs
var response : NSURLResponse?
NSURLConnection.sendSynchronousRequest(req, returningResponse: &response, error: nil)
return ((response as? NSHTTPURLResponse)?.statusCode ?? -1) == 200
}
Improved Vito's solution so the completion is always called:
func fileExists(at url: URL, completion: #escaping (Bool) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
URLSession.shared.dataTask(with: request) { _, response, _ in
completion((response as? HTTPURLResponse)?.statusCode == 200)
}.resume()
}
// Usage
fileExists(at: url) { exists in
if exists {
// do something
}
}
async/await
func fileExists(at url: URL) async throws -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 1.0 // Adjust to your needs
let (_, response) = try await URLSession.shared.data(for: request)
return (response as? HTTPURLResponse)?.statusCode == 200
}
// Usage
if try await fileExists(at: url) {
// do something
}
// or if you don't want to deal with the `throw`
if (try? await fileExists(at: url)) ?? false {
// do something
}