ローカルネットで開発する時に使用するワイルドカード SSL 証明書の取得方法の記事を書いたので、いくつかの言語で簡単な https サーバープログラムを記述しようと思います。
最近の言語は標準ライブラリや定番のライブラリが充実しているので簡単に https サーバーを書くことができます。
コンテンツ
なお、折角 https サーバーで配信するのですから、加速度センサーを使うサンプルコンテンツにしようと思います。
以下のように Android からアクセスするとスマホを回転させても頭が上でありつづけます。
残念ながら iOS デバイスでは動作しません。 PC では API は使えるようですが物理的に加速度センサーがほぼ付いていないので使えません。 Accelerometer | MDN
とりあえず簡単のために index.html に JavaScript も CSS も書いてしまいました。
( index.html
は public
という名前のフォルダーに入れました。 )
<html lang="ja">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="UTF-8">
<title>Stick figure</title>
<style>
#stick-figure {
width: 50%;
margin: 0 auto;
transform: rotate(0rad);
transform-origin: center;
}
#message {
color: red;
font-size: 2rem;
}
</style>
</head>
<body>
<svg viewBox="0 0 200 200" id="stick-figure">
<g stroke="green" fill="none" stroke-width="5">
<circle cx="100" cy="30" r="20" />
<line x1="100" y1="50" x2="100" y2="110" />
<polyline points="80,190 100,110 120,190" />
<polyline points="40,75 100,65 160,75" />
</g>
</svg>
<p id="message"></p>
<script>
const main = () => {
const stickFigure = document.getElementById('stick-figure');
const message = document.getElementById('message');
if (!('Accelerometer' in window)) {
message.innerHTML = 'no Accelerometer';
return;
}
navigator.permissions.query({ name: 'accelerometer' }).then(result => {
if (result.state !== 'granted') {
message.innerHTML = result.state;
return;
}
const acc = new Accelerometer({ frequency: 60 });
acc.addEventListener('reading', e => {
const { x, y } = acc;
const rad = -Math.atan2(y, x) + Math.PI / 2.0;
stickFigure.style.transform = `rotate(${rad}rad)`;
});
acc.start();
});
};
main();
</script>
</body>
</html>
標準のモジュールだけで完結したかったので WEBrick を使いました。 WEBrick モジュールだけで完結するので、リファレンス等が分かりやすかったです。
( この辺は大クラス主義のおかげかもしれません。 )
証明書と中間認証局は別ファイルにしないといけないようです。
#!/usr/bin/env ruby
require 'webrick'
require 'webrick/https'
port = (ENV['PORT'] || 8443).to_i
puts "port=#{port}"
httpd = WEBrick::HTTPServer.new({
DocumentRoot: './public',
BindAddress: '0.0.0.0',
Port: port,
Logger: WEBrick::Log.new(nil, level=WEBrick::BasicLog::WARN),
SSLEnable: true,
SSLCertificate: OpenSSL::X509::Certificate.new(File.open('./certs/cert.pem').read),
SSLExtraChainCert: [OpenSSL::X509::Certificate.new(File.open('./certs/chain.pem').read)],
SSLPrivateKey: OpenSSL::PKey::RSA.new(File.open('./certs/privkey.pem').read)
})
Signal.trap('INT') {
httpd.shutdown
}
httpd.start()
ssl.wrap_socket
というのは Python 3.2 以上では非推奨で Python 3.7 からは禁止だそうです。
まずはじめに ssl context というのを作らなくてはいけないのですが、ドキュメントではクライアント側とサーバー側両方の解説があるので初心者にはわかりにくかったです。
しかもサーバー側を作る時に ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
とするのはチョッとした罠です。
あと、SimpleHTTPRequestHandler
がカレントディレクトリからコンテンツを配信すること前提のようなので chdir するという汚いサンプルになってしまいました。
from http import server
import ssl
import os
if __name__ == '__main__':
address = ('0.0.0.0', 8443)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('certs/fullchain.pem', 'certs/privkey.pem')
os.chdir('public')
httpd = server.HTTPServer(address, server.SimpleHTTPRequestHandler)
httpd.socket = context.wrap_socket(httpd.socket)
try:
httpd.serve_forever()
except KeyboardInterrupt:
pass
httpd.server_close()
Node.js
手抜きして express を使ってしましました。そのおかげで少ない行数で済んでいます。
#!/usr/bin/env node
const fs = require('fs')
const express = require('express')
const https = require('https')
const app = express()
const port = Number(process.env.PORT || 8443)
const options = {
cert: fs.readFileSync('./certs/fullchain.pem'),
key: fs.readFileSync('./certs/privkey.pem')
}
app.use(express.static('public'))
server = https
.createServer(options, app)
.listen(port, () => console.log(`start port=${port}`))
Docker (alpine + lighttpd)
プログラム言語ではありませんが、 Docker (alpine + lighttpd) でもやってみました。
lighttpd では鍵ファイルと証明書ファイルを連結する必要があるので起動時にやっています。
Dockerfile
FROM alpine:3.8
RUN apk --update add lighttpd && rm -rf /var/cache/apk/*
COPY ./public /var/www/localhost/htdocs
RUN cd /etc/lighttpd &&\
sed -e '/^server.modules = (/a"mod_openssl",' \
-e '/^# server\.port/aserver.port = 443' \
-e '/^# ssl.engine/assl.engine = "enable"' \
-e '/^# ssl.pemfile/assl.pemfile = "/etc/ssl/certs/server.pem"' \
-e '/^# ssl.pemfile/assl.ca-file = "/etc/ssl/certs/chain.pem"'\
--in-place \
lighttpd.conf &&\
echo -e '#/bin/sh\n'\
'cat /certs/privkey.pem /certs/cert.pem > /etc/ssl/certs/server.pem\n'\
'cat /certs/chain.pem >/etc/ssl/certs/chain.pem\n'\
'chmod 600 /etc/ssl/certs/*.pem\n'\
'lighttpd -f /etc/lighttpd/lighttpd.conf\n'\
'sleep 1\n'\
'tail -f /var/log/lighttpd/error.log -f /var/log/lighttpd/access.log\n' > /start.sh &&\
chmod +x /start.sh
EXPOSE 443
CMD "/start.sh"
起動
$ docker build . -t <タグ名>
$ docker run --rm -it -p 443:443 -v "$(pwd)/certs:/certs:ro" --name <コンテナ名> <タグ名>
なお、以上のスクリプトは HiroshiOkada/https-example: https 静的サーバーサンプル に置きました。