Dart で 作成した Http Server を 公開するまで


https://dev.to/kyorohiro/series/12061 の　翻訳です。



# Dart 製の Http Server を VPS上に構築してみる。

Dart では、 dart:io を利用することで、簡単に Http Server のコードが書けます。

- ./bin/main.dart

```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 の設定をしましょう!!

```sh
$ 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  

```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
```

## ここまでのコード

https://github.com/kyorohiro/hao_dart_server_and_systemd/tree/dev01


<hr>

# 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

こんな感じのコード

```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/ の下に 証明書ができましたね!!

## ここまでのコード

https://github.com/kyorohiro/hao_dart_server_and_systemd/tree/dev02

<hr>

# SSL に対応させよう!!

無事にSSL Certification を取得できました。
このSSLを読み込んで、 HTTPS サーバーとして動作するようにします。


```dart
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 
```

試しにブラウザーで開いてみると...
おっ、動きますね!!

https://tetorica.net/


## 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 
```


## ここまでのコード

https://github.com/kyorohiro/hao_dart_server_and_systemd/tree/dev03



# Isoalte で　分散

Dart は シングルスレッドで動作するため、CPUリソースがあまりそうです。
せっかくのマルチコアの環境ならCPUを、無駄なく利用したいですよね!!


Dart では、 Isolate を利用して、マルチスレッドライクなコードが書けます。

# こんにちは Isolate 

子プロセス(Isolate)を起動して、文字を表示するだけのコード

```dart
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
```

- code   
https://github.com/kyorohiro/hao_dart_server_and_systemd/blob/master/bin/main_example_isolate_01.dart


# Isolate 間の通信

Isolate 間ではメモリーを共有していません。
データをIsolate間で共有したい場合は、 SendPort ReceivePort を利用して
通信を行います。
 

```dart
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 にメッセージを送信するだけのコードですね..

- code   
https://github.com/kyorohiro/hao_dart_server_and_systemd/blob/master/bin/main_example_isolate_02.dart


## もう少し Isolate
他に、pause resuem errorチェック、などのコードは以下を参考にしてください
https://github.com/kyorohiro/hao_dart_server_and_systemd/blob/master/bin/main_example_isolate.dart


<hr>

# Isolate を Dart で作成した Http Server に適用

Isolate を Http Server に適用して、負荷を分散してみましょう!!
Dart の HttpServer オブジェクトは、 Isolate に対応しているので、
実は、そんなにする事はありません。

shared プロパティをTrueにするだけ..


```dart
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](https://dev.to/kyorohiro/series/12061) の方で続ける予定..

コードは以下

https://github.com/kyorohiro/hao_dart_server_and_systemd