In my iOS app, I'm using a UIWebView and a custom protocol (with my own NSURLProtocol implementation). I've been fairly careful about making sure that whenever I load a url, I load something like this into my UIWebView:
myprotocol://myserver/mypath
and in my NSURLProtocol implementation, I take a mutable copy of the NSURLRequest, convert the URL to http: and send that to my server.
Everything works for HTTP GET requests. The problem I encounter is with POST requests. It seems like the UIWebView doesn't properly encode the form data in the HTTPBody if the request uses my custom protocol.
One work-around, since I'm using HTTPS for my server requests, is that I register my protocol handler to intercept http: instead of myprotocol: and I can convert all calls to https: This other question, here, pointed me toward that solution:
But I'm wondering if there's any alternative and/or better way of accomplishing what I want.
Instead of trying to use POST requests, one work around is to continue using GET requests for myprotocol://
URLs, but transform them in your NSURLProtocol
implementation to an http://
and POST request to your server using the request query string as the body of the POST.
The worry with using GET requests to send large amounts of data is that somewhere along the request chain, the request line might get truncated. This appears to not be a problem, however, with locally-implemented protocols.
I wrote a short Cordova test app to experiment and I found that I was able to send through a little over 1 MiB of data without trouble to the HTTP request echoing service http://http-echo.jgate.de/
Here is my startLoading
implementation:
- (void)startLoading {
NSURL *url = [[self request] URL];
NSString *query = [url query];
// Create a copy of `url` without the query string.
url = [[[NSURL alloc] initWithScheme:@"http" host:@"http-echo.jgate.de" path:[url path]] autorelease];
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:url];
[newRequest setHTTPMethod:@"POST"];
[newRequest setAllHTTPHeaderFields:[[self request] allHTTPHeaderFields]];
[newRequest addValue:@"close" forHTTPHeaderField:@"Connection"];
[newRequest addValue:@"application/x-www-form-urlencoded;charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
[newRequest setHTTPBody:[query dataUsingEncoding:NSUTF8StringEncoding]];
urlConnection = [[NSURLConnection alloc] initWithRequest:newRequest delegate:self];
if (urlConnection) {
receivedData = [[NSMutableData data] retain];
}
}
I then implemented the NSURLConnection
protocol methods to forward to the appropriate NSURLProtocolClient
method, but building up the response data in the case of Transfer-Encoding:chunked
(as is the case for responses from http://http-echo.jgate.de/).
Unfortunately it looks like that http:
and https:
scheme requests are handled slightly differently than other (including custom) schemes by Foundation Framework. Obviously HTTPBody
and HTTPBodyStream
calls on relevant NSURLRequest
returns always nil
for former ones. This is decided already prior call of [NSURLProtocol canInitWithRequest]
therefore custom NSURLProtocol
implementation has no way of influencing that (it is too late).
It seems that different NSURLRequest
class is used for http:
and https:
than 'a default one'. Default GnuStep implementation of this class returns always nil
from HTTPBody
and HTTPBodyStream
calls. Therefore particular implementations (e.g. one under PhoneGap, likely part of Foundation Framework) choose NSURLRequest
-type of class based on scheme prior consulting that with NSURLProtocol
. For custom schemes, you get NSURLRequest
that returns nil
for both HTTPBody
and HTTPBodyStream
which effectively disables use of POST method (and other methods with body) in custom URI scheme handler.
Maybe there is a way how to influence decision of which NSURLRequest
class is actually used but it is currently unknown to me.
As a workaround, you can still use http:
or https:
scheme and decide in [NSURLProtocol canInitWithRequest]
based on other criteria (e.g. host name).