如何在Node.js中从远程URL创建可读流?

25
在Node.js文档的Streams部分中,它说我可以使用"fs.createReadStream(url || path)"。但是,当我实际这样做时,它告诉我"Error: ENOENT: no such file or directory"。我只想将视频从可读流传输到可写流中,但我卡在了创建可读流的步骤中。 我的代码:
const express = require('express')
const fs = require('fs')
const url = 'https://www.example.com/path/to/mp4Video.mp4'
const port = 3000

app.get('/video', (req, res) => {
    const readable = fs.createReadStream(url)
})
app.listen(port, () => {
    console.log('listening on port ' + port)
})

错误:

listening on port 3000
events.js:291
      throw er; // Unhandled 'error' event
      ^

Error: ENOENT: no such file or directory, open 'https://www.example.com/path/to/mp4Video.mp4'
Emitted 'error' event on ReadStream instance at:
    at internal/fs/streams.js:136:12
    at FSReqCallback.oncomplete (fs.js:156:23) {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'https://www.example.com/path/to/mp4Video.mp4'
}

提示:https://www.example.com/path/to/mp4Video.mp4 不是实际的URL。

3个回答

33

fs.createReadStream()无法使用http URL,只能使用file:// URL或文件名路径。不幸的是,在fs文档中没有对此进行描述,但如果您查看fs.createReadStream()源代码并跟进它所调用的内容,您可以发现它最终调用了fileURULtoPath(url),如果不是file: URL,则会引发错误。

function fileURLToPath(path) {
  if (typeof path === 'string')
    path = new URL(path);
  else if (!isURLInstance(path))
    throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path);
  if (path.protocol !== 'file:')
    throw new ERR_INVALID_URL_SCHEME('file');
  return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path);
}

建议使用 got() 库从 URL 获取一个读取流:

const got = require('got');
const mp4Url = 'https://www.example.com/path/to/mp4Video.mp4';

app.get('/video', (req, res) => {
    got.stream(mp4Url).pipe(res);
});

本文中还有更多示例:如何使用Got在Node.js中流式传输文件下载


你也可以使用纯粹的http/https模块来获取readstream,但我发现got()在许多HTTP请求方面都非常有用,因此我使用它。但是,这里是使用https模块的代码。

const https = require('https');
const mp4Url = 'https://www.example.com/path/to/mp4Video.mp4';

app.get("/", (req, res) => {
    https.get(mp4Url, (stream) => {
        stream.pipe(res);
    });
});

可以在两种情况下都添加更高级的错误处理。


2
使用 require('https') 版本非常好,无需安装其他第三方包。 - Martin Cup
https.get 返回流吗?所以我可以像这样做 const myStream = https.get(mp4Url) 吗?我最终想要将其与 fluent-ffmpeg 一起使用。 - Optymystyc
@Optymystyc - 正如你在我的回答中最后一个代码块中所看到的,它不会返回流,而是将流提供给你传递的回调函数。 - jfriend00
不要忘记设置你的内容类型,并且捕获完成和错误事件。 - JCutting8
如何处理302重定向响应?流似乎没有状态码。 - JCutting8
@JCutting8 - 请自己编写问题,展示自己的代码并描述自己的具体情况。在这段代码中,如果https.get()返回302响应,则该响应将直接传递给响应,因为https.get()不会自动跟随302响应。 - jfriend00

1
这对我来说很有效:
import { get } from 'https'
import { Readable } from 'stream'

export const createUrlReadStream = (url: string): Readable => {
  const readable = new Readable({
    read() {}, // No-op
  })

  get(url, (response) => {
    response.on('data', (chunk: any) => {
      readable.push(chunk)
    })

    response.on('end', () => {
      readable.push(null) // End of stream
    })
  }).on('error', (error) => {
    readable.emit('error', error) // Forward the error to the readable stream
  })

  return readable
}

const url = 'https://www.example.com/path/to/mp4Video.mp4'

const readable = createUrlReadStream(url)


0
一个更现代的解决方案是在Node 18+中使用fetch,它返回一个可读流在response.body中。
import { Readable } from 'stream';
const readableStream = await fetch('https://www.example.com/path/to/mp4Video.mp4').then(r => Readable.fromWeb(r.body));
readableStream.pipe(...)

在节点20上,这会产生以下错误:类型“Readable”的“fromWeb”属性不存在。ts(2339) - undefined
这很奇怪,它在规范中有说明:https://nodejs.org/api/stream.html#streamreadablefromwebreadablestream-options - undefined

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