NodeJS - Що насправді означає "зависання сокета"?


276

Я будую веб-скребок з 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)

Це дуже складно налагоджувати, я не знаю, з чого почати. Для того, щоб почати то , що IS сокет вішає помилку? Це помилка 404 чи подібне? Або це просто означає, що сервер відмовився від з'єднання?

Я ніде не можу знайти пояснення цьому!

EDIT: Ось зразок коду, який (іноді) повертає помилки:

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так що це не потрібно, виправте мене, якщо я помиляюся!

EDIT 2: Ось справжній біт коду, який використовується, що викликає помилки. prodURLта інші змінні - це в основному селектори jquery, визначені раніше. Для цього використовується asyncбібліотека для Node.

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);
                }
            ]);
    });
}

26
Це означає, що сокет не надсилає endподія з'єднання протягом періоду очікування. Якщо ви отримуєте запит на cheerio через http.request(не http.get). Вам потрібно зателефонувати, request.end()щоб закінчити надсилання запиту.
— user568109

1
@ user568109 Слід зазначити, я використовую requestслужбу вузла , а не конкретний http.requestзапит (я думаю, я дуже новачок у вузлі!). Це один: github.com/mikeal/request Це здається, що він автоматично завершує запит, ні? РЕДАКТИРУВАННЯ: За словами док., http method, defaults to GETЦе не проблема.
— JVG

2
Тоді це не повинно бути проблемою. Що станеться, якщо ви прокоментуєте частину скребки, включаючи cheerio.load та поверніть той самий вміст. Улов тут cheerio.loadє асинхронним. Тож це може не закінчитися, перш ніж ви почнете робити речі з $.
— user568109

4
Іноді я також виявляв, що якщо я скануватиму сайт занадто агресивно (наприклад, 10+ одночасних з'єднань), вони почнуть реагувати на вимкнення сокетів, тож це може бути і так.
— tobek

1
Просто FYI англійською мовою hang upозначає закінчити електронну розмову, перервавши з'єднання ; походить від повішення старомодного телефону.
— Константин Ван

Відповіді:


161

Є два випадки, коли вас 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();

Тим НЕ менше, то , що ви повинні зробити точно , якщо є проксі - сервер , який вже перенаправляє запит до вгору по течії, є передчасним припиненням вашого внутрішнього запиту на вгору по течії, вказуючи час вашої відсутності інтересу у відповідь, що , в свою чергу , буде говорити вгору по течії сервер, щоб, можливо, зупинити дорогу операцію.


2
Як я можу, як клієнт, просто змусити запит чекати довше? Вона помиляється за 35 секунд, і мені потрібно почекати хвилин.
— Великі гроші

Я стикаюся з тим же питанням. Чи можна дочекатися відповіді та почати надсилати наступний запит, як один за одним. Можливо, я знаю, як обробити цю розетку?
— Діпак

@BigMoney ви можете використовувати setTimeout(). дивіться це запитання: stackoverflow.com/questions/6214902/…
— holla

Ваші дані пережили мене з пекла, я використовував node.js як проксі-сервер між upstream сервером і клієнтом, тайм-аут на запит кинув цю помилку просто тому, що я забув використовувати res.send, дякую
— Farzad YZ

Ви можете отримати "зависання сокета" як клієнт, коли ви намагаєтесь зробити другий запит на веб-сервері розробки Django через те саме з'єднання. Він не підтримує keep-alive. І якщо ваш клієнт очікує, що це станеться, ви отримаєте помилку. Це виглядає в наступних рядках .
— x-yuri

53

Погляньте на джерело :

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;
  }
}

Повідомлення видається, коли сервер ніколи не надсилає відповідь.


2
З функціональної точки зору ви можете пояснити, що це означає? Я намагаюся створити тут захисні засоби, додаючи URL-адреси, що ображають, до масиву, а потім викреслюючи їх згодом. Я читав у кількох місцях, що помилки можуть бути проблемою у черзі з Node, не знаю найкращого способу виправити та уникнути цього.
— JVG

5
Але як довго це чекати?
— CommaToast

2
Він повинен використовувати en.wikipedia.org/wiki/Exponential_backoff для реалізації "як довго".
— Норман Н

це "зависання розетки" безглуздо. Це просто сюрприз від команди nodejs.
— пучу

45

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


1
це була моя проблема, як клієнт, так і сервер у чистому http node.js
— ashley willis

1
@silentorb: Чи можете ви, будь ласка, показати приклад URL-адреси? Я стикаюся з такою ж помилкою в цьому випадку .. Дякую.
— Притам

4
Помилка: "user / login", Успіх: "/ user / login"
— silentorb

4
Людина, я витратив її налагодження майже годину! Побачила свою відповідь і подумала SH **, додала / і це прекрасно працює :) дякую!
— Даніель Грущик

4
Ви врятували мені години з цією відповіддю!
— imhotep

32

Раніше я require('http')споживав послугу https, і вона показала " socket hang up".

Тоді я змінив require('http')до require('https')натомість, і вона працює.


Хоча це може бути вирішенням проблеми, це не відповідь на питання. Плакат хотів відповісти про значення повідомлення про помилку. Крім того, вже є багато якісних відповідей. Ваше не надає додаткової вартості.
— Йоханнес Дорн

19
Дякую за коментар Я витрачаю свій час на цю помилку. Нарешті, я просто спробую це рішення, і воно працює. Просто хочу поділитися. Сподіваємось, корисно для інших не витрачати свій час, не хвалити як якісну відповідь.
— Aekkawit Chanpen

12
@JohannesDorn Це неявна відповідь на питання, що означає помилка. І корисний у цьому.
— Улад Касач

29

нижче - простий приклад, коли я отримав таку ж помилку, коли пропустив додати коментований код у наведеному нижче прикладі. Відміняння коду 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();

2
Це врятувало мою розумність ... Дякую!
— PGallagher

Ти герой! Дякую.
— Ксенхат

17

Розширюючись на відповідь Блендера, це відбувається в ряді ситуацій. Найпоширеніші з яких я стикаюся:

  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з заголовками та такими, які надсилаються з Node, і побачити, чи отримаєте ви там відповідь. Якщо ви не отримаєте відповіді curl, але отримуєте відповідь у своєму браузері, то ваш User-Agentзаголовок, швидше за все, буде заблокований.


3
Ще одна причина, по якій сервер може відмовитись від вашого з'єднання (я просто натиснув це під час переходу на prod замість QA), це якщо ваш сервер очікує запит https замість http.
— mcole

7

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

Замість цього використовуйте порт 3000, fe та робіть httpзапит.


6

У мене була така ж проблема під час використання бібліотеки Nano для підключення до БД Couch . Я спробував налагодити об'єднання з'єднань за допомогою бібліотеки keepaliveagent, і це не вдалося з повідомленням про відключення сокета .

var KeepAliveAgent = require('agentkeepalive');

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

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

Після певних зусиль я зміг усунути проблему - як виявилося, це була дуже-дуже проста помилка. Я підключався до бази даних за допомогою протоколу HTTPS, але я продовжував передавати своєму нанооб'єкту keepalive агент, створений як приклади для використання цієї бібліотеки (вони покладаються на деякі типові параметри, які використовують http).

Одна проста зміна використання HttpsAgent зробила трюк:

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

1
Якщо ще трохи детальніше, якщо запит налаштований на порт 443 і запит видається через модуль http, а не модуль https, ви отримуєте зависання сокета. Було б добре, якби було детальніше, чому сталося відключення (переговори SSL / TLS?). Я бачив, наприклад, рівень деталізації в ASP.NET.
— Річард Коллет

6

Це викликало у мене проблеми, оскільки я робив усе перераховане тут, але все ще отримував помилки. Виявляється, виклик req.abort () насправді призводить до помилки з кодом ECONNRESET, тож ви насправді повинні зафіксувати це у своєму оброблювачі помилок.

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

5

Для requestкористувачів модулів

Часи очікування

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

Зауважте, що час очікування з'єднання видає ETIMEDOUTпомилку, а час очікування зчитування видає ECONNRESETпомилку.


3

У мене була така ж проблема під час запиту на якийсь сервер. У моєму випадку мені допомогло встановлення будь-якого значення User-Agent у заголовках у параметрах запиту.

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

Це не загальний випадок і залежить від налаштувань сервера.


2

Також причина може бути через використання 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);
});

1

Я займаюся розробкою веб (вузла) та Android, відкриваю тренажер пристрою Android Studio та докер разом, обидва вони використовують порт 8601 socket hang up, після закриття симулятора пристроїв Android Studio він скаржився на помилку, і він добре працює в стороні вузла. Не використовуйте симулятор пристрою Android Studio та докер разом.


1

Я отримав подібну помилку при використанні 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
    }
  }

Який має бути "http", а не "https", щоб з'єднуватися з моїм екземпляром CouchDB. Сподіваюся, це може бути корисним для всіх, хто стикається з подібним питанням.


0

У моєму випадку це було тому, що відповідь програми / json була неправильно відформатована (містить трасування стека). Відповідь ніколи не надсилалася на сервер. Це було дуже складно налагоджувати, оскільки журналу не було. Ця нитка дуже допомагає мені зрозуміти, що відбувається.


0

Якщо ви використовуєте node-http-proxy, будь ласка, пам’ятайте про цю проблему, що призведе до помилки зависання сокета: https://github.com/nodejitsu/node-http-proxy/isissue/180 .

Для вирішення, також у цьому посиланні, просто перемістіться, оголосивши маршрут API (для наближення) в межах експрес-маршрутів перед express.bodyParser ().


0

Вчора в цьому питанні запустився мій веб-додаток та сервер node.js через IntelliJ IDEA 2016.3.6. Все, що мені потрібно було зробити - це очистити файли cookie та кеш-пам'ять у своєму браузері Chrome.


0

Якщо у вас є помилка через з'єднання https, і це відбувається миттєво, це може бути проблемою з налаштуванням SSL-з'єднання.

Для мене це питання https://github.com/nodejs/node/isissue/9845, але для вас це може бути щось інше. Якщо це проблема з ssl, ви повинні мати можливість відтворити його за допомогою пакета nodejs tls / ssl, просто намагаючись підключитися до домену


0

Я думаю, що варто відзначити ...

Я створював тести для 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.
})

0

Я вважаю, що це не помилка, а очікувана поведінка для браузера Chrome. Chrome підтримує з'єднання tls живим (на швидкість, я думаю), але сервер node.js припиняє його через 2 хвилини, і ви отримуєте помилку.

Якщо ви спробуєте GET-запит за допомогою веб-браузера, помилки взагалі не буде. Якщо ви закриєте хромоване вікно - ви відразу отримаєте помилку.

То що робити? 1) Ви можете відфільтрувати ці помилки, оскільки вони насправді не є помилками. 2) Можливо, є краще рішення :)


0

Тут, мабуть, є один додатковий випадок, що Electron не є прихильником доменного імені "localhost". У моєму випадку мені потрібно було це змінити:

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

до цього:

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

Після цього проблема просто пішла.

Це означає, що роздільна здатність DNS (локальна або віддалена) також може викликати деякі проблеми.


0

Після довгої налагодження коду js-вузла, рядка з'єднання mongodb, перевірки CORS тощо. Для мене лише перехід на інший номер порту server.listen(port);змусив його працювати postman, спробуйте також. Ніяких змін у proxyналаштуваннях немає лише за замовчуванням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.