How to access vpn api url in device using iOS Swift? - json

I have consuming OData from api url using swiftyJSON. Here api url is connected with VPN.
And api url looks like http://192.xxx.xx.xx:8000/sap/opu/odata/sap/Z_SRV/PRListSetSet?$format=json
When i run in simulator, i can able get data from odata api url but while running in device, no data received from odata api url.
Since no vpn is connected to mobile device. how can i hard code my VPN programmactically to receive data in mobile?
here is how i'm getting data from OData api url:
typealias ServiceResponse = (JSON,Error?) -> Void
class PrListApiManager: NSObject {
static let sharedInstance = PrListApiManager()
let baseURL = apiUrlConstant.prListOdataUrl
func getPrList(onCompletion:#escaping (JSON) -> Void) {
let route = baseURL
makeHTTPGetRequest(path: route) { (json: JSON, error: Error?) in
onCompletion(json as JSON)
}
}
// MARK: perform a GET Request
private func makeHTTPGetRequest(path: String, onCompletion: #escaping ServiceResponse) {
let user = ApiloginConstant.user
let password = ApiloginConstant.password
let loginString = "\(user):\(password)"
guard let loginData = loginString.data(using: String.Encoding.utf8) else {
return
}
let base64LoginString = loginData.base64EncodedString()
print("base 64 login :\(base64LoginString)")
let headers = ["Authorization": "Basic \(base64LoginString)"]
// using URL and request getting a json
let request = URLRequest(url: NSURL(string: path)! as URL)
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = headers
let session = URLSession.init(configuration: config)
session.dataTask(with: request) { (data:Data?, response: URLResponse?, error:Error?) in
if let jsonData = data { // if data has a data and success
do {
let json: JSON = try JSON(data: jsonData)
onCompletion(json,nil)
print("json data:\(json)")
}catch {// error
onCompletion(JSON(),error)
}
} else { // if the data is nil
onCompletion(JSON(),error)
}
}.resume()
}

Connecting to a VPN is probably not your best bet. If your app is intended for public release, this can be a massive security concern and may overload your servers. The reason behind this is because I believe you cannot route only some of the traffic through a VPN. Either everything from the device goes through, or nothing does.
If you really need a way to reach your servers, consider using a proxy that only exposes that specific service out to the internet.
If you still want to go the VPN route, here's an answer that explains how to set it up: https://stackoverflow.com/a/39183930/4663856

Related

Adding HTTP response to cache

I have an object with some text data received from a remote server using a POST request. Each time the page is opened, the application makes a request to the remote server.
How do I do caching?
Here is my code without caching:
import Foundation
struct NewsFeed: Codable {
var status: String = ""
var totalResults: Int = 0
var posts: [PostItem]
}
struct PostItem: Codable {
var id: Int
var title: String
var link: String
var date: String
var category: String
var thumbnail: String
var excerpt: String
var content: String
}
class NewsDecoder: ObservableObject {
#Published var newsFeed: NewsFeed?
#Published var hasContent: Bool = false
init() {
self.getNews()
}
func getNews() {
let urlString = "http://example.com/feed/json_news/all.json"
let url = URL(string: urlString)
guard url != nil else {
return
}
let dataTask = URLSession.shared.dataTask(with: url!) { (data, response, error) in
DispatchQueue.main.async {
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
self.newsFeed = try decoder.decode(NewsFeed.self, from: data!)
self.hasContent = true
} catch {
print("Error: \(error)")
}
}
}
}
dataTask.resume()
}
}
Well, within the HTTP protocol layer, you do not explicitly cache the response. If the backend wishes the client to cache the payload from a POST response which you are later be able to fetch with a GET request, the backend would need to setup the response accordingly by specifying a Content-Location response header and an associated cache control header, according to RFC 7231 4.3.3 POST.
The payload returned has the location specified in the returned URL, and the URL has the same value as the POST's effective request URL. Please read here for more information about this specifically.
If that has been done by the backend and if you have configured your URLSession to use a URLCache on the client, the URLLoading mechanism would cache the payload under the URL returned in the "Content-Location" response header.
If you then issue a GET request with the given URL previously returned in the Content-Location response header, the caching mechanism would take effect.
However, there is no guarantee that POST caching is implemented on the client. When using URLCache it's worth to carefully investigate its behaviour and check if it is working as expected.

Send local notifications about new JSON data from closed app? | Swift

I have a tableView that is being populated using JSON data. At this time anytime there is an update to the JSON the user needs to pull down to refresh the tableView and the new data will load.
Is it possible to have the data refresh in the background when the app is exited or completely not even open on the user's phone and send the user a local notification saying there is new data?
How can this be done? I am more focused on local notifications.
Example of how I am currently getting my app to read JSON:
private func fetchJSON() {
guard let url = URL(string: "https://example.com/example/example.php"),
let value = name.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([structure].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
An example of how the api looks like from the address example.com/example/example.php is the following:
[{"person":"Jackson","number":"1928192",
"position":"driver"},{"person":"Jeff","number":"293829","position":"driver"}]
Step 1 :- Store json in UserDefault or in CoreData
if you are using UserDefault i personally perfer PropertyListEncoder&Decoder.
Step 2 :- Enable Background Capability
Step 3 :- implement local notification code in you app.

HTTP Request and JSON parsing in Swift

So, I'm trying to make a very simple watchOS app in XCode. It consists of a button, two labels and a separator between the two labels. It is a digital assistant app, and needs to interface with Dialogflow (https://dialogflow.com).
The button calls the presentTextInputController function, and I want to use that result as a query to my Dialogflow agent.
I need to make an HTTP request, which in JS would look more like this:
{
url:"https://api.api.ai/v1/query",
method:"post",
body:JSON.stringify({query:"userInput",lang:"en-US",sessionID:"yaydevdiner"}),
headers:{
contentType:"application/json; charset=utf-8",
Authorization:"Bearer <auth_token>"
}
}
The response is a JSON object, and I need to access the jsonObject["result"]["speech"] value as a String to use Label.setText()
Everything I've tried has given errors about type Any and other such things. I also haven't been able to do much debugging since the print output isn't showing up in XCode.
I must mention that I'm an extreme beginner to Swift, and I am not good at handling their types and casting and unpacking and things like that.
Could someone show me how I might handle this request and the subsequent processing of the JSON?
Here is my current code:
//HTTP Request
let parameters = [
"query":name![0] as? String,
"lang":"en-US",
"sessionID":"yaydevdiner"
];
//create the url with URL
let url = URL(string: "https://api.api.ai/v1/query")! //change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer f786fef55008491fb8422cea2be85eb1", forHTTPHeaderField: "Authorization")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject (with: data, options: .mutableContainers) as? [String:Any] {
self.Response.setText(json["result"]["string"]);
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
Response is a text label.
This code gives me an error saying I should have a question mark between
json["result"] and ["speech"]. When I do this, it gives me another error saying "Type Any has no subscript members".
Ok, I figured it out.
Because XCode automatically makes an iOS app with the watchOS app, I decided to try debugging in the iOS app until I got the HTTP request and JSON parsing right.
Inside the JSONSerialization if statement, I had to add another if statement:
if let result = responseJSON["result"] as? [String:Any]{
self.Response.setText(result!["speech"] as? String ?? "Network error Occurred")
}
Thanks for the help from vadian!

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 "

retrieved values position(order) changed every time in swift3

I retrieved values from Json, i print retrieved values order changed(position changed) every time. please check my below code once:
override func viewDidLoad() {
super.viewDidLoad()
var myIds = [104016, 104010, 104014, 104018, 104000, 104038, 104015, 104011, 104015, 104010, 104010, 104010, 104003, 104003, 104011]
for arr in 0 ..< myIds.count-1 {
let url = URL(string: "http://.........\(myIds[arr])")
print("myIds[\(arr)]:\(myIds[arr])")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print ("ERROR")
}
else
{
if let content = data
{
do
{
//Array
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
var i = myJson["Job_title_name"]!
var ist = ""
// print("i=\(i)")
ist = String(describing: i!)
print("myIds[\(arr)]=\(ist)")
}
catch
{
}
}
}
}
task.resume()
}
}
**output:**
myIds[0]:104016
myIds[1]:104010
myIds[2]:104014
myIds[3]:104018
myIds[4]:104000
myIds[5]:104038
myIds[6]:104015
myIds[7]:104011
myIds[8]:104015
myIds[9]:104010
myIds[10]:104010
myIds[11]:104010
myIds[12]:104003
myIds[13]:104003
myIds[0]=WebLogic Admin
myIds[3]=OracleDevloper
myIds[2]=Node Js Developer
myIds[1]=Angular Developer
myIds[6]=HTML&CSS Developer
myIds[5]=Senior Manager
myIds[7]=CRM Developer
myIds[4]=Windows Manager
myIds[8]=HTML&CSS Developer
myIds[9]=Angular Developer
myIds[11]=Angular Developer
myIds[10]=Angular Developer
myIds[12]=java developer
myIds[13]=java developer
This output is changed every time, when stop and run the app. but i'm unable to find the problem. please check my code once.
Required output:
myIds[0]:104016
myIds[1]:104010
myIds[2]:104014
myIds[3]:104018
myIds[4]:104000
myIds[5]:104038
myIds[6]:104015
myIds[7]:104011
myIds[8]:104015
myIds[9]:104010
myIds[10]:104010
myIds[11]:104010
myIds[12]:104003
myIds[13]:104003
myIds[0]=WebLogic Admin
myIds[1]=Angular Developer
myIds[2]=Node Js Developer
myIds[3]=OracleDevloper
myIds[4]=Windows Manager
myIds[5]=Senior Manager
myIds[6]=HTML&CSS Developer
myIds[7]=CRM Developer
myIds[8]=HTML&CSS Developer
myIds[9]=Angular Developer
myIds[10]=Angular Developer
myIds[11]=Angular Developer
myIds[12]=java developer
myIds[13]=java developer
i want the above output. In the above code whats my mistake.
URLSession.shared.dataTask() usually call API in parallel execution so no matter if calling request order will same as response order.
If you want to call request and get response in same order then you should create NSOperation queue and make dependency on it.
Network requests run asynchronously, so this is the expected behaviour. The requests don't finish in the same order as they were started.
If you need your requests to run sequentially, you can run them using DispatchGroups, but this will lead to slower performance due to the fact that your asynchronous requests are executed sequentially instead of in parallel. A better solution is to store the results in a data structure, where you can identify the objects based on a unique identifier other than their index. For your current problem, the best solution is to store the results in a dictionary, where the key is the id and the value is the value from the network request.
Concurrent solution using a Dictionary to store the output values:
override func viewDidLoad() {
super.viewDidLoad()
var myIds = [104016, 104010, 104014, 104018, 104000, 104038, 104015, 104011, 104015, 104010, 104010, 104010, 104003, 104003, 104011]
var jobTitles = [Int:String]()
let group = DispatchGroup()
for arr in 0 ..< myIds.count-1 {
let url = URL(string: "http://.........\(myIds[arr])")
print("myIds[\(arr)]:\(myIds[arr])")
group.enter()
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil{
print ("ERROR")
} else {
if let content = data{
do {
guard let myJson = try JSONSerialization.jsonObject(with: content, options: []) as? [String:Any] else {return}
guard let jobTitle = myJson["Job_title_name"] as? String else {return}
jobTitles[myIds[arr]] = jobTitle
group.leave()
} catch {
}
}
}
}
task.resume()
}
}
}
group.notify(queue: DispatchQueue.main, execute: {
print(jobTitles)
})
Some general advice: don't force unwrap values from a server response and don't use String(describing:) to create a String. Use optional binding or default values to safely unwrap the optionals and either cast your values to String if they are String values or just use String(value) for values from which Strings can be directly initialized (such as Int).