Ugh. I have read a few similar questions here on SOF but so far none of those solutions have worked for me.
I am trying to connect an iOS7 client to a WCF web service. Let me go ahead and point out that I didn't write the WCF service and have never written one - so I'm pretty WCF stupid. As I understand it - the idea is basically create a SOAP xml packet, and send it over, and wait for an xml response. There is a test client running on .NET that works fine, so I had the guy who wrote that turn on Fiddler so I could see what the xml and request headers should look like. Mine now are identical to his, yet the only response I can muster from the server is:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><s:Fault><faultcode xmlns:a="http://schemas.microsoft.com/ws/2005/05/addressing/none">a:ActionNotSupported</faultcode><faultstring xml:lang="en-US">The message with Action 'http://www.tempuri.org/IFoolSpoon_SoWo_Service/Check_Item_test' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).</faultstring></s:Fault></s:Body></s:Envelope>
Since that is scrolling WAY off the screen - it's a ContractMismatch error - whatever that actually means.
Side challenge: in googling the above error message - it seems impossible for anyone to explain the term "Contract" without using the word "contract". ;) Every explanation I saw was basically "it's like...the contract - you know...like the contract between the client and server...it's like a contract." :(
Anyway, here is the relevant obj-c code, with names/urls changed to protect the guilty:
// construct a URL that will ask the service for what we want
NSURL *url = [NSURL URLWithString:#"http://mymachine.myisp.com/SOWO/MyService.svc"];
// build the SOAP envelope
NSString *soapMessage = [NSString stringWithFormat:
#"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
"<s:Body >\n"
"<Check_Item_test xmlns=\"http://derp.org/\">\n"
"<UPC>1090000021</UPC>\n"
"</Check_Item_test>\n"
"</s:Body>\n"
"</s:Envelope>\n"];
soapMessage = [soapMessage stringByReplacingOccurrencesOfString:#"\n" withString:#"\r\n"];
// put URL into a NSURLRequest
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength = [NSString stringWithFormat:#"%d", [soapMessage length]];
NSLog(#"length = %#", msgLength);
[req addValue: #"text/xml" forHTTPHeaderField:#"Content-type"];
[req addValue: #"http://www.tempuri.org/IServiceInterface/Check_Item_test"
forHTTPHeaderField:#"SOAPAction"];
[req addValue: msgLength forHTTPHeaderField:#"Content-Length"];
[req addValue:#"100-continue" forHTTPHeaderField:#"Expect"];
[req setHTTPMethod:#"POST"];
[req setHTTPBody: [soapMessage dataUsingEncoding:NSUTF8StringEncoding]];
Some notes about the above code. There is another developer calling the same service from Ruby and he mentioned a couple of things: changing the line endings to \r\n from \n was his suggestion, since he found out that was a problem for him. I also adjusted some header capitalizations: SOAPaction to SOAPAction, etc. So now I'm matching HIS xml/headers exactly too, but no luck.
My request looks like this (from an OSX Fiddler equivalent):
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body >
<Check_Item_test xmlns="http://derp.org/">
<UPC>1090000021</UPC>
</Check_Item_test>
</s:Body>
</s:Envelope>
That seems to match every other client and all of them work save mine. So yeah - help? I am one degree away from the web.config for the service and I have access to the .svclog on the server. Interestingly enough - my requests aren't getting logged, even tho logMalformedRequests is set to true. Suggestions on where else to check for whatever bitbucket my requests are going into are appreciated.
So yeah - annoyed, frustrated, tired. For something that should be so simple, this has turned out to be a gigantic pain. It's probably something stupid, so perhaps I just need more eyes on it than my own.
TIA.
So the answer turned out to be: stop using SOAP.
I ended up rewriting the service endpoints as REST endpoints and all is fine and dandy.
Related
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.
I am trying to access a Drupal service that takes more than one argument. The method is views.get and the server I'm using is REST 6.x-2.0-beta3. I am retrieving data from the server for 0 or 1 arguments with no trouble. Any argument after the first, however, is simply ignored. I have tested the view on the Drupal site, and it limits results correctly for every argument passed.
I've come to the conclusion that my problem must be the formatting, but I've tried nearly everything I can think of, not to mention a dozen suggestions I've found while Googling for an answer. My code is below:
responseData = [[NSMutableData data] retain];
NSMutableString *httpBodyString =[[NSMutableString alloc] initWithString:#"method=views.get&view_name=apps&args=1&display_id=default"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://drupalserver.com/services/rest/service_views/get.json"]];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[httpBodyString dataUsingEncoding:NSUTF8StringEncoding]];
[httpBodyString release];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
I have tried:
args=1,2
args=[1,2]
args="1,2"
args=["1","2"]
and several others along that vein. Does anyone know the proper way to do this?
You should considering using, https://github.com/workhabitinc/drupal-ios-sdk
Basically, I am sending a POST request with an empty data body:
ASIHTTPRequest *request [ASIHTTPRequest alloc] init];
[request setURL:[NSURL URLWithString:escapedUrlString]];
[request setRequestMethod:#"POST"];
[request addRequestHeader:#"Content-Type" value:#"application/xml"];
[request startSynchronous];
But i am getting this response each time:
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatiblity mapping behavior in the near future.
I am wondering if it's mandatory to set post values.
I don't have much experience with the ASIHTTPRequest wrapper but I can see you are initialising it with alloc:init whilst most examples I've seen have a convenience initialiser requestWithURL:(NSURL*)url i.e.
ASIHTTPRequest *request = [AIHTTPRequest requestWithURL:escapedUrlString];
At a guess I'd say this convenience initaliser will also set some of the required variables for your post request to work, including NSStringEnconding.
From the ASIHTTPRequest documentation at http://allseeing-i.com/ASIHTTPRequest/How-to-use#handling_text_encodings
Handling text encodings
ASIHTTPRequest will attempt to read
the text encoding of the received data
from the Content-Type header. If it
finds a text encoding, it will set
responseEncoding to the appropriate
NSStringEncoding. If it does not find
a text encoding in the header, it will
use the value of
defaultResponseEncoding (this defaults
to NSISOLatin1StringEncoding).
When you call [request
responseString], ASIHTTPRequest will
attempt to create a string from the
data it received, using
responseEncoding as the source
encoding.
As Rog says, use initWithURL or requestWithURL. Anyway, in my case the problem was that I was connecting to a slow server and the request timed out giving the "Incorrect NSStringEncoding value 0x0000 detected" error.
I solved it with:
request.timeOutSeconds = 50;
You can try with more than 50 second if your server is even slower.
I am pulling my hair out trying to conjure up the correct syntax to set the HTTP header information do a byte-range load from an HTTP server.
This is the offending method on NSMutableURLRequest
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field
This is how I am using this method to load the first 512 byte of a URL request.
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"0-512\r\n" forHTTPHeaderField:#"Range"];
So far it is ignored and I always receive the entire data payload. I just want the range of bytes specified (0 - 512). Can someone please relieve my headache?
Update:
I have used curl to confirm that my web server supports byte ranges thusly:
curl --range 0-2047 http://www.somewhere.com/humungodata.dat -o "foobar"
The file size of foobar is 2048
Cheers,
Doug
Problem solved.
By adding additional header fields the code immediately worked correctly. Why? Dunno. But it works:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"GET"];
[request setValue:#"keep-live" forHTTPHeaderField:#"Connection"];
[request setValue:#"300" forHTTPHeaderField:#"Keep-Alive"];
[request setValue:#"bytes=0-2047" forHTTPHeaderField:#"Range"];
Your original code was wrong in the setValue line, it should be #"bytes=0-512". In your followup, you used the correct string, so the other headers should not be needed.
What you have there should be the correct way to add a header value to a URL request, however i thought only posts got header values, maybe im wrong, have you tried doing this on other enviroments and gotten it to work? Maybe take out the \r\n?
I'm trying to add a value to the header for a URL request.
Something like this works just fine:
[urlRequest addValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
But this doesn't even show up in the header:
NSString *authString = [[NSString alloc] initWithString:
[defaults objectForKey:#"auth"]];
[urlRequest addValue:authString forHTTPHeaderField:#"iphoneID"];
I'm completely stumped. The auth string is around 90 characters long. Is this a problem?
Edit:
Here's the code I'm trying:
NSString *authString = [[NSString alloc] initWithString:[defaults objectForKey:#"auth"]];
[urlRequest addValue:authString forHTTPHeaderField:#"iphoneid"];
[urlRequest addValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
I can see the Accept-Encoding header being sent through Wireshark, but iphoneid is nowhere to be found. It's just a string, 80-90 characters long.
Another Update:
So it seems that the problem isn't the field "iphoneid" but rather the authString I'm trying to pass into it. Other strings that I just create with the #"something" work fine, but the auth string that I pull from NSUserDefaults doesn't appear.
Suggestions on how to debug this?
The true problem.
The string I was pulling from NSUserDefaults already had a line ending. When set as a header, another \r\n is appended, which apparently isn't valid. Thus, the header wouldn't appear in any outgoing packets.
The fix:
Use this to trim off the characters before setting as a header value.
[yourString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
Testing checklist:
Verify that you actually have a NSMutableURLRequest (and not a NSURLRequest) at this point. In particular, check your logs for an exception due to "unrecognized selector."
Verify that urlRequest is not nil.
Switch to setValue:forHTTPHeaderField: rather than addValue:forHTTPHeaderField:.
Swap the forHTTPHeaderField: value to #"Accept-Encoding" to see if the field is the problem
Swap #"gzip" for auth to see if the value is the problem.
You need Charles web proxy, to see what header values are really outbound and inbound. Then you can see if the problem is really in your code or some magic on the server discarding things.
There's a free trial, and after you install it if you hit record any traffic the simulator sends will go through the proxy. Very nice.
http://www.charlesproxy.com/