การสร้างเซิร์ฟเวอร์ wss
ถาม:
Workerman สร้างเซิร์ฟเวอร์ wss อย่างไร เพื่อให้ไคลเอนต์สามารถเชื่อมต่อสื่อสารผ่าน wss เช่น ในแอพพลิเคชันมินิโปรแกรมของ WeChat。
ตอบ:
โปรโตคอล wss คือ websocket + SSL ซึ่งคือการเพิ่มชั้น SSL ลงในโปรโตคอล websocket คล้ายกับ https (http + SSL)。
ดังนั้นเพียงแค่เปิดใช้งาน SSL บนพื้นฐานของโปรโตคอล websocket ก็จะสามารถรองรับโปรโตคอล wss ได้。
วิธีที่หนึ่ง ใช้ nginx/apache เป็นพร็อกซี่ SSL (แนะนำ)
เหตุผลที่แนะนำ
- สามารถใช้พอร์ต 443 ได้อีก สามารถเชื่อมต่อได้โดยไม่ต้องระบุพอร์ต
- การจัดการ SSL certificate อย่างรวมศูนย์ สามารถใช้การกำหนดค่าของเว็บไซต์ได้
- สามารถทำการโหลดบาลานซ์
- มีการตรวจสอบล็อกข้อมูลง่าย
- รองรับความเข้ากันได้ที่ดีขึ้น
หลักการและกระบวนการสื่อสาร
-
ไคลเอนต์ทำการเชื่อมต่อ wss ไปที่ nginx/apache
-
nginx/apache จะแปลงข้อมูลโปรโตคอล wss เป็นข้อมูลโปรโตคอล ws และส่งต่อไปยังพอร์ตโปรโตคอล websocket ของ Workerman
-
Workerman ได้รับข้อมูลแล้วดำเนินการตามตรรกะธุรกิจ
-
เมื่อ Workerman ส่งข้อความกลับไปให้ไคลเอนต์ จะเป็นกระบวนการตรงกันข้าม ข้อมูลจะถูกแปลงจาก nginx/apache เป็นโปรโตคอล wss แล้วส่งให้ไคลเอนต์
การกำหนดค่า nginx อ้างอิง
เงื่อนไขเบื้องต้นและการเตรียมการ:
-
ติดตั้ง nginx แล้ว เวอร์ชันไม่ต่ำกว่า 1.3
-
สมมุติว่า Workerman ฟังที่พอร์ต 8282 (โปรโตคอล websocket)
-
ได้มีการขอใบรับรอง (pem/crt และไฟล์ key) วางไว้ที่ /etc/nginx/conf.d/ssl
-
ต้องการใช้ nginx เปิดพอร์ต 443 เพื่อให้บริการพร็อกซี่ wss (สามารถเปลี่ยนพอร์ตตามต้องการ)
-
โดยปกติแล้ว nginx จะทำงานเป็นเซิร์ฟเวอร์เว็บไซต์สำหรับบริการอื่น สำหรับไม่ให้มีผลกระทบต่อการใช้ไซต์ดั้งเดิม ใช้ที่อยู่
domain.com/wssเป็นทางเข้าของพร็อกซี่ wss หมายความว่าที่อยู่เชื่อมต่อของไคลเอนต์คือ wss://domain.com/wss
การกำหนดค่า nginx จะมีลักษณะดังนี้:
server {
listen 443;
# การกำหนดค่าชื่อโดเมนถูกละเว้น...
ssl on;
ssl_certificate /etc/ssl/server.pem;
ssl_certificate_key /etc/ssl/server.key;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
ssl_protocols SSLv3 SSLv2 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
location /wss {
proxy_pass http://127.0.0.1:8282;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Real-IP $remote_addr;
}
# location / {} การกำหนดค่าอื่นๆ ของไซต์...
}
การทดสอบ
// ใบรับรองจะตรวจสอบชื่อโดเมน โปรดเชื่อมต่อด้วยชื่อโดเมน โดยไม่ต้องระบุพอร์ต
ws = new WebSocket("wss://domain.com/wss");
ws.onopen = function() {
alert("เชื่อมต่อสำเร็จ");
ws.send('tom');
alert("ส่งสตริงหนึ่งไปยังเซิร์ฟเวอร์: tom");
};
ws.onmessage = function(e) {
alert("ได้รับข้อความจากเซิร์ฟเวอร์: " + e.data);
};
ใช้ apache เป็นพร็อกซี่ wss
สามารถใช้ apache เป็นพร็อกซี่การส่งต่อให้กับ workerman ได้
การเตรียมการ:
-
GatewayWorker ฟังที่พอร์ต 8282 (โปรโตคอล websocket)
-
ได้ขอใบรับรอง ssl แล้ว สมมุติว่าอยู่ที่ /server/httpd/cert/
-
ใช้ apache ส่งต่อพอร์ต 443 ไปที่พอร์ตที่ระบุ 8282
-
httpd-ssl.conf ถูกโหลดแล้ว
-
openssl ได้ติดตั้งแล้ว
เปิดใช้งานโมดูล proxy_wstunnel_module
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
กำหนดค่า SSL และพร็อกซี่
#extra/httpd-ssl.conf
DocumentRoot "/เว็บไซต์/โฟลเดอร์"
ServerName domain.com
# การกำหนดพร็อกซี
SSLProxyEngine on
ProxyRequests Off
ProxyPass /wss ws://127.0.0.1:8282/wss
ProxyPassReverse /wss ws://127.0.0.1:8282/wss
# เพิ่มการสนับสนุนโปรโตคอล SSL, ลบโปรโตคอลที่ไม่ปลอดภัย
SSLProtocol all -SSLv2 -SSLv3
# เปลี่ยนชุดรหัสดังนี้
SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM
SSLHonorCipherOrder on
# กำหนดค่าใบรับรองสาธารณะ
SSLCertificateFile /server/httpd/cert/your.pem
# กำหนดค่าใบรับรองส่วนตัว
SSLCertificateKeyFile /server/httpd/cert/your.key
# กำหนดค่าใบรับรอง Chain,
SSLCertificateChainFile /server/httpd/cert/chain.pem
การทดสอบ
// ใบรับรองจะตรวจสอบชื่อโดเมน โปรดเชื่อมต่อด้วยชื่อโดเมน โดยไม่มีพอร์ต
ws = new WebSocket("wss://domain.com/wss");
ws.onopen = function() {
alert("เชื่อมต่อสำเร็จ");
ws.send('tom');
alert("ส่งสตริงหนึ่งไปยังเซิร์ฟเวอร์: tom");
};
ws.onmessage = function(e) {
alert("ได้รับข้อความจากเซิร์ฟเวอร์: " + e.data);
};
วิธีที่สอง ใช้ Workerman เปิด SSL โดยตรง (ไม่แนะนำ)
หมายเหตุ
การใช้ nginx/apache เป็นพร็อกซี่ SSL และการตั้งค่า SSL ใน Workerman ให้เลือกอย่างใดอย่างหนึ่ง ไม่สามารถเปิดใช้งานพร้อมกันได้。
การเตรียมการ:
-
Workerman เวอร์ชัน >=3.3.7
-
PHP ได้ติดตั้งส่วนขยาย openssl
-
ได้มีการขอใบรับรอง (pem/crt และไฟล์ key) วางในไดเรกทอรีใดก็ได้ในดิสก์
โค้ด:
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// ใบรับรองควรเป็นใบรับรองที่ขอมา
$context = array(
// ตัวเลือก ssl เพิ่มเติมโปรดดูในคู่มือ http://php.net/manual/zh/context.ssl.php
'ssl' => array(
// กรุณาใช้เส้นทางสัมบูรณ์
'local_cert' => 'เส้นทางบนดิสก์/server.pem', // สามารถใช้ไฟล์ crt ได้
'local_pk' => 'เส้นทางบนดิสก์/server.key',
'verify_peer' => false,
'allow_self_signed' => true, // หากเป็นใบรับรองที่เซ็นด้วยตนเอง ให้เปิดตัวเลือกนี้
)
);
// ที่นี่ตั้งค่าเป็นโปรโตคอล websocket (พอร์ตใดก็ได้ แต่อย่าถูกใช้โดยโปรแกรมอื่น)
$worker = new Worker('websocket://0.0.0.0:8282', $context);
// ตั้งค่า transport เปิดใช้งาน ssl, websocket + ssl คือ wss
$worker->transport = 'ssl';
$worker->onMessage = function(TcpConnection $con, $msg) {
$con->send('ok');
};
Worker::runAll();
ด้วยโค้ดข้างต้น Workerman จะฟังโปรโตคอล wss และไคลเอนต์สามารถเชื่อมต่อกับ workerman ผ่านโปรโตคอล wss เพื่อการสื่อสารที่ปลอดภัยได้。
การทดสอบ
เปิดเบราว์เซอร์ Chrome กด F12 เพื่อเปิดคอนโซลการดีบัก ในช่อง Console ให้พิมพ์ (หรือใส่โค้ดด้านล่างลงในหน้า html แล้วเรียกใช้ด้วย js)
// ใบรับรองจะตรวจสอบชื่อโดเมน โปรดเชื่อมต่อด้วยชื่อโดเมน โดยมีหมายเลขพอร์ต
ws = new WebSocket("wss://domain.com:8282");
ws.onopen = function() {
alert("เชื่อมต่อสำเร็จ");
ws.send('tom');
alert("ส่งสตริงหนึ่งไปยังเซิร์ฟเวอร์: tom");
};
ws.onmessage = function(e) {
alert("ได้รับข้อความจากเซิร์ฟเวอร์: " + e.data);
};
ข้อผิดพลาดทั่วไป
SSL routines:SSL23_GET_CLIENT_HELLO:http request
สาเหตุเกิดจากไคลเอนต์ใช้ ws://domain.com เพื่อเข้าถึง ซึ่งเป็นที่อยู่ที่ถูกต้องควรเป็น wss://domain.com การเข้าถึงควรเริ่มต้นด้วย wss。
ปัญหานี้มักเกิดจากการที่พอร์ตในตอนแรกเป็น ws แต่เปลี่ยนเป็น wss โดยไม่ได้รีเฟรชเพจของไคลเอนต์ ทำให้ยังคงเข้าถึงในแบบ ws ส่งผลให้เกิดข้อผิดพลาด
สามารถข้ามข้อผิดพลาดนี้ได้ เนื่องจากไม่ส่งผลต่อการเชื่อมต่อ wss ปกติ。
หมายเหตุ:
-
หากจำเป็นต้องใช้พอร์ต 443 โปรดใช้วิธีการแรกคือการใช้ nginx/apache เป็นพร็อกซี่เพื่อให้บริการ wss।
-
พอร์ต wss สามารถเข้าถึงได้เฉพาะผ่านโปรโตคอล wss ไม่สามารถเข้าถึงด้วย ws ได้。
-
ใบรับรองมักจะถูกผูกไว้กับชื่อโดเมน ดังนั้นในการทดสอบให้ไคลเอนต์ใช้การเชื่อมต่อด้วยชื่อโดเมน ไม่ควรใช้ ip ในการเชื่อมต่อ。
-
หากเกิดปัญหาไม่สามารถเข้าถึงได้ โปรดตรวจสอบไฟร์วอลล์ของเซิร์ฟเวอร์。
-
วิธีนี้ต้องการ PHP เวอร์ชัน >=5.6 เนื่องจากมินิโปรแกรม WeChat ต้องการ tls1.2 ขณะที่ PHP เวอร์ชันต่ำกว่า 5.6 ไม่รองรับ tls1.2。
บทความที่เกี่ยวข้อง:
ผ่านพร็อกซี่เพื่อรับไคลเอนต์ ip จริง
การตั้งค่า SSL context ของ workerman อ้างอิง