Как лучше всего извлечь тело содержимого из сетевого ответа nodejs?

#node.js #http

Вопрос:

Используя следующий код:

 var net = require("net");
var client = new net.Socket();
client.connect(8080,"localhost",function() {
    client.write("GET /edsa-jox/testjson.json HTTP/1.1rn");
    client.write("Accept-Encoding: gziprn");
    client.write("Host: localhost:8080rnrn");
  }
);

client.on("data", function(data) {
  console.log(data.toString("utf-8", 0, data.length));
});
 

Я получаю следующий ответ:

 HTTP/1.1 200 OK
Date: Thu, 20 May 2021 22:45:26 GMT
Server: Apache/2.4.25 (Win32) PHP/5.6.30
Last-Modified: Thu, 20 May 2021 20:14:17 GMT
ETag: "1f-5c2c89677c5c7"
Accept-Ranges: bytes
Content-Length: 31
Content-Type: application/json

{"message":"message from json"}
 

И этот ответ немедленно отображается в консоли. Но поскольку это происходит из события «данные», я думаю, что оно поступало бы частями, если бы ответ был больше.

Поэтому я также протестировал следующее (при прочих равных условиях):

 var data="";
client.on("data", function(d) {
    console.log("1");
    data  = d.toString("utf-8", 0, d.length);
});

client.on("end", function(d) {
  console.log(data);
});
 

Думая, что я мог бы использовать событие «конец», чтобы убедиться, что у меня есть полный набор данных, прежде чем делать что-то еще. Что, я думаю, сработало, но неожиданностью было то, что «1» был показан сразу, но затем прошло несколько секунд, прежде чем было вызвано событие «конец».

Вопрос 1) Почему существует такая задержка с событием «конец» по сравнению с последним выполненным событием «данные»? Есть ли лучший способ сделать это?

Вопрос 2) С приведенным выше ответом, который содержит как кучу заголовков, так и тело содержимого. Какой подход является лучшим подходом для извлечения части тела?

Обратите внимание, что я хочу сделать это с помощью сетевой библиотеки, а не с помощью fetch или http-библиотек (или любых других абстракций). Я хочу, чтобы это было как можно быстрее.

Ответ №1:

я вижу только две веские причины делать все это вручную :

  • потребности в экстремальной скорости => тогда вам следует рассмотреть возможность использования «go» или другого скомпилированного языка
  • обучение (всегда интересно) Я бы рекомендовал вам использовать express или любой другой пакет npm, чтобы справиться со всем, не изобретая велосипед.

тем не менее, я помогу вам с тем, что знаю : первое, что нужно сделать, это правильно декодировать строки ut8. Вам нужно использовать string_decoder, потому что, если фрагмент данных неполный, и вы вызываете data.toString(‘utf8’), к нему будет добавлен искаженный символ. такое случается не часто, но трудно отлаживается.

вот правильный способ сделать это :

 const { StringDecoder } = require('string_decoder');
var decoder = new StringDecoder('utf8');
var stdout = '';
stream.on('data', (data) => {
    stdout  = decoder.write(data);
});
 

https://blog.raphaelpiccolo.com/post/827

затем, чтобы ответить на ваши вопросы :

  1. я не знаю, может быть, это связано с gzip. Сервер может быть медленным, чтобы остановить соединение, или это вина клиента. Или сама сеть. Я бы попробовал с другими клиентами / серверами, чтобы быть уверенным, и начал профилирование.
  2. вам необходимо прочитать спецификации http для обработки всех пограничных случаев (http1/websockets/http2). Но я думаю, вам повезло, заголовки всегда отделяются от тела двойной новой строкой. затем, если вы пройдете по циклу данных, поступающих из потока, после того, как они будут декодированы, символ за символом, вы сможете найти этот шаблон nn. Все, что последует за этим, будет телом. один особый случай, о котором я думаю, — это сохранение жизни : если клиент и сервер находятся в режиме сохранения жизни, соединение не будет закрываться между вызовами. возможно, вам потребуется проанализировать заголовок «Длина содержимого», чтобы узнать, сколько символов нужно ждать.

Комментарии:

1. Спасибо! Ваш ответ был очень полезен. что касается Q1) когда я добавил заголовок connection:close, событие end было вызвано немедленно. И относительно Q2) поиск двойной новой строки сработал (также добавлен декодер, как вы предложили).