You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node 18环境下node-rest-client执行完成后出现异常TypeError

使用node-rest-client结合nock模拟流响应时的TypeError问题解决

问题概述

使用node-rest-client发起请求,通过nock模拟流响应时,出现以下错误:

TypeError: The "stream" argument must be an instance of ReadableStream, WritableStream, or Stream. Received an instance of Socket

控制台能正常打印响应内容,但后续会抛出未处理的错误事件。

可复现代码

const NodeRestClient = require('node-rest-client').Client;
const nock = require('nock');
const { Readable } = require('stream');
 
// 创建字符串转换的可读流
const streamResponse = new Readable();
streamResponse._read = () => {};
streamResponse.push('Hello, this is a streamable buffer response');
streamResponse.push(null);
 
const apiUrl = 'https://api.example.com';
 
// 用nock拦截请求并返回流响应
nock(apiUrl)
  .get('/endpoint')
  .reply(200, streamResponse, { 'Content-Type': 'application/octet-stream' });
 
// node-rest-client发起请求
const client = new NodeRestClient();
client.get(`${apiUrl}/endpoint`, { responseType: 'arraybuffer' }, (data, response) => {
  if (response.statusCode === 200) {
    console.log('Mocked Streamable Buffer Response:', data.toString('utf-8'));
  } else {
    console.error('Error:', response.statusCode, data);
  }
});

错误堆栈信息

Mocked Streamable Buffer Response: Hello, this is a streamable buffer response
node:events:495
      throw er; // Unhandled 'error' event
      ^

<ref *2> TypeError [ERR_INVALID_ARG_TYPE]: The "stream" argument must be an instance of ReadableStream, WritableStream, or Stream. Received an instance of Socket
    at new NodeError (node:internal/errors:405:5)
    at eos (node:internal/streams/end-of-stream:74:11)
    at IncomingMessage._destroy (node:_http_incoming:234:21)
    at _destroy (node:internal/streams/destroy:109:10)
    at IncomingMessage.destroy (node:internal/streams/destroy:71:5)
    at endReadableNT (node:internal/streams/readable:1384:16)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

问题成因

该错误源于nock与node-rest-client的流处理兼容性问题:

  1. nock在模拟流响应时,会将自定义的Readable流绑定到底层的Socket实例上;
  2. Node.js内置的eos(end-of-stream)工具函数在处理流销毁时,误将Socket实例当作普通流传入;
  3. 在部分Node.js版本中,Socket实例的原型链不被eos的类型校验识别为标准Stream类实例,最终触发类型错误。

解决方法

方法1:直接返回Buffer替代流

既然请求指定了responseType: 'arraybuffer',可以跳过流的创建,直接将响应内容转为Buffer传给nock:

const NodeRestClient = require('node-rest-client').Client;
const nock = require('nock');

// 用Buffer替代可读流
const bufferResponse = Buffer.from('Hello, this is a streamable buffer response');

const apiUrl = 'https://api.example.com';

nock(apiUrl)
  .get('/endpoint')
  .reply(200, bufferResponse, { 'Content-Type': 'application/octet-stream' });

const client = new NodeRestClient();
client.get(`${apiUrl}/endpoint`, { responseType: 'arraybuffer' }, (data, response) => {
  if (response.statusCode === 200) {
    console.log('Mocked Streamable Buffer Response:', data.toString('utf-8'));
  } else {
    console.error('Error:', response.statusCode, data);
  }
});

方法2:修改Socket原型链(临时hack)

如果必须使用流模拟,可以通过修改Socket的原型链,让它被识别为Stream实例:

const { Stream } = require('stream');
const net = require('net');

// 让Socket继承Stream原型,通过类型校验
Object.setPrototypeOf(net.Socket.prototype, Stream.prototype);

// 后续原代码保持不变
const NodeRestClient = require('node-rest-client').Client;
const nock = require('nock');
const { Readable } = require('stream');

const streamResponse = new Readable();
streamResponse._read = () => {};
streamResponse.push('Hello, this is a streamable buffer response');
streamResponse.push(null);

const apiUrl = 'https://api.example.com';

nock(apiUrl)
  .get('/endpoint')
  .reply(200, streamResponse, { 'Content-Type': 'application/octet-stream' });

const client = new NodeRestClient();
client.get(`${apiUrl}/endpoint`, { responseType: 'arraybuffer' }, (data, response) => {
  if (response.statusCode === 200) {
    console.log('Mocked Streamable Buffer Response:', data.toString('utf-8'));
  } else {
    console.error('Error:', response.statusCode, data);
  }
});

替代方案:更换HTTP客户端

使用axios替代node-rest-client,它对nock的流模拟兼容性更好:

const axios = require('axios');
const nock = require('nock');
const { Readable } = require('stream');

const streamResponse = new Readable();
streamResponse._read = () => {};
streamResponse.push('Hello, this is a streamable buffer response');
streamResponse.push(null);

const apiUrl = 'https://api.example.com';

nock(apiUrl)
  .get('/endpoint')
  .reply(200, streamResponse, { 'Content-Type': 'application/octet-stream' });

axios.get(`${apiUrl}/endpoint`, { responseType: 'stream' })
  .then(response => {
    let data = '';
    response.data.on('data', chunk => {
      data += chunk.toString();
    });
    response.data.on('end', () => {
      console.log('Mocked Stream Response:', data);
    });
  })
  .catch(err => {
    console.error('Error:', err);
  });

内容的提问来源于stack exchange,提问作者harish6

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 23:30:16