577
阿里云
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-阿里云