Perform POST request in iOS Swift - ios

I am trying perform a POST request and the request does not go through. I have looked through Perform POST request in Swift already but it does not contain what I'm looking for.
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
var request = NSMutableURLRequest(URL: NSURL(string: "https://us1.lacunaexpanse.com"))
println("request url https://us1.lacunaexpanse.com")
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
let apikey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
println("apikey",apikey)
let username = "username"
let password = "password"
var login = Array(["username", "password", "apikey"])
let jsonDictionary = ["2.0", "jsonrpc", "1", "id", "login", "method", "login", "params"]
println("jsonDictionary",jsonDictionary)
var writeError: NSError?
let jsonData = NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: NSJSONWritingOptions(), error: NSErrorPointer())
var resultAsString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)
resultAsString = resultAsString.stringByAppendingString("empire")
let url = NSURL.URLWithString("string")
println("url",url)
var request2 = NSMutableURLRequest()
println("Post url =%#",url)
request2 = NSMutableURLRequest(URL:url)
request2.HTTPMethod = "POST"
var connection = NSURLConnection(request: request, delegate: self, startImmediately: false)
return true

There are a whole bunch of tactical issues here:
You're creating URLSession, but then issuing NSURLConnection request. Just use URLSession.
Your “request dictionary” isn't a dictionary, but rather an array. For example, to issue the JSON-RPC request, the proper format of the dictionary is:
let requestDictionary: [String: Any] = [
"jsonrpc" : "2.0",
"id" : 1,
"method" : "login",
"params" : ["myuserid", "mypassword", "mykey"]
]
Minor issue, but you are using a lot of variables (via var) where a constant (via let) would be fine. In the spirit of Swift’s safety, use let wherever possible.
According to the Lacuna Expanse API, your URL should be including the module name.
So, for example if doing POST requests in the "Empire" module, the URL is:
let url = URL(string: "https://us1.lacunaexpanse.com/empire")!
You're likely to be doing a lot of requests, so I'd suggest putting the bulk of that in a single function that you can call again and again, without repeating code all over the place. Perhaps a function like the following that takes the following parameters:
module (e.g. “empire” vs “alliance”);
method (e.g. “login” vs “fetch_captcha”);
the parameters appropriate for that request (e.g. for “login”, that would be the “name”, “password”, and the “api_key”); and
closure that will be called when the asynchronous request finishes.
This function then prepares the JSON-RPC request and calls the closure when the request finishes:
#discardableResult
func submitLacunaRequest(module: String, method: String, parameters: Any, completion: #escaping (Result<[String: Any], Error>) -> Void) -> URLSessionTask? {
let session = URLSession.shared
let url = URL(string: "https://us1.lacunaexpanse.com")!
.appendingPathComponent(module)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json-rpc", forHTTPHeaderField: "Content-Type")
let requestDictionary: [String: Any] = [
"jsonrpc": "2.0",
"id" : 1,
"method" : method,
"params" : parameters
]
request.httpBody = try! JSONSerialization.data(withJSONObject: requestDictionary)
let task = session.dataTask(with: request) { data, response, error in
// handle fundamental network errors (e.g. no connectivity)
guard error == nil, let data = data else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
// check that http status code was 200
guard
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
completion(.failure(URLError(.badServerResponse)))
return
}
// parse the JSON response
do {
guard let responseObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw URLError(.badServerResponse)
}
completion(.success(responseObject))
} catch let parseError {
completion(.failure(parseError))
}
}
task.resume()
return task
}
This does all of the necessary wrapping of the method and parameters within a JSON-RPC request. Then, all you need to do to call that method is something like so:
submitLacunaRequest(module: "empire", method: "login", parameters: ["myuserid", "mypassword", "mykey"]) { result in
switch result {
case .failure(let error):
print("error = \(error)")
case .success(let value):
if let errorDictionary = value["error"] as? [String: Any] {
print("error logging in (bad userid/password?): \(errorDictionary)")
} else if let resultDictionary = value["result"] as? [String: Any] {
print("successfully logged in, refer to resultDictionary for details: \(resultDictionary)")
} else {
print("we should never get here")
print("responseObject = \(value)")
}
}
}
For a request that requires a dictionary, such as "create", just go ahead and supply the dictionary:
submitLacunaRequest(module:"empire", method: "create", parameters: [
"name" : "user",
"password" : "password",
"password1" : "password",
"captcha_guid" : "305...dd-....-....-....-e3706...73c0",
"captcha_solution" : "42",
"email" : "test#gmail.com"
]) { result in
switch result {
case .failure(let error):
print("error = \(error)")
case .success(let value):
print("responseObject = \(responseObject)")
}
}
Clearly, in these above, I am just doing minimal error handling, so you could beef this up, but your question was about issuing POST request, and hopefully the above illustrates how that is done.
For Swift 2 version, see previous revision of this answer.

As #jcaron points out, this post is full of bad edits. There's a lot of variables that have different names later in the function and so on. Not to mention you should NEVER post your api key in a SO question or anywhere on the internet for that matter.
To answer your question on to do a post request in Swift, unless you need incredibly granular control over the process, take a look at Alamofire (same guy that wrote AFNetworking).
A POST request is as simple as Alamofire.request(.POST, "http://someapiurl.com") You can also pass in a dictionary of body parameters if you so choose.

Related

iOS - Swift : fetching data from database in main thread, not in background

In my iOS App i'm able to download data from a database, but actually all the operations are made in background and the main thread is still active, even the GUI. I also tried to make a 'sleep' with
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { ... }
With this delay everthing works fine, but it's not a good solution. How can i change my code to do this in the main thread? Possibly with loadingIndicator.
This is my code (checking if username exists):
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String)
{
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
var _errorMsg = ""
var _psw = ""
var parameters : [String : Any]?
parameters = ["username": username,
"action": "login"]
print(parameters!)
let session = URLSession.shared
let url = "http://www.thetestiosapp.com/LoginFunctions.php"
let request = NSMutableURLRequest()
request.url = URL(string: url)!
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField:"Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters!, options: .sortedKeys)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
_psw = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
passwordFromDb = _psw
errorMsg = _errorMsg
}
You’re attempting to update passwordFromDb and errorMsg at the end of this method. But this is an asynchronous method and and those local variables _psw and _errorMsg are set inside the closure. Rather than trying to defer the checking of those variables some arbitrary three seconds in the future, move whatever “post request” processing you need inside that closure. E.g.
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let parameters = ...
let session = URLSession.shared
var request = URLRequest()
...
do {
request.httpBody = ...
let task = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse,
let statusCode = httpResponse.statusCode {
print ("status code = \(statusCode)")
}
guard let data = data else {
print (error ?? "Unknown error")
return
}
let password = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
DispatchQueue.main.async {
// USE YOUR PASSWORD AND ERROR MESSAGE HERE, E.G.:
self.passwordFromDb = password
self.errorMsg = _errorMsg
// INITIATE WHATEVER UI UPDATE YOU WANT HERE
}
}
task.resume()
} catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
}

Google translate api, Requests from this ios client application \u003cempty\u003e are blocked

I got this error message "Requests from this ios client application \u003cempty\u003e are blocked" by calling translate api, I have set to ios app and my bundle id on the web. Please help
let TRANSLATE_API = "https://translation.googleapis.com/language/translate/v2"
let GOOGLE_CLOUD_API_KEY = "<API_KEY>"
let urlParams:[String : Any] = [
"target": target,
"q": textToTranslate,
"key": GOOGLE_CLOUD_API_KEY,
"source": source]
let bundleIdentifier = Bundle.main.bundleIdentifier!
let headers:[String : String] = ["Content-Type": "application/json", "X-Ios-Bundle-Identifier": bundleIdentifier]
// Fetch Request
let urlString = TRANSLATE_API
// Fetch Request
Alamofire.request(urlString, parameters: urlParams)
.validate()
.responseJSON { (response) in
switch(response.result) {
case .success(_):
if response.result.error != nil {
completion(false, response.result.error! as! String)
}
if let json = response.result.value as? [String: Any] {
if let data = json["data"] as? [String: Any] {
if let translations = data["translations"] as? [[String:Any]] {
let translatedTextDict = translations[0]
if let result = translatedTextDict["translatedText"] as? String {
completion(true, result)
}
}
}
}
break
case .failure(_):
completion(false, (response.result.error?.localizedDescription)!)
break
}
}
I ran into the same problem, and after debugging, I found out the problem,
the API key works if you add it to the url, so the change your request to something like:
let BASE_URL = URL(string: "https://translation.googleapis.com/language/translate/v2")!
Alamofire.request(BASE_URL, method: .post, parameters: params, encoding: URLEncoding.default, headers: nil)
I don't know why removing the header "Content Type" affects it.
Also a Tip: never share your keys publicly, and don't put it like that in your code, especially if you're hosting it on a public repository. Put it in a gitignored file and read it when your app starts, or set an environment variable.
Let me know if you have follow up questions.

Swift POST request sends an empty body

Here's the code:
func makePOSTCall(endpoint: String, languageName: String) {
guard let url = URL(string: endpoint) else {
print("Could not create URL.")
return
}
let requestLang: [String: Any] = ["name": languageName]
let requestBody = try? JSONSerialization.data(withJSONObject: requestLang)
var urlRequest = URLRequest(url: url)
urlRequest.httpBody = requestBody
urlRequest.httpMethod = "POST"
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
}
task.resume()
}
This sends a {"name": "Go"} JSON dictionary to Flask. Flask is supposed to append the language name to an array and return the full array in the response. Now, this works when I send the request manually, so it's not Flask's error. But when I send the above from iOS, I get request.json == None in the flask console. Clearly, I'm sending an empty body, but I shouldn't be. Any idea where I went wrong?
I call the function as
#IBAction func pressedMakePOSTCall(_ sender: UIButton) {
makePOSTCall(endpoint: "http://127.0.0.1:5000/lang", languageName: "Go")
}
I tried adding a trailing slash, just get a 404 in the console. The only question similar to mine that I've found is this: How to make HTTP Post request with JSON body in Swift and my code is basically identical.
#weissja19 was correct, I needed to set content type to application/json. Adding
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.setValue("application/json", forHTTPHeaderField: "Accept")
fixed the error. Now the code works as I expected.
P.S. I couldn't catch it because I use the app Paw for testing, which sets content type automatically.
You might want to do it manually:
urlRequest.httpBody = "name=\(languageName)".data(using: .utf8)
Use JSONSerialization will make your POST body like {"name":"abc"} which might not be supported by your server

How to successfully pass string array as parameter alamofire

I have an endpoint that accepts a string array as parameter but I can't get it working with alamofire.
I test my endpoint with postman and it works fine, even in the browser, but with alamofire it fails and just returns the whole thing (as if I didn't put any parameter).
func getQuotes(String url){
//THIS CALL IS NOT WORKING. PARAMETERS ARE NOT SENT PROPERLY - FIX
let stringArray : [String] = ["4250_XSAU", "Test"]
let getQuoteParameters : Parameters = [
//"internal_symbols": stockInternalSymbols
"internal_symbols" : stringArray
]
print("attempting to get quotes on utility queue")
Alamofire.request(url, parameters: getQuoteParameters).responseJSON{ response in
print(response)
/* if (response.result.value != nil){
let jsonResponse = JSON(response.result.value!)
print(jsonResponse)
}
*/
}
}
Am I doing something wrong? When I go to url + "?internal_symbols=["4250_XSAU","Test"] on my browser, or postman, it works just fine.
I also tried setting my "getQuoteParamaters" variable as
let getQuoteParameters : Parameters = [
"internal_symbols" : ["4250_XSAU", "Test"]
]
but it doesn't work neither... it should be the same thing.
Just to clarify, this request ignores completely my parameters, when it should send the array to my backend.
You can simply pass your string array in converting it into a JSON format.Like in the sample code given below:
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let values = ["06786984572365", "06644857247565", "06649998782227"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values)
Alamofire.request(request)
.responseJSON { response in
// do whatever you want here
switch response.result {
case .failure(let error):
print(error)
if let data = response.data, let responseString = String(data: data, encoding: .utf8) {
print(responseString)
}
case .success(let responseObject):
print(responseObject)
}
}
My solution in Swift 3:
let text: [String] = ["location.branches.longitude", "location.branches.latitude"]
let params: Parameters = [
"_source": text
]
Try by add encoding standard in your request, like JSONEncoding.default
func getQuotes(String url){
//THIS CALL IS NOT WORKING. PARAMETERS ARE NOT SENT PROPERLY - FIX
let stringArray : [String] = ["4250_XSAU", "Test"]
let getQuoteParameters : Parameters = [
//"internal_symbols": stockInternalSymbols
"internal_symbols" : stringArray
]
print("attempting to get quotes on utility queue")
Alamofire.request(url, method: .post, parameters: getQuoteParameters, encoding: JSONEncoding.default).responseJSON{ response in
print(response)
/* if (response.result.value != nil){
let jsonResponse = JSON(response.result.value!)
print(jsonResponse)
}
*/
}
Thanks.

Swift 3 reusable post method in helper

I am working on swift 3 application and want to build login system using REST API. First I wanted a way to post data to server (PHP + MYSQL) with parameters so I found this post.
HTTP Request in Swift with POST method
Now I wanted place this code in a method as helper so I can utilise this method from anywhere in app. Hence followed this way:
Where to put reusable functions in IOS Swift?
Current code is as follow:
import Foundation
class Helper {
static func postData(resource: String, params: [String: String]) -> [String:String] {
var request = URLRequest(url: URL(string: "http://localsite.dev/api/\(resource)")!)
request.httpMethod = "POST"
var qryString: String = "?key=abcd"
for (paramKey, paramVal) in params {
qryString = qryString.appending("&\(paramKey)=\(paramVal)")
}
request.httpBody = qryString.data(using: .utf8)
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, httpStatus.statusCode != 200 {
print("Error on HTTP")
return
}
let responseString = String(data: data, encoding: .utf8)
print("success and here is returned data \(responseString)")
}
task.resume()
return ["data" : "some data"]
}
}
Call this using
let loginDetails: [String: String] = ["email": emailTxt.text!, "pass": passTxt.text!]
Helper.postData(resource: "login", params: loginDetails)
In above method rather then printing data I want to return data as per below 4 conditions.
1.If error in request data then I want to return as
[“status”: false, “message”: “Something wrong with request”]
2.If error in HTTP request
[“status”: false, “message”: “Resource not found”]
3.If login fail
[“status”: false, “message”: “Wrong login details”]
4.If login success
[“status”: true, “message”: “Login success”]
If you want to use a third party library for handling HTTP request, I strongly recommend Alamofire.
When I wanna handle HTTP requests I usually create a singleton class:
class HttpRequestHelper {
static let shared = HttpRequestHelper()
func post(to url: URL, params: [String: String], headers: [String: String], completion: (Bool, String) -> ()){
//Make the http request
//if u got a successful response
// parse it to JSON and return it via completion handle
completion(true, message)
//if response is not successful
completion(false, message)
}
}
And you can use it everywhere:
class AnotherClass: UIViewController {
HttpRequestHelper.shared.post(to: url, params: [:], header: [:],
completion:{
success, message in
print(success)
print(message)
})
}
To the POST method more reusable and not just specific to an endpoint, I usually make the completion handler params as Bool, JSON. And then I handle the JSON response from wherever I call the method.
Oh and I use SwiftyJson to parse json, it's the simplest.

Resources