ファイナンス、情報通信技術のスキル・アグリゲーション・サイト
Apache 2.4 では、mod_proxy_wstunnel モジュールを利用して、WebSocket の接続をバックエンドの WebSocket サーバへ通すことができます。
https://httpd.apache.org/
WSL(Windows Subsystem for Linux)の Ubuntu 16.04 LTS 上で、Secure WebSocket (wss://)の設定と動作を試してみます。
Secure WebSocket プロクラムは、「Python Tornado で Secure WebSocket」のサンプルプログラムをもとにしました。
また、SSL 証明書は、プライベート証明書を作成しました。手順については、「プライベート認証局でプライベート SSL/TLS 証明書を発行する」も参照してください。
今回のホスト名とプライベートサーバ証明書の Common Name は、wsl.local としています。Bash 上で、ifconfig コマンドから IP アドレスを確認して、/etc/hosts ファイルと、Windows10 の C:¥Windows¥System32¥drivers¥etc¥hosts に IP アドレスとホスト名を設定しています。
ブラウザからの確認ため、プライベート CA 証明書を Internet Explorer にも設定しました。
まず、モジュールが有効か確認します。有効にするには、以下のコマンドを実行します。
$ a2enmod proxy
$ a2enmod proxy_http
$ a2enmod proxy_wstunnel
Apache 2.4 における設定方法について調べていると、大きく2通りあるようです。
ひとつは、Proxy による場合で、以下はその設定例です。
<VirtualHost wsl.local:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCACertificatePath /etc/ssl/ca/
SSLCACertificateFile /etc/ssl/ca/ca.crt
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
SSLProxyEngine On
ProxyPreserveHost On
ProxyPass /ws wss://wsl.local:8888/ws
ProxyPassReverse /ws wss://wsl.local:8888/ws
</VirtualHost>
もうひとつは、Rewrite による場合です。
以下のコマンドでモジュールを有効にします。
$ a2enmod rewrite
Rewrite による設定例です。
<VirtualHost wsl.local:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCACertificatePath /etc/ssl/ca/
SSLCACertificateFile /etc/ssl/ca/ca.crt
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
SSLProxyEngine On
RewriteEngine On
RewriteCond %{HTTP:Connection} =Upgrade [NC]
RewriteCond %{HTTP:Upgrade} =WebSocket [NC]
RewriteRule /(.*) wss://wsl.local:8888/$1 [P,L]
</VirtualHost>
Tornado を利用した Secure WebSocket サーバ例です。
http://www.tornadoweb.org/
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
clients = []
def check_origin(self, origin):
return True
def open(self):
WSHandler.clients.append(self)
print "Connected.\n"
def on_message(self, message):
print "Received message:%s\n" % message
for con in WSHandler.clients:
con.write_message(message)
def on_close(self):
WSHandler.clients.remove(self)
print "Closed.\n"
application = tornado.web.Application([
(r"/ws", WSHandler),])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(
application, ssl_options={
"certfile": "/etc/ssl/certs/server.crt",
"keyfile": "/etc/ssl/private/server.key",})
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Python の websocket-client モジュールを利用したクライアントプログラムから接続してみます。
https://github.com/
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import ssl
from websocket import create_connection
ws = create_connection("wss://wsl.local/ws",
sslopt={"cert_reqs": ssl.CERT_NONE,
"check_hostname": False})
if len(sys.argv) > 1:
message = sys.argv[1]
else:
message = "hello websocket!"
sndlen = ws.send(message)
rcvmsg = ws.recv()
print "Received:%s\n" % rcvmsg
ws.close()
次に、https:// も Proxy によって Tornado へ転送する設定を追加した場合です。
wss:// も、Proxy による場合の設定例です。
<VirtualHost wsl.local:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCACertificatePath /etc/ssl/ca/
SSLCACertificateFile /etc/ssl/ca/ca.crt
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
SSLProxyEngine On
ProxyPreserveHost On
ProxyPass /ws wss://wsl.local:8888/ws
ProxyPassReverse /ws wss://wsl.local:8888/ws
ProxyPass / https://wsl.local:8888/
ProxyPassReverse / https://wsl.local:8888/
</VirtualHost>
wss:// は、Rewrite による設定例です。
<VirtualHost wsl.local:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
SSLCACertificatePath /etc/ssl/ca/
SSLCACertificateFile /etc/ssl/ca/ca.crt
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
SSLProxyEngine On
RewriteEngine On
RewriteCond %{HTTP:Connection} =Upgrade [NC]
RewriteCond %{HTTP:Upgrade} =WebSocket [NC]
RewriteRule /(.*) wss://wsl.local:8888/$1 [P,L]
ProxyPreserveHost On
ProxyPass / https://wsl.local:8888/
ProxyPassReverse / https://wsl.local:8888/
</VirtualHost>
Tornado のテンプレートを利用した例です。HTML5 & JQuery の WebSocket クライアントで、チャットプログラムを簡略にしたようなデザインです。index.html のファイル名で、templates ディレクトリに保存します。templates ディレクトリは、 Python Tornado プログラムファイルを保存したディレクトリ内に作成します。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import os
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class WSHandler(tornado.websocket.WebSocketHandler):
clients = []
def check_origin(self, origin):
return True
def open(self):
WSHandler.clients.append(self)
print "Connected.\n"
def on_message(self, message):
print "Received message:%s\n" % message
for con in WSHandler.clients:
con.write_message(message)
def on_close(self):
WSHandler.clients.remove(self)
print "Closed.\n"
settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
}
application = tornado.web.Application([
(r"/ws", WSHandler),
(r"/", MainHandler),
], **settings)
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(
application, ssl_options={
"certfile": "/etc/ssl/certs/server.crt",
"keyfile": "/etc/ssl/private/server.key",})
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
テンプレートの index.html です。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tornado Chat Demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script>
function ws() {
var data = {};
$("#chat").prepend("<p>Websocket START</p>");
var s = new WebSocket("wss://wsl.local/ws");
s.onerror = function() {
$("#chat").prepend("<p>ERROR(/ws)</p>");
}
s.onopen = function() {
$("#chat").prepend("<p>OPEN(/ws)</p>");
}
s.onmessage = function(e) {
$("#chat").prepend("<p>" + e.data + "</p>");
}
$("#chatform").submit(function (evt) {
var line = $("#chatform [type=text]").val()
$("#chatform [type=text]").val("")
s.send(line);
return false;
});
}
</script>
</head>
<body>
<div id="chat" style="width: 60em; height: 20em; overflow:auto; border: 1px solid black">
</div>
<form id="chatform">
<p>Message</p>
<input type="text" />
<input type="submit" value="送信" />
</form>
<script>
window.onload = function() {
$("#chat").prepend("<p>ONLOAD</p>");
ws();
};
</script>
</body>
</html>