NodeJS - Что на самом деле означает "зависание сокета"?

Я создаю веб-скребок с Node и Cheerio, и для определенного веб-сайта я получаю следующую ошибку (это происходит только на этом одном веб-сайте, а не на других, которые я пытаюсь очистить).

Это происходит каждый раз в другом месте, поэтому иногда url x который выдает ошибку, в другой раз url x Это нормально, и это совершенно другой URL:

    Error!: Error: socket hang up using [insert random URL, it's different every time]

Error: socket hang up
    at createHangUpError (http.js:1445:15)
    at Socket.socketOnEnd [as onend] (http.js:1541:23)
    at Socket.g (events.js:175:14)
    at Socket.EventEmitter.emit (events.js:117:20)
    at _stream_readable.js:910:16
    at process._tickCallback (node.js:415:13)

Это очень сложно отлаживать, я не знаю, с чего начать. Для начала, что такое ошибка зависания сокета? Это ошибка 404 или подобное? Или это просто означает, что сервер отказал в соединении?

Я не могу найти объяснение этому нигде!

РЕДАКТИРОВАТЬ: Вот пример кода, который (иногда) возвращает ошибки:

function scrapeNexts(url, oncomplete) {
    request(url, function(err, resp, body) {

        if (err) {
            console.log("Uh-oh, ScrapeNexts Error!: " + err + " using " + url);
            errors.nexts.push(url);
        }
        $ = cheerio.load(body);
        // do stuff with the '$' cheerio content here
    });
}

Прямой звонок для закрытия соединения отсутствует, но я использую Node Request который (насколько я могу судить) использует http.get так что это не обязательно, поправьте меня, если я ошибаюсь!

РЕДАКТИРОВАТЬ 2: Вот фактический, используемый бит кода, который вызывает ошибки. prodURL и другие переменные в основном являются селекторами jquery, которые определены ранее. Это использует async библиотека для узла.

function scrapeNexts(url, oncomplete) {
    request(url, function (err, resp, body) {

        if (err) {
            console.log("Uh-oh, ScrapeNexts Error!: " + err + " using " + url);
            errors.nexts.push(url);
        }
        async.series([
                function (callback) {
                    $ = cheerio.load(body);
                    callback();
                },
                function (callback) {
                    $(prodURL).each(function () {
                        var theHref = $(this).attr('href');
                        urls.push(baseURL + theHref);
                    });
                    var next = $(next_select).first().attr('href');
                    oncomplete(next);
                }
            ]);
    });
}

33 ответа

Есть два случая, когда socket hang up получает бросок:

Когда вы клиент

Когда вы, как клиент, отправляете запрос на удаленный сервер и не получаете своевременного ответа. Ваш сокет закончен, который выдает эту ошибку. Вам нужно перехватить эту ошибку и решить, как ее обработать: повторить ли запрос, поставить его в очередь на потом и т. Д.

Когда вы сервер / прокси

Когда вы, как сервер, возможно, прокси-сервер, получаете запрос от клиента, затем начинаете действовать на него (или передаете запрос на вышестоящий сервер), и, прежде чем вы подготовите ответ, клиент решает отменить / отменить запрос.

Эта трассировка стека показывает, что происходит, когда клиент отменяет запрос.

Trace: { [Error: socket hang up] code: 'ECONNRESET' }
    at ClientRequest.proxyError (your_server_code_error_handler.js:137:15)
    at ClientRequest.emit (events.js:117:20)
    at Socket.socketCloseListener (http.js:1526:9)
    at Socket.emit (events.js:95:17)
    at TCP.close (net.js:465:12)

Линия http.js:1526:9указывает на то же самое socketCloseListener упомянутый выше @Blender, в частности:

// This socket error fired before we started to
// receive a response. The error needs to
// fire on the request.
req.emit('error', createHangUpError());

...

function createHangUpError() {
  var error = new Error('socket hang up');
  error.code = 'ECONNRESET';
  return error;
}

Это типичный случай, если клиент является пользователем в браузере. Запрос на загрузку какого-либо ресурса / страницы занимает много времени, и пользователи просто обновляют страницу. Такое действие приводит к прерыванию предыдущего запроса, который на вашей стороне сервера выдает эту ошибку.

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

console.log(res.socket.destroyed); //true

Таким образом, нет смысла отправлять что-либо, кроме явного закрытия объекта ответа:

res.end();

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

Посмотрите на источник:

function socketCloseListener() {
  var socket = this;
  var parser = socket.parser;
  var req = socket._httpMessage;
  debug('HTTP socket close');
  req.emit('close');
  if (req.res && req.res.readable) {
    // Socket closed before we emitted 'end' below.
    req.res.emit('aborted');
    var res = req.res;
    res.on('end', function() {
      res.emit('close');
    });
    res.push(null);
  } else if (!req.res && !req._hadError) {
    // This socket error fired before we started to
    // receive a response. The error needs to
    // fire on the request.
    req.emit('error', createHangUpError());
    req._hadError = true;
  }
}

Сообщение отправляется, когда сервер никогда не отправляет ответ.

Стоит упомянуть один случай: при подключении из Node.js к Node.js с помощью Express я получаю "зависание сокета", если я не префикс запрошенного пути URL с помощью "/".

Ниже приведен простой пример, в котором я получил ту же ошибку, когда пропустил добавление прокомментированного кода в приведенном ниже примере. Раскомментирование кода req.end() решит эту проблему.

var fs = require("fs");
var https = require("https");

var options = {
    host: "en.wikipedia.org",
    path: "/wiki/George_Washington",
    port: 443,
    method: "GET"
};

var req = https.request(options, function (res) {
    console.log(res.statusCode);
});


// req.end();

Я использовал require('http') потреблять сервис https и это показывало socked hang up ".

Тогда я использую require('https') вместо этого, и это работает.

Продолжая ответ Blender, это происходит в ряде ситуаций. Наиболее распространенные из них, с которыми я сталкиваюсь:

  1. Сервер разбился.
  2. Сервер отклонил ваше соединение, скорее всего заблокировано User-Agent,

socketCloseListenerКак указано в ответе Блендера, это не единственное место, где создаются ошибки зависания.

Например, нашел здесь:

function socketOnEnd() {
  var socket = this;
  var req = this._httpMessage;
  var parser = this.parser;

  if (!req.res) {
    // If we don't have a response then we know that the socket
    // ended prematurely and we need to emit an error on the request.
    req.emit('error', createHangUpError());
    req._hadError = true;
  }
  if (parser) {
    parser.finish();
    freeParser(parser, req);
  }
  socket.destroy();
}

Вы могли бы попробовать curl с заголовками и тому подобным, которые отправляются с узла, и посмотрите, получите ли вы ответ там. Если вы не получили ответ с curl, но вы получите ответ в своем браузере, а затем ваш User-Agent заголовок, скорее всего, заблокирован.

За request пользователи модуля

Таймауты

Существует два основных типа тайм-аутов: тайм- ауты соединения и тайм- ауты чтения. Тайм-аут соединения происходит, если истекло время ожидания, когда ваш клиент пытается установить соединение с удаленным компьютером (соответствует connect() позвони в розетку). Тайм-аут чтения происходит каждый раз, когда сервер слишком медленный, чтобы отправить обратно часть ответа.

Обратите внимание, что тайм-ауты соединения испускают ETIMEDOUT ошибка и тайм-ауты чтения испускают ECONNRESET ошибка.

Я думаю, что "зависание сокета" - довольно общая ошибка, указывающая на то, что соединение было прервано со стороны сервера. Другими словами, сокеты, используемые для поддержания соединения между клиентом и сервером, были отключены. (Хотя я уверен, что многие из упомянутых выше моментов полезны для разных людей, я думаю, что это более общий ответ.)

В моем случае я отправлял запрос с полезной нагрузкой, превышающей 20 КБ. Это было отклонено сервером. Я проверил это, удалив текст и повторяя попытки, пока запрос не будет успешным. После определения максимально допустимой длины я подтвердил, что добавление одного символа вызывает ошибку. Я также подтвердил, что проблема не в клиенте, отправив тот же запрос из приложения Python и из Postman. Так или иначе, я уверен, что в моем случае длина полезной нагрузки была моей конкретной проблемой.

Опять же, источник проблемы анекдотичен. Общая проблема - "Сервер говорит нет".

Другой случай, о котором стоит упомянуть (для Linux и OS X), это то, что если вы используете такую ​​библиотеку, как https для выполнения запросов, или если вы передаете https://... в качестве URL локально обслуживаемого экземпляра вы будете использовать порт 443 который является зарезервированным частным портом, и вы можете оказаться в Socket hang up или же ECONNREFUSED ошибки.

Вместо этого используйте порт 3000Fe и сделать http запрос.

Это вызвало у меня проблемы, так как я делал все, что перечислено здесь, но все равно получал ошибки. Оказывается, что вызов req.abort() на самом деле выдает ошибку с кодом ECONNRESET, поэтому вам действительно нужно отловить это в своем обработчике ошибок.

req.on('error', function(err) {
    if (err.code === "ECONNRESET") {
        console.log("Timeout occurs");
        return;
    }
    //handle normal errors
});

У меня была такая же проблема при использовании библиотеки Nano для подключения к Couch DB. Я попытался настроить пул соединений с помощью библиотеки keepaliveagent, и она продолжала давать сбой с сообщением о зависании сокета.

var KeepAliveAgent = require('agentkeepalive');

var myagent = new KeepAliveAgent({
    maxSockets: 10,
    maxKeepAliveRequests: 0,
    maxKeepAliveTime: 240000
});

nano = new Nano({
    url : uri,
    requestDefaults : {
        agent : myagent
    }
});

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

Одно простое изменение в использовании HttpsAgent сделало свое дело:

var KeepAliveAgent = require('agentkeepalive').HttpsAgent;

У меня была такая же проблема во время запроса к какому-либо серверу. В моем случае мне помогло установить любое значение User-Agent в заголовках в параметрах запроса.

const httpRequestOptions = {
    hostname: 'site.address.com',
    headers: {
       'User-Agent': 'Chrome/59.0.3071.115'
    }
};

Это не общий случай и зависит от настроек сервера.

Эта ошибка также может возникнуть при работе с http.request, возможно, ваш запрос еще не завершен.

Пример:

const req = https.request(options, res => {})

И всегда нужно добавлять эту строчку: req.end()С помощью этой функции мы прикажем завершить отправку запроса.

Как в документации сказано:

С http.request() всегда нужно вызывать req.end(), чтобы обозначить конец запроса - даже если в тело запроса не записываются данные.

Также причина может быть из-за использования app экземпляр express вместо server от const server = http.createServer(app) при создании сокета сервера.

Неправильно

const express = require('express');
const http = require('http');
const WebSocket = require('ws');


const app = express();

app.use(function (req, res) {
  res.send({ msg: "hello" });
});

const wss = new WebSocket.Server({ server: app }); // will throw error while connecting from client socket

app.listen(8080, function listening() {
  console.log('Listening on %d', server.address().port);
});

Правильный

const express = require('express');
const http = require('http');
const WebSocket = require('ws');


const app = express();

app.use(function (req, res) {
  res.send({ msg: "hello" });
});

const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

server.listen(8080, function listening() {
  console.log('Listening on %d', server.address().port);
});

Это было давно, но другой случай - выполнение запросов, которые занимают много времени на стороне сервера (более 2 минут, что по умолчанию для экспресс), и параметр тайм-аута не был настроен на стороне сервера. В моем случае я выполнял запрос клиент-> сервер-> сервер (Node.js express), и я должен установить параметр тайм-аута для каждого маршрутизатора запросов на сервере и на клиенте. Поэтому на обоих серверах мне нужно было установить тайм-аут запроса, используя

req.setTimeout([your needed timeout])

на роутере.

Я использовал axios в nodejs и столкнулся с ошибкой зависания sokcet при извлечении данных из URL-адреса.

      const response = await axios.get(url)

выясняется, что это произошло из-за ошибки тайм-аута, и я не обрабатывал ошибку.

Итак, я добавил тайм-аут и обработку ошибок, как показано в следующем фрагменте кода.

      const response = await axios
      .get(url, { timeout: 10000 })
      .catch((error) => {
         if (axios.isAxiosError(error)) {
           const axiosError = error as AxiosError;
           console.log(message, axiosError.message);
           console.log(axiosError.response);
      });

Таким образом, обработка ошибки и указание тайм-аута решили для меня проблему зависания сокета.

Похоже, здесь есть еще один случай, когда Electron не является поклонником доменного имени "localhost". В моем случае мне нужно было это изменить:

const backendApiHostUrl = "http://localhost:3000";

к этому:

const backendApiHostUrl = "http://127.0.0.1:3000";

После этого проблема просто ушла.

Это означает, что разрешение DNS (локальное или удаленное) также может вызывать некоторые проблемы.

Я занимаюсь как веб-разработкой (Android), так и разработкой для Android, и одновременно открываю симулятор устройства Android Studio и докер, оба они используют порт 8601. socket hang up ошибка, после закрытия симулятора устройства Android Studio, и он хорошо работает на стороне узла. Не используйте симулятор устройства Android Studio и докер вместе.

У меня возникла аналогичная ошибка при использовании CouchDB в кластере OCP.

const cloudantSessionStore = sessionStore.createSessionStore(
  {
    type: 'couchdb',
    host: 'https://' + credentials['host'],
    port: credentials['port'],
    dbName: 'sessions',
    options: {
      auth: {
        username: credentials['username'],
        password: credentials['password']
      },
      cache: false
    }
  }

Для подключения к моему экземпляру CouchDB должно быть "http", а не "https". Надеюсь, это может быть полезно для всех, кто сталкивается с подобной проблемой.

В моем случае это была не ошибка, а ожидаемое поведение для браузера Chrome. Chrome поддерживает соединение tls (для скорости, я думаю), но сервер node.js останавливает его через 2 минуты, и вы получаете сообщение об ошибке.

Если вы попытаетесь выполнить запрос GET с помощью пограничного браузера, ошибки не будет вообще. Если вы закроете окно Chrome - вы сразу получите ошибку.

Так что делать? 1) Вы можете отфильтровать эти ошибки, потому что они на самом деле не являются ошибками. 2) Может быть, есть лучшее решение:)

в моем случае проблема была просто из-за возврата ответа с кодом состояния 102 (Обработка...)

Возможно, ваш сервер или соединение с сокетом неожиданно обрываются.

Вчера столкнулся с этой проблемой при запуске моего веб-приложения и сервера node.js через IntelliJ IDEA 2016.3.6. Все, что мне нужно было сделать, это очистить мои куки и кеш в моем браузере Chrome.

Если вы сталкиваетесь с этой ошибкой через соединение https и она возникает мгновенно, это может быть проблемой при настройке соединения SSL.

Для меня это был вопрос https://github.com/nodejs/node/issues/9845 но для вас это может быть что-то еще. Если это проблема с ssl, то вы сможете воспроизвести его с помощью пакета nodejs tls/ssl, просто пытающегося подключиться к домену.

Если вы используете node-http-proxy, учтите эту проблему, которая приведет к ошибке зависания сокета: https://github.com/nodejitsu/node-http-proxy/issues/180.

Для разрешения, также в этой ссылке, просто переместите объявление API-маршрута (для проксирования) в пределах экспресс-маршрутов перед express.bodyParser().

После долгой отладки в коде node js, строке подключения mongodb, проверке CORS и т. Д. Для меня просто переключение на другой номер порта server.listen(port); заставил это работать, в postman, попробуйте это тоже. Без измененийproxy настройки просто по умолчанию.

В моем случае это было потому, что ответ приложения /json был плохо отформатирован (содержит трассировку стека). Ответ никогда не отправлялся на сервер. Это было очень сложно отлаживать, потому что не было журнала. Эта тема очень помогает мне понять, что происходит.

Я использовал nano, и мне потребовалось много времени, чтобы выяснить эту ошибку. Моя проблема заключалась в том, что я использовал неправильный порт. У меня был порт 5948 вместо 5984.

var nano = require('nano')('http://localhost:5984');
var db = nano.use('address');
var app = express();

Я думаю стоит отметить...

Я создавал тесты для API Google. Я перехватывал запрос с помощью временного сервера, а затем пересылал их в настоящий API. Я пытался просто передать заголовки в запросе, но несколько заголовков вызывали проблему с экспрессом на другом конце.

А именно мне пришлось удалить connection, accept, а также content-length заголовки перед использованием модуля запроса для пересылки.

let headers = Object.assign({}, req.headers);
delete headers['connection']
delete headers['accept']
delete headers['content-length']
res.end() // We don't need the incoming connection anymore
request({
  method: 'post',
  body: req.body,
  headers: headers,
  json: true,
  url: `http://myapi/${req.url}`
}, (err, _res, body)=>{
  if(err) return done(err);
  // Test my api response here as if Google sent it.
})

У меня была эта ошибка при запуске двух приложений на одном и том же порту по ошибке . У меня было приложение next.js и еще одно в nest.js, оба они работали на порту 8080, когда я посмотрел на файлы .env, я понял, что у них один и тот же порт, поэтому я изменил его с nest.js на 3000 и все заработало.

Я не говорю, что это причина ошибки, но это возможность.

Другие вопросы по тегам