I'm trying to make an REST API call to a universal devices hub to turn a switch on. It seems like the call is going through, however I am getting an error that says I need credentials, which makes sense because there are credentials needed to get into the interface. However I am not sure how to make this work.
My code is the following
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var lightOn: UIButton!
#IBAction func lightOn(_ sender: Any) {
guard let url = URL(string: "http://0.0.0.0/rest/nodes/ZW002_1/cmd/DFON") else { return }
let userCredential = URLCredential(user: "admin",
password: "admin",
persistence: .permanent)
URLCredentialStorage.shared.setDefaultCredential(userCredential, for: protectionSpace)
// create URL session ~ defaulted to GET
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
// optional chaining to make sure value is inside returnables and not not
if let response = response {
print(response)
}
if let data = data {
// assuming the data coming back is Json -> transform bytes into readable json data
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print("error")
}
}
}.resume() // if this is not called this block of code isnt executed
}
}
I tried piecing together a couple examples online, and the ones I have seen use protectionSpace but when I use it the code returns:
Use of unresolved identifier 'protectionSpace'
Also overall whenever I actually run the simulator I get this exact error:
2017-12-26 13:28:58.656122-0600 hohmtest[6922:1000481] CredStore - performQuery - Error copying matching creds. Error=-25300, query={
atyp = http;
class = inet;
"m_Limit" = "m_LimitAll";
ptcl = http;
"r_Attributes" = 1;
sdmn = "/";
srvr = "192.168.1.73";
sync = syna;
}
<NSHTTPURLResponse: 0x60400042a3e0>
{ URL:
http://192.168.1.73/rest/nodes/ZW002_1/cmd/DON/ } { Status Code: 401,
Headers {
"Cache-Control" = (
"max-age=3600, must-revalidate"
);
Connection = (
"Keep-Alive"
);
"Content-Length" = (
0
);
"Content-Type" = (
"text/html; charset=UTF-8"
);
EXT = (
"UCoS, UPnP/1.0, UDI/1.0"
);
"Last-Modified" = (
"Tue, 26 Dec 2017 11:26:15 GMT"
);
"Www-Authenticate" = (
"Basic realm=\"/\""
);
} }
error
This solution worked for me. This is how I called a REST API that required a username and password. For those wondering, I put this code inside my IBAction button and didn't have to do anything else other than making the button.
let username = "admin"
let password = "admin"
let loginData = String(format: "%#:%#", username, password).data(using: String.Encoding.utf8)!
let base64LoginData = loginData.base64EncodedString()
// create the request
let url = URL(string: "http:/rest/nodes/ZW002_1/cmd/DFON")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginData)", forHTTPHeaderField: "Authorization")
//making the request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse {
// check status code returned by the http server
print("status code = \(httpStatus.statusCode)")
// process result
}
}
task.resume()
********* EXTRA NOTE *************
If yours does not have a username and password and you are trying to call a REST API call in swift here is some code that can help you! BOTH ARE GET REQUESTS!
#IBAction func onGetTapped(_ sender: Any) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/users") else { return }
// create URL session ~ defaulted to GET
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
// optional chaining to make sure value is inside returnables and not not
if let response = response {
print(response)
}
if let data = data {
// assuming the data coming back is Json -> transform bytes into readable json data
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print("error")
}
}
}.resume() // if this is not called this block of code isnt executed
}
Related
I am trying to mimic an HTTP Form POST in iOS to login to a server.
When I use Safari or Chrome and submit the login form outside of my app, I can login without issue. I am using Safari and Chrome dev tools to record/review the "correct" request and response headers, cookies and body during the GET and POST required to login.
When I run my app in Xcode, I use debug print statements or Instruments to review the headers, cookies and body.
Is there a tool/method that will allow me to compare my app's GET and POST header and body vs. what a web browser does? I want an "apples to apples" comparison that will allow me to determine what I am doing wrong...
My code is below. The POST header returns status code = 419. The post body includes the text "Page Expired", which leads me to believe I am not handling tokens or cookies correctly.
Code overview:
I press a UI button to invoke login(). This does a GET of login
page, and saves the hidden _token form input from the response body.
Cookies are saved to cookieStorage.
I press a UI button to invoke loginPost(). This submits a form with
a bogus email and password. I format headers and body. I expect to
get an error indicating email is not registered. POST adds _token to
body. This body seems to match Chrome dev tools for urlencode
formatting. Status code 419 is returned..
Code
class LoginAPI {
public let avLogin = "https://someDomain.com/login"
// save response, data from last getHTMLPage() GET
fileprivate var lastGetResponse: HTTPURLResponse? = nil
fileprivate var lastGetData: String? = nil
// test POST with saved values
var loginToken = ""
var cookies: [HTTPCookie] = []
// MARK: Login
func login() async -> String {
// GET login page,
let loginGetHTML = await self.getHTMLPage(url: self.avLogin)
let loginToken = self.scrapeLoginToken(html: loginGetHTML)
let cookies = self.getCookiesFromResponse(response: self.lastGetResponse)
if let lastResponse = self.lastGetResponse,
let lastURL = lastResponse.url {
HTTPCookieStorage.shared.setCookies(cookies,
for: lastURL, mainDocumentURL: nil)
}
// allow testing of Login, then POST
self.loginToken = loginToken
self.cookies = cookies
// TO DO: add delay, then call loginPost(), and return Data as String
return ""
}
// MARK: POST Login form
func loginPost(url: String, loginToken: String, cookies: [HTTPCookie]) async {
guard let loginURL = URL(string: url) else {return}
let email = "fake123#gmail.com"
let password = "pass123"
var request = URLRequest(url: loginURL)
request.httpMethod = "POST"
request.url = loginURL
// header
request.httpShouldHandleCookies = true
// body
let loginInfo = [
("_token" , loginToken),
("email" , email),
("password", password)
]
let body = urlEncode(loginInfo)
request.httpBody = Data(body.utf8)
let session = URLSession.shared
session.configuration.httpCookieStorage = HTTPCookieStorage.shared
session.configuration.httpCookieAcceptPolicy = .always
session.configuration.httpShouldSetCookies = true
let task = session.dataTask(with: request) { (data, response, error) in
if let error = error {
print ("POST error: \(error)")
}
guard let response = response as? HTTPURLResponse else {
print("invalid POST response")
return
}
print("response")
let statusCode = response.statusCode
let headerFields = response.allHeaderFields
let cookies = headerFields["Set-Cookie"]
// let cookie = response.value(forKey: "Set-Cookie")
print(" status code = \(statusCode)")
print(" cookies = \(cookies.debugDescription)")
print(response)
if let mimeType = response.mimeType,
let data = data,
let page = String(data: data, encoding: .utf8) {
print("mimeType \(mimeType)")
print("page as UTF-8")
print(page)
}
}
task.resume()
}
// MARK: GET
public func getHTMLPage(url urlString: String) async -> String {
var statusCode = 0 // HTTP Response status code
// void prior cached response, data
self.lastGetResponse = nil
self.lastGetData = nil
guard let url = URL(string: urlString) else {
print("Error: Invalid URL: '\(urlString)'")
return ""
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
if let httpResponse = response as? HTTPURLResponse {
statusCode = httpResponse.statusCode
self.lastGetResponse = httpResponse
print("GET response")
print(response)
} else {
print("Error: couldn't get HTTP Response")
return ""
}
guard statusCode == 200 else {
print("Error: Bad HTTP status code. code=\(statusCode)")
return ""
}
let page = String(decoding: data, as: UTF8.self)
self.lastGetData = page
return page
} catch {
print("Error: catch triggerred")
return ""
}
}
// MARK: Login Helper Functions
private func getCookiesFromResponse(response: HTTPURLResponse?) -> [HTTPCookie] {
guard let response = response,
let responseURL = response.url else {
return []
}
guard let responseHeaderFields = response.allHeaderFields as? [String : String] else {
return []
}
let cookies = HTTPCookie.cookies(
withResponseHeaderFields: responseHeaderFields,
for: responseURL)
return cookies
}
// MARK: Login token
public func scrapeLoginToken(html: String) -> String {
look for name="_token", value="40-char-string"
return <40-char-string
}
// MARK: Login urlEncode
public func urlEncode(_ params: [(String, String)]) -> String {
var paramArray: [String] = []
for param in params {
let (name, value) = param
let valueEnc = urlEncode(value)
paramArray.append("\(name)=\(valueEnc)")
}
let body = paramArray.joined(separator: "&")
return body
}
private func urlEncode(_ string: String) -> String {
let allowedCharacters = CharacterSet.alphanumerics
return string.addingPercentEncoding(
withAllowedCharacters: allowedCharacters) ?? ""
}
}
Any debug help or direction would be appreciated!
I was able to resolve this issue with Proxyman, a Mac app that places itself between your mac and the internet, and saves all activity from apps, like Safari and Chrome, as well as apps being developed in Xcode.
Proxyman allowed me to make a simple "apples to apples" comparison, showing me all internet activity made by a browser, my app, or any other app running on my mac, in a common easy to read format.
I am not a web developer, so I didn't realize how easy it would be to debug URLSession headers, cookies, responses, etc. using a proxy server app.
My real problem: I had a simple typo in the web form I was sending to the server, so the server wasn't receiving all necessary form fields.
Days of debug looking into caches, cookies, headers, url encoding, responses, etc. I just didn't see the typo!
I am making an iOS app using SwiftUI that requires login. When the create account button is pressed, the action triggers a function in my NetworkManager class that sends the inputed email and password as a post request and receives the appropriate data back for authentication. It then uses the received data to determine whether the credentials are valid
My issue is that it runs the code that verifies the inputed credentials against the API response before the response is actually received. Consequently, the result is the same each time.
Button(action: {
self.networkManager.signUp(email: self.inputEmail, password: self.inputPassword)
// These if statements run before the above line is executed
if self.networkManager.signUpResponse.code == nil {
// SUCCESSFUL REGISTRATION
ProgressHUD.showSuccess("Account Successfully Created!")
UserDefaults.standard.set(true, forKey: "LoggedIn")
self.showingWelcomePage = false
}
if self.networkManager.signUpResponse.code == 201 {
// REGISTRATION FAILED
ProgressHUD.showError("This account already exists", interaction: false)
}
}) {
Text("Create Account")
.font(.headline)
}
I have tried using DispatchQueue.main.async() or creating my own thread, however nothing seems to work. I need to find a way to pause the main thread in order to wait for this line of code to execute before proceeding without using DispatchQueue.main.sync() as this results in deadlock and program crash.
Here is the code for the function that makes the post request to the API
class NetworkManager: ObservableObject {
#Published var signUpResponse = AccountResults()
func signUp(email: String, password: String) {
if let url = URL(string: SignUpAPI) {
let session = URLSession.shared
let bodyData = ["school": "1",
"email": email,
"password": password]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(AccountResults.self, from: safeData)
DispatchQueue.main.async {
self.signUpResponse = results
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
Thanks in advance!
Try it:
func signUp(email: String, password: String, completion: #escaping((Error?, YourResponse?) -> Void)) {
if let url = URL(string: SignUpAPI) {
let session = URLSession.shared
let bodyData = ["school": "1",
"email": email,
"password": password]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(AccountResults.self, from: safeData)
DispatchQueue.main.async {
self.signUpResponse = results
completion(nil, results)
}
} catch {
print(error)
completion(error, nil)
}
}
}
}
task.resume()
}
}
}
use escaping in your function, I think will get exactly the point server response data or get errors too.
my english is so bad.
I have made a simple app which adds data to a database, and then retrieves it. Whilst creating unit testing, it appears that the URLSession.Shared.dataTask is not running. I can see this through the output of the print statements I have setup. Below is my code:
func addChild(childName:String,dob:String,number1:String,number2:String,parentNum:String,parentPass:String,notes:String){
//url to php file
let url = NSURL(string:"http://localhost/addChild.php")
//request to this file
let request = NSMutableURLRequest(url: url as! URL)
//method to pass data to this file
request.httpMethod = "POST"
//body to be appended to url
let body = "childName=\(childName)&dateOfBirth=\(dob)&contact1=\(number1)&contact2=\(number2)&parentAccNum=\(parentNum)&parentAccPass=\(parentPass)¬es=\(notes)"
request.httpBody = body.data(using: String.Encoding.utf8)
print("a")
//launching the request
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in
print("b")
if (error == nil){
print("c")
//send request
//get main queue in code process to communicate back to user interface
DispatchQueue.main.async(execute: {
do{
//get json result
let json = try JSONSerialization.jsonObject(with: data!,options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
print("d")
//assigning json to parseJSON in guard/secure way
//checking whether the parsing has worked
guard let parseJSON = json else{
print("Error while parsing")
return
}
//get id from parseJSON dictionary
let id = parseJSON["id"]
//if there is some id value
if id != nil{
print(parseJSON)
self.success = true
print("success")
}
}
catch{
print("Caught an error:\(error)")
}
} )
}
//if unable to proceed request
else{
print("Error:\(error)")
}
//launch prepared session
}).resume()
}
And then below is my unit testing script:
import XCTest
#testable import computerScienceCoursework
class addChildTest: XCTestCase {
//Setting up the values of the text fields
var testChildName:String = "Test name"
var testDOB:String = "99/99/99"
var testContact1:String = "00000000000"
var testContact2:String = "11111111111"
var testParAccNum:String = "-1"
var testParAccPass:String = "Password"
var testNotes:String = "Insert notes here"
var newChild = AddChildController()
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testAddChildIsWorking(){
//Assigning the values to the text fields
newChild.addChild(childName: testChildName,dob: testDOB,number1: testContact1,number2: testContact2,parentNum: testParAccNum,parentPass: testParAccPass,notes: testNotes)
XCTAssert(newChild.success == true)
}
}
Problem here is that you donĀ“t know when the async task is finished and the success property is getting updated.
There are some possible solutions for your problem one of them is to add a completion handler to your method.
func addChild(childName:String,dob:String,number1:String,number2:String,parentNum:String,parentPass:String,notes:String, completion: (Bool) -> Void){
//url to php file
let url = NSURL(string:"http://localhost/addChild.php")
//request to this file
let request = NSMutableURLRequest(url: url as! URL)
//method to pass data to this file
request.httpMethod = "POST"
//body to be appended to url
let body = "childName=\(childName)&dateOfBirth=\(dob)&contact1=\(number1)&contact2=\(number2)&parentAccNum=\(parentNum)&parentAccPass=\(parentPass)¬es=\(notes)"
request.httpBody = body.data(using: String.Encoding.utf8)
print("a")
//launching the request
URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { (data:Data?, response:URLResponse?, error:Error?) in
print("b")
if (error == nil){
print("c")
//send request
//get main queue in code process to communicate back to user interface
DispatchQueue.main.async(execute: {
do{
//get json result
let json = try JSONSerialization.jsonObject(with: data!,options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
print("d")
//assigning json to parseJSON in guard/secure way
//checking whether the parsing has worked
guard let parseJSON = json else{
print("Error while parsing")
completion(false)
return
}
//get id from parseJSON dictionary
let id = parseJSON["id"]
//if there is some id value
if id != nil{
print(parseJSON)
self.success = true
print("success")
completion(true)
}
}
catch{
print("Caught an error:\(error)")
completion(false)
}
} )
}
//if unable to proceed request
else{
print("Error:\(error)")
completion(false)
}
//launch prepared session
}).resume()
}
Then in your test method you can the method.
func testAddChildIsWorking()
{
let asyncExpectation = expectationWithDescription("addChildIsWorkingFunction")
newChild.addChild(childName: testChildName, dob: testDOB, number1: testContact1,
number2: testContact2, parentNum: testParAccNum, parentPass: testParAccPass, notes: testNotes) { (success) in
asyncExpectation.fulfill()
}
self.waitForExpectationsWithTimeout(10) { error in
XCTAssert(newChild.success == true)
}
}
waitForExpectationWithTimeout is waiting until a fulfill is trigger or a timeout occurs. In this way you could test your async code.
For more informations check this link
Hope that helps.
I have the following code but instead of outputting either of the statements; both statements are output:
EDITED:
After a lot of trial and error I am still no closer to a solution, I have condensed my code down to just the bare minimum and removed all other controls in an attempt to solve this and still not working, here is my minimal code that is attached to a simple single switch:
//
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mySwitch: UISwitch!
#IBAction func buttonClicked(_ sender: Any) {
let switchOn = mySwitch.isOn
let endpoint = "http://10.0.1.147/setpins"
let urlString = endpoint + "?D2=" + (switchOn ? "0" : "1023")
mySwitch.setOn(!switchOn, animated:true)
var request = URLRequest(url: URL(string: urlString)!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
print("error=\(error)")
return
}
// You can print out response object
print("response = \(response)")
}
task.resume()
}
}
BUT: Still getting the output of each argument output at the same time..
response = Optional(<NSHTTPURLResponse: 0x17402d9e0> { URL: pttp://10.0.1.147/setpins?D1=0 } { status code: 200, headers {
"Access-Control-Allow-Origin" = "*";
Connection = close;
"Content-Length" = 7;
"Content-Type" = "text/plain";
} })
response = Optional(<NSHTTPURLResponse: 0x17002c8a0> { URL: pttp://10.0.1.147/setpins?D2=1023 } { status code: 200, headers {
"Access-Control-Allow-Origin" = "*";
Connection = close;
"Content-Length" = 7;
"Content-Type" = "text/plain";
You don't actually need if-else in that case. Here is your completely rewritten code - try to run it and see if duplication issue goes away.
let switchOn = mySwitch.isOn
switchState.text = switchOn ? "OFF" : "ON"
let endpoint = "http://10.0.1.147/setpins"
let urlString = endpoint + "?D2=" + (switchOn ? "0" : "1023")
mySwitch.setOn(!switchOn, animated:true)
var request = URLRequest(url: URL(string: urlString)!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
print("error=\(error)")
return
}
// You can print out response object
print("response = \(response)")
}
task.resume()
Try moving the task.resume out of the if..else block along with other declarations.
//declare other variables here
if .... {
...
...
...
} else {
...
...
}
let task = URLSession.shared.dataTask(with: request) {
(data: Data?, response: URLResponse?, error: Error?) in
if error != nil{
print("error=\(error)")
return
}
// You can print out response object
print("response = \(response)")
}
task.resume()
I create functionLibrary.swift class to call some functions in some different ViewControllers. Problem is when I execute the app, the println function in the ViewController.swift works first in spite of it is after the some other lines that I called from functionLibrary.
Here is the logIn function from ViewController. (funcLib is a value that type of functionLibrary)
#IBAction func logIn(sender: AnyObject) {
var bodyData = ["op":"users","op2":"login","id":"","id2":"","id3":"","users_emailAddress":"asdasd#gmail.com","users_password":"asdasd"]
var requestData = funcLib.JSONStringify(bodyData) // This turns the data to string
var responseData = funcLib.HTTPPostRequest(requestData) // This is the http post request. Returns a bool and a Dictionary
println(responseData)
Here is the output
(false, [:]) //responseData
response = <NSHTTPURLResponse: 0x7aad7290> { URL: http://asdasd.asdasd.com/services/index.php } { status code: 200, headers {
Connection = "keep-alive";
"Content-Length" = 57;
"Content-Type" = "application/json";
Date = "Sun, 26 Jul 2015 21:09:39 GMT";
Server = asdasd;
"X-Powered-By" = "PHP/5.4.43";
} }// The println in HTTPPostRequest
{
auth = *********;
"user_id" = ***;
} // The println in HTTPPostRequest
The code is working fine. There are no runtime errors. But like I said println is executed first. How can I execute it after request and response Data get the data from funcLib functions?
Thank you.
Looks like funcLib.HTTPPostRequest schedules an async closure and returns immediately, without waiting for the HTTP response. The async approach is fine, but of course you cannot expect to find in responseData any data returned from the HTTP server, and of course the println in logIn will be executed immediately, while the println of the HTTP response will happen later, when the actual response will be received by the closure.
I solved the problem with using dispach_async() method.
Here is the new HTTPPostRequest() function:
func HTTPPostRequest(link:String, bodyData: AnyObject, completion:((data: NSData?) -> Void)) {
println("HTTPPOST")
if let requestURL = NSURL(string: link){
let request = NSMutableURLRequest(URL: requestURL)
request.HTTPMethod = "POST"
let postString = JSONStringify(bodyData)
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
NSURLSession.sharedSession().dataTaskWithRequest(request){ (data, response, error) in
completion(data: NSData(data: data))
if let error = error {
println("error:\(error)")
return
}
}.resume()
}
}
And here is how to call the HTTPPostRequest():
#IBAction func logIn(sender: AnyObject) {
var bodyData: AnyObject = ["op":"users","op2":"login","id":"","id2":"","id3":"","users_emailAddress":"asdasdasd","users_password":"asdasd"] as AnyObject
let response: () = funcLib.HTTPPostRequest("http://asdasd.asdasdasd.com/services/index.php", bodyData: bodyData){data in
dispatch_async(dispatch_get_main_queue()){
if let data = data {
let responseData = NSData(data: data)
let responseDic = NSJSONSerialization.JSONObjectWithData(data , options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
println("\(responseDic)")
}
}
}
}
}