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