Node.jsプログラミング:dnsPromises.resolveTxt()の代替とエラー対策

2025-05-27

dnsPromises.resolveTxt() は、Node.jsの dns モジュールが提供する非同期処理のためのAPIの一つです。具体的には、指定されたホスト名(例えば、example.com)に関連付けられた TXT (Text) レコード をDNSサーバーに問い合わせ、その結果をPromiseとして返します。

TXTレコードとは?

TXTレコードは、ドメイン名に関する任意のテキスト情報をDNSサーバーに登録するために使用されます。様々な用途があり、例えば以下のようなものがあります。

  • 送信ドメイン認証 (SPF, DKIM)
    メール送信元の詐称を防ぐための認証技術で、ポリシーや署名情報をTXTレコードに記述します。
  • ドメイン所有権の認証 (Domain Verification)
    Google Workspaceや他のサービスでドメインの所有権を証明するために使われます。

dnsPromises.resolveTxt() の動作

この関数を呼び出すと、Node.jsはバックグラウンドでDNSサーバーに対して指定されたホスト名のTXTレコードを問い合わせます。この問い合わせは非同期的に行われるため、Node.jsのイベントループをブロックしません。

処理が完了すると、Promiseが以下のいずれかの状態で解決(resolve)または拒否(reject)されます。

  • 拒否 (Reject)
    DNSサーバーへの問い合わせに失敗した場合(例:ホスト名が存在しない、DNSサーバーが応答しないなど)、Promiseは拒否され、エラーオブジェクトが提供されます。
  • 解決 (Resolve)
    DNSサーバーからTXTレコードが正常に返ってきた場合、Promiseは解決され、その結果として TXTレコードの配列 が提供されます。各TXTレコードは、一つまたは複数の文字列を含む配列として表現されます。これは、TXTレコードが複数のパートに分割されている場合があるためです。

使用例

const dns = require('node:dns').promises;

async function getTxtRecords(hostname) {
  try {
    const records = await dns.resolveTxt(hostname);
    console.log(`TXTレコード (${hostname}):`, records);
    records.forEach(record => {
      console.log('  -', record.join(' ')); // 複数の文字列をスペースで結合して表示
    });
  } catch (err) {
    console.error(`TXTレコードの取得に失敗しました (${hostname}):`, err);
  }
}

getTxtRecords('example.com');
getTxtRecords('google.com');
getTxtRecords('invalid-hostname-that-does-not-exist.com');

この例では、getTxtRecords という非同期関数を定義し、dns.resolveTxt()await を使って呼び出しています。成功した場合は、取得したTXTレコードの内容をコンソールに出力し、失敗した場合はエラーメッセージを出力します。



一般的なエラー

  1. Error: getaddrinfo ENOTFOUND <hostname>:

    • 原因
      指定されたホスト名 (<hostname>) がDNSサーバーで見つからなかった場合に発生します。タイプミス、存在しないドメイン名、またはDNSサーバーがホスト名を解決できない状況などが考えられます。
    • トラブルシューティング
      • ホスト名が正しく入力されているか再度確認してください。
      • 他のツール(例: ping, nslookup, dig)を使って、指定されたホスト名が正しく解決できるか確認してください。
      • ネットワーク接続に問題がないか確認してください。
      • 使用しているDNSサーバーに問題がないか確認してください。
  2. Error: queryTxt ENODATA <hostname>:

    • 原因
      DNSサーバーはホスト名を解決できたものの、そのホスト名に関連付けられたTXTレコードが存在しない場合に発生します。
    • トラブルシューティング
      • 指定されたホスト名にTXTレコードが実際に登録されているか、DNS管理ツールなどで確認してください。
      • TXTレコードの登録が最近行われた場合、DNSの伝播に時間がかかっている可能性があります。しばらく待ってから再度試してみてください。
  3. Error: queryTxt ESERVFAIL <hostname>:

    • 原因
      DNSサーバーがクエリに応答できなかった場合に発生します。サーバー側の問題、ネットワークの問題、または一時的な負荷などが考えられます。
    • トラブルシューティング
      • しばらく時間を置いてから再度試してみてください。
      • 他のDNSサーバー(例: Google Public DNS (8.8.8.8, 8.8.4.4) や Cloudflare DNS (1.1.1.1))を使用するように設定を変更して、問題が特定のDNSサーバーに依存しないか確認してください。
      • ネットワーク接続やファイアウォールの設定を確認してください。
  4. TypeError: dns.promises.resolveTxt is not a function:

    • 原因
      使用しているNode.jsのバージョンが dns.promises.resolveTxt() をサポートしていない場合に発生します。Promiseベースの dns APIは、Node.jsの比較的新しいバージョンで導入されました。
    • トラブルシューティング
      • Node.jsのバージョンを確認し、14.17.0以降(推奨)であることを確認してください。古いバージョンの場合は、Node.jsを最新の安定版にアップデートしてください。
  5. Promiseがハングする(解決も拒否もされない):

    • 原因
      まれに、DNSサーバーからの応答が途中で止まってしまい、Promiseがいつまでも解決または拒否されないことがあります。ネットワークの不安定さやDNSサーバーのタイムアウト設定などが考えられます。
    • トラブルシューティング
      • タイムアウト処理を実装することを検討してください。Promise.race() を使用して、一定時間内に結果が得られない場合はPromiseを拒否するようにすることができます。
      • ネットワーク接続の状態を確認してください。
      • 別のDNSサーバーを試してみてください。

トラブルシューティングの一般的なヒント

  • Node.jsのドキュメントを参照する
    Node.jsの公式ドキュメントには、dns モジュールに関する詳細な情報やエラーの説明が記載されています。
  • ログ出力を追加する
    問題が発生している箇所や関連する情報をログに出力するようにコードを変更することで、より詳細なデバッグ情報が得られることがあります。
  • DNSサーバーを変更してみる
    現在使用しているDNSサーバーに問題がある可能性があるため、別のDNSサーバーを試してみるのも有効な手段です。
  • ファイアウォールの設定を確認する
    ファイアウォールがDNSクエリ(通常はポート53番のUDPおよびTCP)をブロックしていないか確認してください。
  • ネットワーク接続を確認する
    インターネット接続が正常に機能しているか確認してください。
  • 他のDNSツールで確認する
    nslookupdig などのコマンドラインツールを使用して、Node.jsのプログラム外からDNSの解決を試してみることで、問題がNode.jsのコードにあるのか、それともDNSサーバーやネットワークにあるのかを切り分けることができます。
  • エラーメッセージをよく読む
    エラーメッセージには、問題の原因に関する重要な情報が含まれています。


基本的なTXTレコードの取得と表示

これは、指定されたホスト名のTXTレコードを取得し、その内容をコンソールに表示する最も基本的な例です。

const dns = require('node:dns').promises;

async function getAndDisplayTxtRecords(hostname) {
  try {
    const records = await dns.resolveTxt(hostname);
    console.log(`ホスト名: ${hostname}`);
    if (records.length > 0) {
      console.log('TXTレコード:');
      records.forEach(record => {
        console.log(`  - ${record.join(' ')}`); // 複数の文字列をスペースで結合して表示
      });
    } else {
      console.log('  TXTレコードは見つかりませんでした。');
    }
  } catch (err) {
    console.error(`TXTレコードの取得に失敗しました (${hostname}):`, err.message);
  }
}

// 例
getAndDisplayTxtRecords('example.com');
getAndDisplayTxtRecords('google.com');
getAndDisplayTxtRecords('invalid-hostname-that-does-not-exist.com');

この例では、getAndDisplayTxtRecords 関数がホスト名を受け取り、dns.resolveTxt() を使ってTXTレコードを取得します。成功した場合、取得したレコードの内容をコンソールに表示します。TXTレコードが存在しない場合は、その旨を通知します。エラーが発生した場合は、エラーメッセージを表示します。

特定のTXTレコードの内容を検索する

取得したTXTレコードの中から、特定の内容を含むレコードを検索する例です。例えば、ドメイン認証に使われる特定のプレフィックスを持つレコードを探す場合などに役立ちます。

const dns = require('node:dns').promises;

async function findTxtRecordStartsWith(hostname, prefix) {
  try {
    const records = await dns.resolveTxt(hostname);
    const foundRecords = records.filter(record =>
      record.some(part => part.startsWith(prefix))
    );

    console.log(`ホスト名: ${hostname}`);
    if (foundRecords.length > 0) {
      console.log(`'${prefix}' で始まるTXTレコード:`);
      foundRecords.forEach(record => {
        console.log(`  - ${record.join(' ')}`);
      });
    } else {
      console.log(`'${prefix}' で始まるTXTレコードは見つかりませんでした。`);
    }
  } catch (err) {
    console.error(`TXTレコードの取得に失敗しました (${hostname}):`, err.message);
  }
}

// 例
findTxtRecordStartsWith('google.com', 'v=spf1'); // SPFレコードを探す
findTxtRecordStartsWith('example.com', 'verification='); // 特定の認証レコードを探す

この例では、findTxtRecordStartsWith 関数がホスト名とプレフィックスを受け取り、取得したTXTレコードの中から、いずれかの部分が指定されたプレフィックスで始まるレコードを抽出して表示します。

複数のホスト名のTXTレコードを同時に取得する

const dns = require('node:dns').promises;

async function getMultipleTxtRecords(hostnames) {
  try {
    const results = await Promise.all(
      hostnames.map(hostname => dns.resolveTxt(hostname))
    );

    results.forEach((records, index) => {
      const hostname = hostnames[index];
      console.log(`ホスト名: ${hostname}`);
      if (records.length > 0) {
        console.log('TXTレコード:');
        records.forEach(record => {
          console.log(`  - ${record.join(' ')}`);
        });
      } else {
        console.log('  TXTレコードは見つかりませんでした。');
      }
      console.log('---');
    });
  } catch (err) {
    console.error('複数のTXTレコードの取得に失敗しました:', err.message);
  }
}

// 例
const hostnames = ['example.com', 'cloudflare.com', 'nodejs.org'];
getMultipleTxtRecords(hostnames);

この例では、getMultipleTxtRecords 関数がホスト名の配列を受け取り、それぞれのホスト名に対して dns.resolveTxt() を呼び出し、その結果を Promise.all() で待ちます。すべてのPromiseが解決された後、各ホスト名のTXTレコードを表示します。

エラーハンドリングの例

try...catch ブロックを使って、発生する可能性のあるエラーを適切に処理する方法を示す例です。

const dns = require('node:dns').promises;

async function getTxtRecordsWithErrorHandler(hostname) {
  try {
    const records = await dns.resolveTxt(hostname);
    console.log(`ホスト名: ${hostname}`, records);
  } catch (err) {
    if (err.code === 'ENOTFOUND') {
      console.error(`エラー: ホスト名 '${hostname}' は見つかりませんでした。`);
    } else if (err.code === 'ENODATA') {
      console.error(`エラー: ホスト名 '${hostname}' にはTXTレコードがありません。`);
    } else {
      console.error(`エラー: ${hostname} のTXTレコード取得中に予期しないエラーが発生しました - ${err.message}`);
    }
  }
}

// 例
getTxtRecordsWithErrorHandler('nonexistent-domain-example.com');
getTxtRecordsWithErrorHandler('example.com'); // TXTレコードがない場合

この例では、エラーオブジェクトの code プロパティをチェックすることで、特定のエラータイプ(ENOTFOUND, ENODATA など)に基づいて異なるエラーメッセージを表示しています。これにより、より具体的なエラー処理を行うことができます。



コールバックベースの dns モジュール (dns.resolveTxt)

Promiseベースの dnsPromises APIが導入される以前から、Node.jsはコールバックベースの dns モジュールを提供していました。dns.resolveTxt() 関数もその一つです。

const dns = require('node:dns');

dns.resolveTxt('example.com', (err, records) => {
  if (err) {
    console.error('TXTレコードの取得に失敗しました:', err);
    return;
  }
  console.log('TXTレコード (example.com):', records);
  records.forEach(record => {
    console.log('  -', record.join(' '));
  });
});

dns.resolveTxt('invalid-hostname-that-does-not-exist.com', (err, records) => {
  if (err) {
    console.error('TXTレコードの取得に失敗しました:', err);
    return;
  }
  console.log('TXTレコード (存在しないホスト名):', records);
});

特徴

  • Promiseへの変換
    コールバックベースのAPIは、util.promisify 関数を使ってPromiseベースに変換することができます。

    const dns = require('node:dns');
    const { promisify } = require('node:util');
    
    const resolveTxtPromise = promisify(dns.resolveTxt);
    
    async function getTxtRecordsWithPromise(hostname) {
      try {
        const records = await resolveTxtPromise(hostname);
        console.log(`TXTレコード (${hostname}):`, records);
        records.forEach(record => {
          console.log('  -', record.join(' '));
        });
      } catch (err) {
        console.error(`TXTレコードの取得に失敗しました (${hostname}):`, err);
      }
    }
    
    getTxtRecordsWithPromise('example.com');
    getTxtRecordsWithPromise('google.com');
    
  • コールバック関数
    結果はコールバック関数を通じて返されます。コールバック関数の最初の引数はエラーオブジェクト (err) で、成功した場合は null になります。2番目の引数 (records) は、成功した場合にTXTレコードの配列として提供されます。

  • 非同期処理
    dns.resolveTxt() は非同期的に動作し、Node.jsのイベントループをブロックしません。

メリットとデメリット

  • デメリット
    • コールバック地獄(ネストが深くなる)に陥りやすい場合があります。
    • エラーハンドリングがPromiseベースに比べて煩雑になることがあります。
  • メリット
    • PromiseベースのAPIが利用できない古いNode.js環境でも動作します。
    • util.promisify を使用することで、Promiseベースのコードに移行することも可能です。

サードパーティのDNS解決ライブラリ

Node.jsの標準モジュール以外にも、DNS解決を行うためのサードパーティのライブラリがいくつか存在します。これらのライブラリは、より高度な機能や柔軟な設定を提供している場合があります。

例として、以下のようなライブラリが考えられます(具体的な利用は各ライブラリのドキュメントを参照してください)。

  • dnscache
    DNSルックアップの結果をキャッシュすることで、パフォーマンスを向上させることができます。
  • native-dns
    低レベルのDNSプロトコルを直接操作するためのライブラリで、より詳細な制御が可能です。
  • dns-lookup
    より柔軟なDNSルックアップオプションを提供します。

特徴

  • コミュニティサポート
    活発なコミュニティを持つライブラリであれば、ドキュメントやサポートが充実している可能性があります。
  • PromiseベースのAPI
    多くのモダンなライブラリは、PromiseベースのAPIを提供しており、非同期処理を扱いやすくなっています。
  • 追加機能
    標準の dns モジュールにはない機能(例: カスタムDNSサーバーの指定、タイムアウト設定、キャッシュ機能など)を提供することがあります。

メリットとデメリット

  • デメリット
    • 外部依存関係が増えるため、プロジェクトの管理が複雑になる可能性があります。
    • ライブラリの品質やメンテナンス状況に依存します。
    • 標準モジュールに比べて学習コストがかかる場合があります。
  • メリット
    • 標準モジュールよりも高度な機能を利用できる場合があります。
    • パフォーマンスの最適化や柔軟な設定が可能な場合があります。
    • PromiseベースのAPIを提供している場合、非同期処理が容易になります。

どちらを選ぶべきか?

  • より高度な機能が必要な場合
    サードパーティのDNS解決ライブラリを検討する価値があります。ただし、依存関係の追加やライブラリの特性を理解する必要があります。
  • 古いNode.js環境を使用している場合
    コールバックベースの dns.resolveTxt() を直接使用するか、util.promisify でPromiseに変換して使用することを検討してください。
  • 最新のNode.js環境を使用している場合
    dnsPromises.resolveTxt() を使用するのが最もシンプルでモダンな方法です。async/await との組み合わせも容易で、コードが読みやすくなります。