Sending post data nsurlsession - json

Hi i'm trying to replicate the following curl command in swift using NSURLSession in a playground.
curl -k -i -H "Accept: application/json" -H "X-Application: " -X POST -d 'username=&password=' https://address.com/api/login
Here's what I've got so far. Where i'm struggling is that i'm unsure how to send the post data e.g.: 'username=&password='. Any help would be appreciated.
Thanks in advance.
import Foundation
import XCPlayground
// Let asynchronous code run
XCPSetExecutionShouldContinueIndefinitely()
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.HTTPAdditionalHeaders = ["Accept" : "application/json", "X-Application" : "<AppKey>"]
let session = NSURLSession(configuration: config)
var running = false
let url = NSURL(string: "https://address.com/api/login")
let task = session.dataTaskWithURL(url!) {
(let data, let response, let error) in
if let httpResponse = response as? NSHTTPURLResponse {
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
println(dataString)
}
running = false
}
running = true
task.resume()

You can create a mutable URLRequest and set the httpBody. But you should also percent escape the values for username and, more importantly, for password.
So, imagine your request being created like so:
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["Accept" : "application/json", "X-Application" : "<AppKey>", "Content-Type" : "application/x-www-form-urlencoded"]
let session = URLSession(configuration: config)
let url = URL(string: "https://identitysso.betfair.com/api/login")!
var request = URLRequest(url: url)
request.setBodyContent(["username": username, "password": password])
let task = session.dataTask(with: request) { data, response, error in
// make sure there wasn't a fundamental networking error
guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
print(error ?? "Unknown error")
return
}
// if you're going to check for NSHTTPURLResponse, then do something useful
// with it, e.g. see if server status code indicates that everything is OK
guard 200 ..< 300 ~= response.statusCode else {
print("statusCode not 2xx; was \(response.statusCode)")
return
}
// since you set `Accept` to JSON, I'd assume you'd want to parse it;
// In Swift 4 and later, use JSONDecoder; in Swift 3 use JSONSerialization
do {
if let responseObject = try JSONSerialization.jsonObject(with: data) as? [String: AnyObject] {
print(responseObject)
}
} catch let parseError {
print(parseError)
print(String(data: data, encoding: .utf8) ?? data as NSData)
}
}
task.resume()
So the question is how setBodyContent builds the request body given a dictionary. Yes, you want to percent-escape anything not in the unreserved character set, but sadly CharacterSet.urlQueryAllowed is not up to the job. So you might do something like:
extension URLRequest {
/// Populate the HTTPBody of `application/x-www-form-urlencoded` request
///
/// - parameter parameters: A dictionary of keys and values to be added to the request
mutating func setBodyContent(_ parameters: [String : String]) {
let parameterArray = parameters.map { (key, value) -> String in
let encodedKey = key.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
let encodedValue = value.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!
return "\(encodedKey)=\(encodedValue)"
}
httpBody = parameterArray
.joined(separator: "&")
.data(using: .utf8)
}
}
extension CharacterSet {
/// Character set containing characters allowed in query value as outlined in RFC 3986.
///
/// RFC 3986 states that the following characters are "reserved" characters.
///
/// - General Delimiters: ":", "#", "[", "]", "#", "?", "/"
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
///
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
/// should be percent-escaped in the query string.
///
/// - parameter string: The string to be percent-escaped.
///
/// - returns: The percent-escaped string.
static let urlQueryValueAllowed: CharacterSet = {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
return allowed
}()
}
Furthermore, I generally use a more complicated setBodyContent that also accepts numeric, boolean, and date types, but I didn't want to digress too far from your core question, how to properly build request for two string key/values pairs.
For Swift 2 rendition, see previous revision of this answer.

maybe this help, i use it to send post data:
var paramString = ""
var request:NSMutableURLRequest = NSMutableURLRequest(URL:url)
request.HTTPMethod = "POST"
var user = "MyUsername".stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
var pass = "MyPassword".stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
paramString = "username="+user!+"&password="+pass!
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let conf: NSURLSessionConfiguration = NSURLSession.sharedSession().configuration
let session = NSURLSession(configuration: conf)
session.dataTaskWithRequest(request) {
data, response, error in
let resp = NSString(data: data, encoding: NSUTF8StringEncoding)
}.resume()
NSUTF8StringEncoding can be replaced with whatever you need

Related

Alamofire POST not working as expected. Error on parameters

I am trying to upload (post) a JSON string using Alamofire and I'm getting an error that leads me to believe there is some issue in the dictionary encoding.
The array I am encoding is an array of objects, all of type String. It looks like this:
class SavedDeliveries: Codable { //not sure if this is the issue (I use this to store default values locally)
var fullName = ""
var address = ""
var city = ""
var zip = ""
var phone = ""
var orders = ""
var ordersListed = ""
var pickup = ""
}
The code including the Alamofire call looks like this:
func postData() {
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"X-Master-Key": "xxx", //API Key
"X-Bin-Name": "deliverydata"]
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(deliveryList)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
print(json!) // printing the JSON and it is correct when validated.
AF.request("https://api.jsonbin.io/v3/b", method: .post, parameters: json, encoding: JSONEncoding.default, headers: headers).responseString { (response) in
switch response.result {
case .success:
print("was successful")
case let .failure(error):
print(error)
}
}
}
I expect it to upload the JSON file but I'm getting an error message that says this:
Cannot convert value of type 'String?' to expected argument type 'Parameters?' (aka 'Optional<Dictionary<String, Any>>')
Not sure if the AF call parameter is expecting a certain kind of dictionary key:value format. If this is not the right call format for uploading JSON, what do I need to change?
Thanks for any help. I'm not a full-time Swift developer but manage an app that is usually within my capabilities. This one has me stumped.
I guess I don't understand much about HTTP requests or Alamofire, but I was able to solve this issue with the following mods to my code (without Alamofire, which seems like overkill in this case).
func postData() {
// Prepare URL
let url = URL(string: "https://api.jsonbin.io/v3/b")
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
// Set HTTP Request Body
let header: HTTPHeaders = [
"Content-Type": "application/json",
"X-Master-Key": "xxx",
"X-Bin-Name": "deliverydata"
]
request.headers = header
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(deliveryList)
let json = String(data: jsonData, encoding: String.Encoding.utf8)
request.httpBody = json!.data(using: String.Encoding.utf8)
// Perform HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
// Check for Error
if let error = error {
print("Error took place \(error)")
return
}
// Convert HTTP Response Data to a String
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print("Response data string:\n \(dataString)")
}
}
task.resume()
}
To make such a request using Alamofire, I'd recommend:
let headers: HTTPHeaders = [
"Content-Type": "application/json",
"X-Master-Key": "xxx", //API Key
"X-Bin-Name": "deliverydata"]
AF.request("https://api.jsonbin.io/v3/b",
method: .post,
parameters: deliveryList,
headers: headers).responseString { response in
switch response.result {
case .success:
print("was successful")
case let .failure(error):
print(error)
}
}
Even better, create a Decodable response type and use responseDecodable to parse it.
I would also suggest not using empty strings as your default values, that can easily lead to sending bad or invalid data to the backend.

Swift - POST request, sending JSON with Vapor 3

I have a problem sending a POST request with Vapor 3 with the body including JSON. I am using https://docs.postman-echo.com/ to test this, where it responds back with the same JSON it is sent.
I have viewed answers on here, but got errors with encoding and content types.
router.get("hooray") { req -> Future<View> in
var postHeaders: HTTPHeaders = .init()
postHeaders.add(name: .contentType, value: "application/json")
postHeaders.add(name: .accept, value: "*/*")
postHeaders.add(name: .acceptEncoding, value: "gzip, deflate")
let oneField = singleGet(foo: "barfoobar")
// { foo: "barfoobar" } - JSON string
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(oneField)
let jsonString = String(data: jsonData, encoding: .utf8)!
let postBody = HTTPBody(string: jsonString)
let httpReq = HTTPRequest(method: .POST, url: "/post")
let httpRes = HTTPClient.connect(hostname: "postman-echo.com", on: req)
.flatMap(to: singleGet.self) { client in
req.http.headers = postHeaders
req.http.contentType = .json
req.http.body = postBody
return client.send(httpReq).flatMap(to: singleGet.self) { res in
return try req.content.decode(singleGet.self)
}
}
return try req.view().render("test", httpRes)
}
struct singleGet: Content {
let foo: String
}
I am getting the correct response with this code, but I was wondering when I rework the code to match this answer, why do I get errors?
I've added the headers and body to the request and comment them out inside the closure, but it won't work this way.
let httpReq = HTTPRequest(method: .POST, url: "/post", headers: postHeaders, body: postBody)

Alamofire returns invalid url, when building an url from json string

Alamofire returns a invalid URL
I am forming an URL string for Searching (urlStrForCustSearch) using the following code
and URL_CUSTOMER_SEARCH = "http://pos1domain.mobi/pos/SV_IOS.asmx/IosJasonToDatatbl":
let customerJson = [[
"customer_id":nil,
"tel":1234567,
"email":nil,
"addr":nil,
"history":nil]]
do{
let JSONData = try JSONSerialization.data(withJSONObject: customerJson, options: [])
let JSONText = String(data: JSONData,
encoding: .utf8)!
let urlStrForCustSearch = URL_CUSTOMER_SEARCH+"?jsonText=\(JSONText)&funname=GET_MM_CUSTOMER_DTL_LST"
print(urlStrForCustSearch)
Alamofire.request(urlStrForCustSearch, method: .post, parameters: [:], encoding: URLEncoding.default, headers: [:]).responseString { response in
if response.result.isSuccess {
print("Response is success")
}else{
print("Response Failed")
print("Reason : \(response)")
}
}
}catch{
print("Cannot parse the dictionary")
}
Console prints desired url
RightURL:
http://pos1domain.mobi/pos/SV_IOS.asmx/IosJasonToDatatbl?jsonText=[{"tel":1234567,"email":null,"history":null,"customer_id":null,"addr":null}]&funname=GET_MM_CUSTOMER_DTL_LST
but on passing the urlStrForCustSearch string to Alamofire as parameter, Alamofire return invalid URL
Response Failed
Reason : FAILURE: invalidURL("http://pos1domain.mobi/pos/SV_IOS.asmx/IosJasonToDatatbl?jsonText=[{\"tel\":1234567,\"email\":null,\"history\":null,\"customer_id\":null,\"addr\":null}]&funname=GET_MM_CUSTOMER_DTL_LST")
As we can see that '\' is added inside the url string
Can any one help me in creating the url string with out '\' string
Please let me know if any input needed.
Backslashes are used to escape special characters in a string.
If you imagine trying to assign a string variable in Swift which contained double quotes like so:
let str = "They said "hi" when I walked in"
Xcode would show an error here as your string actually ends at "They said " (opened and closed double quotes.
To address the issue you need to escape the double quotes that should be part of the string, like so
let str = "They said \"hi\" when I walked in"
Without this both your code in the iOS project would have issues and your API would have issues reading the string, it would not be valid.
When using special characters in a URL, you can encode the string with Percent Encoding which replaces special characters into a format that browsers can understand. If you convert the full url using percent encoding it is not very readable:
http%3A%2F%2Fpos1domain%2Emobi%2Fpos%2FSV%5FIOS%2Easmx%2FIosJasonToDatatbl%3FjsonText%3D%5B%7B%22tel%22%3A1234567%2C%22email%22%3Anull%2C%22history%22%3Anull%2C%22customer%5Fid%22%3Anull%2C%22addr%22%3Anull%7D%5D%26funname%3DGET%5FMM%5FCUSTOMER%5FDTL%5FLST
So it is usually best to just encode the query string (GET params) of the URL.
let baseUrlString = "http://pos1domain.mobi/pos/SV_IOS.asmx/IosJasonToDatatbl"
let queryString = "?jsonText=[{\"tel\":1234567,\"email\":null,\"history\":null,\"customer_id\":null,\"addr\":null}]&funname=GET_MM_CUSTOMER_DTL_LST".addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
let urlString = baseUrlString + queryString
print(urlString)
Output: http://pos1domain.mobi/pos/SV_IOS.asmx/IosJasonToDatatbl%3FjsonText%3D%5B%7B%22tel%22%3A1234567%2C%22email%22%3Anull%2C%22history%22%3Anull%2C%22customer%5Fid%22%3Anull%2C%22addr%22%3Anull%7D%5D%26funname%3DGET%5FMM%5FCUSTOMER%5FDTL%5FLST
UPDATE - Better Solution
I completely forgot that you are using Alamofire, I was addressing the string issue you mentioned. Alamofire can deal with the encoding for you automatically by passing the parameters into the request call.
let parameters: Parameters = ["foo": "bar"]
// All three of these calls are equivalent
Alamofire.request("https://httpbin.org/get", parameters: parameters) // encoding defaults to `URLEncoding.default`
Alamofire.request("https://httpbin.org/get", parameters: parameters, encoding: URLEncoding.default)
Thanks Scriptable,
let queryString = JSONText.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
the above line made the difference and it was ".get" method.
let JSONData = try JSONSerialization.data(withJSONObject: customerJson, options: [])
let JSONText = String(data: JSONData,
encoding: .utf8)!
//Without this line we will get invalid url from alamofire
let queryString = JSONText.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
let urlStringForCust = URL_CUSTOMER_SEARCH+"?jsonText=\(queryString)&funname=GET_MM_CUSTOMER_DTL_LST"
print(urlStringForCust)
Alamofire.request(urlStringForCust, method: .get, parameters: [:], encoding: URLEncoding.default, headers: [:]).responseString { response in
if response.result.isSuccess {

swift REST request with certificate returns errors (code -999)

So I have a request to my server for logging in (I pass login, password and deviceId). As the result I get "token" as a String. I need also to add a certificate to access my REST.
But my code is not working.
class RestService {
private init(){}
static let shared = RestService()
var loginData:NSDictionary?
class func getCertificates() -> [SecCertificate]{
let url = Bundle.main.url(forResource: "certf", withExtension: "cer")!
let localCertificate = try! Data(contentsOf: url) as CFData
guard let certificate = SecCertificateCreateWithData(nil, localCertificate) else {return[]}
return [certificate]
}
let almgr:Alamofire.SessionManager = {
let certificates = getCertificates()
let trustPolicy = ServerTrustPolicy.pinCertificates(certificates: certificates, validateCertificateChain: true, validateHost: true)
let serverTrustPolicies = ["liper":trustPolicy]
let serverTrustPolicyManager = ServerTrustPolicyManager(policies: serverTrustPolicies)
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let man = Alamofire.SessionManager(configuration: URLSessionConfiguration.default, serverTrustPolicyManager: serverTrustPolicyManager)
return man
}()
func loginRest(login:String, password:String, deviceId:String){
let urlStr = RestServices.REST_MAIN_URL + RestServices.REST_LOGIN
let params = ["login":login, "password":password, "deviceId":deviceId]
let headers: HTTPHeaders = ["Content-Type": "application/json"]
RestService.shared.almgr.request(urlStr, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
let _ = RestService.shared.almgr
switch response.result {
case .success:
print("\(self.TAG), receiving response from login with \(response)")
guard let receivedResponse = try! JSONSerialization.jsonObject(with: response.data!, options: []) as? [String:Any] else {
print("\(self.TAG), Error parsing response from login for json")
return
}
if let token:String = receivedResponse["token"] as? String {
print("\(self.TAG), \(token)")
} else {
print("\(self.TAG), error receiving token")
if let errorMessage:String = receivedResponse["status"] as? String {
print("\(self.TAG), error message for login with received response status: \(errorMessage)")
}
return
}
case .failure(let error):
print("\(self.TAG), error receiving response for login with \(error)")
return
}
}
}
}
So whenever I call that login request method in my controller (for ex. after pressing button to login), I get following errors:
2018-01-17 15:43:16.129249+0100 ios-moe[5716:2789249] Task <67AFC6DA-9EFB-47D2-A5B0-FDAA3CC5285A>.<4> HTTP load failed (error code: -999 [1:89])
2018-01-17 15:43:16.129275+0100 ios-moe[5716:2789364] Task <67AFC6DA-9EFB-47D2-A5B0-FDAA3CC5285A>.<4> finished with error - code: -999
I am 100% sure that I am passing proper values and making a proper request, but I always get those errors. What am I doing wrong?
So I figured it out. The problem was in defining trust policies:
let almgr:Alamofire.SessionManager = {
let certificates = getCertificates()
let trustPolicy = ServerTrustPolicy.pinCertificates(certificates: certificates, validateCertificateChain: true, validateHost: true)
// Here I had to modify that dict: (with port and with .disableEvaluation)
let serverTrustPolicies = ["liper:8000":trustPolicy, "liper":.disableEvaluation]
let serverTrustPolicyManager = ServerTrustPolicyManager(policies: serverTrustPolicies)
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let man = Alamofire.SessionManager(configuration: URLSessionConfiguration.default, serverTrustPolicyManager: serverTrustPolicyManager)
return man
}()
Work well now. What is weird, that I have to add port to the certificate pinning, cause without it I get ATS error about wrong certificate. And also the .disableEvaluation has to be without port.. Also, as #MAhipal Singh mentioned, I had to modify info.plist:
"add in info.plist maybe it solve : NSAppTransportSecurity NSAllowsArbitraryLoads "

Ambiguous reference to member `jsonObject(with:options:)` when trying to get data from json

I'm new to Swift and while making one of the tutorials (fairly old) which involves getting credentials from a server through php which returns a JSON, but I'm stuck with the error Ambiguous reference to member jsonObject(with:options:) in the json var, I've searched and trying applying the different solutions but to no avail. :(
Thank you for your time and help.
here is my code:
let userEmail = userEmailTextField.text;
let userPassword = userPasswordTextField.text;
if((userEmail?.isEmpty)! || (userPassword?.isEmpty)!) {
displayMyAlertMessage(userMessage: "All Fields are required.")
return;
}
let myUrl = URL(string: "/UserLogin.php");
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = "email\(userEmail)&password=\(userPassword)";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, URLResponse, error in
if error != nil {
//print = ("error=\(error)");
return
}
var err: Error?
var json = JSONSerialization.jsonObject(with: data, options: .mutableContainers, error: &err) as? NSDictionary
if let parseJSON = json {
var resultValue:String = parseJSON["status"] as String!;
print("result: \(resultValue)")
if(resultValue == "Success") {
//Login Succesful
UserDefaults.standard.set(true, forKey:"isUserLoggedIn");
UserDefaults.standard.synchronize();
self.dismiss(animated: true, completion: nil);
}
}
}
task.resume()
There are two major issues:
The actual error occurs because the response parameter in the completion block is wrong. Rather than the type URLResponse it must be a parameter label / variable.
let task = URLSession.shared.dataTask(with: request) { data, response, error in
Since you are using Swift 3 there is no error parameter in jsonObject(with. The method does throw, you need a do - catch block. And – as always – the option .mutableContainers is completely useless in Swift. Omit the parameter.
do {
if let parseJSON = try JSONSerialization.jsonObject(with: data) as? [String:Any],
let resultValue = parseJSON["status"] as? String {
print("result: ", resultValue)
if resultValue == "Success" {
//Login Succesful
UserDefaults.standard.set(true, forKey:"isUserLoggedIn")
self.dismiss(animated: true, completion: nil)
}
}
} catch {
print(error)
}
Some other notes:
To check the text fields safely use optional binding
guard let userEmail = userEmailTextField.text, !userEmail.isEmpty, let userPassword = userPasswordTextField.text, !userPassword.isEmpty else {
displayMyAlertMessage(userMessage: "All Fields are required.")
return
}
Declare Swift constants always as let (for example resultValue)
Do not use NSArray / NSDictionary in Swift. Use native types.
Do not use parentheses around if conditions and trailing semicolons. They are not needed in Swift.
UserDefaults.standard.synchronize() is not needed either.
String.Encoding.utf8 can be reduced to just .utf8.