NodeJS - O que realmente significa “desligar o soquete”?


277

Estou construindo um raspador de web com Node e Cheerio e, para um determinado site, estou recebendo o seguinte erro (isso acontece apenas neste site, não nos outros que eu tento raspar).

Isso acontece em um local diferente toda vez; portanto, às vezes é o url xque gera o erro, outras vezes url xé bom e é um URL totalmente diferente:

    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)

Isso é muito difícil de depurar, eu realmente não sei por onde começar. Para começar, o que é um erro de desligamento do soquete? É um erro 404 ou semelhante? Ou isso significa apenas que o servidor recusou uma conexão?

Não consigo encontrar uma explicação disso em lugar nenhum!

EDIT: Aqui está um exemplo de código que está (às vezes) retornando erros:

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

Não há ligação direta para fechar a conexão, mas estou usando o Node Requestque (tanto quanto eu sei) usa, http.getpara que isso não seja necessário, corrija-me se estiver errado!

EDIT 2: Aqui está um código em uso real que está causando erros. prodURLe outras variáveis ​​são principalmente seletores de jquery definidos anteriormente. Isso usa a asyncbiblioteca para Nó.

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
Isso significa que o soquete não envia endevento de conexão dentro do período de tempo limite. Se você estiver recebendo o pedido de cheerio via http.request(não http.get). Você precisa ligar request.end()para terminar de enviar a solicitação.
user568109

1
@ user568109 Devo notar que estou usando o requestserviço do nó , não uma http.requestsolicitação específica (acho que sou muito nova no nó!). É esse: github.com/mikeal/request Parece que termina a solicitação automaticamente, não? EDIT: De acordo com os documentos, http method, defaults to GETesse não é o problema.
JVG

2
Então não deve ser o problema. O que acontece se você comentar a parte de raspagem incluindo cheerio.load e retornar o mesmo conteúdo. O problema aqui é, cheerio.loadé assíncrono. Portanto, pode não terminar antes de começar a fazer coisas com $.
precisa saber é o seguinte

4
Às vezes, também descobri que, se eu rastrear um site com muita agressividade (como mais de 10 conexões simultâneas), eles começarão a responder com interrupções de soquete, por isso também pode ser.
tobek

1
Apenas para sua informação, em inglês, hang upsignifica encerrar uma conversa eletrônica cortando a conexão ; originou de desligar o telefone antigo.
Константин Ван

Respostas:


161

Há dois casos em que socket hang upé lançada:

Quando você é um cliente

Quando você, como cliente, envia uma solicitação para um servidor remoto e não recebe resposta oportuna. Seu soquete terminou, o que gera esse erro. Você deve capturar esse erro e decidir como lidar com isso: tente novamente a solicitação, faça uma fila para mais tarde etc.

Quando você é um servidor / proxy

Quando você, como servidor, talvez servidor proxy, recebe uma solicitação de um cliente, começa a agir sobre ele (ou retransmite a solicitação ao servidor upstream) e, antes de preparar a resposta, o cliente decide cancelar / anular o pedido.

Esse rastreamento de pilha mostra o que acontece quando um cliente cancela a solicitação.

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)

Os http.js:1526:9pontos de linha são os mesmos socketCloseListenermencionados acima pelo @Blender, particularmente:

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

Este é um caso típico se o cliente for um usuário no navegador. A solicitação para carregar algum recurso / página leva muito tempo e os usuários simplesmente atualizam a página. Essa ação faz com que a solicitação anterior seja abortada e, no lado do servidor, gera esse erro.

Como esse erro é causado pelo desejo de um cliente, ele não espera receber nenhuma mensagem de erro. Portanto, não é necessário considerar esse erro como crítico. Ignore isso. Isso é encorajado pelo fato de que, com esse erro, o ressoquete que seu cliente ouviu é, embora ainda seja gravável, destruído.

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

Portanto, não há sentido em enviar nada, exceto fechar explicitamente o objeto de resposta:

res.end();

No entanto, o que você deve fazer com certeza, se você é um servidor proxy que já retransmitiu a solicitação para o upstream, é abortar sua solicitação interna para o upstream, indicando sua falta de interesse na resposta, que por sua vez informará o upstream servidor para, talvez, interromper uma operação cara.


2
Como eu, como cliente, apenas faço a solicitação esperar por mais tempo? Está com erro em 35 segundos e preciso esperar cerca de um minuto.
Big Money

Estou enfrentando o mesmo problema. É possível aguardar resposta e começar a enviar a próxima solicitação como uma por uma execução. Posso saber como lidar com esse soquete desligado ?.
Deepak

@ BigMoney você poderia usar setTimeout(). veja esta pergunta: stackoverflow.com/questions/6214902/…
the holla

Seus detalhes me sobreviveu do inferno, eu estava usando node.js como um servidor proxy entre o servidor a montante e cliente, tempo de espera, a pedido jogou Este erro só porque eu esqueci de usar res.send, graças
Farzad YZ

Você pode receber "desligamento do soquete" como cliente quando estiver tentando fazer uma segunda solicitação ao servidor da Web de desenvolvimento do Django pela mesma conexão. Não suporta keep-alive. E caso seu cliente espere, você recebe o erro. Parece ao longo das seguintes linhas .
X-yuri

53

Dê uma olhada na fonte :

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

A mensagem é emitida quando o servidor nunca envia uma resposta.


2
De uma perspectiva funcional, você pode explicar o que isso significa? Estou tentando criar salvaguardas aqui adicionando os URLs ofensivos a uma matriz e raspando-os mais tarde. Li em alguns lugares que os erros podem ser um problema de fila no Node, não sei a melhor maneira de remediar e evitar isso.
JVG

5
Mas quanto tempo espera?
CommaToast

2
Ele deve usar en.wikipedia.org/wiki/Exponential_backoff para a implementação de "quanto tempo".
Norman H

esse "soquete desligado" não tem sentido. Isso é apenas uma surpresa da equipe do nodejs.
Puchu

45

Um caso que vale a pena mencionar: ao conectar o Node.js ao Node.js usando o Express, recebo "socket hang up" se não prefixo o caminho da URL solicitada com "/".


1
que era o meu problema, cliente e servidor em puro http node.js
ashley willis

1
@ silentorb: Você pode mostrar exemplo de URL? Estou enfrentando o mesmo erro neste caso .. Obrigado.
Pritam

4
Erro: "usuário / login", Sucesso: "/ usuário / login"
silentorb 13/15

4
Cara, eu gastei quase uma hora depurando! Vi sua resposta e pensei SH **, adicionou o / e funciona bem :) obrigado!
Daniel Gruszczyk

4
Você me salvou horas com esta resposta!
imhotep 24/07

32

Eu costumava require('http')consumir o serviço https e ele mostrava " socket hang up".

Então mudei require('http')para require('https')vez e ele está funcionando.


Embora isso possa ser uma solução para o problema, é não uma resposta para a pergunta. O pôster queria uma resposta sobre o significado da mensagem de erro. Além disso, já existem muitas respostas de alta qualidade. O seu não fornece valor adicional.
Johannes Dorn

19
Obrigado pelo seu comentário. Perco meu tempo com esse erro. Finalmente, apenas tentei esta solução e funcionou. Só quero compartilhar. Espero que seja útil que outros não percam seu tempo, não elogiando como resposta de alta qualidade.
Aekkawit Chanpen

12
@JohannesDorn Esta é uma resposta implícita à pergunta sobre o significado do erro. E útil nisso.
Ulad kasash

30

abaixo está um exemplo simples em que recebi o mesmo erro quando perdi ao adicionar o código comentado no exemplo abaixo. Remover o comentário do código req.end()resolverá esse problema.

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
Isso salvou minha sanidade ... Obrigado!
PGallagher

Você é um herói! Obrigado.
Xenhat 9/01/19

17

Expandindo a resposta do Blender, isso acontece em várias situações. Os mais comuns que encontro são:

  1. O servidor travou.
  2. O servidor recusou sua conexão, provavelmente bloqueado por User-Agent.

socketCloseListener, conforme descrito na resposta do Blender, não é o único local em que os erros de interrupção são criados.

Por exemplo, encontrado aqui :

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

Você pode tentar curlcom os cabeçalhos e outros itens enviados pelo Node e verificar se obtém uma resposta lá. Se você não obtiver uma resposta curl, mas obtiver uma resposta no seu navegador, User-Agenté provável que seu cabeçalho esteja bloqueado.


3
Outro motivo pelo qual o servidor pode recusar sua conexão (apenas cliquei ao mudar para prod em vez de controle de qualidade) é se o servidor espera uma solicitação https em vez de http.
Mcole

7

Outro caso que vale a pena mencionar (para Linux e OS X) é que, se você usar uma biblioteca como httpspara executar as solicitações ou se passar https://...como uma URL da instância atendida localmente, estará usando a porta 443que é uma porta privada reservada e você pode estar terminando em Socket hang upou ECONNREFUSEDerros.

Em vez disso, use port 3000, fe e faça uma httpsolicitação.


6

Eu tive o mesmo problema ao usar a biblioteca Nano para conectar-se ao Couch DB . Tentei ajustar o pool de conexões com o uso da biblioteca keepaliveagent e ela continuava falhando com a mensagem de desligamento do soquete .

var KeepAliveAgent = require('agentkeepalive');

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

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

Depois de algumas lutas, consegui resolver o problema - como se viu, foi um erro muito, muito simples. Eu estava conectando ao banco de dados via protocolo HTTPS, mas continuei passando para o meu nanoobjeto um agente keepalive criado como os exemplos de uso dessa biblioteca (eles dependem de alguns padrões que usam http).

Uma simples alteração no uso do HttpsAgent fez o truque:

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

1
Para um pouco mais detalhadamente, se a solicitação estiver configurada para a porta 443 e a solicitação for emitida pelo módulo http em vez do módulo https, você poderá desligar o soquete. Seria bom se houvesse mais detalhes sobre o motivo da desconexão (negociação SSL / TLS?). Eu já vi esse nível de detalhe no ASP.NET, por exemplo.
Richard Collette

6

Isso me causou problemas, pois eu estava fazendo tudo listado aqui, mas ainda estava recebendo erros. Acontece que chamar req.abort () realmente gera um erro, com um código ECONNRESET, então você realmente precisa capturá-lo no seu manipulador de erros.

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

5

Para requestusuários do módulo

Timeouts

Existem dois tipos principais de tempos limite: tempos limite de conexão e tempos limite de leitura . Um tempo limite de conexão ocorre se o tempo limite for atingido enquanto o cliente está tentando estabelecer uma conexão com uma máquina remota (correspondente à connect()chamada no soquete). Um tempo limite de leitura ocorre sempre que o servidor fica muito lento para enviar de volta uma parte da resposta.

Observe que o tempo limite da conexão emite um ETIMEDOUTerro e o tempo limite da leitura emite um ECONNRESETerro.


3

Eu tive o mesmo problema durante a solicitação para algum servidor. No meu caso, definir qualquer valor como User-Agent nos cabeçalhos nas opções de solicitação me ajudou.

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

Não é um caso geral e depende das configurações do servidor.


2

Também a razão pode ser por causa do uso de appinstance of em expressvez de serverfromconst server = http.createServer(app) ao criar socket do servidor.

Errado

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

Corrigir

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

Eu desenvolvo a Web (nó) e o Android e abro o simulador e o docker do Android Studio juntos, ambos usam a porta 8601, queixou-se de socket hang uperro, depois de fechar o simulador do Android Studio e funciona bem no lado do nó. Não use o simulador e a janela de encaixe do Android Studio juntos.


1

Eu recebi um erro semelhante ao usar o CouchDB no cluster 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
    }
  }

Que deve ser "http", não "https", para se conectar à minha instância do CouchDB. Espero que possa ser útil para quem enfrenta problemas semelhantes.


0

No meu caso, foi porque uma resposta de application / json estava mal formatada (contém um rastreamento de pilha). A resposta nunca foi enviada ao servidor. Isso foi muito difícil de depurar porque, não havia log. Esse tópico me ajuda muito a entender o que acontece.


0

Caso esteja usando o node-http-proxy, lembre-se deste problema, que resultará em um erro de desligamento do soquete: https://github.com/nodejitsu/node-http-proxy/issues/180 .

Para resolução, também neste link, basta mover a declaração da rota da API (para proxy) dentro das rotas expressas antes de express.bodyParser ().


0

Ontem, deparei-me com esse problema ao executar meu aplicativo Web e servidor node.js. através do IntelliJ IDEA 2016.3.6. Tudo o que eu precisava fazer era limpar meus cookies e cache no meu navegador Chrome.


0

Se você estiver enfrentando esse erro em uma conexão https e isso estiver acontecendo instantaneamente, pode ser um problema ao configurar a conexão SSL.

Para mim, foi esse problema https://github.com/nodejs/node/issues/9845, mas para você poderia ser outra coisa. Se houver um problema com o ssl, você poderá reproduzi-lo com o pacote nodejs tls / ssl apenas tentando se conectar ao domínio


0

Eu acho que vale a pena notar ...

Eu estava criando testes para APIs do Google. Eu estava interceptando a solicitação com um servidor improvisado e depois encaminhando-as para a API real. Eu estava tentando apenas passar os cabeçalhos na solicitação, mas alguns cabeçalhos estavam causando um problema com express do outro lado.

Ou seja, eu tive que excluir connection, accepte content-lengthcabeçalhos antes de usar o módulo de solicitação para encaminhar.

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

No meu caso, não foi um erro, mas o comportamento esperado para o navegador Chrome. O Chrome mantém a conexão tls ativa (por velocidade, eu acho), mas o servidor node.js interrompe após 2 minutos e você recebe um erro.

Se você tentar a solicitação GET usando o navegador de borda, não haverá nenhum erro. Se você fechar a janela do Chrome, receberá um erro imediatamente.

Então o que fazer? 1) Você pode filtrar esses erros, porque eles não são realmente erros. 2) Talvez exista uma solução melhor :)


0

Parece haver um caso adicional aqui: o Electron não é um fã do nome de domínio "localhost". No meu caso, eu precisava mudar isso:

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

para isso:

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

Depois disso, o problema acabou.

Isso significa que a resolução DNS (local ou remota) também pode estar causando alguns problemas.


0

Depois de um longo depuração em código nó js, seqüência de conexão mongodb, verificando CORS etc, para mim apenas mudar para um número de porta diferente server.listen(port);fez o trabalho, para postman, tentar isso também. Nenhuma alteração nas proxyconfigurações apenas os padrões.


-1

Este erro também pode ocorrer ao trabalhar com http.request, provavelmente sua solicitação ainda não foi concluída.

Exemplo:

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

E você sempre precisa adicionar esta linha: req.end() Com esta função, pediremos para terminar o envio da solicitação.

Como na documentação é dito:

Com http.request (), é preciso sempre chamar req.end () para indicar o final da solicitação - mesmo se não houver dados sendo gravados no corpo da solicitação.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.