在前端开发中,我们经常遇到需要代理第三方资源的情况。的电影图片资源就是一个典型例子——直接引用图片链接会遇到防盗链问题,导致图片无法正常显示。本文将深入分析一个基于 Next.js Edge Runtime 的图片代理服务实现,探讨其安全设计、性能优化以及在实际应用中的技术考量。

技术背景

图片防盗链机制

图片采用 Referer 防盗链策略,要求请求必须携带 Referer: https://movie.****.com/ 头信息。这虽然是常见的反爬虫手段,但也给合法引用带来了障碍。

Edge Runtime 的优势

选择 Next.js Edge Runtime 而非传统的 Node.js 环境,主要基于以下考虑:

  1. 全球分布式部署:边缘计算节点靠近用户,降低延迟
  2. 冷启动优化:无需维护长时间运行的服务器进程
  3. 轻量级:资源占用少,成本更低
  4. 原生 Fetch API:现代 Web API 支持,代码更简洁

核心实现分析

1. 路由配置与基础结构

1
2
3
4
5
6
export const runtime = 'edge'; // 指定 Edge Runtime

export async function GET(request) {
// 处理图片代理请求
}

通过在文件头部声明 runtime = 'edge',Next.js 会自动将该路由部署到边缘网络。这种声明式配置大大简化了部署复杂度。

2. 安全校验体系

Token 和 UserAgent 验证

1
2
3
4
5
6
7
const token = searchParams.get("token");
const userAgent = request.headers.get('User-Agent');

if (!isInvalidToken(token) || !isInvalidUA(userAgent)) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
}

这种双重验证既保证了接口安全性,又避免了复杂的 OAuth 流程,特别适合内部服务的调用场景。

域名白名单

1
2
3
4
5
6
if (!parsedUrl.hostname.endsWith('.********.com')) {
return new Response(JSON.stringify({ error: 'Only *.********.com images are allowed' }), {
status: 400,
});
}

严格的域名校验防止了服务被滥用作通用代理。使用 endsWith 而非相等判断,可以兼容子域名(如 img1.********.comimg3.********.com 等)。

3. 请求转发优化

1
2
3
4
5
6
7
const imageResponse = await fetch(url, {
headers: {
Referer: 'https://movie.douban.com/',
'User-Agent': 'Mozilla/5.0 ... Chrome/120.0.0.0 Safari/537.36',
},
});

请求头设置的两个关键点:

  • Referer:绕过防盗链
  • User-Agent:模拟浏览器行为,避免被识别为机器人

4. 响应缓存策略

1
2
3
4
5
6
7
return new Response(imageBuffer, {
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400', // 缓存 1 天
},
});

设置 24 小时缓存可以:

  • 减少对服务器的请求压力
  • 加快重复图片的加载速度
  • 降低代理服务的带宽成本

5. 错误处理机制

1
2
3
4
5
6
7
8
9
try {
// 主要逻辑
} catch (error) {
console.error('Proxy error:', error);
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500,
});
}

全面的错误捕获确保服务稳定性,同时避免向客户端暴露敏感的错误信息。

性能考量

流式传输 vs Buffer

当前实现使用 arrayBuffer() 将整个图片加载到内存中。对于大图片,可以考虑使用流式传输:

1
2
3
4
5
6
7
8
// 流式传输优化版本
const { readable, writable } = new TransformStream();
imageResponse.body.pipeTo(writable);
return new Response(readable, {
status: 200,
headers: { 'Content-Type': contentType }
});

流式传输可以减少内存占用,降低首字节时间(TTFB)。

边缘缓存策略

除了 HTTP 缓存,还可以利用边缘平台的缓存能力:

1
2
3
4
5
6
// Vercel 边缘缓存示例
headers: {
'CDN-Cache-Control': 'public, max-age=86400',
'Cache-Control': 'public, max-age=86400',
}

安全性增强建议

1. 速率限制

1
2
3
4
5
// 基于 IP 的简单限流
const ip = request.headers.get('x-forwarded-for');
const key = `rate_limit:${ip}`;
// 使用边缘存储记录请求次数

2. 签名 URL

1
2
3
4
5
6
7
8
9
// 生成签名 URL
function generateSignedUrl(imageUrl, expiresIn = 3600) {
const expires = Math.floor(Date.now() / 1000) + expiresIn;
const signature = createHash('sha256')
.update(`${imageUrl}${expires}${SECRET_KEY}`)
.digest('hex');
return `/api/proxy-image?url=${encodeURIComponent(imageUrl)}&expires=${expires}&sig=${signature}`;
}

3. 内容安全检查

1
2
3
4
5
// 验证响应内容类型
if (!contentType.startsWith('image/')) {
return new Response('Invalid content type', { status: 400 });
}

实际应用场景

在 Next.js 页面中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
function MoviePoster({ src, alt }) {
const token = generateToken(); // 客户端生成或从 API 获取

return (
<Image
src={`/api/proxy-douban-image?url=${encodeURIComponent(src)}&token=${token}`}
alt={alt}
width={200}
height={300}
/>
);
}

配合 Next.js Image 组件

1
2
3
4
5
6
7
8
9
10
11
12
import Image from 'next/image';

const proxiedSrc = `/api/proxy-douban-image?url=${encodeURIComponent(originalUrl)}&token=${token}`;

<Image
src={proxiedSrc}
alt="Movie poster"
width={300}
height={450}
loader={({ src }) => src} // 避免 Next.js 优化时二次处理
/>

性能测试与监控

建议在部署后实施以下监控:

  1. 响应时间监控:记录 p95、p99 延迟
  2. 缓存命中率:优化缓存策略
  3. 错误率追踪:区分 4xx 和 5xx 错误
  4. 带宽统计:控制成本

总结

本文实现的图片代理服务展示了如何在 Next.js Edge Runtime 中构建一个安全、高效的反向代理。核心亮点包括:

  • 边缘部署:利用全球网络降低延迟
  • 双重验证:Token + 时间戳确保接口安全
  • 缓存优化:减少源站压力和带宽成本
  • 错误处理:完善的异常捕获和日志记录

该方案不仅适用于图片,稍作修改即可用于其他需要代理第三方资源的场景。在实际部署时,建议根据具体需求调整安全策略和缓存时长,在安全性和性能之间找到最佳平衡点。

参考资料


通过这种技术方案,我们不仅解决了图片防盗链问题,还构建了一个安全可控的资源代理服务。随着边缘计算的普及,类似的架构将在更多场景中发挥价值。