NSURLSessionUploadTask response content data is null - ios

Here's where I am so far with NSURLSessionUploadTask:
iOS application starts an NSURLSessionUploadTask using POST
server receives HTTP POST request
server reads content of the request so data is uploaded
server sends HTTP response to iOS consisting of the following:
HTTP/1.1 200 OK
Server: BaseHTTP/0.3 Python/2.7.10
Date: Fri, 30 Oct 2015 16:15:21 GMT
Content-Type: text/html
Content-Length: 96
<html><head><title>POST RESPONSE</title></head><body><p>The file was uploaded.</p></body></html>
iOS application receives response and NSLogs the response via NSURLSessionTaskDelegate -> URLSession:task:didCompleteWithError:
<NSHTTPURLResponse: 0x15d95110> { URL: http://10.157.239.129:42000/ } { status code: 200, headers {
"Content-Length" = 96;
"Content-Type" = "text/html";
Date = "Fri, 30 Oct 2015 16:15:21 GMT";
Server = "BaseHTTP/0.3 Python/2.7.10";
} }
however, when I try to NSLog the response content when the method NSURLSessionTaskDelegate: -> URLSession:dataTask:didReceiveData:
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
// Called when data task has received some data (not applicable for straight upload?)
if (data != NULL)
{
NSLog(#"%s: %#", __PRETTY_FUNCTION__,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
else
{
NSLog(#"%s: but no actual data received.", __PRETTY_FUNCTION__);
}
// If we are not in the "active" or foreground then log some background information to the console
if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive)
{
[BackgroundTimeRemainingUtility NSLog];
}
}
is called I get this for output:
2015-10-30 12:15:22.648 NSURLSessionUploadTaskExample[215:7286] -[ViewController URLSession:dataTask:didReceiveData:]: (null)
what is odd about this is that the response should not have triggered the if statement block if the data is null!
I also have evidence that the response data WAS sent to the iOS application via Wireshark. Here's a packet capture of the HTTP response from the server:
Can anyone tell me why iOS appears to be losing the content in the HTTP response?

Because I was able to trace the HTTP 200 response from the server to the iOS application, I suspected the iOS application had a problem. I set a breakpoint at the line:
NSLog(#"%s: %#", __PRETTY_FUNCTION__,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
and examined the data object with the debugger:
Each of the 96 bytes in the response content was getting to NSURLSessionTaskDelegate: -> URLSession:dataTask:didReceiveData: So the NSString decoding in the NSLog was at fault. Now things were making sense. I was sending a 96 byte ASCII response, not a 96 byte UTF8 response!
I replaced the NSString decoder with the following:
NSLog(#"%s: %#", __PRETTY_FUNCTION__,[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding: NSASCIIStringEncoding]);
Problem solved with the following output:
2015-11-02 10:04:47.086 NSURLSessionUploadTaskExample[561:363449] -[ViewController URLSession:dataTask:didReceiveData:]:<html><head><title>POST RESPONSE</title></head><body><p>The file was uploaded.</p></body></html>
I've also put a very basic NSURLSessionUploadTask example application and webserver on github here.

Related

How to use NSURLSession to determine if resource has changed?

I'm using NSURLSession to request a JSON resource from an HTTP server. The server uses Cache-Control to limit the time the resource is cached on clients.
This works great, but I'd also like to cache a deserialized JSON object in memory as it is accessed quite often, while continuing to leverage the HTTP caching mechanisms built into NSURLSession.
I'm thinking I can save a few HTTP response headers: Content-MD5, Etag, and Last-Modified along with the deserialized JSON object (I'm using those 3 fields since I've noticed not all HTTP servers return Content-MD5, otherwise that'd be sufficient by itself). The next time I receive a response for the JSON object, if those 3 fields are the same then I can reuse the previously deserialized JSON object.
Is this a robust way to determine the deserizlied JSON is still valid. If not, how do I determine if the deserialized object is up to date?
I created a HTTPEntityFingerprint structure which stores some of the entity headers: Content-MD5, Etag, and Last-Modified.
import Foundation
struct HTTPEntityFingerprint {
let contentMD5 : String?
let etag : String?
let lastModified : String?
}
extension HTTPEntityFingerprint {
init?(response : NSURLResponse) {
if let httpResponse = response as? NSHTTPURLResponse {
let h = httpResponse.allHeaderFields
contentMD5 = h["Content-MD5"] as? String
etag = h["Etag"] as? String
lastModified = h["Last-Modified"] as? String
if contentMD5 == nil && etag == nil && lastModified == nil {
return nil
}
} else {
return nil
}
}
static func match(first : HTTPEntityFingerprint?, second : HTTPEntityFingerprint?) -> Bool {
if let a = first, b = second {
if let md5A = a.contentMD5, md5B = b.contentMD5 {
return md5A == md5B
}
if let etagA = a.etag, etagB = b.etag {
return etagA == etagB
}
if let lastA = a.lastModified, lastB = b.lastModified {
return lastA == lastB
}
}
return false
}
}
When I get an NSHTTPURLResponse from an NSURLSession, I create an HTTPEntityFingerprint from it and compare it against a previously stored fingerprint using HTTPEntityFingerprint.match. If the fingerprints match, then the HTTP resource hasn't changed and thus I do not need to deserialized the JSON response again; however, if the fingerprints do not match, then I deserialize the JSON response and save the new fingerprint.
This mechanism only works if your server returns at least one of the 3 entity headers: Content-MD5, Etag, or Last-Modified.
More Details on NSURLSession and NSURLCache Behavior
The caching provided by NSURLSession via NSURLCache is transparent, meaning when you request a previously cached resource NSURLSession will call the completion handlers/delegates as if a 200 response occurred.
If the cached response has expired then NSURLSession will send a new request to the origin server, but will include the If-Modified-Since and If-None-Match headers using the Last-Modified and Etag entity headers in the cached (though expired) result; this behavior is built in, you don't have to do anything besides enable caching. If the origin server returns a 304 (Not Modified), then NSURLSession will transform this to a 200 response the application (making it look like you fetched a new copy of the resource, even though it was still served from the cache).
This could be done with simple HTTP standard response.
Assume previous response is something like below:
{ status code: 200, headers {
"Accept-Ranges" = bytes;
Connection = "Keep-Alive";
"Content-Length" = 47616;
Date = "Thu, 23 Jul 2015 10:47:56 GMT";
"Keep-Alive" = "timeout=5, max=100";
"Last-Modified" = "Tue, 07 Jul 2015 11:28:46 GMT";
Server = Apache;
} }
Now use below to tell server not to send date if it is not modified since.
NSURLSession is a configurable container, you would probably need to use http option "IF-Modified-Since"
Use below configuration kind before downloading the resource,
NSURLSessionConfiguration *backgroundConfigurationObject = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"myBackgroundSessionIdentifier"];
[backgroundConfigurationObject setHTTPAdditionalHeaders:
#{#"If-Modified-Since": #"Tue, 07 Jul 2015 11:28:46 GMT"}];
if resource for example doesn't change from above set date then below delegate will be called
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) downloadTask.response;
if([httpResponse statusCode] == 304)
//resource is not modified since last download date
}
Check the downloadTask.response status code is 304 .. then resource is not modified and the resource is not downloaded.
Note save the previous success full download date in some NSUserDefaults to set it in if-modifed-since

NSURLConnection, NSURLAuthenticationChallenge failure response

When trying to log in to my app with iOS7 I'm getting a failure response with some description. This is NSLog of NSURLAuthenticationChallenge failureResponse:
<NSHTTPURLResponse: 0xa383570> { URL: <URL> } { status code: 401,headers {
"Content-Length" = 1385;"Content-Type" = "text/html;charset=utf-8";
Date = "Fri, 13 Jun 2014 12:14:24 GMT";
Server = "Apache-Coyote/1.1";
"Www-Authenticate" = "Basic realm=\"booo\"";
"x-errorCode" = 02;
"x-errorDescription" = "com.idealination.core.server.client.http.ServerException:
Request failed: <URL> Status: CLIENT_ERROR_UNAUTHORIZED
Error: #Unauthorized :: Invalid userid or password.";
} }
and I need that last line to know what error do I get. But when I use iOS6 and iPhone 3gs I get only:
<NSHTTPURLResponse: 0x1c507ae0>
What should I do to get a response like using iOS7? And why I'm getting a different response?
You should not be looking for the last line, you should be looking for the HTTP Status Code, in this case 401.
if(urlResponse.statusCode == 401) { }
If you need to convert that into what the status code means as a string use
NSString *status = [NSHTTPURLResponse localizedStringForStatusCode:urlResponse.statusCode];

iOS HTTP request - getting the error response message

This is my bit of code doing a GET request to a REST api.
Im not sure how to get back the message if I get an error:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSURL *URL = [NSURL URLWithString:urlString];
[request setURL:URL];
[request setHTTPMethod:#"GET"];
NSError *err = nil;
NSHTTPURLResponse *res = nil;
NSData *retData = [NSURLConnection sendSynchronousRequest:request returningResponse:&res error:&err];
if (err) // This part is never called.
{
NSLog(#"Error: %#", err);
}
else
{
if (res.statusCode != 200)
{
// show the user the status message
NSLog(#"Error: %#", res); // This part is called
}
else
{
}
}
I want to get the error message if it was not successful. But the if (err) block is never called. err is still null, although the statuscode is 400.
And if successful I will get back a json response.
In the code above I get back a statusCode of 400
The error block is not called because the error object is created only if a system level error occurs. This does not happen because the request is sent correctly and the server sends a response. If you are in control of the server, you should probably make it return status code 200 and include an app level status code in the response, that would tell your app that the entered credentials are incorrect.
Edit:
To get status message you can use
+ (NSString *)localizedStringForStatusCode:(NSInteger)statusCode
This is a class method of the NSHTTPURLResponse class.
if (res.statusCode != 200)
{
// show the user the status message
NSLog(#"Error: %#", [NSHTTPURLResponse localizedStringForStatusCode: res.statusCode]); // This part is called
}
Take a look at the NSError class reference:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/Reference/Reference.html
You can try to log the error message from the localizedDescription.
you are receiving this status code because- The Web server (running the Web site) thinks that the data stream sent by the client (e.g. your Web browser or our CheckUpDown robot) was 'malformed' i.e. did not respect the HTTP protocol completely. So the Web server was unable to understand the request and process it
to log above problem in respect to ios visit this link
If you read the documentation of sendSynchronousRequest...
error
Out parameter used if an error occurs while processing the request. May be NULL.
this mean that erro will be a valid NSError object in case there is a problem to resolve the request, like a malformed URL.
If the request can be resolved error will be NULL and according with HTTP protocol and depending to the server that you are trying to connect, the NSHTTPURLResponse object will contain all the information about the request.
In general is an error think that every status code different than 200 is an error, for example for a REST based API 204 mean empty data, and in this case the request is finished successfully but the requested resource is just empty data, and this is not an error.
So about your question, is absolutely fine that error is NULL most of the time, if is not mean that there is an issue before reach the target server, in general you have to consider both, error and according to the server that you are trying to talk the status code maps, in most of cases the REST pattern

Is it possible to retrieve the description along with the statuscode when JSON parsing in iOS

In my iPhone application I am making some web service calls using JSON. In the return data i am getting a status code (200, 404 etc), along with that they are sending a message.
Here is the result what I got in Rest client:
Status Code: 401 Invalid Access token
Cache-Control: private
Connection: Keep-Alive, Proxy-Support
Content-Length: 0
Date: Wed, 13 Mar 2013 06:37:26 GMT
Proxy-Support: Session-Based-Authentication
Server: Microsoft-IIS/7.5
Via: 1.1 WIN-JO3AMACI965
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
In this, I want to get the message "Invalid Access token" along with the status code 401. Is it possible?
Thanks in advance.
Assuming you're using NSURLConnection to send the request, you can get the status code from the response:
NSError* error = nil;
NSHTTPURLResponse* response = nil;
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
int statusCode = response.statusCode;
NSString* statusText = [NSHTTPURLResponse localizedStringForStatusCode:statusCode];
If you're not using a synchronous request, you can use the delegate methods as described here.
Hope that helped.

Post on website works on simulator but not on device?

I want to use the web service in my app and upload post on the website, at first I used ASIFormRequest API, it worked on simulator but did not work on device, I asked a question here and someone told me to change the API and use the RestKit (Here is the link for my previous question)
Right now I'm using RestKit API and I got the same problem, I cannot post on the website by device, but It works when I use simulator. Although it doesn't allow me to post but I can login both by the simulator and the device.
Here is my code for POST:
- (IBAction)addLinkPressed:(UIButton *)sender {
[RKClient clientWithBaseURLString:#"http://MyWebsite.com"];
NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
self.linkField.text, #"url",
self.linkTitleField.text, #"title",
self.linkSummaryField.text, #"summary",
nil];
[[RKClient sharedClient] post:#"/send_link.php" params:params delegate:self];
}
- (void)objectLoader:(RKObjectLoader*)objectLoader didFailWithError:(NSError*)error {
NSRange range = [[error localizedDescription] rangeOfString:#"-1012"];
if (range.length > 0){
//Do whatever here to handle authentication failures
}
RKLogError(#"Hit error: %#", error);
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response
{
if ([request isGET]) {
// Handling GET /foo.xml
if ([response isOK]) {
// Success! Let's take a look at the data
NSLog(#"Retrieved XML: %#", [response bodyAsString]);
}
} else if ([request isPOST]) {
// Handling POST /other.json
if ([response isJSON]) {
NSLog(#"Got a JSON response back from our POST!");
}
} else if ([request isDELETE]) {
// Handling DELETE /missing_resource.txt
if ([response isNotFound]) {
NSLog(#"The resource path '%#' was not found.", [request resourcePath]);
}
}
NSLog(#"HTTP status code: %d", response.statusCode);
NSLog(#"HTTP status message: %#", [response localizedStatusCodeString]);
NSLog(#"Header fields: %#", response.allHeaderFields);
NSLog(#"Body: %#", response.bodyAsString);
}
Here is the error that I receive for RestKit API:
2012-09-11 22:33:01.053 (NameOfMyApp)[16271:707] I restkit.support:RKCache.m:189 Invalidating cache at path: /var/mobile/Applications/897B1DEA-1767-4F5C-AC8F-1809075C5CA9/Library/Caches/RKClientRequestCache-(Mywebsite).com/SessionStore
2012-09-11 22:33:01.057 (NameOfMyApp)[16271:707] I restkit.network.reachability:RKReachabilityObserver.m:123 Reachability observer initialized with IP address: 0.0.0.0.
2012-09-11 22:33:01.075 (NameOfMyApp)[16271:707] I restkit.network.reachability:RKReachabilityObserver.m:391 Network availability has been determined for reachability observer <RKReachabilityObserver: 0x10066330 host=0.0.0.0
2012-09-11 22:33:01.075 (NameOfMyApp)[16271:707] I restkit.network.reachability:RKReachabilityObserver.m:391 Network availability has been determined for reachability observer <RKReachabilityObserver: 0x10066330 host=0.0.0.0 isReachabilityDetermined=YES isMonitoringLocalWiFi=NO reachabilityFlags=-R -----l->
2012-09-11 22:33:01.325 (NameOfMyApp)[16271:707] I restkit.network:RKRequest.m:689 Status Code: 200
2012-09-11 22:33:01.328 (NameOfMyApp)[16271:707] HTTP status code: 200
2012-09-11 22:33:01.332 (NameOfMyApp)[16271:707] HTTP status message: no error
2012-09-11 22:33:01.338 (NameOfMyApp)[16271:707] Header fields: {
"Cache-Control" = "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
Connection = "Keep-Alive";
"Content-Encoding" = gzip;
"Content-Length" = 4679;
"Content-Type" = "text/html";
Date = "Wed, 12 Sep 2012 03:33:02 GMT";
Expires = "Thu, 19 Nov 1981 08:52:00 GMT";
"Keep-Alive" = "timeout=5, max=100";
Pragma = "no-cache";
Server = Apache;
Vary = "Accept-Encoding";
}
2012-09-11 22:33:01.345 (NameOfMyApp)[16271:707] Body: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Ater these code, it shows the HTML code of the address that I use for POST ( http://MyWebsite.com/send_link.php)
Any Idea how can would be appreciated, I really need help :(
I found the answer, In simulator it doesn't need to put http:// at the beginning of the url addresses, but on device, if I do not input http:// the website that I use their web service doesn't allow me to POST. Isn't that weird!
Thank you guys for your comments.
One more thing is, when I close the app and reopen it I have to do login, any idea how I can make the username and password persistence?

Resources