有没有一种方法可以将 ReadableStream<Uint8Array> 输入到 NextApiResponse 中?

8
我希望在我的NextJS应用程序中展示谷歌地图照片。要获取这些图片的特定URL,需要API密钥,但同时我不想将这个API密钥公开。
我的目标是实现一个NextJS API路由,从谷歌地图照片中获取并返回指定的图片,同时也能够直接从图像标签访问,如下所示:
<img src={`/api/photos/${place?.photos[0].photo_reference}`} alt='' />

我在网上找到了一些不同的来源,建议将来自Google请求的响应流直接传输到输出响应的响应流中:

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

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

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

4个回答

6

对我而言可行的办法是,将类型为ReadableStream<Uint8Array>response.body转换为NodeJS.ReadableStream,然后使用pipe函数。

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

只有这个对我起作用。 - Joe Roddy
3
我收到了“TypeError: readableStream.pipe不是一个函数”的错误。你在使用node-fetch吗?我正在使用Node.js 18内置的fetch,所以可能是这个问题。 - dprothero

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的“edge”运行时
你可以简单地执行以下操作:
return Response(response.body)

这对我有效 ✌️ - Ritik
1
当我尝试这样做时,我得到了类型“ReadableStream<Uint8Array>的参数不能赋给类型“Iterable<any> | AsyncIterable<any>”。ts(2345)的错误提示。 - undefined

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

-1
如果您正在使用 Vercel,您可以直接从 Edge 流式传输。 您可以将 API 路由转换为使用 experimental-edge 运行时。
// 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)
}

使用next.js重写在配置中(通过路径或正则表达式仅传递该ID) 使用中间件来匹配这些URL以处理重写,无需传输和流式传输

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接