The Android SDK integration process document describes the complete process of importing and configuring the Android SDK, parsing IP addresses, applying the SDK to a network library, and verifying the integration. This topic describes the specific procedure for using HTTPDNS with gRPC.
1. Background
gRPC is a high-performance, open-source Remote Procedure Call (RPC) framework from Google. It is based on the HTTP/2 protocol and is widely used in the microservices model. By default, gRPC uses the system DNS for domain name resolution. However, it also provides a NameResolver extension mechanism that allows developers to customize the resolution logic.
On Android, if your application uses gRPC, you can implement a custom NameResolver to integrate HTTPDNS into gRPC. This replaces the system DNS for domain name resolution.
Note: This solution applies to gRPC clients that use grpc-okhttp as the transport layer, which is the recommended method for Android.
2. How it works
The gRPC domain name resolution flow is as follows:
ManagedChannel
↓
NameResolver (custom endpoint)
↓
LoadBalancer
↓
Transport (grpc-okhttp)
↓
TCP connectionBy creating a custom NameResolver, you can integrate HTTPDNS at the domain name resolution stage. The custom NameResolver returns a list of resolved IP addresses to gRPC, and the gRPC LoadBalancer then handles load balancing and failover.
3. Integration steps
3.1 Add dependencies
Add the gRPC dependencies to your build.gradle file:
dependencies {
// EMAS HTTPDNS
implementation 'com.aliyun.ams:alicloud-android-httpdns:x.x.x'
// gRPC Android
implementation 'io.grpc:grpc-okhttp:x.x.x'
implementation 'io.grpc:grpc-stub:x.x.x'
implementation 'io.grpc:grpc-protobuf-lite:x.x.x'
}
3.2 Initialize HTTPDNS
Initialize the HTTPDNS service in your Application class:
public class MyApplication extends Application {
@Overridepublic void onCreate() {
super.onCreate();
// Initialize HTTPDNS
InitConfig config = new InitConfig.Builder()
.setContext(this)
.setSecretKey("your_secret_key") // Optional
.setEnableCacheIp(true, 24 * 60 * 60 * 1000) // Enable cache
.setEnableExpiredIp(true) // Allow use of expired IPs
.setTimeoutMillis(2000)
.build();
HttpDns.init("your_account_id", config);
}
}
3.3 Implement a custom NameResolver
Create the EmasHttpDnsNameResolver class:
import io.grpc.EquivalentAddressGroup;
import io.grpc.NameResolver;
import io.grpc.Status;
import com.alibaba.sdk.android.httpdns.HttpDns;
import com.alibaba.sdk.android.httpdns.HttpDnsService;
import com.alibaba.sdk.android.httpdns.HTTPDNSResult;
import com.alibaba.sdk.android.httpdns.RequestIpType;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
public class EmasHttpDnsNameResolver extends NameResolver {
private final String host;
private final int port;
private Listener2 listener;
public EmasHttpDnsNameResolver(String host, int port) {
this.host = host;
this.port = port;
}
@Overridepublic String getServiceAuthority() {
return host;
}
@Overridepublic void start(Listener2 listener) {
this.listener = listener;
resolve();
}
@Overridepublic void refresh() {
resolve();
}
private void resolve() {
List<EquivalentAddressGroup> addressGroups = new ArrayList<>();
try {
// Call HTTPDNS to resolve the domain name
HttpDnsService httpDnsService = HttpDns.getService("your_account_id");
HTTPDNSResult result = httpDnsService.getHttpDnsResultForHostSyncNonBlocking(
host,
RequestIpType.auto
);
// If HTTPDNS returns null, fall back to the system DNS
if (result == null ||
(result.getIps() == null || result.getIps().length == 0) &&
(result.getIpv6s() == null || result.getIpv6s().length == 0)) {
InetAddress[] systemAddrs = InetAddress.getAllByName(host);
for (InetAddress addr : systemAddrs) {
addressGroups.add(new EquivalentAddressGroup(
new InetSocketAddress(addr, port)
));
}
} else {
// Add IPv4 addresses
if (result.getIps() != null) {
for (String ip : result.getIps()) {
addressGroups.add(new EquivalentAddressGroup(
new InetSocketAddress(ip, port)
));
}
}
// Add IPv6 addresses
if (result.getIpv6s() != null) {
for (String ip : result.getIpv6s()) {
addressGroups.add(new EquivalentAddressGroup(
new InetSocketAddress(ip, port)
));
}
}
}
// Return the resolution result
ResolutionResult resolutionResult = ResolutionResult.newBuilder()
.setAddresses(addressGroups)
.build();
listener.onResult(resolutionResult);
} catch (Exception e) {
listener.onError(Status.UNAVAILABLE.withDescription(
"HTTPDNS resolve failed: " + e.getMessage()
));
}
}
@Overridepublic void shutdown() {
// Clean up resources
}
}
3.4 Implement NameResolverProvider
Create the EmasHttpDnsResolverProvider class:
import io.grpc.NameResolver;
import io.grpc.NameResolverProvider;
import java.net.URI;
public class EmasHttpDnsResolverProvider extends NameResolverProvider {
private static final String SCHEME = "emashttpdns";
@Overrideprotected boolean isAvailable() {
return true;
}
@Overrideprotected int priority() {
return 5;
}
@Overridepublic NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
if (!SCHEME.equals(targetUri.getScheme())) {
return null;
}
String host = targetUri.getHost();
int port = targetUri.getPort() == -1 ? 443 : targetUri.getPort();
return new EmasHttpDnsNameResolver(host, port);
}
@Overridepublic String getDefaultScheme() {
return SCHEME;
}
}
3.5 Register the NameResolver
Register the custom NameResolver in your Application class:
public class MyApplication extends Application {
@Overridepublic void onCreate() {
super.onCreate();
// Initialize HTTPDNS (see step 3.2)
// ...
// Register the custom NameResolver
NameResolverRegistry.getDefaultRegistry()
.register(new EmasHttpDnsResolverProvider());
}
}
3.6 Use the gRPC client
When creating the gRPC channel, use the custom scheme:
// Create the channel
ManagedChannel channel = OkHttpChannelBuilder
.forTarget("emashttpdns://your-grpc-server.com:443")
.overrideAuthority("your-grpc-server.com") // Important: This ensures correct TLS SNI
.useTransportSecurity() // Enable TLS
.build();
// Create the stub and make the call
YourServiceGrpc.YourServiceBlockingStub stub =
YourServiceGrpc.newBlockingStub(channel);
YourResponse response = stub.yourMethod(request);
4. Verify the integration
After the integration is complete, you can verify its success using methods such as hijacking impersonation or fault injection testing. For more information, see the Verify Successful Network Library Integration document.
5. Summary
By implementing the gRPC NameResolver interface, you can integrate HTTPDNS into your gRPC client. This approach provides the following advantages:
Simple implementation: You only need to implement the NameResolver interface to integrate the HTTPDNS service.
High versatility: This method works for various gRPC scenarios and is compatible with security mechanisms such as certificate validation and Server Name Indication (SNI).
High availability: It supports automatic load balancing and failover across multiple IP addresses. If HTTPDNS resolution fails, it automatically falls back to the system DNS.
Performance optimization: It helps prevent DNS hijacking and improves resolution speed and success rates.