Agent

Swift向けのPromiseベースHTTPクライアント。Combineフレームワークとの統合によりリアクティブプログラミングパターンを支援。URLRequestオブジェクトベースのシンプルな設定、関数型プログラミングアプローチ、Swift Concurrency対応。

HTTPクライアントNode.jsブラウザ軽量プラグイン非同期

ライブラリ

SuperAgent

概要

SuperAgentは「エレガントなHTTP APIライブラリ」として開発された、Node.jsとブラウザの両方で動作するHTTPクライアントライブラリです。「小さく進歩的なクライアントサイドHTTPリクエストライブラリ」をコンセプトに、同じAPIでサーバーサイドとクライアントサイドの両方でHTTP通信を実装可能。軽量性(minified+gzipped で約50KB)と表現力豊かなチェイン可能なAPIを特徴とし、プラグインエコシステムによる拡張性と柔軟な非同期処理パターンで、現代のWeb開発に最適なHTTPクライアントとしての地位を確立しています。

詳細

SuperAgent 2025年版はNode.jsとブラウザのクロスプラットフォーム対応を実現した軽量HTTPクライアントの決定版として進化を続けています。TJ Holowaychuk氏により開発され、現在はlads.jsチームによって維持されている10年以上の開発実績を誇るライブラリです。Express.jsとの親和性が高く、同一のAPIでサーバーサイド(Node.js core httpモジュール使用)とクライアントサイド(XHR使用)の両方でHTTP通信を実装可能。コールバック、Promise、async/awaitの全ての非同期パターンをサポートし、豊富なプラグインエコシステムで機能拡張が容易です。

主な特徴

  • クロスプラットフォーム対応: Node.jsとブラウザで同一APIを使用
  • 軽量設計: minified + gzipped で約50KBの軽量実装
  • 表現力豊かなAPI: メソッドチェインによる直感的なHTTPリクエスト記述
  • 多様な非同期パターン: callback、Promise、async/await対応
  • 豊富なプラグイン: 機能拡張とカスタマイズが容易
  • 自動コンテンツタイプ処理: JSON、form-dataの自動フォーマット

メリット・デメリット

メリット

  • Node.jsとブラウザで同一APIが使用でき、学習コストとコード重複を削減
  • 軽量でありながら豊富な機能を提供し、パフォーマンスと機能性を両立
  • Express.jsエコシステムとの優れた統合性とコミュニティサポート
  • メソッドチェインによる直感的で読みやすいコード記述
  • プラグインシステムによる柔軟な機能拡張とカスタマイズ
  • callback、Promise、async/awaitの全ての非同期パターンをサポート

デメリット

  • 単機能なHTTPクライアントのため複雑な認証やミドルウェア機能は限定的
  • プラグインに依存する機能が多く、適切なプラグイン選択が必要
  • 大規模アプリケーションでは axios や fetch の方が標準化されている
  • TypeScript型定義は外部ライブラリに依存し、型安全性に課題
  • HTTP/2サポートが限定的で、最新プロトコル対応に制約
  • ブラウザサポートでWeakRefとBigIntポリフィルが必要(Opera 85、iOS Safari 12.2-12.5)

参考ページ

書き方の例

インストールと基本セットアップ

# SuperAgentのインストール
npm install superagent

# TypeScript型定義も追加(TypeScript使用時)
npm install @types/superagent --save-dev

# yarnの場合
yarn add superagent
yarn add @types/superagent --dev

# Node.js環境での確認
node -e "const request = require('superagent'); console.log('SuperAgent installed');"

基本的なリクエスト(GET/POST/PUT/DELETE)

const request = require('superagent');

// 基本的なGETリクエスト
request
  .get('https://api.example.com/users')
  .end((err, res) => {
    if (err) {
      console.error('エラー:', err);
      return;
    }
    console.log('ステータス:', res.status);
    console.log('ヘッダー:', res.headers);
    console.log('データ:', res.body); // 自動的にJSONパース
  });

// クエリパラメータ付きGETリクエスト
request
  .get('https://api.example.com/users')
  .query({ page: 1, limit: 10, sort: 'created_at' })
  .set('Accept', 'application/json')
  .set('User-Agent', 'MyApp/1.0')
  .end((err, res) => {
    if (err) throw err;
    console.log(`取得URL: ${res.request.url}`);
    console.log(`ユーザー数: ${res.body.length}`);
  });

// POSTリクエスト(JSON送信)
const userData = {
  name: '田中太郎',
  email: '[email protected]',
  age: 30
};

request
  .post('https://api.example.com/users')
  .send(userData) // 自動的にJSON形式で送信
  .set('Accept', 'application/json')
  .set('Authorization', 'Bearer your-jwt-token')
  .end((err, res) => {
    if (err) {
      console.error('ユーザー作成エラー:', err.message);
      if (res) {
        console.error('ステータス:', res.status);
        console.error('エラー詳細:', res.body);
      }
      return;
    }
    
    if (res.status === 201) {
      console.log('ユーザー作成成功:', res.body);
      console.log(`新規ユーザーID: ${res.body.id}`);
    }
  });

// POSTリクエスト(フォームデータ送信)
request
  .post('https://api.example.com/login')
  .type('form') // application/x-www-form-urlencoded
  .send({ username: 'testuser', password: 'secret123' })
  .end((err, res) => {
    if (err) throw err;
    console.log('ログイン結果:', res.body);
  });

// PUTリクエスト(データ更新)
const updatedData = {
  name: '田中次郎',
  email: '[email protected]'
};

request
  .put('https://api.example.com/users/123')
  .send(updatedData)
  .set('Authorization', 'Bearer your-jwt-token')
  .end((err, res) => {
    if (err) throw err;
    console.log('ユーザー更新完了:', res.body);
  });

// DELETEリクエスト
request
  .delete('https://api.example.com/users/123')
  .set('Authorization', 'Bearer your-jwt-token')
  .end((err, res) => {
    if (err) throw err;
    if (res.status === 204) {
      console.log('ユーザー削除完了');
    }
  });

// レスポンス情報の詳細確認
request
  .get('https://api.example.com/test')
  .end((err, res) => {
    if (err) throw err;
    
    console.log('=== レスポンス詳細 ===');
    console.log(`ステータス: ${res.status} ${res.statusText}`);
    console.log(`Content-Type: ${res.headers['content-type']}`);
    console.log(`データサイズ: ${res.headers['content-length']} bytes`);
    console.log(`レスポンス時間: ${res.duration}ms`);
    console.log(`リクエストURL: ${res.request.url}`);
    console.log(`リクエストメソッド: ${res.request.method}`);
  });

Promise と async/await での非同期処理

const request = require('superagent');

// Promiseチェインを使用した処理
request
  .get('https://api.example.com/users')
  .then(res => {
    console.log('ユーザー取得成功:', res.body);
    return res.body;
  })
  .then(users => {
    console.log(`総ユーザー数: ${users.length}`);
    return users.filter(user => user.active);
  })
  .then(activeUsers => {
    console.log(`アクティブユーザー数: ${activeUsers.length}`);
  })
  .catch(err => {
    console.error('エラー:', err.message);
    if (err.response) {
      console.error('ステータス:', err.response.status);
      console.error('エラーボディ:', err.response.body);
    }
  });

// async/await を使用した処理
async function fetchUserData() {
  try {
    // ユーザー一覧取得
    const usersResponse = await request
      .get('https://api.example.com/users')
      .query({ active: true, limit: 50 })
      .set('Authorization', 'Bearer your-token');
    
    console.log(`アクティブユーザー: ${usersResponse.body.length}人`);
    
    // 各ユーザーの詳細情報を並行取得
    const userDetails = await Promise.all(
      usersResponse.body.slice(0, 5).map(async (user) => {
        const detailResponse = await request
          .get(`https://api.example.com/users/${user.id}`)
          .set('Authorization', 'Bearer your-token');
        return detailResponse.body;
      })
    );
    
    console.log('詳細情報取得完了:', userDetails);
    
    // 新しいユーザー作成
    const newUser = await request
      .post('https://api.example.com/users')
      .send({
        name: '新規ユーザー',
        email: '[email protected]',
        department: 'engineering'
      })
      .set('Authorization', 'Bearer your-token');
    
    console.log('新規ユーザー作成:', newUser.body);
    return newUser.body;
    
  } catch (error) {
    console.error('ユーザーデータ処理エラー:', error.message);
    
    // エラーハンドリング
    if (error.response) {
      console.error(`HTTPエラー: ${error.response.status}`);
      console.error('エラーレスポンス:', error.response.body);
    } else if (error.code === 'ECONNREFUSED') {
      console.error('サーバーへの接続が拒否されました');
    } else if (error.timeout) {
      console.error('リクエストがタイムアウトしました');
    }
    
    throw error;
  }
}

// 関数実行
fetchUserData()
  .then(result => console.log('処理完了:', result))
  .catch(err => console.error('最終エラー:', err.message));

// 複数のAPIエンドポイントからの並行データ取得
async function fetchDashboardData() {
  try {
    console.log('ダッシュボードデータ取得開始...');
    
    const [usersRes, postsRes, statsRes] = await Promise.all([
      request.get('https://api.example.com/users').set('Authorization', 'Bearer token'),
      request.get('https://api.example.com/posts').set('Authorization', 'Bearer token'),
      request.get('https://api.example.com/stats').set('Authorization', 'Bearer token')
    ]);
    
    const dashboardData = {
      users: usersRes.body,
      posts: postsRes.body,
      statistics: statsRes.body,
      loadTime: Date.now()
    };
    
    console.log('ダッシュボードデータ取得完了');
    return dashboardData;
    
  } catch (error) {
    console.error('ダッシュボードデータ取得失敗:', error.message);
    throw error;
  }
}

// リトライ機能付きリクエスト
async function retryableRequest(url, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      console.log(`試行 ${attempt}/${maxRetries}: ${url}`);
      
      const response = await request
        .get(url)
        .timeout({ response: 5000, deadline: 10000 })
        .retry(2); // SuperAgent内蔵のリトライ機能
      
      console.log(`成功: ${response.status}`);
      return response.body;
      
    } catch (error) {
      console.log(`試行 ${attempt} 失敗:`, error.message);
      
      if (attempt === maxRetries) {
        console.error('最大試行回数に達しました');
        throw error;
      }
      
      // 指数バックオフ
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`${delay}ms 待機後に再試行...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

高度な設定とカスタマイズ(ヘッダー、認証、タイムアウト等)

const request = require('superagent');

// カスタムヘッダーとリクエスト設定
request
  .get('https://api.example.com/data')
  .set('User-Agent', 'MyApp/2.0 (SuperAgent)')
  .set('Accept', 'application/json')
  .set('Accept-Language', 'ja-JP,en-US;q=0.8')
  .set('X-API-Version', 'v2.1')
  .set('X-Request-ID', `req-${Date.now()}`)
  .set('Cache-Control', 'no-cache')
  .end((err, res) => {
    if (err) throw err;
    console.log('リクエストヘッダー:', res.request.header);
    console.log('レスポンスヘッダー:', res.headers);
  });

// Basic認証
request
  .get('https://api.example.com/private')
  .auth('username', 'password') // Basic認証
  .end((err, res) => {
    if (err) throw err;
    console.log('認証済みデータ:', res.body);
  });

// Bearer Token認証
request
  .get('https://api.example.com/protected')
  .set('Authorization', 'Bearer your-jwt-token-here')
  .end((err, res) => {
    if (err) throw err;
    console.log('保護されたリソース:', res.body);
  });

// API Key認証
request
  .get('https://api.example.com/service')
  .set('X-API-Key', 'your-api-key-here')
  .set('X-Secret-Key', 'your-secret-key')
  .end((err, res) => {
    if (err) throw err;
    console.log('APIキー認証成功:', res.body);
  });

// タイムアウト設定
request
  .get('https://api.example.com/slow-endpoint')
  .timeout({
    response: 5000,  // サーバーからの応答まで5秒
    deadline: 10000  // リクエスト全体で10秒
  })
  .end((err, res) => {
    if (err) {
      if (err.timeout) {
        console.error('タイムアウトエラー:', err.message);
      } else {
        console.error('その他のエラー:', err.message);
      }
      return;
    }
    console.log('応答時間内に完了:', res.body);
  });

// プロキシ設定
request
  .get('https://api.example.com/data')
  .proxy('http://proxy.company.com:8080')
  .end((err, res) => {
    if (err) throw err;
    console.log('プロキシ経由でアクセス:', res.body);
  });

// Cookie設定
request
  .get('https://api.example.com/user-data')
  .set('Cookie', 'session_id=abc123; user_pref=dark_mode; lang=ja')
  .end((err, res) => {
    if (err) throw err;
    console.log('Cookieを使用したリクエスト:', res.body);
    console.log('レスポンスCookie:', res.headers['set-cookie']);
  });

// SSL/TLS設定(Node.js環境)
const fs = require('fs');

// クライアント証明書を使用
request
  .get('https://secure-api.example.com/data')
  .cert(fs.readFileSync('./client-cert.pem'))
  .key(fs.readFileSync('./client-key.pem'))
  .ca(fs.readFileSync('./ca-cert.pem'))
  .end((err, res) => {
    if (err) throw err;
    console.log('クライアント証明書認証成功:', res.body);
  });

// SSL証明書検証の無効化(開発環境のみ)
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
request
  .get('https://self-signed.example.com/api')
  .end((err, res) => {
    if (err) throw err;
    console.log('自己署名証明書サイトへのアクセス:', res.body);
  });

// リダイレクト制御
request
  .get('https://api.example.com/redirect-endpoint')
  .redirects(5) // 最大5回のリダイレクト
  .end((err, res) => {
    if (err) throw err;
    console.log('最終URL:', res.request.url);
    console.log('リダイレクト回数:', res.redirects.length);
  });

// カスタムパーサー設定
request
  .get('https://api.example.com/xml-data')
  .set('Accept', 'application/xml')
  .parse((res, callback) => {
    let data = '';
    res.on('data', chunk => data += chunk);
    res.on('end', () => {
      try {
        // カスタムXMLパースロジック
        const parsedData = { raw: data, parsed: true };
        callback(null, parsedData);
      } catch (err) {
        callback(err, null);
      }
    });
  })
  .end((err, res) => {
    if (err) throw err;
    console.log('カスタムパース結果:', res.body);
  });

// レスポンス圧縮対応
request
  .get('https://api.example.com/large-data')
  .set('Accept-Encoding', 'gzip, deflate')
  .buffer(true) // レスポンスをバッファリング
  .parse(request.parse.json)
  .end((err, res) => {
    if (err) throw err;
    console.log('圧縮データ受信完了:', res.body);
  });

エラーハンドリングとリトライ機能

const request = require('superagent');

// 包括的なエラーハンドリング
function handleRequest(url, options = {}) {
  return new Promise((resolve, reject) => {
    request
      .get(url)
      .set(options.headers || {})
      .timeout(options.timeout || 10000)
      .end((err, res) => {
        if (err) {
          // エラータイプ別の詳細処理
          if (err.timeout) {
            console.error(`タイムアウトエラー: ${url}`);
            console.error(`設定タイムアウト: ${options.timeout || 10000}ms`);
            reject(new Error('REQUEST_TIMEOUT'));
          } else if (err.code === 'ECONNREFUSED') {
            console.error(`接続拒否: ${url}`);
            reject(new Error('CONNECTION_REFUSED'));
          } else if (err.code === 'ENOTFOUND') {
            console.error(`DNSエラー: ${url}`);
            reject(new Error('DNS_ERROR'));
          } else if (err.status >= 400 && err.status < 500) {
            console.error(`クライアントエラー: ${err.status} - ${url}`);
            console.error('エラーレスポンス:', err.response?.body);
            reject(new Error(`CLIENT_ERROR_${err.status}`));
          } else if (err.status >= 500) {
            console.error(`サーバーエラー: ${err.status} - ${url}`);
            console.error('エラーレスポンス:', err.response?.body);
            reject(new Error(`SERVER_ERROR_${err.status}`));
          } else {
            console.error(`未知のエラー: ${url}`, err.message);
            reject(err);
          }
        } else {
          resolve(res);
        }
      });
  });
}

// 使用例
async function safeApiCall() {
  try {
    const response = await handleRequest('https://api.example.com/data', {
      headers: { 'Authorization': 'Bearer token' },
      timeout: 15000
    });
    console.log('成功:', response.body);
  } catch (error) {
    switch (error.message) {
      case 'REQUEST_TIMEOUT':
        console.log('リクエストがタイムアウトしました。後で再試行してください。');
        break;
      case 'CONNECTION_REFUSED':
        console.log('サーバーが利用できません。システム管理者に連絡してください。');
        break;
      case 'DNS_ERROR':
        console.log('ネットワーク接続を確認してください。');
        break;
      default:
        console.log('予期しないエラーが発生しました:', error.message);
    }
  }
}

// SuperAgent内蔵のリトライ機能
request
  .get('https://api.example.com/unstable')
  .retry(3) // 最大3回リトライ
  .end((err, res) => {
    if (err) {
      console.error('3回リトライしても失敗:', err.message);
    } else {
      console.log('リトライ後に成功:', res.body);
    }
  });

// カスタムリトライロジック
async function retryRequest(url, maxRetries = 3, backoffFactor = 1000) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      console.log(`試行 ${attempt}/${maxRetries}: ${url}`);
      
      const response = await new Promise((resolve, reject) => {
        request
          .get(url)
          .timeout({ response: 5000, deadline: 10000 })
          .end((err, res) => {
            if (err) reject(err);
            else resolve(res);
          });
      });
      
      console.log(`成功: ${response.status}`);
      return response.body;
      
    } catch (error) {
      console.log(`試行 ${attempt} 失敗:`, error.message);
      
      // 致命的エラーの場合は即座に停止
      if (error.status === 401 || error.status === 403 || error.status === 404) {
        console.error('致命的エラーのため停止:', error.status);
        throw error;
      }
      
      if (attempt === maxRetries) {
        console.error('最大試行回数に達しました');
        throw error;
      }
      
      // 指数バックオフで待機
      const delay = backoffFactor * Math.pow(2, attempt - 1);
      console.log(`${delay}ms 待機後に再試行...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// エラー監視とログ記録
const errorLogger = {
  log: (error, url, method = 'GET') => {
    const logEntry = {
      timestamp: new Date().toISOString(),
      url: url,
      method: method,
      error: error.message,
      status: error.status,
      responseTime: error.response?.duration || null,
      stack: error.stack
    };
    
    console.error('API Error Log:', JSON.stringify(logEntry, null, 2));
    
    // 実際のプロダクションでは外部ログサービスに送信
    // await sendToLogService(logEntry);
  }
};

// ログ記録付きリクエスト
async function loggedRequest(method, url, data = null) {
  const startTime = Date.now();
  
  try {
    let req = request[method.toLowerCase()](url);
    
    if (data) {
      req = req.send(data);
    }
    
    const response = await req;
    const responseTime = Date.now() - startTime;
    
    console.log(`成功: ${method} ${url} - ${response.status} (${responseTime}ms)`);
    return response.body;
    
  } catch (error) {
    const responseTime = Date.now() - startTime;
    error.responseTime = responseTime;
    
    errorLogger.log(error, url, method);
    throw error;
  }
}

// 使用例
async function apiOperations() {
  try {
    // 通常のリクエスト
    const users = await loggedRequest('GET', 'https://api.example.com/users');
    
    // リトライ付きリクエスト
    const unstableData = await retryRequest('https://api.example.com/unstable', 3, 500);
    
    // 新規作成(エラーログ記録付き)
    const newUser = await loggedRequest('POST', 'https://api.example.com/users', {
      name: 'テストユーザー',
      email: '[email protected]'
    });
    
    console.log('全ての操作が成功しました');
    return { users, unstableData, newUser };
    
  } catch (error) {
    console.error('API操作中にエラーが発生:', error.message);
    throw error;
  }
}

ファイルアップロードとストリーミング

const request = require('superagent');
const fs = require('fs');
const path = require('path');

// ファイルアップロード(マルチパート)
function uploadFile(filePath, uploadUrl, additionalFields = {}) {
  return new Promise((resolve, reject) => {
    const fileName = path.basename(filePath);
    
    request
      .post(uploadUrl)
      .field('description', additionalFields.description || 'Uploaded file')
      .field('category', additionalFields.category || 'general')
      .field('public', additionalFields.public || 'false')
      .attach('file', filePath, fileName)
      .set('Authorization', 'Bearer your-upload-token')
      .on('progress', (event) => {
        if (event.direction === 'upload') {
          const progress = (event.loaded / event.total) * 100;
          console.log(`アップロード進捗: ${progress.toFixed(1)}%`);
        }
      })
      .end((err, res) => {
        if (err) {
          console.error('ファイルアップロードエラー:', err.message);
          reject(err);
        } else {
          console.log('ファイルアップロード完了:', res.body);
          resolve(res.body);
        }
      });
  });
}

// 使用例
async function uploadExample() {
  try {
    const result = await uploadFile('./document.pdf', 'https://api.example.com/upload', {
      description: '重要な文書',
      category: 'documents',
      public: 'false'
    });
    
    console.log('アップロード結果:', result);
    console.log(`ファイルID: ${result.fileId}`);
    console.log(`ダウンロードURL: ${result.downloadUrl}`);
    
  } catch (error) {
    console.error('ファイルアップロード失敗:', error.message);
  }
}

// 複数ファイルの並行アップロード
async function uploadMultipleFiles(files, uploadUrl) {
  const uploadPromises = files.map(async (filePath, index) => {
    try {
      console.log(`ファイル ${index + 1}/${files.length} アップロード開始: ${filePath}`);
      
      const result = await uploadFile(filePath, uploadUrl, {
        description: `バッチアップロード ${index + 1}`,
        order: index + 1
      });
      
      console.log(`ファイル ${index + 1} 完了:`, result.fileName);
      return result;
      
    } catch (error) {
      console.error(`ファイル ${index + 1} 失敗:`, error.message);
      return { error: error.message, file: filePath };
    }
  });
  
  const results = await Promise.all(uploadPromises);
  
  const successful = results.filter(r => !r.error);
  const failed = results.filter(r => r.error);
  
  console.log(`\n=== アップロード結果 ===`);
  console.log(`成功: ${successful.length}件`);
  console.log(`失敗: ${failed.length}件`);
  
  if (failed.length > 0) {
    console.log('失敗したファイル:');
    failed.forEach(f => console.log(`  ${f.file}: ${f.error}`));
  }
  
  return { successful, failed };
}

// バイナリデータの直接送信
function uploadBinaryData(binaryData, fileName, uploadUrl) {
  return new Promise((resolve, reject) => {
    request
      .post(uploadUrl)
      .set('Content-Type', 'application/octet-stream')
      .set('Content-Disposition', `attachment; filename="${fileName}"`)
      .set('Authorization', 'Bearer your-token')
      .send(binaryData) // Buffer またはストリーム
      .end((err, res) => {
        if (err) reject(err);
        else resolve(res.body);
      });
  });
}

// ファイルダウンロード
function downloadFile(fileUrl, outputPath) {
  return new Promise((resolve, reject) => {
    const writeStream = fs.createWriteStream(outputPath);
    
    request
      .get(fileUrl)
      .set('Authorization', 'Bearer your-download-token')
      .on('response', (res) => {
        const fileSize = parseInt(res.headers['content-length'], 10);
        console.log(`ダウンロード開始: ${fileSize} bytes`);
      })
      .on('progress', (event) => {
        if (event.direction === 'download' && event.total) {
          const progress = (event.loaded / event.total) * 100;
          console.log(`ダウンロード進捗: ${progress.toFixed(1)}%`);
        }
      })
      .pipe(writeStream)
      .on('finish', () => {
        console.log(`ダウンロード完了: ${outputPath}`);
        resolve(outputPath);
      })
      .on('error', (err) => {
        console.error('ダウンロードエラー:', err.message);
        reject(err);
      });
  });
}

// ストリーミングアップロード
function streamUpload(readableStream, uploadUrl, fileName) {
  return new Promise((resolve, reject) => {
    request
      .post(uploadUrl)
      .field('filename', fileName)
      .field('timestamp', new Date().toISOString())
      .attach('stream', readableStream, fileName)
      .set('Authorization', 'Bearer your-token')
      .on('progress', (event) => {
        if (event.direction === 'upload' && event.total) {
          const progress = (event.loaded / event.total) * 100;
          console.log(`ストリームアップロード: ${progress.toFixed(1)}%`);
        }
      })
      .end((err, res) => {
        if (err) reject(err);
        else resolve(res.body);
      });
  });
}

// 使用例:大きなファイルのストリーミングアップロード
async function streamLargeFile() {
  try {
    const filePath = './large-file.zip';
    const readStream = fs.createReadStream(filePath);
    
    console.log('ストリーミングアップロード開始...');
    const result = await streamUpload(
      readStream, 
      'https://api.example.com/stream-upload',
      'large-file.zip'
    );
    
    console.log('ストリーミングアップロード完了:', result);
    
  } catch (error) {
    console.error('ストリーミングアップロード失敗:', error.message);
  }
}

プラグインとミドルウェア統合

const request = require('superagent');

// プラグイン関数の作成
function authPlugin(token) {
  return function(req) {
    req.set('Authorization', `Bearer ${token}`);
    return req;
  };
}

function loggingPlugin() {
  return function(req) {
    const startTime = Date.now();
    
    req.on('response', (res) => {
      const duration = Date.now() - startTime;
      console.log(`${req.method} ${req.url} - ${res.status} (${duration}ms)`);
    });
    
    req.on('error', (err) => {
      const duration = Date.now() - startTime;
      console.log(`${req.method} ${req.url} - ERROR: ${err.message} (${duration}ms)`);
    });
    
    return req;
  };
}

function retryPlugin(maxRetries = 3) {
  return function(req) {
    const originalEnd = req.end;
    
    req.end = function(callback) {
      let attempts = 0;
      
      const attemptRequest = () => {
        attempts++;
        console.log(`試行 ${attempts}/${maxRetries + 1}`);
        
        originalEnd.call(this, (err, res) => {
          if (err && attempts <= maxRetries && shouldRetry(err)) {
            console.log(`リトライ ${attempts}/${maxRetries}: ${err.message}`);
            setTimeout(attemptRequest, 1000 * attempts); // 段階的遅延
          } else {
            callback(err, res);
          }
        });
      };
      
      attemptRequest();
    };
    
    return req;
  };
  
  function shouldRetry(err) {
    return err.status >= 500 || err.code === 'ECONNRESET' || err.timeout;
  }
}

// カスタムミドルウェア
function apiBasePlugin(baseUrl, version = 'v1') {
  return function(req) {
    // 相対URLを絶対URLに変換
    if (!req.url.startsWith('http')) {
      req.url = `${baseUrl}/${version}/${req.url.replace(/^\//, '')}`;
    }
    
    // デフォルトヘッダー設定
    req.set('Accept', 'application/json');
    req.set('Content-Type', 'application/json');
    req.set('User-Agent', 'MyApp/1.0 (SuperAgent)');
    
    return req;
  };
}

// プラグインを使用したAPIクライアント
class ApiClient {
  constructor(baseUrl, token, options = {}) {
    this.baseUrl = baseUrl;
    this.token = token;
    this.options = {
      version: 'v1',
      timeout: 10000,
      retries: 3,
      logging: true,
      ...options
    };
  }
  
  request(method, endpoint) {
    let req = request[method](endpoint);
    
    // プラグインチェーンの適用
    req = req.use(apiBasePlugin(this.baseUrl, this.options.version));
    
    if (this.token) {
      req = req.use(authPlugin(this.token));
    }
    
    if (this.options.logging) {
      req = req.use(loggingPlugin());
    }
    
    if (this.options.retries > 0) {
      req = req.use(retryPlugin(this.options.retries));
    }
    
    req = req.timeout(this.options.timeout);
    
    return req;
  }
  
  get(endpoint) {
    return this.request('get', endpoint);
  }
  
  post(endpoint, data) {
    return this.request('post', endpoint).send(data);
  }
  
  put(endpoint, data) {
    return this.request('put', endpoint).send(data);
  }
  
  delete(endpoint) {
    return this.request('delete', endpoint);
  }
}

// 使用例
const apiClient = new ApiClient('https://api.example.com', 'your-jwt-token', {
  version: 'v2',
  timeout: 15000,
  retries: 5,
  logging: true
});

// プラグインが自動適用されたリクエスト
async function useApiClient() {
  try {
    // GET リクエスト
    const users = await new Promise((resolve, reject) => {
      apiClient.get('users')
        .query({ active: true, limit: 50 })
        .end((err, res) => {
          if (err) reject(err);
          else resolve(res.body);
        });
    });
    
    console.log('ユーザー一覧:', users);
    
    // POST リクエスト
    const newUser = await new Promise((resolve, reject) => {
      apiClient.post('users', {
        name: 'プラグインテストユーザー',
        email: '[email protected]'
      }).end((err, res) => {
        if (err) reject(err);
        else resolve(res.body);
      });
    });
    
    console.log('新規ユーザー:', newUser);
    
  } catch (error) {
    console.error('APIクライアントエラー:', error.message);
  }
}

// グローバルプラグインの設定
const globalPlugin = function(req) {
  // 全てのリクエストに適用される共通設定
  req.set('X-Client-Version', '1.0.0');
  req.set('X-Timestamp', new Date().toISOString());
  
  // リクエスト開始時のログ
  console.log(`[REQUEST] ${req.method} ${req.url}`);
  
  return req;
};

// 全てのSuperAgentリクエストにプラグインを適用
request.use = request.use || [];
request.use.push(globalPlugin);

// Express.js ミドルウェアとの統合例
function createExpressApiHandler(apiBaseUrl, token) {
  return async (req, res, next) => {
    try {
      const apiResponse = await new Promise((resolve, reject) => {
        request
          .get(`${apiBaseUrl}${req.path}`)
          .query(req.query)
          .set('Authorization', `Bearer ${token}`)
          .use(loggingPlugin())
          .end((err, apiRes) => {
            if (err) reject(err);
            else resolve(apiRes);
          });
      });
      
      res.json(apiResponse.body);
      
    } catch (error) {
      console.error('API プロキシエラー:', error.message);
      res.status(error.status || 500).json({
        error: 'API request failed',
        message: error.message
      });
    }
  };
}

// Express.js での使用
// app.get('/api/*', createExpressApiHandler('https://external-api.com', 'token'));