我想在我的 NextJS 应用程序中显示 Google Place Photos。要从指定的 URL 获取这些图像,需要 API 密钥,但同时,我不想将此 API 密钥公开给公众。
我的目标是实现一个 NextJS API 路由,从 Google Places Photos 获取并返回指定的图像,同时还可以直接从图像标签访问,如下所示:
<img src={`/api/photos/${place?.photos[0].photo_reference}`} alt='' />
我在网上发现了一些不同的来源,建议我将响应流从我的谷歌请求直接传输到传出响应的响应流,如下所示:
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const response = await fetch(
`https://maps.googleapis.com/maps/api/place/photo
&photo_reference=${id}
&key=${process.env.GOOGLE_PLACE_API_KEY}`,
);
if (!response.ok) {
console.log(response);
res.status(500).end();
return;
}
response.body.pipe(res);
}
然而,由于response.body是一个ReadableStream,它没有.pipe()函数。相反,它有 .pipeTo() 和 .pipeThrough()。
然后我尝试了
response.body.pipeTo(res);
但是,这也不起作用,因为 res 是 NextApiResponse 而不是 WritableStream。虽然我在网上搜索过,但我还没有找到如何像WritableStreams那样写入NextApiResponse。
最后,我尝试手动将响应转换为缓冲区并将其写入 NextApiResponse,如下所示:
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const response = await fetch(
`https://maps.googleapis.com/maps/api/place/photo
&photo_reference=${id}
&key=${process.env.GOOGLE_PLACE_API_KEY}`,
);
if (!response.ok) {
console.log(response);
res.status(500).end();
return;
}
const resBlob = await response.blob();
const resBufferArray = await resBlob.arrayBuffer();
const resBuffer = Buffer.from(resBufferArray);
const fileType = await fileTypeFromBuffer(resBuffer);
res.setHeader('Content-Type', fileType?.mime ?? 'application/octet-stream');
res.setHeader('Content-Length', resBuffer.length);
res.write(resBuffer, 'binary');
res.end();
}
虽然这完成了响应,但没有显示图像。
如何直接将检索到的谷歌地点图像从服务器传递到前端,以便它可以被标签使用?
对我有用的是简单地将
response.body
类型的 ReadableStream<Uint8Array>
转换为 NodeJS.ReadableStream
,然后使用 pipe
函数。
const readableStream = response.body as unknown as NodeJS.ReadableStream;
readableStream.pipe(res);
对已接受答案的评论之一指出,它不适用于 Node.js 的内置
fetch
(在 Node.js >= 17.5 中可用)。为了让它发挥作用:
使用 Next.js 的“nodejs”运行时
import {Readable} from 'stream';
// omitting handler code for readability
const nodeReadableStream = Readable.from(response.body);
nodeReadableStream.pipe(res);
使用 Next.js 的“边缘”运行时
你可以简单地做:
return Response(response.body)
简单地迭代读者的值就可以完成这项工作。 这不需要任何黑客攻击或任何其他操作,并且与其他解决方案一样高效。
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const image = await fetch("https://domain-to-your-cool-image")
res.setHeader("Content-Type", "image/png");
if (!image.body) {
res.end()
return
}
const reader = image.body.getReader()
while (true) {
const result = await reader.read()
if (result.done) {
res.end()
return
}
res.write(result.value)
}
}
有点晚了,但是尽管
response.body
上的输入不完全是 Node.js 流,但它仍然足够兼容,可以在 stream.pipeline
中使用,这可能是一个常见的用例:
import { pipeline } from 'streams/promises';
const res = await fetch('...');
await pipeline(res.body, yourTargetStream);
experimental-edge
runtume// pages/api/png.ts
import { NextResponse, type NextRequest } from 'next/server'
export const config = {
runtime: 'experimental-edge',
}
// Streamable content
const RESOURCE_URL =
'https://mdn.github.io/dom-examples/streams/png-transform-stream/png-logo.png'
export default async function handler(request: NextRequest) {
const r = await fetch(RESOURCE_URL)
const reader = r.body.getReader()
const response = new ReadableStream({
async start(controller) {
while (true) {
const { done, value } = await reader.read()
// When no more data needs to be consumed, break the reading
if (done) {
break
}
// Enqueue the next data chunk into our target stream
controller.enqueue(value)
}
// Close the stream
controller.close()
reader.releaseLock()
},
})
return new Response(response)
}