ASIHTTPRequest, request sent twice - iphone

I´ve just started using ASIHTTPRequest for iOs and I have a small issue with it. All requests are sent twice to the server even though I only get one reply from the library to my delegate methods.
Both sync and async requests have this issue. I use Xcode 4 with ARC but have disabled it for ASIHTTPRequest by adding -fno-objc-arc as compiler flags.
Any idea what´s wrong..?
Code:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
}

This has bitten me too. I was using a GET request to validate a multi-use voucher code on a server. When we added a rate limitation for redeeming codes some customers reported hitting the limit before they should have. Turns out that some of the validations triggered two redeems.
Your request is using the GET method.
The default behavior when using GET is to allow persistent connections (the Keep-Alive HTTP header).
When using a persistent connection your GET request might get retransmitted if something on the network looks wonky (that's a technical term) instead of the request just failing. This is usually desirable because GET requests often do not have any side effects on the server.
POST or PUT requests on the other hand default to not use a persistent connection and will not retransmit your operation, which could well be a credit card purchase or something else with significant side effects.
If you wish to prevent your ASIHTTPRequest GET sometimes sending 2 or more server requests (due to network issues outside your control) you can simply set this flag:
request.shouldAttemptPersistentConnection = NO;
This should take care of the spurious GET duplicates on the server.

Thank you for your replies. I moved to the new MKNetworkKit and never looked back at ASIHttpRequest. https://github.com/MugunthKumar/MKNetworkKit
Øystein

It might be sending a HEAD request to fetch the response size followed by a GET request to actually get the content. See this section of the documentation for more information.

It could be because persistent connections are in use, so you're seeing a failed request on a old connection followed by a working request on a new connection. (GregInYEG is also correct that it could be a HEAD request.)
If you gather a network trace using a tool like wireshark or charlesproxy then it would be possible to see exactly what is happening.

Related

Synchronous request or asynchronous when checking user's login data

In my application i need to implement verification if user has entered correct login and password or not. the login and the password are stored at the web server so i have to organize correct connection to the server. I'm an absolute beginner in everything about http requests and all that stuff. Actually i downloaded ASIHTTPRequest library and added it to my project just yesterday. My main problem is that i don't have an actual server by now (and i' m using just a conventional URL which later will be replaced with true server name but i want my code to be correct already)so i cannot test myself whether i'm doing things correctly or not.So my questions are:
1)What is the best way to organize verifying user's login and password? Should i use synchronous request or asynchronous? For all i know synchronous requests are rare in use cause they stop the application while the request is being performed but there's really nothing else needed to be done in this event so i'm a bit confused.What would you use?
2)I suppose verifying user's login and password by using http requests is pretty common task so there must be a general rule what kind of data the web server returns. I don't want to invent a wheel. should i use NSString returned by responseString to check if user's login and password match? What does server returns usually in such cases? How should my code look like? Something like
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:someUrl];
[request startSynchronous];
NSString *response = [request responseString];
if ([response isEqualToString:#"login and password match"])
//user enters next screen
else
//user is notified about the error
or something else? What would you do?
3)This request is not only i need to implement. Later i'm going to connect to the same URL with a different request. So how does the server know what kind of request is currently being used?
I really need your advice. Great thanks in advance
I have tried to answer your question,
Q:1. Synchronous or Asynchronous request model for login?
-> As per apple's documentation
A synchronous load is built on top of the asynchronous loading code made
available by the class. The calling thread is blocked while the asynchronous
loading system performs the URL load on a thread spawned specifically for
this load request.
also,
NSURLConnection provides support for downloading the contents of an
NSURLRequest in a synchronous manner using the class method
sendSynchronousRequest:returningResponse:error:. Using this method is
not recommended, because it has severe limitations:
The client application blocks until the data has been completely
received, an error is encountered, or the request times out.
Minimal support is provided for requests that require authentication.
There is no means of modifying the default behavior of response
caching or accepting server redirects.
As you are unaware of server side implementation, which may involve:
1. Redirection and other mechanisms for fulfilling the request.
2. It may require some proxy authentication or other similar stuff.
Q:2. What does server returns usually in such cases?
In general, a web service is implemented at server-side which returns XML or JSON as repsonse which you have to parse and use.
example response may look like:
for XML:
<auth>
<statusCode>0</statusCode>
<statusMessage>Login Successful.</statusMessage>
</auth>
for JSON
{
"statusCode" = "0"
"statusMessage" = "Login Successful."
}
tags(for XML) and keys(for JSON) will depend upon you sever implementation.
3. How does the server know what kind of request is currently being used?
-> The URL which you will use for request will tell server, what you are looking for?
for example
http://www.example.com/mywebapp/getItem?id="1";
Thanks,
or
http://www.example.com/mywebapp/removeItem?id="1";
The bold path item represents services which you are calling.

ASIHTTPRequest returns error 406 when trying to read RSS Feed

I'm trying to read the following URL: http://www.bandsintown.com/Godwrath/rss
My response string is empty and [request responseStatusCode] returns 406. I've tried adding the following with no success:
[request addRequestHeader:#"Accept" value:#"text/xml"];
[request addRequestHeader:#"Accept" value:#"application/rss+xml"];
Have any of you ever bumped into this problem?
Greets,
Shai.
Use CharlesProxy (or wireshark, or ...) to capture the http traffic from:
Your iOS app
A working client (eg. a web browser)
Compare the 2 and try to correct any differences (you can probably ignore User-Agent:, Connection:, If-Modified-Since: and a few other headers)
If you still can't get it working edit your question to add in the request captured in charlesproxy from the browser, your changed code to create the request and the request captured from your app.
406 generally means your browser doesn't understand the return data from the web server. In the example you give, you overwrite the Accept header. You can't provide the same header tag twice if I remember correctly...

ASIHttpRequest DELETE method with body parameters

I use ASIHttpRequest (v. 1.8-95) for Iphone and wanted to create a synchronous DELETE request together with some body data. I went this way:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:nsUrl];
[request appendPostData:[#"some body params" dataUsingEncoding:NSUTF8StringEncoding]];
[request setRequestMethod:#"DELETE"];
[request startSynchronous];
Although I was confirmed on the client side via
NSLog(#"request: method:%#", request.requestMethod);
that the method was correctly set to "DELETE"
on the server side a "POST" request was received !
If I just omit
[request appendPostData: ..]
a correct DELETE is received on the server side)
So what's wrong with my request ?
Thanks for any solutions.
Regards
creator_11
Searching the asihttprequest group ( http://groups.google.com/group/asihttprequest/search?group=asihttprequest&q=delete&qt_g=Search+this+group ) turns up some relevant posts including a suggested workaround:
call buildPostBody on your request
after you've populated the body, but
before you set the request method.
HTTP verbs and usages can't just be mixed and matched. OK, they can, but you'd have to change the server to support your non-standard usage. DELETE should use the URI of the resource to be deleted, and thats it. No POST params, no attachment.
If really you want to send a little extra data along with the delete, you can set it in the headers of the request (addRequestHeader:value:), and server side pull that info out, but avoid that if you can. The reason is, the DELETE should be deleting one 'thing' referred to by it's URI. If the business logic of the server application says that delete should affect some other objects (eg cascading delete), the client application shouldn't know about that.
Can you explain what you're trying to POST while performing a DELETE, maybe I can offer an alternative solution.

Network manager with NSURLConnection

I am trying to create something like a Network Manager using NSUrlConnections.
For that, I want to be able to send multiple requests, but I also want to be able to identify the client(delegate) that made the request when the response arrives.
I have created a NSDictionary like this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:SERVER_TIMEOUT];
....
[clients setObject:client forKey:connection];
in "- (void)connectionDidFinishLoading:(NSURLConnection *)connection" I have something like this:
client = (id<RTANetworkDelegate>)[clients objectForKey:connection];
[clients removeObjectForKey:connection];
The Network Manager is the delegate for all the connections, I do some preprocessing and then I send the (parsed) response to the right delegate, that sent the request in the first place.
Unfortunately, it appears that a NSMutableURLRequest cannot be set as a key in a dictionary since it does not have the copyWithZone method and I get the error:
-[NSURLConnection copyWithZone:]:
unrecognized selector sent to
instance
Any help would be much appreciated!
Thanks!
=======================================
[Edit] I already found this in the meantime:
http://blog.emmerinc.be/index.php/2009/03/15/multiple-async-nsurlconnections-example/
It seems to solve my problem.. I still don't know if it's the best solution though. I thought I would post it here since it might help others too.
You could use the -hash value of the connection object as the key:
[clients setObject:client forKey:[connection hash]];
I'd stay away from the actual URL or anything similar as two requests could potentially have the same URL.

ASIFormDataRequest- POST of file continues seems to continue after authentication fails

I'm posting some data to a http authenticated url with ASIFormDataRequest.
When the authentication fails and the authentication dialog delegate is invoked the upload progress seems to still proceed fully.
So in these cases:
1) The user's credentials are not yet stored in the keychain
2) The user's credentials which are stored in the keychain fail authentication (expired etc.)
I see this behavior:
I see the request come in to my
server and the 401 denied error
returned to the client
The uploadFailed delegate is not
called.
Progress bar delegate slowly fills as
the file appears to still be pushed
out on the network connection. It
completes in a time consistent with
the amount of time to fully upload
The built in authentication dialog
modal appears
User enters correct credentials
Progress bar delegate resets
Upload begins again - progress bar
fills as post data is received on
server
Finished delegate method is called as
expected.
Everything has uploaded just fine
with this second attempt
Here's where I setup my operation:
[self setRequest:[ASIFormDataRequest requestWithURL:uploadURL]];
[request setDelegate:self];
[request setDidFailSelector:#selector(uploadFailed:)];
[request setDidFinishSelector:#selector(uploadFinished:)];
[request setUseKeychainPersistence:TRUE];
[request setShouldPresentAuthenticationDialog:TRUE];
[request setShouldPresentCredentialsBeforeChallenge:TRUE];
[request setPostValue:captionTextField.text forKey:#"caption"];
[request setPostValue:[siteID stringValue] forKey:#"site_id"];
[request setFile:fileToUpload forKey:#"site_photo"];
[request setUploadProgressDelegate:progressView];
[request startAsynchronous];
I am thinking I need to issue a [request cancel] upon the authentication failing but I'm not sure where I should be doing this.
Is it expected behavior that the POST will still chug away even after the server has returned a 401?
Appreciate any guidance or pointers to existing questions that address this.
A 401 "error" is an HTTP status code, not a request failure. Your request went through okay and you got a response, which happens to be an authentication error notice. You are responsible for handling the response, whatever it might be.
There are many possible status codes you can get from a successful request, other than 401. As an aside, you may want to think about how to handle those kinds of responses as well, depending on what the end user is doing and what responses are appropriate.
The method -uploadFinished: should not generally be waiting until the data is fully uploaded before you see any NSLog statements or other notification of the request finishing.
So one thing to do is change the -uploadFailed: and -uploadFinished: method names to -requestFailed: and -requestFinished: to more accurately reflect what is happening in the logic of your application.
In your delegate's -requestFinished: method, check the responseStatusCode property of the request and issue the appropriate commands:
- (void) requestFinished:(ASIHTTPRequest *)request {
if ([request responseStatusCode] == 401) {
//
// cancel the current request and/or trigger a new,
// separate authentication request, passing along a
// reference to the request's body data, etc.
//
}
}
This is fairly common behaviour for HTTP clients - they do not attempt to read the reply from the server till they have fully sent the request, including the attached file.
It's common behaviour for a client to pre-emptively send the authentication if it has already has a request from the same server rejected with a 401 with in the same session - I am unsure if ASIHTTPRequest does this, but if it does one solution would be to make a GET request to the server before you do the POST. If the GET is successfully authenticated then the cached credentials should be sent for the post and hence there won't be a 401 error.
The only other option I can think of would be to move to cookie based authentication instead, if you are in control of the server, or use authentication in a custom http header. But I think my suggestion of doing a GET request first may be the best approach.