NSURLRequestCachePolicy.UserProtocolCachPolicy requirements - swift

I am using the following code for caching, the response received form the server has the following headers. Is there any header that needs to be set from the request side, for the caching to work for 10 seconds of age.
Connection Received Resopnse Headers= [Date: Sat, 12 Sep 2015 22:51:16
GMT, Transfer-Encoding: Identity, Server: Apache-Coyote/1.1,
Content-Type: application/json;charset=UTF-8, Expires: Sat, 12 Sep
2015 22:51:26 GMT, Cache-Control: max-age=10, must-revalidate]
The mighty code which is not caching.
import UIKit
class HTTPJSONDonwload: NSObject , NSURLConnectionDataDelegate , NSURLConnectionDelegate {
static let httpjsonDownloader:HTTPJSONDonwload = HTTPJSONDonwload()
func startDownload(){
let serverRequest = getServerURL()
NSURLConnection(request: serverRequest, delegate: self, startImmediately: true)
}
func getServerURL() -> NSMutableURLRequest{
let request:NSMutableURLRequest = NSMutableURLRequest(URL:NSURL(string:"http://citiesfav-jcitiesj.rhcloud.com/Cache/getAllCities")! )
request.cachePolicy = NSURLRequestCachePolicy.UseProtocolCachePolicy
request.HTTPMethod = "POST"
return request
}
func connection(connection: NSURLConnection, didReceiveData data: NSData) {
print("Connection Data= \(NSString(data: data, encoding: NSUTF8StringEncoding))")
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
print("Connection Received Resopnse Headers= \((response as! NSHTTPURLResponse).allHeaderFields)")
}
func connection(connection: NSURLConnection, willCacheResponse cachedResponse: NSCachedURLResponse) -> NSCachedURLResponse? {
print("Connection will cache Response")
return cachedResponse
}
}
After removing must-revalidate from the header it was still fetching the request.
Connection Received Resopnse Headers= [Cache-Control: max-age=10,
Transfer-Encoding: Identity, Date: Sun, 13 Sep 2015 18:35:43 GMT,
Content-Type: application/json;charset=UTF-8, Server:
Apache-Coyote/1.1, Expires: Sun, 13 Sep 2015 18:35:53 GMT]
Later findings show the POST request does get cached, but does not work like GET, where max-age is considered.
func startDownload(){
let serverRequest = getServerURL()
let cache = NSURLCache.sharedURLCache()
let response = cache.cachedResponseForRequest(serverRequest)
if response != nil {
serverRequest.cachePolicy = NSURLRequestCachePolicy.ReturnCacheDataDontLoad
}
NSURLConnection(request: serverRequest, delegate: self, startImmediately: true)
}

tl;dr
You need to use GET instead of POST.
Lengthy Explanation
The issue is that you're request is a POST.
func getServerURL() -> NSMutableURLRequest{
...
request.HTTPMethod = "POST"
...
}
In general, POST requests are used to create (or sometimes also to update) a resource on the server. Reusing the cached response for a creation or update request doesn't make much sense because you have to send the request to the server anyway (otherwise nothing is going to be created or updated). It seems that iOS automatically circumvents the cache on POST requests.
In your particular case, however, you don't really need the POST because you're merely reading data from the server. That means you should use a GET request instead.
func getServerURL() -> NSMutableURLRequest{
...
request.HTTPMethod = "GET"
...
}
I verified that the iOS system actually reuses the cache with the following snippet.
let d = HTTPJSONDonwload()
// Initial request. Can not reuse cache.
d.startDownload()
// Subsequent request after 5 seconds. Should be able to reuse the cache.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(5 * NSEC_PER_SEC)), dispatch_get_main_queue()) {
d.startDownload()
}
// Subsequent request after 11 seconds. Cannot reuse the cache because
// the expiration timeout is 10 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(11 * NSEC_PER_SEC)), dispatch_get_main_queue()) {
d.startDownload()
}
When I run this in the simulator and monitor the network calls with Charles Proxy, I indeed only see two events:
The first call is the initial request
and the second call is the third request which was issued after a delay of 11 seconds.
Note that the second request, which was issued after 5 seconds, does not appear which means that the response was retrieved from the cache. The delegate methods of NSURLConnection will, however, still be called just as if the response came from the network. With the logging output in your code you'll, therefore, see all three requests on the console.
Connection Received Resopnse Headers= [Server: Apache-Coyote/1.1, Content-Type: application/json;charset=UTF-8, Keep-Alive: timeout=15, max=100, Proxy-Connection: Keep-alive, Date: Mon, 14 Sep 2015 06:28:05 GMT, Content-Encoding: gzip, Content-Length: 36, Cache-Control: max-age=10, Vary: Accept-Encoding]
Connection Data= Optional({"1":"New York"})
Connection will cache Response
Connection Received Resopnse Headers= [Server: Apache-Coyote/1.1, Content-Type: application/json;charset=UTF-8, Keep-Alive: timeout=15, max=100, Proxy-Connection: Keep-alive, Date: Mon, 14 Sep 2015 06:28:05 GMT, Content-Encoding: gzip, Content-Length: 36, Cache-Control: max-age=10, Vary: Accept-Encoding]
Connection Data= Optional({"1":"New York"})
Connection Received Resopnse Headers= [Server: Apache-Coyote/1.1, Content-Type: application/json;charset=UTF-8, Keep-Alive: timeout=15, max=99, Proxy-Connection: Keep-alive, Date: Mon, 14 Sep 2015 06:28:16 GMT, Content-Encoding: gzip, Content-Length: 36, Cache-Control: max-age=10, Vary: Accept-Encoding]
Connection Data= Optional({"1":"New York"})
Connection will cache Response
Note that there is no Connection will cache Response after the second request because the response was retrieved from the cache and there is no point in caching it again.

Related

How to check if the API return nothing with Alamofire?

I am using Alamofire to make an API request to insert user data to my database, if the user email is exist, the response will return
[Response]:
[Status Code]: 200
[Headers]:
Connection: Keep-Alive
Content-Length: 27
Content-Type: text/html; charset=UTF-8
Date: Thu, 22 Apr 2021 06:39:05 GMT
Keep-Alive: timeout=5, max=99
Server: Apache/2.4.25 (Debian)
[Body]:
Email address already exist
[Network Duration]: 0.013917088508605957s
[Serialization Duration]: 0.0s
[Result]: success(Optional(27 bytes))
and if user email is not exist, it will insert data to the database, and return nothing like this
(literally nothing, there's no character or whatsoever, just a blank page if I open the api in the web browser)
And here is the response
[Response]:
[Status Code]: 200
[Headers]:
Connection: Keep-Alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Thu, 22 Apr 2021 06:54:43 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.25 (Debian)
[Body]: None
[Network Duration]: 0.8882529735565186s
[Serialization Duration]: 0.0s
[Result]: success(nil)
Now I want to make a validation to check if user email is exist or not by checking the response body. if the response body is Email address already exist, it will display an error alert. and if response body is None, it will display a successful alert. My question is, how do I check if the response body is None? here is validation code
let parameters = ["USERNAME": "\(USERNAME)", "EMAIL": "\(EMAIL)", "FULL_NAME": "\(FULL_NAME)", "NO_TELEPON": "\(NO_TELEPON)", "PASSWORD": "\(PASSWORD)", "USER_TOKEN": "\(USER_TOKEN)"]
AF.request("http://172.16.5.56:8081/User/InsertNewUser", parameters: parameters).response{ response in
debugPrint(response)
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8){
print(utf8Text)
postResponse = utf8Text
if postResponse == "Email address already exist" {
self.haptics.notificationOccurred(.error)
self.activeAlert = .first
self.showAlert = true
}else if postResponse == "None"{ // This is not working
self.haptics.notificationOccurred(.success)
self.activeAlert = .second
self.showAlert = true
}
}
}
The response (as you shared) in fail-type case is:
[Response]:
[Status Code]: 200
[Headers]:
Connection: Keep-Alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Thu, 22 Apr 2021 06:54:43 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.25 (Debian)
[Body]: None
[Network Duration]: 0.8882529735565186s
[Serialization Duration]: 0.0s
[Result]: success(nil)
Shows [Result]: success(nil) which means the response.data is literally nil.
I propose the following solution:
Alamofire
.request("http://172.16.5.56:8081/User/InsertNewUser")
.response { (response) in
if let data = response.data,
let string = String(data: data, encoding: .utf8) {
if string == "Email address already exist" {
self.haptics.notificationOccurred(.error)
self.activeAlert = .first
self.showAlert = true
}
//else if ... {
//handle other cases
//}
} else {
self.haptics.notificationOccurred(.success)
self.activeAlert = .second
self.showAlert = true
}
}
NOTE: I would advise against direct string comparisons unless unavoidable.
Atleast agree on a common response template and ensure the responses are not prone to typos or silly mistakes.
#staticVoidMan has already provided a solution for your current backend setup. However, if you update your backend to return more appropriate responses, Alamofire can do a lot more for you automatically.
In short, you need to make your backend behave in a standard fashion following JSON API best practices. These include:
In the failure case, return a failure status code (probably 403), and a JSON payload describing the error.
In the success case, you could return a 201, which indicates the new user has been created, in which case you should include the new user object JSON in the response, or a 204, which is the proper status code for an empty response.
In these cases you can define proper Decodable payloads which Alamofire can parse for you, as well as a proper value for the empty responses, and Alamofire will ensure it's returned in the 204 case.

Wrong Content-Type in response in IErrorHandler

I would like to send response to my service in JSON format. I catch my custom errors in my custom behavior:
void IErrorHandler.ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
XDocument errorMsg = XDocument.Parse("<errorMessage>" + error.Message + "</errorMessage>");
var jsonWriter = new JsonErrorBodyWriter(errorMsg);
fault = Message.CreateMessage(version, null, jsonWriter);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
HttpResponseMessageProperty prop = new HttpResponseMessageProperty();
prop.StatusCode = HttpStatusCode.Unauthorized;
prop.Headers.Add("Content-Type", "application/json; charset=utf-8");
prop.Headers[HttpRequestHeader.ContentType] = "application/json; charset=utf-8";
--Tried different ways to achieve this
fault.Properties.Add(HttpResponseMessageProperty.Name, prop);
}
But I get wrong content-type in response. And also I couldn't manage to write any custom header like :
prop.Headers.Add("Test", "Value");
Reponse:
HTTP/1.1 401 Unauthorized
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Wed, 30 Sep 2020 08:41:15 GMT
Content-Length: 37
{"description":"Autorization Failed"}
What is wrong in my code?

How to get last-modified from http header with dartio HttpClient

Hi there I want get Last_modified from http header with dart.io HttpClient()
code sample is:
var client = new HttpClient();
HttpClientRequest req = await client.getUrl(Uri.parse("sayagh.asnafhormozgan.ir/wp-content/tables/essentials.csv"));
var a = req.headers.value("lastModifiedHeader");
but a returns null;
how I can get Last modified?
but when I get it with curl:
curl -v "sayagh.asnafhormozgan.ir/wp-content/tables/drawer.csv"
* Trying 51.89.173.235:80...
* TCP_NODELAY set
* Connected to sayagh.asnafhormozgan.ir (51.89.173.235) port 80 (#0)
> GET /wp-content/tables/drawer.csv HTTP/1.1
> Host: sayagh.asnafhormozgan.ir
> User-Agent: curl/7.65.3
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx
< Date: Thu, 19 Sep 2019 10:05:37 GMT
< Content-Type: text/csv
< Content-Length: 599
< Connection: keep-alive
< Last-Modified: Thu, 19 Sep 2019 09:38:30 GMT
< Accept-Ranges: bytes
< Cache-Control: max-age=0
< Expires: Thu, 19 Sep 2019 10:05:37 GMT
<
You'll need to wait for the second future to get the response, i.e.:
A getUrl request is a two-step process, triggered by two Futures. When
the first future completes with a HttpClientRequest, the underlying
network connection has been established, but no data has been sent. In
the callback function for the first future, the HTTP headers and body
can be set on the request. Either the first write to the request
object or a call to close sends the request to the server.
See https://api.dartlang.org/stable/2.5.0/dart-io/HttpClient-class.html
Also it should be 'last-modified' and not 'lastModifiedHeader' (or even better use the static const variable HttpHeaders.lastModifiedHeader), e.g.:
HttpClient client = HttpClient();
HttpClientRequest req = await client.getUrl(Uri.parse(
'http://sayagh.asnafhormozgan.ir/wp-content/tables/essentials.csv'));
HttpClientResponse response = await req.close();
print(response.headers.value(HttpHeaders.lastModifiedHeader));

HTTP OPTIONS in Xamarin Forms

Getting "204 Status Code" as No Content
Here is a simple example of OPTIONS request:
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Options, new Uri("http://myapi.com"));
var response = await httpClient.SendAsync(request);
Just wondering why should you need to make one? OPTIONS is used to identify allowed request methods:
To find out which request methods a server supports, one can use curl
and issue an OPTIONS request:
curl -X OPTIONS http://example.org -i
The response then contains an Allow header with the allowed methods:
HTTP/1.1 200 OK
Allow: OPTIONS, GET, HEAD, POST
Cache-Control: max-age=604800
Date: Thu, 13 Oct 2016 11:45:00 GMT
Expires: Thu, 20 Oct 2016 11:45:00 GMT
Server: EOS (lax004/2813)
x-ec-custom-error: 1
Content-Length: 0
Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS

can't get web page source from url in Swift

I'm currently using SwiftHTTP for getting source of url address. I am using 'get method' for getting source code of url address which is
do {
let opt = try HTTP.GET(self.my_url_address!)
opt.start { response in
if let err = response.error {
print("error: \(err.localizedDescription)")
return
}
print(response.description)
}
} catch let error {
print("got an error creating the request: \(error)")
}
after this code run I got this output in Xcode output screen
URL: http://myweburl.com/detay/swat-under-siege.html
Status Code: 200
Headers: Content-Type: text/html
Connection: keep-alive
CF-RAY: 38391215a60e2726-FRA
Set-Cookie: ASPSESSIONIDSABBBSDT=HPKKPJGCDLKMDMILNGHPCAGD; path=/
Date: Mon, 24 Jul 2017 18:51:24 GMT
Vary: Accept-Encoding
X-Powered-By: ASP.NET Transfer-Encoding: Identity
Server: cloudflare-nginx
Content-Encoding: gzip
Cache-Control: private
The status code is 200 but the output is not the source code of url. How can I fix this?
Response is correct. I've tried requesting the website (the real one) and it works:
print(response.data.base64EncodedString())
If you decode the BASE64 data, it will render valid HTML code.
The issue seems related to encoding. After checking the website's head tag, it states that the charset is windows-1254
String(data: response.data, encoding: .windowsCP1254) // works. latin1, etc.
Your issue is similar to SWIFT: NSURLSession convert data to String