Skip to content

Commit 6374b87

Browse files
committed
feat(ios): add http(s) proxy/scheme
This adds a new URLSchemeHandler which can proxy http(s) requests to external servers. This is useful for some CORS issues and webview bugs that affect the use of cookies in CORS requests via XHR and fetch. For that reason cookies will be synced between proxied requests on the native layer and the webview.
1 parent d93f4e4 commit 6374b87

File tree

3 files changed

+94
-26
lines changed

3 files changed

+94
-26
lines changed

src/ios/IONAssetHandler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
@property (nonatomic, strong) NSString * basePath;
77
@property (nonatomic, strong) NSString * scheme;
8+
@property (nonatomic) Boolean isRunning;
89

910
-(void)setAssetPath:(NSString *)assetPath;
1011
- (instancetype)initWithBasePath:(NSString *)basePath andScheme:(NSString *)scheme;

src/ios/IONAssetHandler.m

Lines changed: 87 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,72 @@ - (instancetype)initWithBasePath:(NSString *)basePath andScheme:(NSString *)sche
1919

2020
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
2121
{
22+
self.isRunning = true;
23+
Boolean loadFile = true;
2224
NSString * startPath = @"";
2325
NSURL * url = urlSchemeTask.request.URL;
24-
NSString * stringToLoad = url.path;
26+
NSDictionary * header = urlSchemeTask.request.allHTTPHeaderFields;
27+
NSMutableString * stringToLoad = [NSMutableString string];
28+
[stringToLoad appendString:url.path];
2529
NSString * scheme = url.scheme;
30+
NSString * method = urlSchemeTask.request.HTTPMethod;
31+
NSData * body = urlSchemeTask.request.HTTPBody;
32+
NSData * data;
33+
NSInteger statusCode;
2634

2735
if ([scheme isEqualToString:self.scheme]) {
2836
if ([stringToLoad hasPrefix:@"/_app_file_"]) {
2937
startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_app_file_" withString:@""];
38+
} else if ([stringToLoad hasPrefix:@"/_http_proxy_"]||[stringToLoad hasPrefix:@"/_https_proxy_"]) {
39+
if(url.query) {
40+
[stringToLoad appendString:@"?"];
41+
[stringToLoad appendString:url.query];
42+
}
43+
loadFile = false;
44+
startPath = [stringToLoad stringByReplacingOccurrencesOfString:@"/_http_proxy_" withString:@"http://"];
45+
startPath = [startPath stringByReplacingOccurrencesOfString:@"/_https_proxy_" withString:@"https://"];
46+
NSURL * requestUrl = [NSURL URLWithString:startPath];
47+
WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore];
48+
WKHTTPCookieStore* cookieStore = dataStore.httpCookieStore;
49+
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
50+
[request setHTTPMethod:method];
51+
[request setURL:requestUrl];
52+
if (body) {
53+
[request setHTTPBody:body];
54+
}
55+
[request setAllHTTPHeaderFields:header];
56+
[request setHTTPShouldHandleCookies:YES];
57+
58+
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
59+
if(error) {
60+
NSLog(@"Proxy error: %@", error);
61+
}
62+
63+
// set cookies to WKWebView
64+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
65+
if(httpResponse) {
66+
NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[httpResponse allHeaderFields] forURL:response.URL];
67+
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:cookies forURL:httpResponse.URL mainDocumentURL:nil];
68+
cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
69+
70+
for (NSHTTPCookie* c in cookies)
71+
{
72+
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
73+
//running in background thread is necessary because setCookie otherwise fails
74+
dispatch_async(dispatch_get_main_queue(), ^(void){
75+
[cookieStore setCookie:c completionHandler:nil];
76+
});
77+
});
78+
};
79+
}
80+
81+
// Do not use urlSchemeTask if it has been closed in stopURLSchemeTask
82+
if(self.isRunning) {
83+
[urlSchemeTask didReceiveResponse:response];
84+
[urlSchemeTask didReceiveData:data];
85+
[urlSchemeTask didFinish];
86+
}
87+
}] resume];
3088
} else {
3189
startPath = self.basePath;
3290
if ([stringToLoad isEqualToString:@""] || [url.pathExtension isEqualToString:@""]) {
@@ -36,36 +94,39 @@ - (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)ur
3694
}
3795
}
3896
}
39-
NSError * fileError = nil;
40-
NSData * data = nil;
41-
if ([self isMediaExtension:url.pathExtension]) {
42-
data = [NSData dataWithContentsOfFile:startPath options:NSDataReadingMappedIfSafe error:&fileError];
43-
}
44-
if (!data || fileError) {
45-
data = [[NSData alloc] initWithContentsOfFile:startPath];
46-
}
47-
NSInteger statusCode = 200;
48-
if (!data) {
49-
statusCode = 404;
50-
}
51-
NSURL * localUrl = [NSURL URLWithString:url.absoluteString];
52-
NSString * mimeType = [self getMimeType:url.pathExtension];
53-
id response = nil;
54-
if (data && [self isMediaExtension:url.pathExtension]) {
55-
response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil];
56-
} else {
57-
NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"};
58-
response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers];
59-
}
60-
61-
[urlSchemeTask didReceiveResponse:response];
62-
[urlSchemeTask didReceiveData:data];
63-
[urlSchemeTask didFinish];
6497

98+
if(loadFile) {
99+
NSError * fileError = nil;
100+
data = nil;
101+
if ([self isMediaExtension:url.pathExtension]) {
102+
data = [NSData dataWithContentsOfFile:startPath options:NSDataReadingMappedIfSafe error:&fileError];
103+
}
104+
if (!data || fileError) {
105+
data = [[NSData alloc] initWithContentsOfFile:startPath];
106+
}
107+
statusCode = 200;
108+
if (!data) {
109+
statusCode = 404;
110+
}
111+
NSURL * localUrl = [NSURL URLWithString:url.absoluteString];
112+
NSString * mimeType = [self getMimeType:url.pathExtension];
113+
id response = nil;
114+
if (data && [self isMediaExtension:url.pathExtension]) {
115+
response = [[NSURLResponse alloc] initWithURL:localUrl MIMEType:mimeType expectedContentLength:data.length textEncodingName:nil];
116+
} else {
117+
NSDictionary * headers = @{ @"Content-Type" : mimeType, @"Cache-Control": @"no-cache"};
118+
response = [[NSHTTPURLResponse alloc] initWithURL:localUrl statusCode:statusCode HTTPVersion:nil headerFields:headers];
119+
}
120+
121+
[urlSchemeTask didReceiveResponse:response];
122+
[urlSchemeTask didReceiveData:data];
123+
[urlSchemeTask didFinish];
124+
}
65125
}
66126

67127
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask
68128
{
129+
self.isRunning = false;
69130
NSLog(@"stop");
70131
}
71132

src/www/util.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ var WebView = {
1111
if (url.startsWith('file://')) {
1212
return window.WEBVIEW_SERVER_URL + url.replace('file://', '/_app_file_');
1313
}
14+
if (url.startsWith('http://')) {
15+
return window.WEBVIEW_SERVER_URL + url.replace('http://', '/_http_proxy_');
16+
}
17+
if (url.startsWith('https://')) {
18+
return window.WEBVIEW_SERVER_URL + url.replace('https://', '/_https_proxy_');
19+
}
1420
if (url.startsWith('content://')) {
1521
return window.WEBVIEW_SERVER_URL + url.replace('content:/', '/_app_content_');
1622
}

0 commit comments

Comments
 (0)