Webview/H5場景下如何使用HTTPDNS進行域名解析__最佳實踐_HTTPDNS-阿裏雲
1. 背景說明
阿裏雲移動服務團隊提供的HTTPDNS服務解決了傳統的域名劫持、節點調度精確性以及DNS解析開銷巨大等問題,為良好的移動端用戶體驗提供了非常精簡的網絡優化方案。
移動場景下基於HTML的網頁呈現模型是移動應用中非常重要的技術手段,越來越多的Hybrid APP地湧現對終端的用戶體驗也提出了新的挑戰。本文將介紹針對Webview/H5業務場景下的HTTPDNS最佳實踐。
2. 實踐方案
在Android以及iOS平台下,原生係統均提供了係統API以實現Webview中的網絡請求攔截與自定義邏輯注入,因此我們可以通過上述攔截API攔截H5場景下的各類網絡請求,在攔截函數中進行HTTPDNS解析、Header頭注入以及Native網絡庫的調用。
2.1 Android示例
我們在HTTPDNS Android Demo github中提供了Android SDK以及HTTPDNS API接口的使用例程,這裏我們通過展示Android SDK的例程演示如何在Webview/H5業務場景下使用HTTPDNS服務。注:API < 21時隻能攔截到請求的url參數,其他應用層定製的參數都沒法正常獲取(包括method、header、body等),所以隻能攔截GET請求。
// 初始化httpdns
httpdns = HttpDns.getService(getApplicationContext(), MainActivity.accountID);
// 預解析熱點域名
httpdns.setPreResolveHosts(new ArrayList<>(Arrays.asList("www.apple.com")));
webView = (WebView) this.findViewById(R.id.wv_container);
webView.setWebViewClient(new WebViewClient() {
@SuppressLint("NewApi")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
// 新的API可以攔截POST請求
if (request != null && request.getUrl() != null && request.getMethod().equalsIgnoreCase("get")) {
String scheme = request.getUrl().getScheme().trim();
Map<String, String> headerFields = request.getRequestHeaders();
String url = request.getUrl().toString();
Log.d(TAG, "request.getUrl().toString(): " + url);
// 假設我們對所有css文件的網絡請求進行httpdns解析
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) && request.getUrl().toString().contains(".css")) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 異步獲取域名解析結果
String ip = httpdns.getIpByHostAsync(oldUrl.getHost());
if (ip != null) {
// 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
Log.d(TAG, "Get IP: " + ip + " for host: " + oldUrl.getHost() + " from HTTPDNS successfully!");
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
connection = (HttpURLConnection) new URL(newUrl).openConnection();
// 設置HTTP請求頭Host域
connection.setRequestProperty("Host", oldUrl.getHost());
}
// copy請求header參數
for (String header : headerFields.keySet()) {
connection.setRequestProperty(header, headerFields.get(header));
}
// 注*:對於POST請求的Body數據,WebResourceRequest接口中並沒有提供,這裏無法處理
Log.d(TAG, "ContentType: " + connection.getContentType());
return new WebResourceResponse(connection.getContentType(), "UTF-8", connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// API < 21 隻能攔截URL參數
if (!TextUtils.isEmpty(url) && Uri.parse(url).getScheme() != null) {
String scheme = Uri.parse(url).getScheme().trim();
Log.d(TAG, "url: " + url);
// 假設我們對所有css文件的網絡請求進行httpdns解析
if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) && url.contains(".css")) {
try {
URL oldUrl = new URL(url);
URLConnection connection = oldUrl.openConnection();
// 異步獲取域名解析結果
String ip = httpdns.getIpByHostAsync(oldUrl.getHost());
if (ip != null) {
// 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
Log.d(TAG, "Get IP: " + ip + " for host: " + oldUrl.getHost() + " from HTTPDNS successfully!");
String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
connection = (HttpURLConnection) new URL(newUrl).openConnection();
// 設置HTTP請求頭Host域
connection.setRequestProperty("Host", oldUrl.getHost());
}
Log.d(TAG, "ContentType: " + connection.getContentType());
return new WebResourceResponse(connection.getContentType(), "UTF-8", connection.getInputStream());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
});
webView.loadUrl(targetUrl);
2.2 iOS示例
HTTPDNS iOS Demo github中提供了iOS SDK以及HTTPDNS API接口的使用例程,下麵是在Webview/H5業務場景下使用HTTPDNS服務的示例。
解決方案針對UIWebView,其步驟為:注冊NSURLProtocol子類 -> 使用NSURLProtocol子類攔截Webview請求 -> 使用NSURLSession重新發起請求 -> 將NSURLSession請求的響應內容返回給Webview。
在Webview發起請求前注冊NSURLProtocol子類,在示例的WebViewController.m中。
// 注冊攔截請求的NSURLProtocol
[NSURLProtocol registerClass:[WebViewURLProtocol class]];
self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.webView];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.apple.com"]];
[self.webView loadRequest:req];
在WebViewURLProtocol中攔截Webview請求,示例中假設隻攔截css請求。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReq = [request mutableCopy];
NSString *originalUrl = mutableReq.URL.absoluteString;
NSURL *url = [NSURL URLWithString:originalUrl];
// 異步接口獲取IP地址
NSString *ip = [[HttpDnsService sharedInstance] getIpByHostAsync:url.host];
if (ip) {
// 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, url.host);
NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
if (NSNotFound != hostFirstRange.location) {
NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
NSLog(@"New URL: %@", newUrl);
mutableReq.URL = [NSURL URLWithString:newUrl];
[mutableReq setValue:url.host forHTTPHeaderField:@"host"];
// 添加originalUrl保存原始URL
[mutableReq addValue:originalUrl forHTTPHeaderField:@"originalUrl"];
}
}
return [mutableReq copy];
}
使用HTTPDNS SDK替換原請求中的HOST。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReq = [request mutableCopy];
NSString *originalUrl = mutableReq.URL.absoluteString;
NSURL *url = [NSURL URLWithString:originalUrl];
// 異步接口獲取IP地址
NSString *ip = [[HttpDnsService sharedInstance] getIpByHostAsync:url.host];
if (ip) {
// 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, url.host);
NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
if (NSNotFound != hostFirstRange.location) {
NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
NSLog(@"New URL: %@", newUrl);
mutableReq = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:newUrl]];
[mutableReq setValue:url.host forHTTPHeaderField:@"host"];
// 添加originalUrl保存原始URL
[mutableReq addValue:originalUrl forHTTPHeaderField:@"originalUrl"];
}
}
return [mutableReq copy];
}
使用NSURLSession重新發起請求。
- (void)startLoading {
NSMutableURLRequest *request = [self.request mutableCopy];
// 表示該請求已經被處理,防止無限循環
[NSURLProtocol setProperty:@(YES) forKey:protocolKey inRequest:request];
[self startRequest];
}
將NSURLSession請求返回的響應信息傳遞給Webview。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
NSLog(@"receive response: %@", response);
// 獲取原始URL
NSString* originalUrl = [dataTask.currentRequest valueForHTTPHeaderField:@"originalUrl"];
if (!originalUrl) {
originalUrl = response.URL.absoluteString;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSURLResponse *retResponse = [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:originalUrl] statusCode:httpResponse.statusCode HTTPVersion:(__bridge NSString *)kCFHTTPVersion1_1 headerFields:httpResponse.allHeaderFields];
[self.client URLProtocol:self didReceiveResponse:retResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed];
} else {
NSURLResponse *retResponse = [[NSURLResponse alloc] initWithURL:[NSURL URLWithString:originalUrl] MIMEType:response.MIMEType expectedContentLength:response.expectedContentLength textEncodingName:response.textEncodingName];
[self.client URLProtocol:self didReceiveResponse:retResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
[self.client URLProtocol:self didFailWithError:error];
} else {
[self.client URLProtocolDidFinishLoading:self];
}
}
立即試用HTTPDNS服務,點擊此處。
最後更新:2016-11-23 16:04:07
上一篇:
如何利用HTTPDNS降低DNS解析開銷__最佳實踐_HTTPDNS-阿裏雲
下一篇:
HTTPDNS域名解析場景下如何使用Cookie?__最佳實踐_HTTPDNS-阿裏雲
服務__係統管理_用戶指南(Linux)_數據管理-阿裏雲
視頻:3分鍾看懂OSS__數據操作常見問題_產品使用問題_對象存儲 OSS-阿裏雲
查詢訂單__訂單服務接口_API文檔_域名-阿裏雲
圖片URL規則__接入圖片服務_老版圖片服務手冊_對象存儲 OSS-阿裏雲
提交媒體信息作業__媒體信息接口_API使用手冊_媒體轉碼-阿裏雲
還在害怕wannacry?成都銳盾、阿裏雲推商業軟件免費試用
刪除NAT網關__NAT網關相關接口_API 參考_雲服務器 ECS-阿裏雲
配置SQL條件過濾遷移數據__數據遷移_用戶指南_數據傳輸-阿裏雲
AutoSnapshotPolicyType__數據類型_API 參考_雲服務器 ECS-阿裏雲
查詢流控曆史__直播流操作接口_API 手冊_CDN-阿裏雲
相關內容
常見錯誤說明__附錄_大數據計算服務-阿裏雲
發送短信接口__API使用手冊_短信服務-阿裏雲
接口文檔__Android_安全組件教程_移動安全-阿裏雲
運營商錯誤碼(聯通)__常見問題_短信服務-阿裏雲
設置短信模板__使用手冊_短信服務-阿裏雲
OSS 權限問題及排查__常見錯誤及排除_最佳實踐_對象存儲 OSS-阿裏雲
消息通知__操作指南_批量計算-阿裏雲
設備端快速接入(MQTT)__快速開始_阿裏雲物聯網套件-阿裏雲
查詢API調用流量數據__API管理相關接口_API_API 網關-阿裏雲
使用STS訪問__JavaScript-SDK_SDK 參考_對象存儲 OSS-阿裏雲