POST Queries in Swift for given website - ios

I am trying to make queries to get the fuel type and consumption of a specified car (the user enters both make and model) for an iOS app written in Swift.
The app is targeted for Spain, and I have found a website that allows the user to enter make and model, and it returns the details for that car (http://coches.idae.es/portal/BaseDatos/MarcaModelo.aspx). I have seen using the tool WireShark, that the query is based on POST instead of GET. But I am not quite sure how I can make the requests within the app I am developing, or how to handle the info that is sent to me back from the sender.
Is there any way to make those requests to the given website? If so, I would really appreciate some help on the subject, I am new in iOS development and am looking forward to learning as much as possible.
Thanks :)

Many people prefer to use AFNetworking for making HTTP requests. However you don't need to do that. You said that its a POST request. Setting that up is easy even without AFNetworking using NSMutableURLRequest. I'm assuming you have a link to the API and not just to the aspx page. My Spanish is pretty weak so I can't look up the API reference for you but here's how you can make the request and receive data from the server. You will have to put the correct values and parse the responses:
let request = NSMutableURLRequest(URL: NSURL(string: "/* Paste URL here */")!)
request.HTTPMethod = "POST"
// Do this as many times are required for filling in the headers.
request.addValue("/* The value for the HTTP header */", forHTTPHeaderField: "/*The header field like Accept-Type, etc..*/")
// If you need an HTTP body too then make the JSONObj as a dictionary or array or whatever and then
let data = NSJSONSerialization.dataWithJSONObject(JSONObj, options: [])
request.HTTPBody = data // This needs to be NSData.
// Now make the request.
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, { (data, response, error) -> Void in
if error == nil
{
assert(data != nil)
let JSON = NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [NSObject: AnyObject]
// If you are using swift 2 this needs to be in a do try catch statement.
// TODO: Use JSON for whatever.
}
else
{
print(error!.localizedDescription)
}
}
task?.resume()
Let me know if you have any other questions or if the API doesn't use JSON or is completely different.

Related

iOS Swift URLSession POST request getting duplicated for slow API calls

I have a download task that work by first calling a REST API for which the server needs to generate a fairly large file which takes it several minutes to generate, as it is CPU and disk IO intensive. The client waits for the server to give a JSON response with the URL of the file it generated. The file download then starts after it gets the first result.
For the calls that generate a particularly large file, which causes the server to be very slow to respond, I am seeing duplicate requests that my code is not initiating.
Initially the someone who works on the server side told me about the duplicate requests. Then I set up a way to inspect network traffic. This was done by setting up a Mac connected to a wired network and enabling network sharing and using Proxyman to inspect the traffic from the iPhone to the API server. I see multiple instances of the same API request on the network layer but my code was never notified.
Code looks like this
#objc class OfflineMapDownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
#objc func download(){
let config = URLSessionConfiguration.background(withIdentifier: "OfflineMapDownloadSession")
config.timeoutIntervalForRequest = 500
config.shouldUseExtendedBackgroundIdleMode = true
config.sessionSendsLaunchEvents = true
urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
getMapUrlsFromServer(bounds)
}
func getMapUrlsFromServer(){
var urlString = "http://www.fake.com/DoMakeMap.php"
if let url = URL(string: urlString) {
let request = NSMutableURLRequest(url: url)
//...Real code sets up a JSON body in to params...
request.httpBody = params.data(using: .utf8 )
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.timeoutInterval = 500
urlSession?.configuration.timeoutIntervalForRequest = 500
urlSession?.configuration.timeoutIntervalForResource = 500
request.httpShouldUsePipelining = true
let backgroundTask = urlSession?.downloadTask(with: request as URLRequest)
backgroundTask?.countOfBytesClientExpectsToSend = Int64(params.lengthOfBytes(using: .utf8))
backgroundTask?.countOfBytesClientExpectsToReceive = 1000
backgroundTask?.taskDescription = "Map Url Download"
backgroundTask?.resume()
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
if (downloadTask.taskDescription == "CTM1 Url Download") {
do {
let data = try Data(contentsOf: location, options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject> {
if let ctm1Url = jsonResult["CTM1Url"] as? String {
if let filesize = jsonResult["filesize"] as? Int {
currentDownload?.ctm1Url = URL(string: ctm1Url)
currentDownload?.ctm1FileSize = Int32(filesize)
if (Int32(filesize) == 0) {
postDownloadFailed()
} else {
startCtm1FileDownload(ctm1Url,filesize)
}
}
}
}
} catch {
postDownloadFailed()
}
}
}
There is more to this download class as it will download the actual file once the first api call is done. Since the problem happens before that code would be executed, I did not include it in the sample code.
The log from Proxyman shows that the API call went out at (minutes:seconds) 46:06, 47:13, 48:21, 49:30, 50:44, 52:06, 53:45
It looks like the request gets repeated with intervals that are just over 1 minute.
There is an API field where I can put any value and it will be echoed back to me by the server. I put a timestamp there generated with CACurrentMediaTime() and log in Proxyman shows that indeed its the same API call so there is no way my code is getting called multiple times. It seems as though the iOS networking layer is re-issuing the http request because the server is taking a very long time to respond. This ends up causing problems on the server and the API fails.
Any help would be greatly appreciated.
This sounds a lot like TCP retransmission. If the client sends a TCP segment, and the server does not acknowledge receipt within a short span of time, the client assumes the segment didn't make it to the destination, and it sends the segment again. This is a significantly lower-level mechanism than URLSession.
It's possible the HTTP server application this API is using (think Apache, IIS, LigHTTPd, nginx, etc.) is configured to acknowledge with the response data to save packeting and framing overhead. If so, and if the response data takes longer than the client's TCP retransmission timeout, you will get this behavior.
Do you have a packet capture of the connection? If not, try collecting one with tcpdump and reviewing it in Wireshark. If I'm right, you will see multiple requests, and they will all have the same sequence number.
As for how to fix it if that's the problem, I'm not sure. The server should acknowledge requests as soon as they are received.
I think the problem is in using URLSessionConfiguration.background(withIdentifier:) for this api call.
Use this method to initialize a configuration object suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.
So the problem is that the system is retrying your request unnecessarily because of this wrong API usage.
Here's what I recommend -
Use default session configuration (NOT background).
Do this api call that initiates this long job, do NOT have client wait on this job, from server side return a job_id back to client as soon as this job is initiated.
Client can now poll server every X seconds using that job_id value to know about the status of the job, even can show progress on client side if needed.
When job is completed, and client polls next time, it gets the download URL for this big file.
Download the file (using default / background session configuration as you prefer).

Alamofire Network error exceptions handling

I am currently developing an application for iOS. Most of the features that I wanted implemented I have already finished, but there is one feature in particular that I really need to have - Network Errors handling.
So for example: A user is trying to refresh his data inside my application. Instead of my app crashing or simply not doing anything, I would love for that exception to be caught, identified and then display a corresponding message on screen using AlertDialogs. for example:
Network Error - title;
Unreachable host, please check your network connectivity and try again - Message;
OK - button;
I was able to have this working in my Android application and it's quite useful, however, I am quite new to Swift and iOS development, so, please help me out here and point me in the right direction.
I am currently using latest Alamofire for sending HTTP Requests, here is my example of HTTP Request that I have implemented inside my application.
func loadProfile() {
let url = Constants.profileURL
let headers: HTTPHeaders = ["Cookie": "username; password"]
AF.request(url, method: .post, headers: headers).response {response in
if let data = response.data, let dataString = String(data: data, encoding: .utf8) {
if dataString.contains(Constants.loginSuccess) {
//Do something
}
}
}
}
Alamofire's DataResponse (and all response types) contain the Result of serialization. You can use that value to check whether serialization, and the request in general, succeeded or failed. You can then pass the result value to completion handlers to be dealt with as you wish.

Reload data of a json file after being updated

after hours of search I need your help. I have setup a program for iOS showing recipes of my private cooking book. The recipes are stored in a json file on my web server. I use another application on my MAC to update the recipes in this json file and to store it on my web server. Unfortunately the updated recipes are not shown in my iOS-application. The iOS-app shows always the old data - I guess they are stored locally on my iPhone/iPad after the first installation of the app?
How can I enforce that at every launch of the iOS-app on my iPhone/iPad the actual json data from the web server are used and not the old one.
I retrieve the data via this function I call from the viewDidLoad() and before the reloadData() of the UITableView showing the titles of the recipes
func initKochbuch() {
let url = URL(string: selektiertesKochbuch)!
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: url) { (data, response, error) in
guard let data = data else {
debugPrint("Fehler beim Laden", error ?? "Unbekannter Fehler")
return
}
self.kochbuch_local.rezepte = try! JSONDecoder().decode([Rezept].self, from: data)
self.initRezeptAnsicht()
OperationQueue.main.addOperation {self.tableView.reloadData()}
}
task.resume()
}
What do I have to do in addition? Thanks for your support.
Few possibilities:
hope you are not storing the data locally(coredata/plist/any file), and loading it anywhere in your app.
Your web server might be caching the old data, please check once.
See if you have any hardcoded sample data which was used for testing purpose but not removed after actual implementation.
Hope this helps.

Push Notification From Specific Device To Specific Device Using Firebase iOS Swift

I would be very thankful for help with Push Notifications. My app has a chat where users can send text messages directly to each other. But without Push Notifications it doesn't make much sense. It is all set up on Firebase. How could I send Push Notifications from a specific device to a specific device? I did try Firebase Notifications and OneSignal due to a recommendation on Stackoverflow. But I am only able to trigger the notifications from Firebase or OneSignal directly and not from a specific device to a specific device.
Does someone has experience with this?
If your data is being stored in Firebease I would use Cloud Messaging for Push notifications as well. There are multiple ways to achieve this messaging notification functions, I will resume the one I think is the easiest.
Assuming you are using FCM and you have configured your app for it.
First of all you need to store the token of the users device, this would be stored as a string with each of the users info:
let token = Messaging.messaging().fcmToken
So in your Firebase DB, in each of your users object data you would have a key storing a string with the token, if you want the user to receive notifications in multiple devices, you must store the multiple tokens of the user using an array of strings, or even an array of objects and store the device type, etc. Thats your choice and your needs.
The ideal push notification environment is usually composed by a middle server that receive the request and manage and send the notification to the corresponding users, in this case you can skip this and send the not directly from your app, using FCM POST request service:
Example of how to build a FCM POST request to send a notification:
let url = URL(string: "https://fcm.googleapis.com/fcm/send")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("key=--HERE-GOES-YOUR-API-KEY--", forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
Here you put all not data, read the docs to understand all the keys and variables that you can send, yo would need to make a previous firebase request in which you get the device tokens.
var notData: [String: Any] = [
"to" : "HERE YOU PUT THE DEVICE TOKEN(s)",
"notification": [
title : "not title",
body : "not body",
icon : "not icon"
],
"data": [
//More notification data.
]
]
And then you send the post request, It will return an object if the notification was succeed and more data.
request.httpBody = notData.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}
task.resume()
I've been using this same solution for a mobile native app and a react web app and it works like a charm.
In Swift 4, everything that Karlo posted above works fine, except for the data(using:) function is not longer available.
That will need to be something like this:
request.httpBody = try? JSONSerialization.data(withJSONObject: notData, options: [])
Once I made that change everything compiled and worked beautifully.
You can target push notifications to a specific device easily. There's a tutorial given for that specific purpose here (https://firebase.google.com/docs/cloud-messaging/ios/first-message).
Now you can follow the below steps:
When the 'user' sends a message to 'other user', you can send the information of 'other user' to the server.
From server, you can send the push notification to the 'other user'

Swift (Xcode) REST API Connection using OAuth 1

Quick background, I am extremely new in this realm. I am aware that this type of question has been asked before and answered successfully. The issue that I am experiencing is caused by the inability to wrap my head around the process of establishing the connection. I have spent hours (into days) searching for the answer and I am still unsuccessful. This has become my "white whale" so to speak.
I am using Xcode 9 with Swift version 4. Many of the answer I come across use Objective-C and I cannot mix and match. So I would like to UNDERSTAND why I am unable to connect and the correct process to connect so I can write the code with the understanding of what I am doing. Lastly, I have signed up (and completed) a few paid Udemy courses to try and learn the process correctly. I have been able to connect to API sources but OAuth 1 is tripping me up. Any constructive help would be incredibly appreciated.
Background:
I am attempting to connect to the Fat Secret database. I would like to connect a search bar to the food.search functionality and also the food.get for another search bar.
Company- FatSecret
URL for API- platform.fatsecret.com/rest/server.api
URL to FatSecret documentation (I have gone through this MANY times)- http:// {space} platform.fatsecret. {space }com/api/Default. {space} aspx?screen=rapiauth
Parameters- Parameters {
oauth_consumer_key - consumer_key (I have a consumer key)
oauth_signature_method - "HMAC-SHA1"
oauth_timestamp - The date and time, expressed in the number of seconds since January 1, 1970 00:00:00 GMT. The timestamp value must be a positive integer and must be equal or greater than the timestamp used in previous requests
oauth_nonce - A randomly generated string for a request that can be combined with the timestamp to produce a unique value
oauth_version - Must be "1.0"
}
As I previously stated, the answer to my question is displayed above. I understand that part but I do not understand how to incorporate it into my code.
Past code-
let url = URL(string: "I am unable to post more than 2 links due to my rep so I put {space} in the above url to circumvent the error. I used the listed url from the parameters")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil { print("success")
} task.resume()
The above code is what I used to establish the connection. I receive "success" in the console so I expanded my parameters.
let url = URL(string: "I am unable to post more than 2 links due to my rep so I put {space} in the above url to circumvent the error. I used the listed url from the parameters")!
let task = URLSession.shared.dataTask(with: url) {
(data, response, error) in
if error != nil { print(error)
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
} catch {
} task.resume()
The above code produces nothing in the console. I believe (sorry for my ignorance) that the reason I am not getting a response is because I am not sending any authorization in the request, nor am I am sending in the correct encoding. I imagine that I can create the parameters by var/let statements and then call on those statements but I am not able to see the way to do that. I could likely also store all of my connection information in a different swift file or class and call on that when I need to access data. This base signature is required with every request. I have to imagine that best practice would be setting it up that way but again, I can't visualization the process. It becomes a trial and error process that results in incredible frustration.
Again, any help would be incredibly appreciated. I apologize for the length of this post. Thank you for taking the time to read this post.
This may be late but I have successfully managed to implement the FatSecret REST API and have created a small Xcode project that shows how I handled OAuth. The only calls that can be made are food.search and food.get. https://github.com/NicholasBellucci/FatSecretSwift

Resources