node-fetch请求拦截器实现:日志、修改与重定向

node-fetch请求拦截器实现:日志、修改与重定向

【免费下载链接】node-fetch A light-weight module that brings the Fetch API to Node.js 【免费下载链接】node-fetch 项目地址: https://gitcode.com/gh_mirrors/no/node-fetch

你是否在开发Node.js应用时遇到过需要统一处理HTTP请求的场景?比如记录所有请求日志、统一添加认证头、或者动态修改请求参数?使用node-fetch的拦截器功能,这些需求都能轻松实现。本文将详细介绍如何在node-fetch中实现请求拦截器,解决日志记录、请求修改和重定向控制等常见问题。

拦截器原理与基础实现

请求拦截器本质上是在请求发送前和响应返回后插入自定义逻辑的机制。node-fetch虽然没有内置拦截器API,但通过高阶函数封装fetch方法,可以完美实现拦截器功能。

基础拦截器架构

// 创建拦截器包装函数
function createInterceptor(fetch) {
  return async (url, options = {}) => {
    // 请求拦截逻辑 - 在发送请求前执行
    const modifiedOptions = await requestInterceptor(url, options);
    
    // 发送原始请求
    const response = await fetch(url, modifiedOptions);
    
    // 响应拦截逻辑 - 在返回响应前执行
    return responseInterceptor(response);
  };
}

// 使用拦截器包装原始fetch
const fetch = createInterceptor(originalFetch);

这个架构包含三个核心部分:请求拦截器(修改请求参数)、原始请求执行、响应拦截器(处理返回结果)。下面我们将逐步完善这个架构,实现具体功能。

请求日志记录实现

日志记录是拦截器最常见的应用场景,有助于调试和监控系统。一个完整的请求日志应该包含请求信息、响应状态和耗时统计。

完整日志拦截器实现

// 请求日志拦截器
async function requestInterceptor(url, options) {
  // 记录请求开始时间
  const startTime = Date.now();
  
  // 打印请求基本信息
  console.log(`[${new Date().toISOString()}] 发送请求: ${options.method || 'GET'} ${url}`);
  console.log('请求头:', options.headers);
  
  // 如果有请求体且是JSON,打印请求体
  if (options.body && options.headers?.['Content-Type']?.includes('application/json')) {
    try {
      console.log('请求体:', JSON.parse(options.body));
    } catch (e) {
      console.log('请求体:', options.body);
    }
  }
  
  // 在options中附加开始时间,供响应拦截器使用
  options._startTime = startTime;
  return options;
}

// 响应日志拦截器
async function responseInterceptor(response) {
  // 计算请求耗时
  const duration = Date.now() - response.url._startTime;
  
  // 打印响应信息
  console.log(`[${new Date().toISOString()}] 收到响应: ${response.status} ${response.statusText}`);
  console.log('响应头:', response.headers.raw());
  console.log(`请求耗时: ${duration}ms\n`);
  
  return response;
}

日志优化与最佳实践

  1. 日志分级:使用debug模块替代console.log,实现日志分级输出
  2. 敏感信息过滤:拦截器中过滤Authorization、Cookie等敏感信息
  3. 异步日志:使用异步日志库避免阻塞请求流程
// 生产环境日志优化示例
const debug = require('debug')('api:requests');
const sensitiveHeaders = new Set(['authorization', 'cookie', 'set-cookie']);

function sanitizeHeaders(headers) {
  const sanitized = {...headers};
  sensitiveHeaders.forEach(header => {
    if (sanitized[header]) sanitized[header] = '[REDACTED]';
  });
  return sanitized;
}

请求修改与增强

拦截器的另一个强大功能是统一修改请求参数,如添加认证信息、设置超时时间等。这在处理API认证、版本控制等场景非常有用。

统一添加认证头

// 认证拦截器
async function authInterceptor(url, options) {
  const headers = new Headers(options.headers || {});
  
  // 为所有/api/*请求添加认证头
  if (url.startsWith('/api/') || url.includes('/api/')) {
    // 从环境变量或存储中获取token
    const token = process.env.API_TOKEN;
    if (token) {
      headers.set('Authorization', `Bearer ${token}`);
    }
  }
  
  // 添加API版本头
  headers.set('X-API-Version', 'v2');
  
  return {
    ...options,
    headers
  };
}

请求参数动态修改

有时需要根据请求内容动态修改参数,例如为GET请求添加时间戳防止缓存:

// 请求参数修改拦截器
async function queryInterceptor(url, options) {
  if (options.method?.toUpperCase() === 'GET') {
    // 解析URL
    const urlObj = new URL(url);
    
    // 添加时间戳参数防止缓存
    urlObj.searchParams.set('_t', Date.now().toString());
    
    return {
      ...options,
      url: urlObj.toString()
    };
  }
  
  return options;
}

高级应用:重定向控制与循环检测

node-fetch内置了重定向处理功能,但通过拦截器可以实现更精细的控制。查看src/index.js源码可知,node-fetch在第138-244行处理了重定向逻辑,我们可以通过拦截器扩展这一功能。

自定义重定向策略

// 重定向拦截器
async function redirectInterceptor(response) {
  // 获取重定向状态码
  if ([301, 302, 307, 308].includes(response.status)) {
    const location = response.headers.get('Location');
    
    // 记录重定向历史
    response.redirectHistory = response.redirectHistory || [];
    response.redirectHistory.push({
      from: response.url,
      to: location,
      status: response.status
    });
    
    // 自定义重定向规则:阻止特定域名重定向
    if (location.includes('external-domain.com')) {
      console.warn('阻止重定向到外部域名:', location);
      // 返回自定义响应,而非执行重定向
      return new Response(JSON.stringify({
        error: 'Redirect blocked',
        originalLocation: location
      }), {
        status: 403,
        headers: {'Content-Type': 'application/json'}
      });
    }
    
    // 检测循环重定向
    if (response.redirectHistory.some(item => item.to === response.url)) {
      throw new Error(`循环重定向检测: ${response.url}`);
    }
  }
  
  return response;
}

结合内置重定向配置

node-fetch的Request构造函数支持redirect选项,可与拦截器配合使用:

// 创建带有自定义重定向策略的fetch
const fetchWithRedirect = createInterceptor(originalFetch, {
  requestInterceptors: [
    (url, options) => ({
      ...options,
      // 设置最大重定向次数
      redirect: 'follow',
      follow: 5  // 最多重定向5次
    })
  ]
});

完整拦截器集成方案

将上述功能整合为一个完整的拦截器系统,支持多拦截器组合和优先级控制。

拦截器管理器实现

class InterceptorManager {
  constructor() {
    this.requestInterceptors = [];
    this.responseInterceptors = [];
  }
  
  // 添加请求拦截器
  useRequest(interceptor) {
    this.requestInterceptors.push(interceptor);
    return this.requestInterceptors.length - 1;
  }
  
  // 添加响应拦截器
  useResponse(interceptor) {
    this.responseInterceptors.push(interceptor);
    return this.responseInterceptors.length - 1;
  }
  
  // 移除拦截器
  eject(id, type = 'request') {
    const list = type === 'request' ? this.requestInterceptors : this.responseInterceptors;
    if (list[id]) list[id] = null;
  }
  
  // 应用请求拦截器
  async applyRequestInterceptors(url, options) {
    let config = {url, ...options};
    
    for (const interceptor of this.requestInterceptors) {
      if (interceptor) {
        config = await interceptor(config.url, config);
      }
    }
    
    return config;
  }
  
  // 应用响应拦截器
  async applyResponseInterceptors(response) {
    let res = response;
    
    for (const interceptor of this.responseInterceptors) {
      if (interceptor) {
        res = await interceptor(res);
      }
    }
    
    return res;
  }
}

完整使用示例

// 初始化拦截器管理器
const interceptorManager = new InterceptorManager();

// 注册拦截器(按顺序执行)
interceptorManager.useRequest(logRequestInterceptor);  // 日志拦截器
interceptorManager.useRequest(authInterceptor);        // 认证拦截器
interceptorManager.useRequest(queryInterceptor);       // 参数修改拦截器

// 响应拦截器
interceptorManager.useResponse(logResponseInterceptor); // 响应日志
interceptorManager.useResponse(redirectInterceptor);    // 重定向控制

// 创建带拦截器的fetch
const fetch = async (url, options = {}) => {
  // 应用请求拦截器
  const config = await interceptorManager.applyRequestInterceptors(url, options);
  
  // 执行原始请求
  const response = await originalFetch(config.url, config);
  
  // 附加拦截器信息
  response.interceptorConfig = config;
  
  // 应用响应拦截器
  return interceptorManager.applyResponseInterceptors(response);
};

// 使用增强后的fetch
fetch('https://api.example.com/data', {
  method: 'GET'
})
.then(response => response.json())
.then(data => console.log('获取数据:', data))
.catch(error => console.error('请求失败:', error));

常见问题与解决方案

错误处理与边界情况

拦截器中需要妥善处理错误,避免单个拦截器失败影响整个请求:

// 健壮的拦截器实现
async function safeInterceptor(url, options) {
  try {
    // 拦截器逻辑
    return modifiedOptions;
  } catch (error) {
    console.error('拦截器执行失败:', error);
    // 返回原始options,确保请求能继续执行
    return options;
  }
}

与node-fetch特性兼容

使用拦截器时需注意与node-fetch特有功能的兼容性,如取消请求(AbortController):

// 支持取消请求的拦截器
async function abortInterceptor(url, options) {
  // 设置默认超时时间
  if (!options.signal) {
    const controller = new AbortController();
    // 5秒超时
    const timeoutId = setTimeout(() => controller.abort(), 5000);
    
    return {
      ...options,
      signal: controller.signal,
      // 存储超时ID以便清除
      _timeoutId: timeoutId
    };
  }
  return options;
}

总结与最佳实践

通过本文介绍的拦截器模式,我们可以在node-fetch中实现强大的请求处理功能。以下是一些最佳实践建议:

  1. 单一职责原则:每个拦截器只负责一项功能,便于维护和测试
  2. 拦截器顺序:日志拦截器通常放在最前面,认证拦截器放在参数修改之前
  3. 错误处理:所有拦截器都应包含错误处理,避免级联失败
  4. 性能考量:避免在拦截器中执行耗时操作,必要时使用异步处理

拦截器模式极大地增强了node-fetch的灵活性,使我们能够以声明式的方式处理各种请求场景。无论是日志记录、认证授权还是请求转换,拦截器都能提供简洁而强大的解决方案。

官方文档中还有更多关于错误处理和请求限制的内容,可以参考docs/ERROR-HANDLING.mddocs/v3-LIMITS.md了解更多高级用法。

掌握请求拦截器的使用,将使你的Node.js HTTP请求处理更加优雅和高效。现在就尝试在项目中实现拦截器,体验它带来的便利吧!

【免费下载链接】node-fetch A light-weight module that brings the Fetch API to Node.js 【免费下载链接】node-fetch 项目地址: https://gitcode.com/gh_mirrors/no/node-fetch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值