阅读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-阿里云