Как подтолкнуть к потоку Node после ошибки в 10+? - PullRequest
0 голосов
/ 20 ноября 2019

Недавно я взял старый потоковый код (написанный, когда 8.x был LTS) и попытался обновить его до 12.x. Это привело к интересному разрыву в том, как я справлялся с ошибками файла ENOENT.

Вот упрощение:

const { createServer } = require('http')
const { createReadStream } = require('fs')

const PORT = 3000

const server = createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'application/json'
  })

  const stream = createReadStream(`not-here.json`, {encoding: 'utf8'})

  stream.on('error', err => {
    stream.push(JSON.stringify({data: [1,2,3,4,5]}))
    stream.push(null)
  })

  stream.pipe(res)
})

server.listen(PORT)
server.on('listening', () => {
  console.log(`Server running at http://localhost:${PORT}/`)
})

В узле 8 вышеприведенный код работает нормально. Я могу перехватить ошибку, записать что-нибудь в поток и позволить ему нормально закрыться.

В Node 10+ (проверено 10, 12 и 13) поток уже уничтожен, когда вызывается мой обратный вызов ошибки,Я не могу выдвинуть новые вещи в поток и обработать ошибку изящно для клиентской стороны.

Было ли это преднамеренным изменением, и могу ли я все еще обработать эту ошибку хорошим способом для стороны клинта?

Ответы [ 2 ]

1 голос
/ 20 ноября 2019

Одна возможность. Откройте файл самостоятельно и создайте поток только с этим успешно открытым файлом. Это позволит вам обработать ENOENT (или любые другие ошибки при открытии файла), прежде чем вы попадете в механизм обработки ошибок в грязном потоке. Похоже, что потоковая архитектура в наибольшей степени соответствует прерыванию при ошибке, а не восстановлению с каким-либо альтернативным поведением.

const { createServer } = require('http');
const fs = require('fs');

const PORT = 3000;

const server = createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'application/json'});

    fs.open('not-here.json', {encoding: 'utf8'}, (err, fd) => {
        if (err) {
            // send alternative response here
            res.end(JSON.stringify({data: [1,2,3,4,5]}));
        } else {
            const stream = fs.createReadStream(null, {fd, encoding: 'utf8'});
            stream.pipe(res);
        }
    });
});

server.listen(PORT);
server.on('listening', () => {
    console.log(`Server running at http://localhost:${PORT}/`)
});

Вы также можете попробовать поэкспериментировать с параметрами autoDestroy или autoClose в своем потоке, чтобы увидетьесли какой-либо из этих флагов позволит потоку по-прежнему быть открытым, чтобы вы могли вставить в него данные, даже если файл создал ошибку открытия или чтения. Документация по этим флагам не очень полная, поэтому потребуется некоторая комбинация экспериментов по программированию и изучения кода, чтобы увидеть, можно ли ими манипулировать, чтобы все еще добавлять данные в поток после того, как ваш поток получил ошибку.

0 голосов
/ 21 ноября 2019

Ответ от jfriend00 указал мне правильное направление. Вот два разных способа, которыми я решил это. Мне нужна функция, которая возвращает поток, а не обрабатывает ошибку в функции обработчика req. Это больше похоже на то, что я на самом деле делаю в реальном коде.

Обработка ошибки из потока:

Как и выше, за исключением того, что я позаботился о том, чтобы вручную уничтожить поток. Правильно ли это заботится о дескрипторе внутреннего файла? Я думаю, что это так.

const server = createServer((req, res) => {
  res.writeHead(200, {
    'Content-Type': 'application/json'
  })

  getStream().pipe(res)
})


function getStream() {
  const stream = createReadStream(`not-here.json`, {
    autoClose: false,
    encoding: 'utf8'
  })

  stream.on('error', err => {
    // handling "no such file" errors
    if (err.code === 'ENOENT') {
      // push JSON data to stream
      stream.push(JSON.stringify({data: [1,2,3,4,5]}))

      // signal the end of stream
      stream.push(null)
    }

    // destory/close the stream regardless of error
    stream.destroy()

    console.error(err)
  })

  return stream
}

Обработка ошибки при открытии файла:

Как подсказывает jfriend00.

const { promisify } = require('util')
const { Readable } = require('stream')
const { open, createReadStream } = require('fs')

const openAsync = promisify(open)

const server = createServer(async (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'application/json'
  })

  const stream = await getStream()
  stream.pipe(res)
})

async function getStream() {
  try {
    const fd = await openAsync(`not-here.json`)
    return createReadStream(null, {fd, encoding: 'utf8'})
  } catch (error) {
    console.log(error)

    // setup new stream
    const stream = new Readable()

    // push JSON data to stream
    stream.push(JSON.stringify({data: [1,2,3,4,5]}))

    // signal the end of stream
    stream.push(null)

    return stream
  }
}

Мне все еще нравится обрабатыватьв потоке лучше, но хотелось бы услышать причины, по которым вы можете сделать это так или иначе.

...