有没有办法将 ReadableStream<Uint8Array> 传输到 NextApiResponse 中?

问题描述 投票:0回答:5

我想在我的 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();
}

虽然这完成了响应,但没有显示图像。

如何直接将检索到的谷歌地点图像从服务器传递到前端,以便它可以被标签使用?

javascript node.js google-maps-api-3 next.js node-fetch
5个回答
7
投票

对我有用的是简单地将

response.body
类型的
ReadableStream<Uint8Array>
转换为
NodeJS.ReadableStream
,然后使用
pipe
函数。

const readableStream = response.body as unknown as NodeJS.ReadableStream;
readableStream.pipe(res);

4
投票

对已接受答案的评论之一指出,它不适用于 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)

0
投票

简单地迭代读者的值就可以完成这项工作。 这不需要任何黑客攻击或任何其他操作,并且与其他解决方案一样高效。

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)
  }
}

0
投票

有点晚了,但是尽管

response.body
上的输入不完全是 Node.js 流,但它仍然足够兼容,可以在
stream.pipeline
中使用,这可能是一个常见的用例:

import { pipeline } from 'streams/promises';

const res = await fetch('...');
await pipeline(res.body, yourTargetStream);

-1
投票
  1. 如果您使用 Vercel,您可以直接从 Edge 进行流式传输。您可以转换 API 路由以使用
    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)
}
  1. 在配置中使用next.js重写(通过路径或正则表达式仅传递该id)
  2. 使用中间件来匹配这些url来处理重写,无需传输和流
© www.soinside.com 2019 - 2024. All rights reserved.