I am currently doing many server side computations that take a while to process.
In the mean time I redirect the user to the same page until it has a response.
This is resulting in a "too many HTTP redirects" error. Is there a way to disable this, or increase its threshold?
This happens on the simulator and on the phone.
here is the relevant code:
//Define request
NSURLRequest *request = [requestGenerator theRequest];
// Execute URL and read response
NSError *error = nil;
NSHTTPURLResponse *httpResponse;
NSData *resp = [NSURLConnection sendSynchronousRequest:request returningResponse:&httpResponse error:&error];
//If we get a good response
if(resp != nil && httpResponse != NULL && [httpResponse statusCode] >= 200 && [httpResponse statusCode] < 300)
{
...
}
if (error) {
[self showErrorMessage:error];
}
Redirecting to the same URL is extremely wasteful. At the very least, assign the "job" an ID and return that to the calling app. It can then poll the server, passing up the ID, to see when the job completes. You should not poll too frequently, perhaps once every few seconds.
You can also just use a single request-response. Your server will simply not respond until it has the data. On the iPhone the default time-out is set to 60 seconds. Your server may be able to start a response (write out a little data) so the connection is established; but do not complete & close the connection until processing completes.
Finally, if you do a simply request ... long delay ... response; and expect it will take > 60 seconds - consider extending the timeout of the request via NSMutableURLRequest's setTimeoutInterval.
Note: in your example you are using a synchronous request. If you do that outside of a thread, your app can block. If it blocks for > 20 seconds, it will be killed by the watchdog service. It's very easy to use the asynch methods with delegates to catch the responses, so give them a whirl.
Finally, if you use the asynch methods; you can catch the 3xx redirect and handle it yourself; but you know, your redirect technique seems evil so don't do it.
Related
I have the following code:
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://some-example-domain.com/api"]
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:30.0];
[NSURLConnection sendAsynchronousRequest:theRequest
queue: [NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error && data) {
// success!! - no errors and data returned
NSLog(#"success");
} else {
// error!! - something whent wrong
NSLog(#"failure: %#", [error localizedDescription]);
}
}
];
which works well - except for odd occasions when the server only sends part of the desired response (eg. half of a JSON response from an API) (it still is 'successful' according to my 'if' statement)
Is there some way using this block based method that I can check to see that the data received is complete??
I have tried looking into the NSURLResponse *response - but can't quite figure out how to use it (or, if it is indeed useful in this scenario). Any ideas on how to test for 'partially received' data returned by the block?
There are potentially two different failure modes for this query that aren't handled, and you'll need to check for them separately:
"successful" HTTP connection, but an malformed response
"successful" HTTP connection, but status code indicates a problem on the server
In the case of NSURLConnection, the error is only set when the connection fails, not when a problem is reported from the server (for example: a 404 error or a 330 response).
Generally, when you are talking to an HTTP or HTTPS service, you'll need to check the -statusCode in the NSURLResponse, which in the case of these services will actually be an NSHTTPURLResponse. If there's an error on the server, such as 408 for request timed out on the server, you need to handle that separately from a connection failure (which will cause the error to be set).
Even if you get back a nice [response statusCode] == 200, you will likely still want to check for malformed responses, which you'll need to do when you parse the data that comes back. This is a less likely scenario, but if the server is flakey, you may get a partial response or an encoding failure.
I am experiencing an issue on iOS 4.3+ with ASIHTTPRequest where a request is fired but no data (Request methed, url, headers, etc) reaches the server. The connection times out because it never hears back.
The server hears the empty request (after some delay), then hears a valid request which is of course never reported to higher level code because the connection has timed out. This is all kind of strange because the request was not configured to resend data.
Often this happens after the app has been pushed to the background for some time (15 min or more) and the phone has been allowed to sleep.
My configuration of the request is as follows:
NSMutableData *postData = nil;
NSString *urlString = [NSString stringWithFormat:#"%#%#",[self baseURL],requestPath];
OTSHTTPRequest *request = [OTSHTTPRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setCachePolicy:ASIFallbackToCacheIfLoadFailsCachePolicy];
[request setTimeOutSeconds:45];
//Set up body
NSString *queryString = [self _RPcreateQueryString:query];
if ([queryString length]>0) {
if(method == RPAsyncServerMethodPost || method == RPAsyncServerMethodPut){
postData = [[[queryString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES] mutableCopy] autorelease];
}else{
NSURL *url = [NSURL URLWithString:[urlString stringByAppendingFormat:#"?%#",queryString]];
[request setURL:url];
if (!url) return nil; //url String malformed.
}
}
// ... ///
// method setting stripped for brevity
[request addRequestHeader:#"Content-Type" value:#"application/x-www-form-urlencoded"];
if(headers){
for (NSString* head in headers) {
if ([[headers valueForKey:head] isKindOfClass:[NSString class]])
[request addRequestHeader:head value:[headers valueForKey:head]];
}
}
[request addRequestHeader:#"Content-Length" value:postLength];
[request setPostBody:postData];
OTSHTTPRequest is simply a subclass of ASIHTTPRequest that contains properties for a string tag, a pretty description, and other bling for use by consuming objects and does not override any ASI stuff.
Can anyone shed a light on why/how ASI could open a connection and then send absolutely nothing for minutes at a time?
Edit: Just to clarify. The connections DO make contact with the server, it just never sends any data through the connection from what my server logs can tell. This seems to always happen on app wake and effects all connections including NSURLConnections spawned by MapKit. the whole app just seems to loose its marbles.
I also see a bunch of background tasks ending badly right before this, but i can never catch them while in the debugger.
It doesn't look like you are starting your request based on the code that you have provided. Try call the -[startSynchronous] or -[startAsynchronous] methods of your OTSHTTPRequest object after you are done setting its various properties.
Are you setting the delegate, either I overlooked it or you stripped it out.
I didnt want to say anything till a few days passed with out the issue. The solution in this case was very obscure.
It appears the version of TestFlight i was using has a bug in it that may have contributed to this issue. Since its removal i have not experienced the issue.
in ASIHTTPRequest class
I debug - (void)main method of the NSOperation with wireshark . I want to find which method send data.
But i debug to the end of startRequest in main method of NSOperation. I can't grab any data.
Because the read stream opens a socket connection with the server specified by the myUrl parameter when the CFHTTP request was created, some amount of time must be allowed to pass before the stream is considered to be open. Opening the read stream also causes the request to be serialized and sent.
base the above document of apple about "Communicating with HTTP Servers"
the most chance to send data is the below code .But it don't. I can't find something in wireShark.
CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
streamSuccessfullyOpened = YES;
}
}
where send data ???? like socket send or sendto function.
[request startAsynchronous];
and
[request startSynchronous];
If your program is for command line, just use [request startSynchronous] in main, because if you use asynchronous request, the main thread continue, when main thread terminate, maybe asynchronous request do nothing.
Before I dive into ASIHttpRequest's source code:
From the documentation (http://allseeing-i.com/ASIHTTPRequest/How-to-use) of ASIHttpRequest it is not completely clear to me when an ASIHttpRequest will call requestDidFinishSelector or requestDidFailSelector.
When does a request fail?
Is it correct to assume that whenever the server responds (with a corresponding HTTP status code, be it a 1xx, 2xx, 3xx, 4xx or 5xx, the request is considered successful, and therefore requestDidFinishSelector will apply?
Is 'failure' the fact that the server could not be reached?
As I type this, this seems to be the most logical answer... anyway.
Yes, ASIHTTPRequest doesn't use HTTP status codes (except for redirection), so it's up to you to look out for problems (ex: 404) in your requestDidFinishSelector selector.
int statusCode = [request responseStatusCode];
NSString *statusMessage = [request responseStatusMessage];
requestDidFailSelector will be called only if there is the server can not be reached (time out, no connection, connection interrupted, ...)
You're right — a request succeeds if the server responds with a HTTP status code. You can ask for this status code by using ASIHTTPRequest's responseStatusCode.
If the request URL is invalid or your app has no access to the Internet, then your request fails. So you should implement requestDidFailSelector (or specify your own error handling method) and handle errors in there in any case.
I am having a heck of time with something that seems rather
straightforward but I can't seem to get working. I am building an
iPhone app that retrieves data from a web host. I am trying to
establish an asynchronous connection to the host as I want to keep the
device freed up during the connection. (sendSynchronousRequest freezes
the phone until the request is done.) Here is my connection code:
//temp url to see if data is returned:
NSURL *theURL = [NSURL URLWithString:#"http://www.theappleblog.com/feed"];
NSURLRequest *dataRequest = [NSURLRequest requestWithURL:theURL
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60];
/* establish the connection */
theConnection = [[NSURLConnection alloc]
initWithRequest:dataRequest
delegate:self
startImmediately:YES];
if (theConnection == nil) {
NSLog(#"Connection Failure!");
self.urlData = nil;
} else {
self.urlData = [[NSMutableData data] retain];
}
I have all of the appropriate delegate methods set up:
-(void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse*)response
{
[urlData setLength:0];
UIApplication *application = [UIApplication sharedApplication];
application.networkActivityIndicatorVisible = YES;
NSLog(#"Received Response!");
}
-(void)connection:(NSURLConnection *)connection
didReceiveData:(NSData*)incrementalData
{
[self.urlData appendData:incrementalData];
NSNumber *resourceLength = [NSNumber
numberWithUnsignedInteger:[self.urlData length]];
NSLog(#"resourceData length: %d", [resourceLength intValue]);
NSLog(#"filesize: %d", self.urlDataSize);
NSLog(#"float filesize: %f", [self.urlDataSize floatValue]);
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSLog(#"Connection finished loading\n");
NSLog(#"Succeeded! Received %d bytes of data",[urlData length]);
_isFinished = YES;
UIApplication *application = [UIApplication sharedApplication];
application.networkActivityIndicatorVisible = NO;
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
NSLog(#"Error: %#",[error localizedDescription]);
}
As you can see, I've got a boat-load of log messages because I wanted
to see if anything was coming through at all. My connection test
comes back as TRUE but no data ever gets loaded. Like I said, I'm sure
I must be doing (or not doing) something really stupid. But what?
Any help would be most appreciated.
Thanks, Lawrence
The problem was that I was continuing program flow and not stopping to allow the connection to finish downloading. Thank you for all of the suggestions.
A few suggestions:
In your first snippet, instead of theConnection try assigning to self.theConnection to make sure the property accessor methods are called. In various places you also interchange self.urlData and urlData. Might want to make them all consistent by going through self.
You may want to make the dataRequest a property as well and go through self to make sure it gets retained.
self.urlData = [[NSMutableData data] retain]; -- you shouldn't need that extra retain. Going through self does it for you. The extra retain will make it so the object will stick around and leak even when all is done.
On a related note, in your NSLogs make sure you print out the retain count for each object. Also, you may want to have a general clean-up routine around to release these structures (or sets self.foo = nil) so at any point if it barfs out you can call the routine to get things cleaned up.
Get a copy of Charles or WireShark (or run tcpdump in the console) and watch the network traffic. It'll help pinpoint at what stage in the flow it's failing.
This doesn't answer your question directly, but you might consider looking into Ben Copsey's ASIHTTPRequest library, which wraps around CFNetwork classes and includes asynchronous, threaded request code that takes a lot of work out of doing this properly.
Initial thought - is the object that has all this code being retained properly? It could be it's being released, and thus deallocating the connection...
Otherwise you should at least see the response.
Nothing obviously wrong up there. I have code that's substantially the same and works fine. The only differences I see are:
I use NSURLRequestUseProtocolCachePolicy instead of
NSURLRequestReloadIgnoringLocalCacheData
I use connection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; instead of specifying startImmediately: YES (since it's the default)
It would be helpful to know which, if any, of your delegate methods are getting called. At the very least, you ought to get at least one or the other of connectionDidFinishLoading or connectionDidFail being called. If not, then something's probably wrong with your delegate setup - are you ABSOLUTELY sure that the delegate method signatures are correct, and that you're passing in the right item as the delegate?
It's hard to zero in on anything since it's not clear from your question which, if any of the NSLog statements executes. Please be a little more clear about the results you're getting.
From a quick look the only thing that looks like a potential problem is that you have a race condition when you set up the connection. If the network is especially fast there's a slim but nonzero chance that -connection: didReceiveData: will be called before self.urlData has been allocated. I'd suggest allocating urlData either before initiating the connection or in -connection:didReceiveResponse: to prevent this.
If that doesn't fix it, more information from you is needed.
You say this code works fine using the asynchronous method? Testing that URL, it redirects to another, so what if you implement the redirection delegate methods? What if you try some different URLs?
Is the app otherwise responsive? Is the runloop running? What does the app do after starting the request -- does it return from its event handler back to the runloop? (If not, you'll never get any of your callbacks, because they're delivered via the runloop.)
NSURLConnection is designed to work with any type of protocol, not just HTTP. This means that connection:didFail:withError is called for connection errors, but not for protocol errors. You're not checking for protocol errors -- you have to catch them in connection:didReceiveResponse:. The trick is that the response object that is passed in is actually a NSHTTPURLResponse object. If you check the response's statusCode, I'll bet you're getting an HTTP error such as 404 or server 500.