Node.js最佳实践

本文介绍如何在Node.js应用开发中集成使用HTTPDNS。

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,广泛应用于服务端开发、API构建、微服务架构等场景。

我们提供Node.js环境下的HTTPDNS SDK:httpdns-nodejs-sdk。Node.js应用可以集成SDK,并参考Node.js最佳实践来使用HTTPDNS能力。

以下是SDK的使用说明和最佳实践:

一、快速入门

1.1 开通服务

请参考快速入门开通HTTPDNS。

1.2 获取配置

请参考开发配置EMAS控制台开发配置中获取AccountId/SecretKey等信息,用于初始化SDK。

二、安装

Node.js SDK及接入示例下载:httpdns-nodejs-sdk.zip

三、配置和使用

3.1 初始化配置

应用启动后,需要先初始化SDK,才能调用HTTPDNS能力。初始化主要是配置AccountId/SecretKey等信息及功能开关。示例代码如下:

import { createClient } from 'httpdns-nodejs-sdk';

const client = createClient({
  accountId: 'your-account-id',
  secretKey: 'your-secret-key',
});

3.1.1 日志配置

应用开发过程中,如果要输出HTTPDNS的日志,可以在初始化时配置logger,示例代码如下:

const client = createClient({
  ...,
  logger: console
});

3.2 域名解析

3.2.1 同步非阻塞解析(推荐)

推荐使用同步非阻塞解析方法,该方法立即返回缓存结果,不阻塞程序执行,示例代码如下:

function resolve() {
  const result = client.getHttpDnsResultForHostSyncNonBlocking('www.aliyun.com');
  if (result) {
    console.log('解析结果:', result.ipv4);
    console.log('域名:', result.domain);
    console.log('IPv6:', result.ipv6);
    console.log('TTL:', result.ttl);
    console.log('成功:', result.success);
  } else {
    console.log('缓存未命中,异步解析中...');
    // 异步解析已在后台启动,下次调用将返回结果
  }
}

3.2.2 同步解析

当需要确保获取解析结果时,可以使用同步解析方法,该方法会等待解析完成,示例代码如下:

async function resolve() {
  try {
    const result = await client.getHttpDnsResultForHostSync('www.aliyun.com');
    console.log('域名:', result.domain);
    console.log('IPv4:', result.ipv4);
    console.log('IPv6:', result.ipv6);
    console.log('TTL:', result.ttl);
    console.log('成功:', result.success);
  } catch (error) {
    console.error('解析失败:', error);
  }
}

四、Node.js最佳实践

4.1 原理说明

  1. 在应用中创建自定义DNS lookup函数

  2. 需要使用HTTPDNS解析的请求,配置使用自定义lookup

  3. lookup函数中获取请求域名,通过HTTPDNS解析为IP

  4. lookup函数中返回解析后的IP,用于发起真正的请求,从而避免走LocalDNS进行解析

4.2 网络库集成

4.2.1 Axios集成

Axios集成通过Agent方式实现,核心步骤如下:

步骤1:创建自定义lookup函数

function createHTTPDNSLookup(httpdnsClient) {
  return (hostname, options, callback) => {
    // 标准化参数
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
  
    // 确保callback存在
    if (typeof callback !== 'function') {
      throw new Error('callback must be a function');
    }
  
    // 使用 HTTPDNS 解析域名
    const result = this.httpdnsClient.getHttpDnsResultForHostSyncNonBlocking(hostname);
  
    if (result && result.success) {
      const hasIPv4 = result.ipv4 && result.ipv4.length > 0;
      const hasIPv6 = result.ipv6 && result.ipv6.length > 0;
  
      if (hasIPv4 || hasIPv6) {
        if (options && options.all) {
          // 返回所有IP
          const addresses = [
            ...(hasIPv4 ? result.ipv4.map(ip => ({ address: ip, family: 4 })) : []),
            ...(hasIPv6 ? result.ipv6.map(ip => ({ address: ip, family: 6 })) : [])
          ];
          console.log(`[DNS Lookup] HTTPDNS 解析成功: ${hostname} -> 返回所有IP (${addresses.length}个)`);
          callback(null, addresses);
        } else {
          // 优先IPv4,其次IPv6
          if (hasIPv4) {
            console.log(`[DNS Lookup] HTTPDNS 解析成功: ${hostname} -> ${result.ipv4[0]} (IPv4)`);
            callback(null, result.ipv4[0], 4);
          } else {
            console.log(`[DNS Lookup] HTTPDNS 解析成功: ${hostname} -> ${result.ipv6[0]} (IPv6)`);
            callback(null, result.ipv6[0], 6);
          }
        }
        return;
      }
    }
  
    console.log(`[DNS Lookup] HTTPDNS 无可用IP,降级到系统 DNS: ${hostname}`);
    dns.lookup(hostname, options, callback);
  };
}

步骤2:创建自定义Agent

const https = require('https');
const httpsAgent = new https.Agent({
  lookup: createHTTPDNSLookup(httpdnsClient),
  keepAlive: true,
  maxSockets: 10
});

步骤3:创建Axios实例

const axios = require('axios');

const instance = axios.create({
  httpsAgent: httpsAgent,
  timeout: 10000
});

// 使用
const response = await instance.get('https://www.aliyun.com');

4.2.2 urllib集成

urllib集成同样使用Agent方式,核心步骤如下:

步骤1:创建自定义lookup函数

function createHTTPDNSLookup(httpdnsClient) {
  return (hostname, options, callback) => {
    // 标准化参数
    if (typeof options === 'function') {
      callback = options;
      options = {};
    }
  
    // 确保callback存在
    if (typeof callback !== 'function') {
      throw new Error('callback must be a function');
    }
  
    // 使用 HTTPDNS 解析域名
    const result = this.httpdnsClient.getHttpDnsResultForHostSyncNonBlocking(hostname);
  
    if (result && result.success) {
      const hasIPv4 = result.ipv4 && result.ipv4.length > 0;
      const hasIPv6 = result.ipv6 && result.ipv6.length > 0;
  
      if (hasIPv4 || hasIPv6) {
        if (options && options.all) {
          // 返回所有IP
          const addresses = [
            ...(hasIPv4 ? result.ipv4.map(ip => ({ address: ip, family: 4 })) : []),
            ...(hasIPv6 ? result.ipv6.map(ip => ({ address: ip, family: 6 })) : [])
          ];
          console.log(`[DNS Lookup] HTTPDNS 解析成功: ${hostname} -> 返回所有IP (${addresses.length}个)`);
          callback(null, addresses);
        } else {
          // 优先IPv4,其次IPv6
          if (hasIPv4) {
            console.log(`[DNS Lookup] HTTPDNS 解析成功: ${hostname} -> ${result.ipv4[0]} (IPv4)`);
            callback(null, result.ipv4[0], 4);
          } else {
            console.log(`[DNS Lookup] HTTPDNS 解析成功: ${hostname} -> ${result.ipv6[0]} (IPv6)`);
            callback(null, result.ipv6[0], 6);
          }
        }
        return;
      }
    }
  
    console.log(`[DNS Lookup] HTTPDNS 无可用IP,降级到系统 DNS: ${hostname}`);
    dns.lookup(hostname, options, callback);
  };
}

步骤2:创建自定义Agent

const urllib = require('urllib');
const https = require('https');

const httpsAgent = new https.Agent({
  lookup: createHTTPDNSLookup(httpdnsClient),
  keepAlive: true,
  maxSockets: 10
});

步骤3:发起请求

async function request(url, options = {}) {
  // 根据URL类型选择Agent
  if (url.startsWith('https://')) {
    options.httpsAgent = httpsAgent;
  } else {
    options.agent = httpAgent; // 类似创建httpAgent
  }
  
  options.timeout = options.timeout || 10000;
  
  try {
    return await urllib.request(url, options);
  } catch (error) {
    // 自动重试逻辑
    if (isConnectionError(error) && !options._retried) {
      options._retried = true;
      delete options.httpsAgent; // 使用系统DNS重试
      return await urllib.request(url, options);
    }
    throw error;
  }
}

五、API

5.1 初始化

初始化配置,在应用启动时调用。

const { createClient } = require('httpdns-nodejs-sdk');

const client = createClient({
  accountId: 'your-account-id',
  secretKey: 'your-secret-key'
});

参数:

参数名

类型

是否必须

功能

accountId

String

必选参数

Account ID

secretKey

String

可选参数

加签密钥

bootstrapIPs

Array

可选参数

启动IP列表

timeout

Number

可选参数

解析超时时间

maxRetries

Number

可选参数

最大重试次数

enableHTTPS

Boolean

可选参数

是否使用HTTPS

enableCache

Boolean

可选参数

是否缓存IP

logger

Object

可选参数

日志记录器

5.2 同步解析

解析指定域名。

const result = await client.getHttpDnsResultForHostSync('www.aliyun.com');
console.log('解析结果:', result);

参数:

参数名

类型

是否必须

功能

domain

String

必选参数

域名

options

Object

可选参数

解析选项

返回JSON字段说明:

字段名

类型

功能

domain

String

域名

ipv4

Array

v4类型的ip列表

ipv6

Array

v6类型的ip列表

ttl

Number

过期时间

success

Boolean

解析是否成功

timestamp

Date

解析时间戳

5.3 同步非阻塞解析

立即返回缓存结果,不阻塞等待。

const result = client.getHttpDnsResultForHostSyncNonBlocking('www.aliyun.com');
if (result) {
  console.log('缓存结果:', result.ipv4);
}

5.4 客户端管理

获取客户端状态和管理客户端生命周期。

// 检查客户端健康状态
const isHealthy = client.isHealthy();

// 获取当前服务IP列表
const serviceIPs = client.getServiceIPs();

// 手动更新服务IP
await client.updateServiceIPs();

// 关闭客户端
await client.close();

六、总结

本文介绍了Node.js环境下HTTPDNS SDK的使用方法和最佳实践。通过Agent方式集成网络库,可以实现高性能、高可用的域名解析服务。主要特点包括:

  1. 简单易用:提供简洁的API接口,支持同步和同步非阻塞解析

  2. 高可用性:支持故障转移和降级策略,确保服务稳定性

  3. 性能优化:内置缓存机制和连接复用,提升解析效率

  4. 安全可靠:支持鉴权解析和HTTPS通信,保障数据安全

通过遵循本文的最佳实践建议,开发者可以在Node.js应用中高效地集成HTTPDNS服务。