Websocket закрывается при сообщении клиента [JavaScript/Node.js]

Я создаю простой сервер Node.js WebSocket, но после первоначального рукопожатия у меня возникла проблема.

Вначале я использовал только Chrome и командную строку для мониторинга между веб-сокетом HTML5 и сервером Node.js. Потребовалось немного времени, чтобы реализовать протокол, но я только что закончил очень простую версию декодирования сообщений на стороне сервера. Однако у меня были трудные времена, потому что всякий раз, когда я вызывал ws.send('some kind of message'), веб-сокет закрывался на стороне клиента. Глядя на вкладку сети в DevTools, похоже, что сообщение будет отправлено от клиента и получит немедленный ответ об ошибке (Opcode -1), и эта ошибка будет зарегистрирована в консоли:

WebSocket connection to 'ws://localhost:4000/' failed: A server must not mask any frames that it sends to the client.

Я изучил, что все это значит, и не могу понять, почему мой код выдает это. Я попытался восстановить его, а также отправить тестовое сообщение после подтверждения, которое сработало. Единственное, что я не пробовал, так это использование другого браузера, поэтому я попробовал его сегодня. И это сработало, как и ожидалось.

Ниже приведен весь мой соответствующий код.

Библиотеки, константы и прослушивания:

const hostname = 'localhost';
const webport = 8080;
const socketport = 4000;
const http = require('http');
const net = require('net');
const mysql = require('mysql');
const rlm = require('readline');
const crypt = require('crypto');

...

server.listen(webport,hostname);
socketServer.listen(socketport,hostname);

HTTP-сервер:

const server = http.createServer(
    function(req,res) {
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write("
<html>
    <head>
        <title>Test Title</title>
    </head>
    <body>
        <h1>Here's the thing</h1>
        <p>im baby</p>
    </body>
    <script>
        const ws = new WebSocket('ws://"+hostname+":"+socketport+"');
        ws.addEventListener('message',function(data){
            console.info(data.data)
        });
    </script>
</html>
        ");               // Reformatted for better reading
        res.end();
    });

Сетевой сервер:

var sockets = new Map();
var socketInfo = {};

 const socketDelimiters = {
    'Accept-Encoding':',',
    'Accept-Language':';',
    'Sec-WebSocket-Extensions':'; '
}

const socketServer = net.Server(function(s) {
    s.on('data',function(e) {
/*
 * If the socket is not registered, read first message as
 * the beginning to a handshake
 */
        if (sockets.get(s)==null) {
            var str = ""+e;
            var tempobj = str.split("\r\n");
            var newObj = {};
            for(var i in tempobj) {
                if (tempobj[i].length>0) {
                    var tempProperty = tempobj[i].split(': ');
                    if (tempProperty.length>1) {
                        if (socketDelimiters[tempProperty[0]]!=null){
                            tempProperty[1] = tempProperty[1].split(
                            socketDelimiters[tempProperty[0]]);
                        }
                        newObj[tempProperty[0]] = tempProperty[1];
                    } else {
                        newObj.header = tempProperty;
                    }
                }
            }
            var protocolReturn = "
                HTTP/1.1 101 Switching Protocols\r\n
                Upgrade: websocket\r\n
                Connection: Upgrade\r\n
                Sec-Websocket-Accept: "+createAcceptKey(newObj['Sec-WebSocket-Key'])
                +"\r\n\r\n";      //Reformatted for better reading
            s.write(protocolReturn);
            s.pipe(s);
            sockets.set(s,newObj['Sec-WebSocket-Key']);
            socketInfo[newObj['Sec-WebSocket-Key']] = {
                socket:s,
                isReading:false,
                message:null,
                mask:null,
                handshake: newObj
            };
            s.write(Buffer.from([0x81,0x04,0x74,0x65,0x73,0x74])); // 'test'
            s.pipe(s);
        } else {

/*
 *  If the socket is found and registered, decode the incoming message
 */
            var firstBytes = e.readUInt16BE(0);
            console.info(firstBytes);
            var length=((firstBytes & 0x007F)/0x0001);
            var FIN = ((firstBytes & 0x8000))!=0;
            var opcode = (firstBytes & 0x0F00)/0x0100;
            var mask = ((firstBytes & 0x0080)!=0);
            if (opcode!=8) {
                console.info("end: "+FIN);
                console.info("mask: "+mask);
                console.info("op code: "+opcode);
                console.info("length: "+length);
                var mask = [];
                for(var i=0; i<4; i++) {
                    var b = e.readUInt8(2+i);
                    mask.push(b);
                }
                var val=[];
                for(var i=0; i<length; i++) {
                    var b = e.readUInt8(6+i) ^ mask[i%4];
                    val.push(b);
                }
                var newVal = new Buffer.from(val);
                console.info(newVal.toString('utf8'));
            }
        }
    })

        // Handles error
    s.on('error',function(err) {
        console.info(err);
    })

        // Takes socket out of the socket list on close
    s.on('close',function(hasError) {
        if (hasError) {console.info("Please see error")}
        delete socketInfo[sockets.get(s)];
        sockets.delete(s);
    });
});

// Generates accept key from given key
function createAcceptKey(key) {
    var inKeyHash = crypt.createHash('sha1');
    inKeyHash.update(key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    return (inKeyHash.digest('base64'));
}

Что все это должно делать («<» означает «сервер-клиент», «>» означает «клиент-сервер»)

> [handshake initiation]
< [handshake confirmation]
< test
> [anything the client sends through the console]

/*
 *All I do for the client to server bit at the end is go into the console,
 * and plug in something like this
 */
ws.send('blah blah blah')

Это отлично работает в Firefox, но, как объяснялось выше, в Chrome выдает ошибку, утверждая, что сервер отправил замаскированный кадр в тот же момент, когда клиент отправляет сообщение на сервер.

Есть ли причина, по которой хром читает замаскированный кадр, а фаерфокс нет?

Обновлено:

Теперь я попытался использовать это в другом браузере (точнее, в браузере OBS), и он выдает ту же ошибку на стороне сервера, что и подключение к Chrome (я добавил прослушиватель событий для отправки сообщения в сокет, открытый на на стороне клиента). Кто-нибудь знает, почему это работает только в Firefox?

Вы должны опубликовать свой ответ как ответ.

Josh Lee 10.07.2019 15:55

@JoshLee Только что сделал это, не знал, что смогу это сделать. Спасибо!

Mefarius 11.07.2019 22:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
347
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Решил это два дня назад, не осознавал, что могу опубликовать свой собственный ответ (все еще новичок в размещении здесь, извините!)

Многое из того, что я понял о сокетах Node.js, пришло из сетевой документации. В этом есть пример взаимодействия сервера и клиента. Команда pipe() используется после записи на стороне сервера, поэтому я предположил, что она необходима при записи клиенту сокета.

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

ТЛ, ДР;

Запись в сокет оказалась проще, чем я думал:

// Expected:
socket.write('blah blah blah');
socket.pipe(socket);

// Reality
socket.write('blah blah blah');

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