Dart で Torrentクライアントを作ろう ( 3 )  ローカルサーバー

第3回目です。宜しくお願いします。
今回は、解説用に、Dartで非常に単純なP2Pアプリを作成しました。次回から、このアプリを拡張しながら、モダンなP2Pアプリへと進化させていきます。

以前、書いた「UPnP を利用したポートマップについての解説」とかぶります。本記事は読み飛ばして良いでしょう。


### 少しづつ機能追加しながら完成させる進め方

本章から実際にデータ配信をするP2Pアプリを作成していきます。少しづつ機能追加していき、Torrentクライアントのような高機能なアプリへと進化させて行きたいと思います。

##### サーバー・クライアン方式から
進め方のアイデアが2つあります。ひとつは、データ配信をするためのアプリとして、もっとも一般的なサーバー・クライアント方式のアプリを作成をして、それをじょじょに拡張していくことで、最終的にTorrentクライアントへと進化させる。

##### Torrentクライアントをハックしながら
もうひとつは、Torrentクライアントをハックして、作れそうな機能から作っていく。

本書では、両方やります。本章ではひとつめのアプローチで進めたいと思います。


### はじめの一歩
自分のもっているデータを他の誰かに渡す方法はたくさんあります。githubコードをあげて見てもらったり。Qiitaに記事を投稿してみたり。ニコニコ動画に動画をあげてみたり。Pixivにイラストを投稿してみたり。
と、さまざまです。

###### ウェブサーバーをご家庭のPC上で公開する
データ配信のためにもっとも利用されているアプリはWebサーバーアプリです。最初に上げた例も、すべて、ウェブサーバーを利用した例でした。

まずは、このウェブサーバーアプリを利用したデータ配信の仕組み作成したいと思います。ご家庭のPCをWebサーバーとして動作させて、データを配信してみましょう。

SMSやメールで、立ち上げたサーバーのアドレスをデータを共有したい相手におくる事で、データを共有する事ができます。

### Httpサーバーの仕組み
Internet Explorer、Firefox、Chromeといったアプリは、Webサーバーからデータをもらいそのデータを表示します。テキストならば、そのテキストを表示します。画像ならば、その画像を表示します。Html形式ならば、整形して表示します。

とても複雑な仕組みのように思えますが、Webサーバーが担当している部分はシンプルで、依頼されたデータをブラウザーアプリに渡す事だけです。

##### リクエストされたデータを送信する

Webサーバーは、リクエストされたデータを決められた書式で送信します。
以下のようにとても単純です。

```lang:hetima.netに、hetima.pngを依頼メッセージ

GET /hetima.png HTTP/1.1
Host: hetima.net
Connection: close

```

```lang:1024バイトのデータxxxxxxx...を返す

HTTP/1.1 200 OK
Connection: close
Content-Length: 1024
response.write("Content-Type: image/png\r\n");

xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx
```

### ローカルサーバーで公開する時注意点

##### ルータ(NAT) が邪魔をする。
ネットワークにアクセスする場合、ルーターを通す事がほとんどではないでしょうか？
ルータを通すと、ローカル環境で立ち上げたサーバーに、外部からアクセスしてもらう事が困難になります。
ルーターを通す事で、外部に公開されているアドレスと異なるアドレスが端末にふられるためです。
実際に確認してみましょう。

```lang:コマンドラインとかで
netstat
```
「192.168.100.1」といったアドレスが端末に割り振られている事が確認できます。これは、プライベートネットワーク用のアドレスですね。このアドレスを相手に教えてもアクセスしてもらう事はできません。


##### upnpを使って外部へ接続する。
外部へのアドレスはルーターが知っています。ならば、ルーターに教えてもらうと良いでしょう。ほとんどのルータでは、UPNPという仕組みで依頼を出すことができます。
今回はこれを使用して、他の端末でデータ通信をできるようにしました。

※upnp以外にも、UDP hole punching STUN、ICE といった仕組みがあります。


### UPNPを用いて、ポートマッピングを実現しよう


UPNPを実現するには、UDPとTCPを用いて通信できる必要があります。
具体的には、

* UDP Multicast を利用して、使用中のルーターに、ポートマッピングを依頼すめためのアドレスを教えてもらう。
* TCP を使って、教えてもらったアドレスからポートマッピングの依頼をだす。

といった事をします。具体的には、

##### 依頼先を調べる

```html:UDPで以下のようなメッセージを通知する
M-SEARCH * HTTP/1.1
MX: 3
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
ST: urn:schemas-upnp-org:service:WANIPConnection:1

```

```html:UDPで以下のような返答がある
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
ST: urn:schemas-upnp-org:service:WANIPConnection:1
USN: uuid:E8088BD3-E808-8BD3-A042-E8088BD3A042::urn:schemas-upnp-org:service:WANIPConnection:1
EXT:
SERVER: E588 UPnP/1.0 MiniUPnPd/1.6
LOCATION: http://192.168.100.1:54616/rootDesc.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 1
BOOTID.UPNP.ORG: 1
CONFIGID.UPNP.ORG: 1337
DATE: Sun, 14 Sep 2014 00:42:14 GMT
```


LOCATIONで指定されたアドレスのXMLファイルの\<controlURL\>タグの中に依頼先のアドレスが記載されています。このアドレスへ依頼をだせば、インターネットから、あなたの端末へアクセスできるようになります。


##### ポートマッピングの依頼をだす
POSTリクエストをだす。

```html:ポートマッピングする
POST 192.168.100.1 HTTP/1.1
Host: 192.168.100.1
Connection: close
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"
Content-Length: 629

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body><m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>48083</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>8083</NewInternalPort>
<NewInternalClient>192.168.100.100</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>test</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</m:AddPortMapping></SOAP-ENV:Body>
</SOAP-ENV:Envelope>

```


```html:200OKと返答と返答がある
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Connection: close
Content-Length: 263
Server: E588 UPnP/1.0 MiniUPnPd/1.6
EXT:
DATE: Sun, 14 Sep 2014 01:39:27 GMT

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"/>
</s:Body>
</s:Envelope>

```

##### 外部の端末から見えているアドレスを調べる

```html:リクエストをだす
POST 192.168.100.1 HTTP/1.1
Host: 192.168.100.1
Connection: close
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"
Content-Length: 324
```

```html:レスポンス
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Connection: close
Content-Length: 360
Server: E588 UPnP/1.0 MiniUPnPd/1.6
EXT:
DATE: Sun, 14 Sep 2014 01:46:07 GMT
```


### 実装する
実装しなければならない機能は多々あれど、ひとつひとつは非常にシンプルな構造になっている事が理解できたでしょう。お好みの言語とTCPとUDPのAPIを利用すれば、さらっと、実装できるはずです。
今回紹介した「ローカルにサーバーを立ち上げて、データ配信するアプリ」をDartで実際に作ってみました。参考までにどうぞ。

* https://github.com/kyorohiro/HetimaDelphinium

* https://chrome.google.com/webstore/detail/hetimadelphinium/adjffeadhaldmgbcdapganbifcdghpnb

本アプリを立ち上げて、スタートボタンを押すと、サーバーが立ち上がります。
本アプリへ、ファイルをドラック&ドロップすると、そのファイルがサーバー上で公開されます。

### 次回予告
今回作成したアプリには重大欠点があります。これらを改善していきながら、P2Pアプリの基本的な考え方を解説していきます。