https://dev.to/kyorohiro/series/12061 の 翻訳です。
Dart 製の Http Server を VPS上に構築してみる。
Dart では、 dart:io を利用することで、簡単に Http Server のコードが書けます。
- ./bin/main.dart
import 'dart:io' as io;
void main(List<String> arguments) async {
try {
print("start bind");
var httpServer = await io.HttpServer.bind("0.0.0.0", 80);
print("binded");
await for (var request in httpServer) {
print("receive requested ${request.uri}");
request.response.write("Hello");
request.response.close();
}
} catch (e, s) {
print("${e}");
print("${s}");
}
}
しかし、これを、 VPS上に設定するとなるともう少し ノウハウが必要です。
ubuntu 20.20
Firewall
まずは Firewall の設定をしましょう!!
$ ufw status
$ ufw default deny
$ ufw allow 22/tcp
$ ufw allow 80/tcp
$ ufw allow 443/tcp
$ ufw enable
$ ufw status
- port 22 は気をつけてください!! ssh で接続しているのに、Firewall で切断するとアクセスできなくなってしまいます。 詰みますね..
Setup for Systemd
次は、作成した Http Server を 再起動後も動作するDeamon として登録しましょう!!
$ dart2native bin/main.dart
$ mv bin/main.exe /opt/main.exe
Shell を間に挟んでおきます..
- ./darthelloserver.sh
# !/bin/sh
# cd /app/hao_dart_server_and_systemd; dart bin/main.dart
/opt/main.exe
そして、Systemd へ登録するための設定を書きます!!
- ./darthelloserver.service
[Unit]
Description=Dart Hello Http Server
After=syslog.target network-online.target
[Service]
ExecStart = /opt/darthelloserver.sh
Restart = always
Type = simple
[Install]
WantedBy=multi-user.target
WantedBy=network-online.target
準備ができたので、Systemd に登録しましょう!!
$ cp darthelloserver.sh /opt/darthelloserver.sh
$ chmod 655 /opt/darthelloserver.sh
$ cp darthelloserver.service /etc/systemd/system/darthelloserver.service
$ systemctl enable darthelloserver
$ systemctl start darthelloserver
[PS]今回作成したDeamon は systemd-networkd-wait-online があることが前提なので、
念のため、確認して置いてくださいね!!
$ systemctl list-unit-files | grep network
$ systemctl enable systemd-networkd
$ systemctl enable systemd-networkd-wait-online
ここまでのコード
Let's Encrypt から SSL Certification を取得しましょう!!
http のサーバはこれで動作するようになりました。 https にしたいですよね。
まずは、SSL Certification を取得しましょう!!
Certbot を インストール
Let'sEncrypt と通信するために Certbotをインストールしましょう!!
$ apt-get install certbot -y
HttpServer For Certbot
前回作成したHttpServer を修正して、 Certbot に対応します。
Certbotは'${WebRoot}/.well-known/acme-challenge/' に ファイルを作成するので、
'http://${HOST}/.well-known/acme-challenge/xxx' から Get リクエストがあった場合、そのファイルを返すようにする必要があります。
- bin/main.dart
こんな感じのコード
import 'dart:io' as io;
const String cerbotWebRootPath = "/var/www/html";
void main(List<String> arguments) async {
try {
print("start bind");
var httpServer = await io.HttpServer.bind("0.0.0.0", 80);
print("binded");
await for (var request in httpServer) {
try {
print("receive requested ${request.uri}");
if (request.uri.path.startsWith("/.well-known/")) {
var acmeChallengeFilePath = "" +
cerbotWebRootPath +
request.uri.path.replaceAll(RegExp("\\?.*"), "");
acmeChallengeFilePath = acmeChallengeFilePath.replaceAll("/..", "/");
var acmeChallengeFile = io.File(acmeChallengeFilePath);
var acmeChallengeData = await acmeChallengeFile.readAsString();
request.response.write(acmeChallengeData);
request.response.close();
}
request.response.write("Hello");
request.response.close();
} catch (e, s) {
print("${e}");
print("${s}");
}
}
} catch (e, s) {
print("${e}");
print("${s}");
}
}
サーバーを更新しましょう!!
$ dart2native ./bin/main.dart
$ mv bin/main.exe /opt/main.exe
$ systemctl restart darthelloserver
では、Certbot を動かしてみます。
$ certbot certonly --webroot -w /var/www/html -d tetorica.net -m kyorohiro@gmail.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for tetorica.net
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/tetorica.net/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/tetorica.net/privkey.pem
Your cert will expire on 2021-07-02. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
ほい!!
/etc/letsencrypt/live/tetorica.net/ の下に 証明書ができましたね!!
ここまでのコード
SSL に対応させよう!!
無事にSSL Certification を取得できました。
このSSLを読み込んで、 HTTPS サーバーとして動作するようにします。
import 'dart:io' as io;
const String cerbotWebRootPath = "/var/www/html";
const String privkeyPath = "/etc/letsencrypt/live/tetorica.net/privkey.pem";
const String fullchainPath = "/etc/letsencrypt/live/tetorica.net/fullchain.pem";
void main(List<String> arguments) async {
try {
print("start bind");
onRequest(io.HttpRequest request) async {
try {
print("receive requested ${request.uri}");
if (request.uri.path.startsWith("/.well-known/")) {
var acmeChallengeFilePath = "" +
cerbotWebRootPath +
request.uri.path.replaceAll(RegExp("\\?.*"), "");
acmeChallengeFilePath = acmeChallengeFilePath.replaceAll("/..", "/");
var acmeChallengeFile = io.File(acmeChallengeFilePath);
var acmeChallengeData = await acmeChallengeFile.readAsString();
request.response.write(acmeChallengeData);
request.response.close();
}
request.response.write("Hello");
request.response.close();
} catch (e, s) {
print("${e}");
print("${s}");
}
}
var httpServer = await io.HttpServer.bind("0.0.0.0", 80);
print("binded 80");
httpServer.listen((request) {
onRequest(request);
});
String key = io.Platform.script.resolve(privkeyPath).toFilePath();
String crt = io.Platform.script.resolve(fullchainPath).toFilePath();
io.SecurityContext context = new io.SecurityContext();
context.useCertificateChain(crt);
context.usePrivateKey(key, password: "");
var httpsServer = await io.HttpServer.bindSecure("0.0.0.0", 443, context);
print("binded 443");
httpsServer.listen((request) {
onRequest(request);
});
} catch (e, s) {
print("${e}");
print("${s}");
}
}
こんな感じ..
反映させましょう。
$ dart2native ./bin/main.dart
$ mv bin/main.exe /opt/main.exe
$ systemctl restart darthelloserver
試しにブラウザーで開いてみると...
おっ、動きますね!!
SSL の更新方法について
Let's Encrypt の SSL は期限が短いです。
毎月、SSLを更新するに、CRON を設定しておきましょう!!
まずは、Cronが動作してるか確認!!
$ systemctl list-unit-files | grep cron
cron.service enabled enabled
そして、設定!!
$ crontab -u root -e
00 04 03 * * certbot renew && systemctl restart darthelloserver
ここまでのコード
Isoalte で 分散
Dart は シングルスレッドで動作するため、CPUリソースがあまりそうです。
せっかくのマルチコアの環境ならCPUを、無駄なく利用したいですよね!!
Dart では、 Isolate を利用して、マルチスレッドライクなコードが書けます。
こんにちは Isolate
子プロセス(Isolate)を起動して、文字を表示するだけのコード
import 'dart:isolate' as iso;
onMain(message) async {
//
print("child:arg:${message}");
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(milliseconds: 100));
print("child:print:${i}");
}
}
main() async {
iso.Isolate.spawn(onMain, "Hi");
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(milliseconds: 100));
print("parent:print:${i}");
}
}
$ dart bin/main_example_isolate_01.dart
child:arg:Hi
parent:print:0
child:print:0
parent:print:1
child:print:1
parent:print:2
child:print:2
parent:print:3
child:print:3
parent:print:4
Isolate 間の通信
Isolate 間ではメモリーを共有していません。
データをIsolate間で共有したい場合は、 SendPort ReceivePort を利用して
通信を行います。
import 'dart:isolate' as iso;
onMain(message) async {
//
print("child:arg:${message}");
iso.SendPort sendPort = message['p'];
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(milliseconds: 100));
print("child:print:${i}");
sendPort.send("hi${i}");
}
}
main() async {
iso.ReceivePort receivePort = iso.ReceivePort();
receivePort.listen((message) {
print("parent:onMessage: ${message}");
});
iso.Isolate.spawn(onMain, {"v": "Hi", "p": receivePort.sendPort});
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(milliseconds: 100));
print("parent:print:${i}");
}
receivePort.close();
}
$ dart bin/main_example_isolate_02.dart
child:arg:{v: Hi, p: SendPort}
parent:print:0
child:print:0
parent:onMessage: hi0
parent:print:1
child:print:1
parent:onMessage: hi1
parent:print:2
child:print:2
parent:onMessage: hi2
parent:print:3
child:print:3
parent:onMessage: hi3
parent:print:4
child:print:4
parent:onMessage: hi4
子Isolate から 親Isolate にメッセージを送信するだけのコードですね..
もう少し Isolate
他に、pause resuem errorチェック、などのコードは以下を参考にしてください
https://github.com/kyorohiro/hao_dart_server_and_systemd/blob/master/bin/main_example_isolate.dart
Isolate を Dart で作成した Http Server に適用
Isolate を Http Server に適用して、負荷を分散してみましょう!!
Dart の HttpServer オブジェクトは、 Isolate に対応しているので、
実は、そんなにする事はありません。
shared プロパティをTrueにするだけ..
import 'dart:io' as io;
import 'dart:isolate' as iso;
onIsolateMain(message) async {
var server = await io.HttpServer.bind("0.0.0.0", 8080, shared: true);
await for (var request in server) {
print("${request.uri}");
request.response.write("${message}");
request.response.close();
}
}
main() async {
var server = await io.HttpServer.bind("0.0.0.0", 8080, shared: true);
server.listen((request) {
print("${request.uri}");
request.response.write("parent");
request.response.close();
});
//
for (int i = 0; i < 10; i++) {
iso.Isolate.spawn(onIsolateMain, "${i}");
}
}
うーむ、何もしなくて良い..
$ curl http://tetorica.net:8080/
parent
$ curl http://tetorica.net:8080/
1
$ curl http://tetorica.net:8080/
0
$ curl http://tetorica.net:8080/
4
おわり
続きは、dev.to/kyorohiro/this_series の方で続ける予定..
コードは以下