I'm using AFNetworking and like it very much.
I need to get JSON data from my server and it's OK, it works perfectly.
I added the setDownloadProgressBlock but I think it can't work with JSON download: maybe it's not possible to get the estimated amount of bytes to download.
My code:
NSMutableURLRequest *request = [[VinocelaHTTPClient sharedClient] requestWithMethod:#"GET" path:#"ws/webapp/services/pull" parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
}];
[operation setDownloadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
NSLog(#"Get %d of %d bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation start];
And my result :
Get 27129 of -1 bytes
Get 127481 of -1 bytes
Get 176699 of -1 bytes
So, I think AFNetworking can't estimate the real size to download when downloading JSON data contrary to a zip file or an image ?
From perusing the source, it seems that the progress callback is just passed the expectedContentLength property of the cached, internal NSHTTPURLResponse object. So, if for some reason your server isn't correctly sending the Content-Length header, and/or is doing chunked transfer encoding, that value is unknown, and the value NSURLResponseUnknownLength is returned (which happens to be defined as -1).
Try inspecting the headers returned by an HTTP request outside the context of your app. If you get a Content-Length header with a sane value, the problem likely lies in AFNetworking itself. If it is absent, the problem lies with the server. I've never seen an HTTP server send a JSON response using chunked transfer encoding (most of the time the content size should be relatively small and known at the time the headers are sent), but it's within spec for it to do so.
Related
I've been trying to retreive my json data for my iOS App. I tried many different sollutions but none of these worked properly for me. So this was the code I was using to read the json from the url and convert it.
let url = NSURL(string: "http://www.blind3d.byethost7.com/service.php")!
func load() {
do {
let request = NSURLRequest(URL: url)
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: nil)
self.handleData(data)
}
catch let error as NSError {
print("wieso dont you do siss : \(NSURLRequest(URL: url))")
self.handleError(error)
}
}
func handleError(error : NSError?) {
print("wieso dont you do siss : \(NSURLRequest(URL: url))")
NSLog("%#", "Error with loading from \(url): \(error)")
}
func handleData(data : NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)
handleJSON(json)
}
catch let error as NSError {
handleError(error)
}
}
but somehow this isn't running properly. I am always getting this error when I am executing this method: NSJSONSerialization
Error with loading from http://www.blind3d.byethost7.com/service.php: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})
The json data I wanted to use for my app is here
Thank you for your help guys
The problem occurs because there it no actual JSON in the data variable. I tried your web service, and this is what you get returned in the data, along with all the other error html tags:
"This site requires Javascript to work, please enable Javascript in your
browser or use a browser with Javascript support"
The full response:
<html><body><script type="text/javascript" src="/aes.js" ></script><script>function toNumbers(d){var e=[];d.replace(/(..)/g,function(d){e.push(parseInt(d,16))});return e}function toHex(){for(var d=[],d=1==arguments.length&&arguments[0].constructor==Array?arguments[0]:arguments,e="",f=0;f<d.length;f++)e+=(16>d[f]?"0":"")+d[f].toString(16);return e.toLowerCase()}var a=toNumbers("f655ba9d09a112d4968c63579db590b4"),b=toNumbers("98344c2eee86c3994890592585b49f80"),c=toNumbers("26049265c821fd7227c09955cbb61ebc");document.cookie="__test="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/";location.href="http://www.blind3d.byethost7.com/service.php?ckattempt=1";</script><noscript>This site requires Javascript to work, please enable Javascript in your browser or use a browser with Javascript support</noscript></body></html>
This seems to happen, because there is some Javascript injected in the webpage you are trying to parse from, probably for statistics, or some other unknown reasons.
For checking by yourself, print your data - print(data), before calling self.handleData(data)
Try removing \r\n or escape them with '\' like
\\r\\n
and you are good to go. BTW, using json is painful in swift like this, SwiftyJSON is a necessary library if you deal with json frequently.
This is the result of installed "testCookie-nginx-module"
It's supposed to prevent DDOS attacks on your hosting
When you visit your site for the first time, it sends you this JS code, which your browser is supposed to process and set a special cookie (its name it _test)
Only with this cookie attached to your IP your browser can see the original content (your content: html php json etc.)
Seems the only way for you - is to process this JS (with AES, HEX and other JS functions, get the right _test cookie and send another request with this cookie)
I'm using POST:parameters:constructingBodyWithBlock:. I believe the pieces I'm attempting to send to be correct, but I'm getting a HTTP 400 from the server. So something is wrong with the overall packet or how a piece of it is being encoded. I have a few theories about that, but before I do anything else I'd like to see what the data I'm sending actually looks like.
What is the best way to see what AFNetworking is actually sending for debugging purposes?
I've tried tracing quite deep, but when I reach AFMultipartBodyStream I get lost. I'm not sure how to capture its data without severely hacking at AFNetworking.
For anyone curious later, I've found this works nicely in the failure or success block:
NSMutableData *data = [NSMutableData data];
NSInputStream *stream = [operation.request.HTTPBodyStream copy];
[stream open];
BOOL done = NO;
while (!done) {
NSMutableData *buffer = [NSMutableData dataWithLength:1024];
buffer.length = [stream read:buffer.mutableBytes maxLength:buffer.length];
if (buffer.length) {
[data appendData:buffer];
} else {
done = YES;
}
}
[stream close];
The resulting data object can be written to disk, but can only be converted to a string directly if the parameters were entirely string (and not a binary file).
I have the following doubts about the JSON data returned from using both "GET" versus "POST" request. In the following URL JSON DATA, the data is not always updated based on the server changes (eg: database). For example, if I delete all the suggestion records from the database when I had 3 previously, it still returns 3 suggestion records in my JSON response body when I call dataTaskWithRequest.
However, if I change to POST, then the JSON response body will always be updated with the actual records from the server. In my server code (Using CakePHP), I did not check for post or get data. Actually, it was intended to be a GET method, but for some reason, only POST method seems to always fetch the most up to date data from JSON as opposed to GET.
Below is my code from my iOS client, but I'm not too sure if its very useful. I was wondering if there is a cache issue for GET request as opposed to POST request? However, I tried disabling cache for NSURLSessionConfig but it had no impact.
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
The code base is below:
NSString *requestString = [NSString stringWithFormat:#"%#/v/%#.json", hostName, apptIDHash];
NSURL *url = [NSURL URLWithString:requestString];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
if (!error) {
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
NSError *jsonError;
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
[self printJSONOutputFromDictionary:jsonObject];
if (!jsonError) {
block(jsonObject, nil);
}
else{
block(nil, jsonError);
}
}
else{
NSError *statusError = [self createServerUnavailableNSError:httpResp];
block(nil, statusError);
}
}
else{
block(nil, error);
}
}];
[dataTask resume];
In the above code fragment, the JSON body is always showing outdated data.
I really want to understand the issue, and would really appreciate if anyone could explain this issue for me.
Try adding the following request header:
[req addRequestHeader:#"Cache-Control" value:#"no-cache"];
I encountered the same problem as you and adding the above code solved the problem for me.
Taken from ASIHTTPRequest seems to cache JSON data always
I'm trying to use AFNetworking 2.0 to perform my network requests but I'm running into some odd behavior. I've subclassed AFHTTPSessionManager as suggested in the documentation and provided a class method that returns a singleton object that has the base url set as well as sets my auth header.
+ (id)sharedInstance {
static dispatch_once_t once;
static MyHTTPClient *sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] initWithBaseURL: NSURLURLWithString:kPlatformAPIBaseURL]];
});
//Uncommenting this line makes the error go away
//sharedInstance.responseSerializer = [AFJSONResponseSerializer serializer];
//get latest session id everytime someone gets an instance of the client
sharedInstance.sessionId = [MySessionManager getSessionId];
return sharedInstance;
}
- (instancetype)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if(self) {
self.sessionId = [FSSessionManager getSessionId];
self.serializer = [AFHTTPRequestSerializer serializer];
[_serializer setValue:_sessionId forHTTPHeaderField:kAuthorizationHeader];
[_serializer setValue:#"application/json" forHTTPHeaderField:kAcceptHeader];
self.requestSerializer = _serializer;
}
return self;
}
- (void)setSessionId:(NSString *)sessionId {
_sessionId = sessionId;
[self.serializer setValue:_sessionId forHTTPHeaderField:kAuthorizationHeader];
}
My app uses this to make a POST request to authenticate my user. That works great. I then make a GET request to retrieve a list of objects. Also works great. I then make the same GET request and I get back a network error Error Domain=AFNetworkingErrorDomain Code=-1016 "Request failed: unacceptable content-type: application/json" It's the exact same GET request but it fails on the second call. When I uncomment the sharedInstance.responseSerializer line so I create a new instance of the response serializer each time I get a reference to my shared instance then I don't get this error anymore.
Can a responseSerializer not be used multiple times safely? It feels like some sort of state is hanging around across requests. What's the correct way to set this up?
A response serializer can be used multiple times safely. Based on the error message you posted, "unacceptable content-type: application/json ", it appears you're setting responseSerializer to something else elsewhere in your code. JSON will serialize properly as long as it's set to [AFJSONResponseSerializer serializer].
I am trying to retrieve some JSON data with AFNetworking.
My server component (PHP/ZendFramework) delivers the following response:
>curl -i http://test.local:8080/public/appapi/testaction/format/json
HTTP/1.1 200 OK
Date: Mon, 06 Feb 2012 10:37:20 GMT
Server: Apache
X-Powered-By: PHP/5.3.8
Content-Length: 35
Content-Type: application/json
{"entries":"test","abcxxx":"test2"}
I try to use the following code to retrieve this json data:
NSString *baseurl = #"http://test.local:8080";
NSString *path = #"/public/appapi/testaction/format/json";
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:baseurl]];
[client registerHTTPOperationClass:[AFJSONRequestOperation class]];
[client setAuthorizationHeaderWithUsername:#"myusername" password:#"mypassword"];
[client getPath:path parameters:nil success:^(__unused AFHTTPRequestOperation *operation, id JSON) {
//NSLog(#"sjson: %#", [JSON valueForKeyPath:#"entries"]);
NSLog(#"sjson: %#", JSON);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error Code: %i - %#",[error code], [error localizedDescription]);
}];
When running in the simulator the console shows the following output:
2012-02-06 11:22:39.800 App[3642:15803] sjson: <6a736f6e 466c6963 6b724170 69287b22 6d657468 6f64223a 7b225f63 6f6e7465 6e74223a 22666c69 636b722e 74657374 2e656368 6f227d2c 2022666f 726d6174 223a7b22 5f636f6e 74656e74 223a226a 736f6e22 7d2c2022 6170695f 6b657922 3a7b225f 636f6e74 656e7422 3a223336 32306666 32343265 37323336 34633135 32373031 31353438 62393335 3066227d 2c202273 74617422 3a226f6b 227d29>
Any hint on what I am doing wrong would be greatly appreciated :) I tried accessing the Twitter API and that works perfectly fine...
This is confusing, and something I've been trying to figure out how to properly document / design better, but you need to set your Accept header for the request to application/json.
Registering AFJSONRequestOperation says "any request that comes through, I'll look at and handle if it is a JSON request". The way you hint that a request is JSON is to either set the appropriate Accept header, or add .json as the extension. Right now, the request is going through the list of operation classes, but it only catches on AFHTTPRequestOperation, which has a response object type of NSData (what you're seeing in the log).
Sorry about the confusion!
Here are the 3 lines I had to add to get my app to send and receive JSON:
httpClient.parameterEncoding = AFJSONParameterEncoding;
[httpClient setDefaultHeader:#"Accept" value:#"application/json"];
[httpClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
if you're using a custom accept header, like i am, AFNetworking won't recognize that you're requesting json, like mattt explained. one way around this is to simply convert the data into a json object in your completion block:
^(AFHTTPRequestOperation *operation, id JSON)
{
id payload = [NSJSONSerialization JSONObjectWithData:JSON options:NSJSONWritingPrettyPrinted error:nil];
NSLog(#"Fetched: %#", payload);
}
I had the same issue but after I added the Accept header something like in #Tylerc230 answer, the request start to fail because received format "text/html" wasn't in expected content type, so I added #"text/html" in acceptableContentTypes function of AFJSONRequestOperation.m .