Adding HTTP response to cache - json

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.

Related

Unable to read JSON from rest API

Trying to read this JSON Data:
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
But my app keeps crashing and I'm struggling to understand why (still fairly new to swift). I think the data is stored in a dictionary and I'm just handling it incorrectly. Could someone please explain the correct way to decode this JSON and how I would show it on the view? I tried JSONSerialization in place of JSONDecoder but got the same results so not sure if that's the right direction.
Model:
struct Model: Codable{
var userId: Int? = nil
var id: Int? = nil
var title: String? = nil
var completed: Bool? = nil
enum CodingKeys: CodingKey{
case userId, id, title, completed
}
}
JSON Load Function:
func loadData(){
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
//create url request
var request = URLRequest(url: url)
//specify the method to use
request.httpMethod = "GET"
//set HTTP request Header, can set more than one.
request.setValue("application/json", forHTTPHeaderField: "Accept")
//send the request
let dataTask = URLSession.shared.dataTask(with: request){ data, response, error in
if let data = data{
if let users = try? JSONDecoder().decode(Model.self, from: data){
DispatchQueue.main.async {
self.dataInfo = users
}
}else{
print("Data Not Got")
}
}
if let response = response{
print("Response Got")
}
if let error = error{
print("\(error)")
}
}
dataTask.resume()
}
Swift UI View:
struct ContentView: View {
#State var dataInfo = Model()
var body: some View {
VStack{
Button("Go"){
loadData()
}
Text(dataInfo.title!)
}
By using ! after title, you're doing what is called a "force unwrap" -- telling the system that although the variable/property is declared as an Optional (in this case String?) that you're going to guarantee that it is not nil and there is in fact a value there. The problem is, before you've done the API call, that property is in fact nil, causing your program to crash.
Here's one way to change it (explanation follows):
struct Model: Codable{
var userId: Int
var id: Int
var title: String
var completed: Bool
}
struct ContentView: View {
#State var dataInfo : Model?
var body: some View {
VStack{
Button("Go"){
loadData()
}
if let dataInfo = dataInfo {
Text(dataInfo.title)
}
}
}
func loadData() {
// your previous code here
}
}
In this version, dataInfo is an Optional, and it gets set when the API call is made. Then, if let dataInfo = dataInfo does something called "optional binding," basically telling the system to only run the following code in the event that dataInfo isn't nil.
Finally, I've changed your Model to have non-Optional properties, since the API call you're using returns values for all of those fields. If you wanted to keep your previous model, you'd probably want to change my code to something like:
if let title = dataInfo?.title {
Text(title)
}
Check out the Swift Programming Language book for more information on Optionals and how to use them: https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html

Count JSON Requests from Api

I am using an api which allows me 600 requests per hour.
My app is not finished so i test sometimes the JSON Requests. I guess about 10 requests per hour.
But the provider says I did 600.
How is that possible?
Is there a way to count the requests from my side?
I am doing the requests with my widget (Widgetkit). Is it possible that it does a realtime download request for every second?
My actual JSON Request:
class NetworkManager: ObservableObject {
#Published var posts = [Post]()
#Published var clubName = "..."
#Published var teamId = "30"
#Published var gastMannschaft = "..."
let testStr = UserDefaults(suiteName: "gro")!.string(forKey: "test")
init() {
fetchData() // fetch data must be called at least once
}
func fetchData() {
let teamId = testStr ?? "47"
if let url = URL(string: "..." + teamId) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = gettingInfo {
do {
let results = try decoder.decode(Results.self, from: safeData)
DispatchQueue.main.async {
self.clubName = results.data[0].away_name
if #available(iOS 14.0, *) {
WidgetCenter.shared.reloadAllTimelines()
} else {
// Fallback on earlier versions
}
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
There might be a possibility that you are making some redundant requests which you might not be aware of.
To track your requests you could log them using for example Charles Proxy. This would reveal detailed information about requests you have made over the time you are performing them over the proxy.

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

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

Issue with JSON decoding in Swift (data missing)

I'm trying to query the NASA image API (latest docs here) using Swift 4. I set up and tested my request with JSONPlaceholder to make sure my network request and decoding was setup correctly. Everything was working fine, but when I switched the URL and corresponding JSON data structure, I get an error saying 'the data couldn't be read because it is missing.'
I've used Postman to verify that JSON is being returned, and to build the JSON data structure.
Is this a common error from decoding JSON or is it something with the network request? Or am I missing something with using the NASA API?
let NASAURL = URL(string: "https://images-api.nasa.gov/search?q=moon")
let session = URLSession(configuration: .default)
let task = session.dataTask(with: NASAURL!) { (rdata, response, error) in
NSLog("Data Description: " + (rdata!.debugDescription) + "\nResponse: " + response.debugDescription + "\nError Description: " + error.debugDescription)
guard rdata != nil else{
NSLog("No data")
return
}
guard error == nil else{
NSLog(response.debugDescription + "\n")
NSLog(error.debugDescription)
NSLog(error.debugDescription)
return
}
let decoder = JSONDecoder()
do{
NSLog(rdata.debugDescription)
let usr = try decoder.decode(Collect.self, from: rdata!) // Throws
NSLog(usr.href)
} catch {
NSLog("Error: " + error.localizedDescription)
}
}
task.resume()
// Collect is in its own class/file
struct Collect: Codable {
var href: String
//var items: [Items]
}
Below is the printout from the above log statements...
2017-09-29 19:50:24.135288-0500 OpenNASA[16993:10774203] Data Description: 67669 bytes
Response: Optional(<NSHTTPURLResponse: 0x60000003db00> { URL: https://images-api.nasa.gov/search?q=moon } { status code: 200, headers {
"Access-Control-Allow-Origin" = "*";
"Cache-Control" = "public, max-age=300, s-maxage=600";
"Content-Encoding" = gzip;
"Content-Length" = 9334;
"Content-Type" = "application/json; charset=UTF-8";
Date = "Sat, 30 Sep 2017 00:48:11 GMT";
Server = "nginx/1.4.6 (Ubuntu)";
"Strict-Transport-Security" = "max-age=31536000";
Vary = "Accept-Encoding";
"access-control-allow-headers" = "Origin,Content-Type,Accept,Authorization,X-Requested-With";
"access-control-allow-methods" = GET;
} })
Error Description: nil
2017-09-29 19:50:24.137324-0500 OpenNASA[16993:10774203] Optional(67669 bytes)
2017-09-29 19:56:01.843750-0500 OpenNASA[16993:10774203] Error: The data couldn’t be read because it is missing.
You Codable should be like below:
struct Collect: Codable {
var collection: Links
}
struct Links: Codable {
var links: [Href]
}
struct Href: Codable {
var href: String
}
You have to call like below:
let usr = try decoder.decode(Collect.self, from: rdata!) // Throws
let links = usr.collection.links
for link in links {
print(link.href)
}

Send POST request with JSON object and query param to REST webservice using alamofire

I'm trying to send POST request to REST webservice using alamofire
I'm passing json object as POST body, and i'm getting the response and everything works fine till now
Alamofire.request(.POST, path, parameters: createQueryParams(), encoding: .JSON)
.responseArray { (request, response, myWrapper, error) in
if let anError = error
{
completionHandler(nil, error)
println("Error in handling request or response!")
return
}
completionHandler(myWrapper, nil)
}
private class func createQueryParams() -> [String:AnyObject]{
var parameters:[String:AnyObject] = [String:AnyObject]()
parameters["lat"] = lLat!
parameters["lng"] = lLon!
if category != nil { // here is the problem
parameters["category"] = category!
}
return parameters
}
I have a category filter, if there is a value in category variable, i want to send it as QueryParam (should encoding be .URL? but how can i send json object ??)
this code does not work
if category != nil {
parameters["category"] = category!
}
How can i do this? Hope I can explain it clearly
Thanks in advance
You could solve it this way:
let mutableUrlRequest = NSMutableUrlRequest(URL: URL.URLByAppendingPathComponent(path)
mutableUrlRequest.HTTPMethod = .POST
let request = Alamofire.ParameterEncoding.URL.encode(mutableUrlRequest, parameters: createQueryParameters()).0
Alamofire.request(request)
However, I would advise you to look into the Router declaration of Alamofire and try this one. With it you can create dynamic requests and all of them are declared in a single file.
Edit:
Oh wait you can forget the previous edit the solution is quite simple and you also answered it by yourself. Yes you just have to change the encoding to .URL, you still are able to send json objects, because Alamofire itself decodes then the json object to a string for queryparams.
Alamofire.request(.POST, path, parameters:createQueryParams(), encoding: .URL).responseArray...
Edit 2:
Since the first edit did not work, try this:
let url = NSURL(string: path)!
let urlRequest = NSURLReqeust(URL: url)
request.HTTPMethod = "Post"
let encoding = Alamofire.ParameterEncoding.URL
let request = encoding.encode(urlRequest, createQueryParams())
Alamofire.request(request)