Жонли чат, ошхонадаги буюртма экрани, биржа курси ёки онлайн ўйин каби иловалар маълумотни саҳифани янгиламасдан, шу заҳоти кўрсатиши керак. Бунинг учун кўпинча WebSocket технологияси танланади. У браузер ва сервер ўртасида бир марта очиладиган ва узоқ вақт очиқ турадиган икки томонлама канал яратади. Муаммо шундаки, дастурчи компьютерида аъло ишлаган WebSocket илова оддий хостингга юкланганда кўпинча умуман уланмайди. Қуйида сабабини ва тўғри ечимни кўриб чиқамиз.
WebSocket оддий HTTP сўровдан нимаси билан фарқ қилади
Оддий сайт "сўров ва жавоб" тамойилида ишлайди: браузер саҳифани сўрайди, сервер PHP скриптни бажаради, жавоб қайтаради ва уланиш тугайди. WebSocket ҳам HTTP сўров билан бошланади, лекин бу сўровда Upgrade: websocket сарлавҳаси бўлади. Сервер рози бўлса, 101 Switching Protocols жавобини қайтаради ва шундан кейин ўша TCP уланиш ёпилмайди. Энди сервер ҳам, браузер ҳам исталган пайтда бир-бирига хабар юбора олади.
Демак, WebSocket учун серверда доимий ишлаб турадиган, уланишларни ушлаб турадиган жараён керак. Бу одатда Node.js, Python ёки Go'да ёзилган алоҳида дастур бўлиб, у ўз портини тинглайди ва минглаб очиқ уланишларни хотирада сақлайди. Айнан шу талаб оддий хостинг билан тўқнаш келади.
Нега оддий хостингда WebSocket ишламайди
Оддий (виртуал) хостингда битта сервер кўплаб мижозлар ўртасида бўлинган. У ерда PHP скриптлар сўров келганда ишга тушади ва бир неча сонияда тугаши керак, бажарилиш вақти чекланган. Мижозга ўз портини очиш ва унга ташқаридан уланиш имкони одатда берилмайди, доимий ишлаб турадиган фон жараёнлари эса ресурс чекловлари сабабли тўхтатилиши мумкин. Веб-сервер созламалари ҳам мижознинг қўлида эмас, шунинг учун Upgrade сарлавҳасини ички дастурга узатадиган прокси қоидасини қўшиб бўлмайди.
Шу сабабли PHP'да ёзилган WebSocket кутубхоналари (масалан, Ratchet) ҳам оддий хостингда барқарор ишламайди: улар ҳам доимий жараён ва очиқ порт талаб қилади. Агар лойиҳа оддий хостингда қолиши шарт бўлса, реал вақтга яқин ишлашни бошқа йўл билан қилиш керак.
Оддий хостингда қолиш керак бўлса: муқобил йўллар
Кўп ҳолларда "жонли" кўриниш учун WebSocket аслида шарт эмас. Масалан, мактаб сайтида дарс жадвали ўзгаришлари ёки интернет-дўкондаги буюртма ҳолати учун браузер ҳар 5-10 сонияда кичик JSON сўров юборса етарли. Бу даврий сўров (polling) усули оддий хостингда муаммосиз ишлайди, чунки ҳар бир сўров қисқа ва одатий PHP скрипт сифатида бажарилади.
long polling ва Server-Sent Events каби усуллар ҳам бор, лекин улар ҳар бир фойдаланувчи учун PHP жараённи узоқ вақт банд қилади. Бир вақтда ўнлаб фойдаланувчи уланса, хостингдаги жараёнлар лимити тезда тугайди ва сайт бошқа сўровларга жавоб бермай қўяди. Шунинг учун оддий хостингда қисқа даврий сўров энг хавфсиз танлов, ҳақиқий WebSocket эса алоҳида серверда ишлатилади.
Мисол: ресторан ошхонаси учун буюртма экрани
Тошкентдаги ресторан официантлар планшетда буюртма киритишини ва у ошхонадаги экранда дарҳол пайдо бўлишини хоҳлайди. Бир-икки сониялик кечикиш ҳам ошпазлар учун ноқулай, шунинг учун бу ерда WebSocket ўринли. Асосий сайт ва меню оддий хостингда қолиши мумкин, WebSocket сервери эса кичик VPS'да ишлайди. Қуйида Node.js ва ws кутубхонаси асосидаги минимал сервер келтирилган: у ҳар бир келган буюртмани барча уланган экранларга тарқатади ва узилиб қолган уланишларни тозалайди.
// server.js (npm install ws)
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ host: '127.0.0.1', port: 8080 });
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', () => { ws.isAlive = true; });
ws.on('message', (data) => {
// yangi buyurtmani barcha ulangan ekranlarga tarqatish
for (const client of wss.clients) {
if (client.readyState === 1) client.send(data.toString());
}
});
});
// 30 soniyada bir "tirikmi" tekshiruvi
setInterval(() => {
for (const ws of wss.clients) {
if (!ws.isAlive) { ws.terminate(); continue; }
ws.isAlive = false;
ws.ping();
}
}, 30000);
Эътибор беринг, сервер фақат 127.0.0.1 манзилини тинглайди, яъни ташқаридан тўғридан-тўғри уланиб бўлмайди. Ташқи уланишларни Nginx қабул қилади, шифрлайди ва ичкарига узатади.
Nginx орқали wss:// уланишни созлаш
Агар сайт HTTPS орқали очилса, браузер шифрланмаган ws:// уланишни аралаш контент сифатида блоклайди, шунинг учун wss:// керак. Энг қулай йўл: Let's Encrypt сертификати Nginx'да туради, Nginx эса /ws/ манзилига келган сўровларни ички Node.js серверига прокси қилади. Муҳим нуқта Upgrade ва Connection сарлавҳаларини узатиш ва HTTP/1.1 версиясини ёқишдир, акс ҳолда Nginx уланишни оддий сўров деб ҳисоблайди.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name misol.uz;
# ssl_certificate ... (Let's Encrypt sertifikati)
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
}
proxy_read_timeout қиймати ҳам аҳамиятли. Стандарт ҳолатда Nginx 60 сония давомида маълумот келмаса уланишни ёпади. Юқоридаги сервердаги 30 сониялик ping бу муддатдан олдин трафик ҳосил қилади, узунроқ тайм-аут эса қўшимча захира беради. Браузер томонида уланиш қуйидагича очилади:
const sock = new WebSocket('wss://misol.uz/ws/');
sock.onmessage = (e) => showOrder(JSON.parse(e.data));
sock.onclose = () => setTimeout(() => location.reload(), 3000);
Уланишни текшириш ва серверни доимий ишлатиш
Созлама тўғрилигини браузердан олдин curl билан текшириш мумкин. Жавобнинг биринчи қаторида HTTP/1.1 101 Switching Protocols чиқса, Nginx ҳам, Node.js ҳам тўғри ишлаяпти. Агар 400 ёки 502 чиқса, аввал Node.js жараёни ишлаётганини, кейин Nginx конфигурациясидаги сарлавҳаларни текширинг.
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
https://misol.uz/ws/
Node.js серверни терминалда қўлда очиб қолдирманг: SSH сессияси ёпилганда у ҳам тўхташи мумкин. Уни systemd хизмати қилиб қўйинг, шунда сервер қайта юкланганда ҳам, дастур йиқилганда ҳам автоматик кўтарилади.
# /etc/systemd/system/ws-kitchen.service
[Service]
WorkingDirectory=/opt/ws-kitchen
ExecStart=/usr/bin/node server.js
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now ws-kitchen
sudo nginx -t && sudo systemctl reload nginx
Бир нечта жараён ва масштаблаш ҳақида
Битта Node.js жараён ўртача юкламадаги илова учун етарли. Агар кейинроқ бир нечта жараён ёки бир нечта сервер ишлатсангиз, бир жараёнга уланган мижоз бошқа жараёндаги хабарни кўрмайди. Бундай ҳолда жараёнлар хабарларни умумий навбат орқали алмашиши керак, кўпинча бу вазифани Redis'нинг pub/sub имконияти бажаради.
Қисқаси, WebSocket учун доимий жараён, очиқ порт ва веб-сервер созламаларига кириш керак, буларнинг барчаси эса VPS'да бор. sayt.uz кабинетидан VPS буюртма қилиб, асосий сайтни хостингда қолдирган ҳолда фақат реал вақт қисмини алоҳида серверга чиқариш мумкин.