閱讀577 返回首頁    go 阿裏雲


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請求。

  1. // 初始化httpdns
  2. httpdns = HttpDns.getService(getApplicationContext(), MainActivity.accountID);
  3. // 預解析熱點域名
  4. httpdns.setPreResolveHosts(new ArrayList<>(Arrays.asList("www.apple.com")));
  5. webView = (WebView) this.findViewById(R.id.wv_container);
  6. webView.setWebViewClient(new WebViewClient() {
  7. @SuppressLint("NewApi")
  8. @Override
  9. public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
  10. // 新的API可以攔截POST請求
  11. if (request != null && request.getUrl() != null && request.getMethod().equalsIgnoreCase("get")) {
  12. String scheme = request.getUrl().getScheme().trim();
  13. Map<String, String> headerFields = request.getRequestHeaders();
  14. String url = request.getUrl().toString();
  15. Log.d(TAG, "request.getUrl().toString(): " + url);
  16. // 假設我們對所有css文件的網絡請求進行httpdns解析
  17. if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) && request.getUrl().toString().contains(".css")) {
  18. try {
  19. URL oldUrl = new URL(url);
  20. URLConnection connection = oldUrl.openConnection();
  21. // 異步獲取域名解析結果
  22. String ip = httpdns.getIpByHostAsync(oldUrl.getHost());
  23. if (ip != null) {
  24. // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
  25. Log.d(TAG, "Get IP: " + ip + " for host: " + oldUrl.getHost() + " from HTTPDNS successfully!");
  26. String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
  27. connection = (HttpURLConnection) new URL(newUrl).openConnection();
  28. // 設置HTTP請求頭Host域
  29. connection.setRequestProperty("Host", oldUrl.getHost());
  30. }
  31. // copy請求header參數
  32. for (String header : headerFields.keySet()) {
  33. connection.setRequestProperty(header, headerFields.get(header));
  34. }
  35. // 注*:對於POST請求的Body數據,WebResourceRequest接口中並沒有提供,這裏無法處理
  36. Log.d(TAG, "ContentType: " + connection.getContentType());
  37. return new WebResourceResponse(connection.getContentType(), "UTF-8", connection.getInputStream());
  38. } catch (MalformedURLException e) {
  39. e.printStackTrace();
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }
  45. return null;
  46. }
  47. @Override
  48. public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
  49. // API < 21 隻能攔截URL參數
  50. if (!TextUtils.isEmpty(url) && Uri.parse(url).getScheme() != null) {
  51. String scheme = Uri.parse(url).getScheme().trim();
  52. Log.d(TAG, "url: " + url);
  53. // 假設我們對所有css文件的網絡請求進行httpdns解析
  54. if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) && url.contains(".css")) {
  55. try {
  56. URL oldUrl = new URL(url);
  57. URLConnection connection = oldUrl.openConnection();
  58. // 異步獲取域名解析結果
  59. String ip = httpdns.getIpByHostAsync(oldUrl.getHost());
  60. if (ip != null) {
  61. // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
  62. Log.d(TAG, "Get IP: " + ip + " for host: " + oldUrl.getHost() + " from HTTPDNS successfully!");
  63. String newUrl = url.replaceFirst(oldUrl.getHost(), ip);
  64. connection = (HttpURLConnection) new URL(newUrl).openConnection();
  65. // 設置HTTP請求頭Host域
  66. connection.setRequestProperty("Host", oldUrl.getHost());
  67. }
  68. Log.d(TAG, "ContentType: " + connection.getContentType());
  69. return new WebResourceResponse(connection.getContentType(), "UTF-8", connection.getInputStream());
  70. } catch (MalformedURLException e) {
  71. e.printStackTrace();
  72. } catch (IOException e) {
  73. e.printStackTrace();
  74. }
  75. }
  76. }
  77. return null;
  78. }
  79. });
  80. 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中。

  1. // 注冊攔截請求的NSURLProtocol
  2. [NSURLProtocol registerClass:[WebViewURLProtocol class]];
  3. self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
  4. [self.view addSubview:self.webView];
  5. NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://www.apple.com"]];
  6. [self.webView loadRequest:req];

在WebViewURLProtocol中攔截Webview請求,示例中假設隻攔截css請求。

  1. + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
  2. NSMutableURLRequest *mutableReq = [request mutableCopy];
  3. NSString *originalUrl = mutableReq.URL.absoluteString;
  4. NSURL *url = [NSURL URLWithString:originalUrl];
  5. // 異步接口獲取IP地址
  6. NSString *ip = [[HttpDnsService sharedInstance] getIpByHostAsync:url.host];
  7. if (ip) {
  8. // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
  9. NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, url.host);
  10. NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
  11. if (NSNotFound != hostFirstRange.location) {
  12. NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
  13. NSLog(@"New URL: %@", newUrl);
  14. mutableReq.URL = [NSURL URLWithString:newUrl];
  15. [mutableReq setValue:url.host forHTTPHeaderField:@"host"];
  16. // 添加originalUrl保存原始URL
  17. [mutableReq addValue:originalUrl forHTTPHeaderField:@"originalUrl"];
  18. }
  19. }
  20. return [mutableReq copy];
  21. }

使用HTTPDNS SDK替換原請求中的HOST。

  1. + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
  2. NSMutableURLRequest *mutableReq = [request mutableCopy];
  3. NSString *originalUrl = mutableReq.URL.absoluteString;
  4. NSURL *url = [NSURL URLWithString:originalUrl];
  5. // 異步接口獲取IP地址
  6. NSString *ip = [[HttpDnsService sharedInstance] getIpByHostAsync:url.host];
  7. if (ip) {
  8. // 通過HTTPDNS獲取IP成功,進行URL替換和HOST頭設置
  9. NSLog(@"Get IP(%@) for host(%@) from HTTPDNS Successfully!", ip, url.host);
  10. NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
  11. if (NSNotFound != hostFirstRange.location) {
  12. NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
  13. NSLog(@"New URL: %@", newUrl);
  14. mutableReq = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:newUrl]];
  15. [mutableReq setValue:url.host forHTTPHeaderField:@"host"];
  16. // 添加originalUrl保存原始URL
  17. [mutableReq addValue:originalUrl forHTTPHeaderField:@"originalUrl"];
  18. }
  19. }
  20. return [mutableReq copy];
  21. }

使用NSURLSession重新發起請求。

  1. - (void)startLoading {
  2. NSMutableURLRequest *request = [self.request mutableCopy];
  3. // 表示該請求已經被處理,防止無限循環
  4. [NSURLProtocol setProperty:@(YES) forKey:protocolKey inRequest:request];
  5. [self startRequest];
  6. }

將NSURLSession請求返回的響應信息傳遞給Webview。

  1. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
  2. NSLog(@"receive response: %@", response);
  3. // 獲取原始URL
  4. NSString* originalUrl = [dataTask.currentRequest valueForHTTPHeaderField:@"originalUrl"];
  5. if (!originalUrl) {
  6. originalUrl = response.URL.absoluteString;
  7. }
  8. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  9. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
  10. NSURLResponse *retResponse = [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:originalUrl] statusCode:httpResponse.statusCode HTTPVersion:(__bridge NSString *)kCFHTTPVersion1_1 headerFields:httpResponse.allHeaderFields];
  11. [self.client URLProtocol:self didReceiveResponse:retResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed];
  12. } else {
  13. NSURLResponse *retResponse = [[NSURLResponse alloc] initWithURL:[NSURL URLWithString:originalUrl] MIMEType:response.MIMEType expectedContentLength:response.expectedContentLength textEncodingName:response.textEncodingName];
  14. [self.client URLProtocol:self didReceiveResponse:retResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed];
  15. }
  16. completionHandler(NSURLSessionResponseAllow);
  17. }
  18. - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
  19. [self.client URLProtocol:self didLoadData:data];
  20. }
  21. - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
  22. if (error) {
  23. [self.client URLProtocol:self didFailWithError:error];
  24. } else {
  25. [self.client URLProtocolDidFinishLoading:self];
  26. }
  27. }

立即試用HTTPDNS服務,點擊此處

最後更新:2016-11-23 16:04:07

  上一篇:go 如何利用HTTPDNS降低DNS解析開銷__最佳實踐_HTTPDNS-阿裏雲
  下一篇:go HTTPDNS域名解析場景下如何使用Cookie?__最佳實踐_HTTPDNS-阿裏雲