「Android SDK 統合フロー」ドキュメントでは、Android SDK のインポートと構成、IP アドレスの解析、SDK のネットワークライブラリへの適用、および統合の認証に関する完全なフローについて説明しています。このトピックでは、Android WebView シナリオで HTTPDNS を統合するための特定のソリューションについて説明します。
1. 背景
Alibaba Cloud HTTPDNS は、DNS ハイジャックを回避する効果的な方法です。ネットワークリクエストを開始する際に、HTTPDNS が提供する API を呼び出すことで、システムのデフォルトの DNS 名前解決をバイパスし、DNS ハイジャックのリスクを軽減または排除できます。
WebView によってロードされた Web ページでは、Web ページがネットワークリクエストを開始すると、WebView 内でリクエストをインターセプトし、ネイティブ層(アプリ)に実際のネットワークリクエストを実行させることができます。このプロセス中に、HTTPDNS によって提供される解決結果を使用してシステム DNS 解決を置き換え、DNS ハイジャックを防ぐことができます。
現在、Android の主流のネットワークリクエストライブラリは OkHttp であるため、このドキュメントでは主に OkHttp の実装に焦点を当てています。
このドキュメントは、Android WebView シナリオで HTTPDNS にアクセスするためのリファレンスソリューションです。提供されている関連コードは参照コードであり、オンラインの本番環境で直接使用できるコードではありません。実装前に、このドキュメントをよく読んで、合理的な評価を行うことをお勧めします。
Android システムはサービスプロバイダーによって異なります。したがって、最初にテスト環境でこのトピックで説明されているソリューションを実装し、例外が発生するかどうかを確認することをお勧めします。ご質問がある場合は、テクニカルサポートからフィードバックをお寄せください。そうすることで、タイムリーに最適化できます。
このドキュメントでは、WebView シナリオで HTTPDNS によって解決された IP を使用してリクエストを行う方法のみを説明しています。HTTPDNS 自体の解決サービスを理解するには、まず「Android SDK 統合」をご参照ください。
2. WebView が GET リクエストのみをインターセプトすることの制限と影響
Android の WebView では、WebViewClient.shouldInterceptRequest() メソッドを通じて、GET リクエストに関する詳細情報のみを取得できます。POST などの他のリクエストメソッドの場合、body は開発者に提供されないため、HTTPDNS 名前解決で完全にインターセプトして置き換えることはできません。
ただし、これは HTTPDNS にアクセスすることが無意味であることを意味するものではなく、主に次の理由によります。
ほとんどの Web 静的リソースは GET リクエストを使用します 一般に、Web ページに必要な静的リソース(画像、スクリプト、スタイルシートなど)は、ほぼすべて GET リクエストによって取得されます。これらの GET リクエストをインターセプトし、HTTPDNS を使用することで、主要なリソース読み込みプロセスをカバーし、ハイジャックされるリスクを大幅に軽減できます。
API 呼び出しのかなりの割合も GET を使用します 多くのインターフェース(特にクエリタイプまたはキャッシュ可能なインターフェース)は GET リクエストを使用します。したがって、これらのリクエストをインターセプトすることで、HTTPDNS を介して DNS ハイジャックを回避することもできます。
リソースはほとんど CDN でホストされており、HTTPDNS は近くのスケジューリングを改善します ほとんどの静的リソースは通常 CDN ノードに分散されており、CDN が近くにアクセスできるかどうかはドメイン名解決の精度によって異なります。HTTPDNS を使用すると、システム DNS ハイジャックをバイパスして、ドメイン名解決が最適な CDN ノードを指すようにすることで、ユーザーはより良いアクセス速度とエクスペリエンスを得ることができます。
GET リクエストのみをインターセプトすることで、ほとんどのハイジャックシナリオを回避できます DNS ハイジャックは、多くの場合、人気のあるドメイン名または大規模な静的リソースドメイン名をターゲットにしており、これらのドメイン名へのアクセスはほぼすべて GET リクエストです。HTTPDNS が現在 GET リクエストでのみ使用できる場合でも、トラフィックの大部分をカバーし、80% 以上の保護を実現できます。
要約すると、WebView レベルでは、現在 GET リクエストのみをインターセプトして書き換えることができますが、実際のビジネスでは、このメソッドは通常、主要なトラフィックをカバーし、CDN シナリオでより正確な近くのスケジューリングを実現するため、セキュリティとパフォーマンスの両方を向上させる上で重要な役割を果たします。
3. デモコード
HTTPDNS+WebView+OkHttp ベストプラクティスの完全なコードは、WebView+HTTPDNS+OkHttp Android デモにあります。
4. 実装の説明
次の手順は、OkHttp と WebView に基づいて HTTPDNS にアクセスし、ネイティブ層で Web ページによって開始されたリクエストをインターセプトして処理する方法を示しています。
4.1 OkHttp 構成
4.1.1 カスタム DNS 解決
OkHttp は、呼び出し元が DNS 名前解決をカスタマイズできるインターフェース Dns を提供します。HTTPDNS をこの API に統合して、カスタム DNS 名前解決を実装できます。次のサンプルコードを使用します。
OkHttpClient.Builder()
// DNS 解決ロジックをカスタマイズします。
.dns(object : Dns {
override fun lookup(hostname: String): List<InetAddress> {
val inetAddresses = mutableListOf<InetAddress>()
HttpDns.getService(accountId)
.getHttpDnsResultForHostSyncNonBlocking(hostname, RequestIpType.auto)?.apply {
if (!ipv6s.isNullOrEmpty()) {
for (i in ipv6s.indices) {
inetAddresses.addAll(
InetAddress.getAllByName(ipv6s[i]).toList()
)
}
} else if (!ips.isNullOrEmpty()) {
for (i in ips.indices) {
inetAddresses.addAll(
InetAddress.getAllByName(ips[i]).toList()
)
}
}
}
if (inetAddresses.isEmpty()) {
inetAddresses.addAll(Dns.SYSTEM.lookup(hostname))
}
return inetAddresses
}
})
.build()
new OkHttpClient.Builder()
// DNS 解決ロジックをカスタマイズします。
.dns(new Dns() {
@NonNull
@Override
public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
ArrayList<InetAddress> inetAddresses = new ArrayList<>();
HTTPDNSResult result = HttpDns.getService(accountiD)
.getHttpDnsResultForHostSync(hostname, RequestIpType.auto);
if (result != null) {
if (result.getIpv6s() != null && result.getIpv6s().length > 0) {
for (int i = 0; i < result.getIpv6s().length; i++) {
InetAddress[] ipV6InetAddresses = InetAddress.getAllByName(result.getIpv6s()[i]);
inetAddresses.addAll(Arrays.asList(ipV6InetAddresses));
}
} else if (result.getIps() != null && result.getIps().length > 0) {
for (int i = 0; i < result.getIps().length; i++) {
InetAddress[] ipV4InetAddresses = InetAddress.getAllByName(result.getIps()[i]);
inetAddresses.addAll(Arrays.asList(ipV4InetAddresses));
}
}
}
if (inetAddresses.isEmpty()) {
inetAddresses.addAll(Dns.SYSTEM.lookup(hostname));
}
return inetAddresses;
}
})
.build();HTTPDNS ドメイン名解決が失敗した場合、ドメイン名解決の基盤となるロジックとしてローカル DNS を使用することをお勧めします。
4.1.2 リダイレクトの無効化
このソリューションでは、リダイレクトはネットワークレイヤーでは処理されません。代わりに、WebView がデフォルトの動作に基づいてリダイレクトを処理します。このアプローチにより、リダイレクトシナリオでの Cookie やリソース読み込みエラーなどの例外を防ぐことができます。以下のサンプルコードは実装方法を示しています:
OkHttpClient.Builder()
.followRedirects(false) // HTTP リダイレクトを無効にします。
.followSslRedirects(false) // HTTPS リダイレクトを無効にします。
.build()new OkHttpClient.Builder()
.followRedirects(false) // HTTP リダイレクトを無効にします。
.followSslRedirects(false) // HTTPS リダイレクトを無効にします。
.build();信頼できないネットワーク環境では、リダイレクト要求が改ざんされる可能性があります。クライアント側でセキュリティ保護を実装する必要があります。
4.2 WebView はネットワークリクエストをインターセプトします
4.2.1 WebViewClient を実装する
WebView は WebViewClient インターフェースを提供します。shouldInterceptRequest() メソッドをオーバーライドすることで、Web ページからのリソース読み込みリクエストをインターセプトできます。このメソッドを使用すると、Web ページによって開始されたリクエストを ネイティブ層(OkHttp)を使用してデータリクエストに変更し、結果を WebView に返すことができます。サンプルコード:
webview.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
if (shouldIntercept(request)) {
return getResponseByOkHttp(request)
}
return super.shouldInterceptRequest(view, request)
}
}webview.setWebViewClient(new WebViewClient(){
@Nullable
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (shouldIntercept(request)) {
return getResponseByOkHttp(request);
}
return super.shouldInterceptRequest(view, request);
}
});4.2.2 リクエストをインターセプトするかどうかを判断する
shouldIntercept() メソッドでは、次の場合にのみインターセプトが実行されます。
httpまたはhttpsプロトコルのリクエストのみをインターセプトします。他のプロトコルはインターセプトされず、WebView自体によって処理されます。GETリクエストのみをインターセプトします(理由については、上記「WebView が GET リクエストのみをインターセプトすることの制限と影響」を参照してください)。
サンプルコード:
private fun shouldIntercept(webResourceRequest: WebResourceRequest?): Boolean {
if (webResourceRequest == null) {
return false
}
val url = webResourceRequest.url ?: return false
// HTTP 以外のリクエストはインターセプトされません。
if ("https" != url.scheme && "http" != url.scheme) {
return false
}
// GET リクエストのみがインターセプトされます。
if ("GET".equals(webResourceRequest.method, true)) {
return true
}
return false
}private boolean shouldIntercept(WebResourceRequest request) {
if (request == null || request.getUrl() == null) {
return false;
}
// HTTP 以外のリクエストはインターセプトされません。
if (!"http".equals(request.getUrl().getScheme()) && !"https".equals(request.getUrl().getScheme())) {
return false;
}
// GET リクエストのみがインターセプトされます。
if ("GET".equalsIgnoreCase(request.getMethod())) {
return true;
}
return false;
}4.2.3 OkHttp を使用してネットワークリクエストを行う
リクエスト URL、ヘッダー、およびその他の情報に基づいて、OkHttp を使用してネットワークリクエストを開始します。
WebResourceResponseを使用してカプセル化してから、応答結果を WebView に返します。
サンプルコード:
private fun getResponseByOkHttp(webResourceRequest: WebResourceRequest?): WebResourceResponse? {
if (webResourceRequest == null) {return null}
try {
val url = webResourceRequest.url.toString()
val requestBuilder =
Request.Builder().url(url).method(webResourceRequest.method, null)
val requestHeaders = webResourceRequest.requestHeaders
if (!requestHeaders.isNullOrEmpty()) {
requestHeaders.forEach {
requestBuilder.addHeader(it.key, it.value)
}
}
val response = okHttpClient.newCall(requestBuilder.build()).execute()
val code = response.code
if (code != 200) {
return null
}
val body = response.body
if (body != null) {
val contentType = body.contentType()
val encoding = contentType?.charset()
val mediaType = contentType?.toString()
var mimeType = "text/plain"
if (!TextUtils.isEmpty(mediaType)) {
val mediaTypeElements = mediaType?.split(";")
if (!mediaTypeElements.isNullOrEmpty()) {
mimeType = mediaTypeElements[0]
}
}
val responseHeaders = mutableMapOf<String, String>()
for (header in response.headers) {
responseHeaders[header.first] = header.second
}
var message = response.message
if (message.isBlank()) {
message = "OK"
}
val resourceResponse =
WebResourceResponse(mimeType, encoding?.name(), body.byteStream())
resourceResponse.responseHeaders = responseHeaders
resourceResponse.setStatusCodeAndReasonPhrase(code, message)
return resourceResponse
}
} catch (e: Throwable) {
e.printStackTrace()
}
return null
}private WebResourceResponse getResponseByOkHttp(WebResourceRequest request) {
try {
String url = request.getUrl().toString();
Request.Builder requestBuilder = new Request.Builder()
.url(url)
.method(request.getMethod(), null);
Map<String, String> requestHeaders = request.getRequestHeaders();
if (requestHeaders != null) {
for (Map.Entry<String, String> entry : requestHeaders.entrySet()) {
requestBuilder.addHeader(entry.getKey(), entry.getValue());
}
}
Response response = okHttpClient.newCall(requestBuilder.build()).execute();
if (200 != response.code()) {
return null;
}
ResponseBody body = response.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
Charset encoding = contentType.charset();
String mediaType = contentType.toString();
String mimeType = "text/plain";
if (!TextUtils.isEmpty(mediaType)) {
String[] mediaTypeElements = mediaType.split(";");
if (mediaTypeElements.length > 0) {
mimeType = mediaTypeElements[0];
}
}
Map<String, String> responseHeaders = new HashMap<>();
for (String key : response.headers().names()) {
responseHeaders.put(key, response.header(key));
}
String message = response.message();
if (TextUtils.isEmpty(message)) {
message = "OK";
}
WebResourceResponse resourceResponse = new WebResourceResponse(mimeType, encoding.name(), body.byteStream());
resourceResponse.setResponseHeaders(responseHeaders);
resourceResponse.setStatusCodeAndReasonPhrase(response.code(), message);
return resourceResponse;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}5. 検証
統合が完了したら、「ネットワークライブラリの統合が成功したことを確認する」ドキュメントをご参照の上、ハイジャック偽装やフォールトインジェクションテストなどの方法を使用して、統合が成功したことを確認してください。
5. まとめ
HTTPDNS は DNS ハイジャックのリスクを効果的に軽減できます。WebView では、リクエストをインターセプトし、ネイティブネットワークライブラリ(OkHttp)と HTTPDNS を組み合わせて使用することで、ドメイン名解決のセキュリティを大幅に向上させることができます。
WebView メカニズムの制限により、インターセプトは GET リクエストでのみ実装できます。ただし、実際のビジネスでは、これはすでに静的リソースの読み込みや一般的な GET インターフェースなどのほとんどのシナリオをカバーしています。
開発者は、テスト環境でこのトピックで説明されているソリューションを実装し、オンラインの例外と適応状況に注意を払い、ビジネスニーズに応じてさらに最適化またはカスタマイズされた処理を行うことを強くお勧めします。
さらに質問がある場合、またはテクニカルサポートが必要な場合は、テクニカルサポートからフィードバックをお寄せください。HTTPDNS のエクスペリエンスと互換性を最適化します。