我有一个非常标准的问题 - 我希望我的 NextJS 应用程序能够提供存储在 GCP Cloud Storage 中的图像,但我想保护这些图像,并且仅对我的 Web 应用程序的授权用户可用,因此存储这些图像的存储桶是私人的。
我知道有两种方法可以解决这个问题:
我可以使用 GCP 的 Javascript SDK 轻松为我想要提供的每个图像创建签名 URL。一旦你获得了 GCP 凭证,这非常容易,但签名本身实际上在计算上相当昂贵。我在 Cloud Run 中的 1vCPU 实例上运行此应用程序,仅签名就花费了 100 毫秒到 200 毫秒。
最重要的是,签名的 URL 确实会影响您的缓存选项。不理想。
使用此选项,我们可以简单地将图像从 GCP 方向传递到 HTTP 请求者。只要 Cloud Run 和 Cloud Storage 之间的延迟较低,假设流处理不会对 CPU 造成太大负担,这就会恢复我所有的缓存选项,并且是我的首选。
NextJS 13 的文档总体来说相当不错,但我发现 streaming 部分 有点不足以满足我的需求。我正在寻找有关如何在我的应用程序中执行此操作的具体示例。
经过一番挖掘和实验,我想出了这个,并且很好地完成了工作。
// File location: app/api/image/[id]/route.ts
import { Storage } from '@google-cloud/storage';
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
const storage = new Storage({
projectId: '$PROJECT_ID',
});
interface GCPFileStream {
size: number;
name: string;
contentType: string;
stream: ReadableStream;
}
interface RequestParams {
params: {
id: string;
};
}
// Our HTTP handler
export async function GET(request: Request, { params }: RequestParams): Promise<Response> {
// NOTE: Don't forget to check auth, either in the handler or some middleware
const fileStream = await readFile(Number(params.id));
return new NextResponse(fileStream.stream, {
status: 200,
headers: new Headers({
'Cache-Control': 'max-age=2592000, stale-while-revalidate=86400, private',
'Content-Type': fileStream.contentType,
'Content-Length': fileStream.size.toString(),
}),
});
}
// The helper function to read the file from GCP
async function readFile(imageId: number): Promise<GCPFileStream> {
const bucketName = '$BUCKET_NAME';
const fileName = `${imageId}.jpg`;
// fetch file size, content type, etc
const fileMetadata = await storage.bucket(bucketName).file(fileName).getMetadata();
const stats = fileMetadata[0];
const readStream = storage.bucket(bucketName).file(fileName).createReadStream();
return {
size: Number(stats.size) || 0,
name: stats.name || '',
contentType: stats.contentType || '',
stream: readStream as unknown as ReadableStream,
};
}
欢迎提出改进建议!