NodeJS - Co tak naprawdę oznacza „rozłączanie gniazd”?


276

Buduję skrobak sieciowy z Node i Cheerio, a dla pewnej witryny pojawia się następujący błąd (zdarza się to tylko na tej jednej stronie, nie ma innych, które próbuję zeskrobać).

Zdarza się to za każdym razem w innym miejscu, więc czasami powoduje to url xbłąd, innym razem url xjest w porządku i jest to zupełnie inny adres 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)

Jest to bardzo trudne do debugowania, tak naprawdę nie wiem od czego zacząć. Na początek, czym jest błąd zawieszenia gniazda? Czy to błąd 404 lub podobny? Czy to tylko oznacza, że ​​serwer odrzucił połączenie?

Nigdzie nie mogę tego wyjaśnić!

EDYCJA: Oto próbka kodu, który (czasami) zwraca błędy:

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

Nie ma bezpośredniego połączenia, aby zamknąć połączenie, ale używam Node Requesttego (o ile wiem), http.getwięc nie jest to wymagane, popraw mnie, jeśli się mylę!

EDYCJA 2: Oto rzeczywisty, używany fragment kodu, który powoduje błędy. prodURLa inne zmienne to głównie selektory jquery, które zostały wcześniej zdefiniowane. Używa asyncbiblioteki dla Węzła.

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
Oznacza to, że gniazdo nie wysyła endzdarzenia połączenia w określonym czasie. Jeśli otrzymujesz prośbę o cheerio przez http.request(nie http.get). Musisz zadzwonić, request.end()aby zakończyć wysyłanie żądania.
user568109

1
@ user568109 Powinienem zauważyć, że używam requestusługi węzła , a nie konkretnego http.requestżądania (myślę, że jestem nowy w węźle!). To jest ten: github.com/mikeal/request Wygląda na to, że automatycznie kończy żądanie, nie? EDYCJA: Według dokumentów, http method, defaults to GETwięc nie o to chodzi.
JVG,

2
To nie powinien być problem. Co się stanie, jeśli skomentujesz część zgarniającą, w tym cheerio.load, i zwrócisz tę samą treść. Połów tutaj cheerio.loadjest asynchroniczny. Więc może się nie skończyć, zanim zaczniesz robić rzeczy za pomocą $.
user568109

4
Czasem też zauważyłem, że jeśli zbyt agresywnie zaindeksuję witrynę (np. Ponad 10 jednoczesnych połączeń), zaczną reagować na zawieszanie się gniazd, więc może to być również to.
tobek

1
Po prostu FYI po angielsku hang upoznacza zakończenie elektronicznej rozmowy przez przerwanie połączenia ; powstało z odłożenia starego telefonu.
Константин Ван

Odpowiedzi:


161

Istnieją dwa przypadki socket hang uprzucenia:

Kiedy jesteś klientem

Gdy Ty, jako klient, wysyłasz żądanie do zdalnego serwera i nie otrzymujesz żadnej odpowiedzi w odpowiednim czasie. Twoje gniazdo jest zakończone, co generuje ten błąd. Powinieneś wychwycić ten błąd i zdecydować, jak go obsłużyć: czy ponowić żądanie, ustawić je w kolejce na później itp.

Gdy jesteś serwerem / proxy

Gdy jako serwer, być może serwer proxy, otrzymasz żądanie od klienta, a następnie zaczniesz na nim reagować (lub przekażesz żądanie do serwera nadrzędnego), a przed przygotowaniem odpowiedzi klient zdecyduje się anulować / przerwać prośba.

Ten ślad stosu pokazuje, co się dzieje, gdy klient anuluje żądanie.

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)

Linia http.js:1526:9wskazuje na to samo, o którym socketCloseListenermowa powyżej przez @Blender, w szczególności:

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

Jest to typowy przypadek, gdy klient jest użytkownikiem przeglądarki. Żądanie załadowania części zasobów / strony trwa długo, a użytkownicy po prostu odświeżają stronę. Takie działanie powoduje, że poprzednie żądanie zostanie przerwane, co po stronie serwera zgłasza ten błąd.

Ponieważ ten błąd jest spowodowany życzeniem klienta, nie oczekuje on żadnego komunikatu o błędzie. Dlatego nie trzeba uważać tego błędu za krytyczny. Po prostu to zignoruj. Zachęca to fakt, że po takim błędzie resgniazdo, którego słuchał klient, jest, choć nadal zapisywalne, zniszczone.

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

Więc nie ma sensu niczego wysyłać, z wyjątkiem jawnego zamknięcia obiektu odpowiedzi:

res.end();

Jednak to, co należy zrobić na pewno, jeśli jest serwer proxy, który już przekazał wniosek do Upstream jest do przerwania swojej wewnętrznej wniosku do Upstream, wskazując swój brak zainteresowania w odpowiedzi, co z kolei powie upstream serwer, aby być może zatrzymać kosztowną operację.


2
Jak mogę, jako klient, po prostu sprawić, aby żądanie czekało dłużej? Błąd pojawia się po 35 sekundach i muszę poczekać około minuty.
Big Money

Mam do czynienia z tym samym problemem. Czy można poczekać na odpowiedź i rozpocząć wysyłanie następnego żądania, takiego jak jedno po drugim. Czy mogę wiedzieć, jak obsługiwać to gniazdo rozłączone ?.
Deepak

@BigMoney możesz użyć setTimeout(). zobacz to pytanie: stackoverflow.com/questions/6214902/...
holla,

Twoje dane przetrwały mnie z piekła, użyłem node.js jako serwera proxy między serwerem nadrzędnym a klientem, przekroczenie limitu czasu na żądanie spowodowało wygenerowanie tego błędu tylko dlatego, że zapomniałem użyć res.send, dzięki
Farzad YZ

Możesz otrzymać „rozłączenie gniazda” jako klient, gdy próbujesz wysłać drugie żądanie do programistycznego serwera WWW Django przez to samo połączenie. Nie obsługuje keep-alive. A jeśli Twój klient tego oczekuje, pojawia się błąd. Wygląda następująco .
x-yuri

53

Spójrz na źródło :

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

Wiadomość jest emitowana, gdy serwer nigdy nie wysyła odpowiedzi.


2
Czy z funkcjonalnego punktu widzenia możesz wyjaśnić, co to oznacza? Usiłuję zbudować tutaj zabezpieczenia, dodając obrażające adresy URL do tablicy, a następnie usuwając je później. Czytałem w kilku miejscach, że błędy mogą być problemem w kolejce z Węzłem, nie znam najlepszego sposobu, aby to naprawić i tego uniknąć.
JVG

5
Ale jak długo to czeka?
CommaToast,

2
Powinien używać en.wikipedia.org/wiki/Exponential_backoff do implementacji „jak długo”.
Norman H

to „rozłączanie gniazd” jest bez znaczenia. To tylko niespodzianka od zespołu nodejs.
puchu

45

Warto wspomnieć o jednym przypadku: kiedy łączę się z Node.js do Node.js za pomocą Express, dostaję „zawieszanie się gniazda”, jeśli nie poprzedzę żądanej ścieżki URL znakiem „/”.


1
to był mój problem, zarówno klienta, jak i serwera w czystym http node.js
ashley willis

1
@silentorb: Czy możesz podać przykładowy adres URL? W tym przypadku mam do czynienia z tym samym błędem. Dzięki.
Pritam

4
Błąd: „user / login”, Success: „/ user / login”
silentorb,

4
Człowieku, spędziłem prawie godzinę na debugowaniu! Zobaczyłem twoją odpowiedź i pomyślałem SH **, dodałem / i działa dobrze :) dzięki!
Daniel Gruszczyk

4
Zaoszczędziłeś mi godziny dzięki tej odpowiedzi!
imhotep

32

Kiedyś korzystałem require('http')z usługi https , która pokazywała „ socket hang up”.

Potem zmieniłem require('http')na require('https')i to działa.


Choć może to być rozwiązanie problemu, to nie jest odpowiedź na pytanie. Plakat chciał uzyskać odpowiedź na temat znaczenia komunikatu o błędzie. Ponadto istnieje już wiele wysokiej jakości odpowiedzi. Twój nie zapewnia dodatkowej wartości.
Johannes Dorn,

19
Dziękuję za komentarz. Tracę czas na ten błąd. Wreszcie, po prostu wypróbowuję to rozwiązanie i działa. Po prostu chcę się podzielić. Mam nadzieję, że inni mogą nie marnować czasu, a nie chwalić jako odpowiedź wysokiej jakości.
Aekkawit Chanpen

12
@JohannesDorn Jest to domyślna odpowiedź na pytanie, co oznacza błąd. I w tym przydatny.
Ulad Kasach

29

poniżej jest prosty przykład, w którym dostałem ten sam błąd, gdy brakowało mi dodania skomentowanego kodu w poniższym przykładzie. Usunięcie komentarza z kodu req.end()rozwiązuje ten problem.

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
To uratowało mi zdrowie psychiczne ... Dzięki!
PGallagher

Jesteś bohaterem! Dziękuję Ci.
Xenhat

17

Rozwijając odpowiedź Blendera, dzieje się to w wielu sytuacjach. Najczęściej spotykane przeze mnie to:

  1. Serwer się zawiesił.
  2. Serwer odrzucił twoje połączenie, najprawdopodobniej zablokowane przez User-Agent.

socketCloseListener, jak opisano w odpowiedzi Blendera, nie jest jedynym miejscem, w którym powstają błędy zawieszenia.

Na przykład tutaj :

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

Możesz spróbować curlz nagłówkami i innymi, które są wysyłane z Węzła i sprawdzić, czy dostaniesz tam odpowiedź. Jeśli nie otrzymasz odpowiedzi curl, ale otrzymasz odpowiedź w przeglądarce, User-Agentnajprawdopodobniej Twój nagłówek zostanie zablokowany.


3
Innym powodem, dla którego serwer może odmówić połączenia (właśnie trafiłem, przechodząc do prod zamiast QA), jest to, że twój serwer oczekuje żądania https zamiast http.
mcole

7

Innym przypadkiem, o którym warto wspomnieć (w przypadku systemu Linux i OS X) jest to, że jeśli korzystasz z biblioteki, takiej jak httpsdo wykonywania żądań, lub jeśli podasz https://...jako adres URL lokalnie obsługiwanej instancji, będziesz używać portu, 443który jest zarezerwowanym portem prywatnym, a ty może skończyć się w Socket hang uplubECONNREFUSED błędów.

Zamiast tego użyj port 3000, fe i wykonaj httpżądanie.


6

Miałem ten sam problem podczas korzystania z biblioteki Nano do łączenia się z wersją DB kanapy . Próbowałem dostroić pule połączeń za pomocą biblioteki keepaliveagent i nadal nie udawało się to z komunikatem rozłączenia się gniazda .

var KeepAliveAgent = require('agentkeepalive');

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

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

Po kilku zmaganiach udało mi się rozwiązać problem - jak się okazało, był to bardzo, bardzo prosty błąd. Łączyłem się z bazą danych za pomocą protokołu HTTPS, ale ciągle przekazywałem mojemu obiektowi nano agenta utrzymywania aktywności utworzonego jako przykłady użycia tego pokazu biblioteki (opierają się one na niektórych ustawieniach domyślnych, które używają http).

Jedna prosta zmiana w użyciu HttpsAgent załatwiła sprawę :

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

1
Dla nieco więcej szczegółów, jeśli żądanie jest skonfigurowane dla portu 443, a żądanie jest wysyłane za pośrednictwem modułu http zamiast modułu https, wówczas rozłącza się gniazdo. Byłoby miło, gdyby było więcej szczegółów na temat tego, dlaczego nastąpiło rozłączenie (negocjacja SSL / TLS?). Widziałem ten poziom szczegółowości na przykład w ASP.NET.
Richard Collette

6

To spowodowało problemy, ponieważ robiłem wszystko tutaj wymienione, ale wciąż zgłaszano błędy. Okazuje się, że wywołanie req.abort () w rzeczywistości generuje błąd z kodem ECONNRESET, więc trzeba go złapać w module obsługi błędów.

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

5

Dla requestużytkowników modułu

Limit czasu

Istnieją dwa główne typy limitów czasu: limit połączenia i limitów odczytu . Connect limitu czasu występuje, jeśli limit czasu jest trafiony podczas gdy klient próbuje nawiązać połączenie ze zdalnym komputerem (odpowiadającejconnect()rozmowy na gnieździe). Odczytu limitu czasu występuje w każdej chwili serwer jest zbyt powolny, aby odesłać część odpowiedzi.

Pamiętaj, że limity czasu połączenia emitująETIMEDOUT błąd, a przekroczenia limitu czasu odczytu powodują ECONNRESETbłąd.


3

Miałem ten sam problem podczas żądania do jakiegoś serwera. W moim przypadku ustawienie dowolnej wartości na User-Agent w nagłówkach w opcjach żądania pomogło mi.

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

To nie jest ogólny przypadek i zależy od ustawień serwera.


2

Przyczyną może być również użycie appinstancji expresszamiast serverzconst server = http.createServer(app) jednocześnie tworząc gniazdo serwera.

Źle

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

Poprawny

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

Zajmuję się zarówno tworzeniem aplikacji internetowych (węzłów), jak i Androidem, a jednocześnie otwieram symulator urządzenia dokującego Android Studio i dokera, oba używają portu 8601, narzekał na socket hang upbłąd, po zamknięciu symulatora urządzenia Android Studio i działa dobrze po stronie węzła. Nie używaj razem symulatora urządzenia Android Studio i dokera.


1

Wystąpił podobny błąd podczas korzystania z CouchDB w klastrze 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
    }
  }

Który powinien być „http”, a nie „https”, aby połączyć się z moją instancją CouchDB. Mam nadzieję, że może to być pomocne dla każdego, kto ma podobny problem.


0

W moim przypadku było tak, ponieważ odpowiedź aplikacji / json była źle sformatowana (zawiera ślad stosu). Odpowiedź nigdy nie została wysłana na serwer. Debugowanie było bardzo trudne, ponieważ nie było dziennika. Ten wątek bardzo pomaga mi zrozumieć, co się dzieje.



0

Wystąpił wczoraj ten problem podczas uruchamiania mojej aplikacji internetowej i serwera node.js przez IntelliJ IDEA 2016.3.6. Wszystko, co musiałem zrobić, to wyczyścić pliki cookie i pamięć podręczną w przeglądarce Chrome.


0

Jeśli ten błąd występuje podczas połączenia https i występuje natychmiast, może to być problem z ustanowieniem połączenia SSL.

Dla mnie to był ten problem https://github.com/nodejs/node/issues/9845, ale dla ciebie może to być coś innego. Jeśli jest to problem z ssl, powinieneś być w stanie go odtworzyć za pomocą pakietu tod / ssl nodejs, który próbuje się połączyć z domeną


0

Myślę, że warto zauważyć ...

Tworzyłem testy interfejsów API Google. Przechwyciłem żądanie za pomocą prowizorycznego serwera, a następnie przesłałem je do prawdziwego interfejsu API. Próbowałem po prostu przekazać nagłówki w żądaniu, ale kilka nagłówków powodowało problem z ekspresowym na drugim końcu.

Mianowicie, musiałem usunąć connection, accepti content-lengthnagłówki przed użyciem modułu żądania do przekazania dalej.

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

W moim przypadku nie był to błąd, ale oczekiwane zachowanie przeglądarki Chrome. Chrome utrzymuje połączenie tls przy życiu (myślę, że dla szybkości), ale serwer node.js zatrzymuje go po 2 minutach i pojawia się błąd.

Jeśli spróbujesz uzyskać polecenie GET przy użyciu przeglądarki krawędzi, nie wystąpi żaden błąd. Jeśli zamkniesz okno chrome - natychmiast otrzymasz błąd.

Co więc zrobić? 1) Możesz filtrować te błędy, ponieważ tak naprawdę nie są to błędy. 2) Może jest lepsze rozwiązanie :)


0

Wydaje się, że istnieje tutaj jeden dodatkowy przypadek, którym jest to, że Electron nie jest fanem nazwy domeny „localhost”. W moim przypadku musiałem to zmienić:

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

do tego:

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

Po tym problem zniknął.

Oznacza to, że rozpoznawanie DNS (lokalne lub zdalne) może również powodować pewne problemy.


0

Po długim debugowaniu w kodzie js węzła, ciągu połączenia mongodb, sprawdzaniu CORS itp. Dla mnie po prostu przełączenie na inny numer portu server.listen(port);sprawiło, że działało postman, spróbuj też. Brak zmian w proxyustawieniach, tylko wartości domyślne.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.