Fiwareを使って都市OSを動かしてみよう

Munu


データ仕様の現状と課題
スマートシティの標準規格(案)
データモデルのユースケース
ツール


Column
Link
用語集

Coppell

Technologies

 7. ブローカーとしてのfiware/orion 7.3節




7.3 Consumerプログラムの作成
2022年4月13日
 FiwareのAPIはNGSIの標準に従った、Web APIと呼ばれるものです。ConsumerやProviderとのインタフェースもこのWeb APIで行います。Web APIを受け付ける機能をWeb Serverと言いますが、これは色々な場面で一般的に使われる機能なので、有償のものから無償のものまで沢山の種類があります。本書では、簡単に構築できることからPythonというプログラミング言語を使ってWeb ServerをPCのWindows上に構築します。


7.3.1 Pythonのインストール
2022年4月13日
 まず、Pythonのサイト にアクセスすると、次のような画面が表示されます。赤点線のところにdownloadとあるので、クリックします。


そうすると、以下の様にプルダウンメニューが表示されるとともに、下部に各バージョンの一覧が表示されます。Windowsの最新版をクリックしてダウンロードします。ダウンロードしたら、そのexeファイルをダブりクリックすれば、インストールが始まります。

インストーラの初期画面では、以下の様にオプションが表示されますが、Add Python x.x to PATHはチェックしておいた方が良いです。これをチェックしなくてもPythonは動作しますが、一緒にインストールされるツールがPATH変数に追加されないので、動作させるのに苦労します。但し、自動的に登録してくれるバスは、インストーラーが既定値で持っているインストール先だけの様で、インストール場所を変更するとうまく動作しません。

インストールが完了したら、以下のような結果になります。
次に動作確認します。IDLEというツールを起動します。そうすると、以下のようにPythonのGUIが起動します。このままPythonのコードを入力すると、インタプリタとして動作します。
例えば、以下のような感じです。



7.3.2 Context Consumerrの起動
2022年4月13日
  Subscription用のConsumerやRegistration用のProviderはOrion/Fiwareから呼び出されるので、サーバ側となります。今回はセキュリティの心配がないので 、Pythonからhttp.serverというモジュールを使って簡便に構築します。

注:http.serverのドキュメントに記載の通り、業務用のシステムには推奨されていません。また、現在はスレッドを使う事ができますが、本書ではマルチスレッドは考慮していません
以下のPythonプログラムをconsumer.pyというファイルに書き込みます。書き込むツールはテキストで書き込めるものであればなんでも良いのですが、筆者は今まで同様メモ帳を使いました。余談ですが、上記のリンクからconsumer.pyをダウンロードする場合はブラウザで開くのではなく、右クリックで一度ダウンロードしてからご利用下さい。そうしないと、日本語が文字化けする事があります。

import http.server as s
from urllib.parse import urlparse    #url anslysis module
import json

class MyHandler(s.BaseHTTPRequestHandler):
   def do_POST(self):               #server_foreverからPOST時に呼ばれる
       # urlパラメータを取得
       parsed = urlparse(self.path) #url文字列を分解してnamed tupleに変換
       # urlパラメータを解析
       urlpath = parsed.path        #パスの文字列を取り出す
       urldir = urlpath.split('/')
       # print(urldir)
       if (len(urldir) < 3 or urldir[1] != "subscriptions"):
           # 登録したsubscriptionからのPOSTではない
           print ('Context ConsUmer error (1): invalid resource: ', urlpath)
           self.send_response(400)  #レスポンスのステータスを"Bad request"に設定
           self.send_header('Content-length', 0)
                                    #ヘッダのcontect-lengthを設定
           self.end_headers()       #ヘッダの後に空行を出力
           self.wfile.write(''.encode())
           return()
       if urldir[2] == 'temperature-change':#temperatureのsubscription
           content_len = self.headers.get("content-length")
                                    #Headerのcontent-lengthの値を採取
           if content_len =='':
               print ('Context Consumer error (2): missing message body')
               self.send_response(400)
               self.send_header('Content-length', 0)
               self.end_headers()
               self.wfile.write(''.encode())
               return()
           content_len = int(content_len)
           req_body = self.rfile.read(content_len).decode("utf-8")
           json_dict = json.loads(req_body)
           print('部屋 : {}'.format(json_dict['data'][0]['name']['value']['value']))
           print('室温 : {}'.format(json_dict['data'][0]['temperature']['value']))
           body = ''
       else:
           print ('Context Consumer error (3): missing message body')
           self.send_response(400)
           self.send_header('Content-length', 0)
           self.end_headers()
           self.wfile.write(''.encode())
           return()
       # 返信を組み立て
       self.send_response(200)      #レスポンスのステータスを"正常"に設定
       self.send_header('Content-type', 'text/html; charset=utf-8')
                                    #ヘッダのcontect-typeを設定
       self.send_header('Content-length', '0')
                                    #ヘッダのcontect-lengthを設定
       self.end_headers()           #ヘッダの後に空行を出力
       self.wfile.write(body.encode())
                                    #クライアントに応答
   def do_GET(self):                #server_foreverからGET時に呼ばれる
       print('Context Consumer error (4): invalid method: GET')
       self.send_response(400)
       self.send_header('Content-length', '0')
       self.end_headers()
       self.wfile.write(''.encode())
   def do_PUT(self):                #server_foreverからPUT時に呼ばれる
       print('Context Consumer error (4): invalid method: PUT')
       self.send_response(400)
       self.send_header('Content-length', '0')
       self.end_headers()
       self.wfile.write(''.encode())

port = 3000
httpd = s.HTTPServer(("", port), MyHandler)
print('Context Consumerを起動しました。ポート:%s' % port)
httpd.serve_forever()

このファイルをダブルクリックすると、Python が自動的に起動し、ポート番号3000でリクエストを待ち受けます。この待ち受けを技術的には「listenする」と言います。



 試しに、webブラウザから問い合わせてみましょう。問い合わせは、http://localhost:3000です。この時の表示は以下のようになるはずです。
Context Consumerを起動しました。ポート:3000
Context Consumer error (4): invalid method: GET
127.0.0.1 - - [07/May/2022 14:50:37] "GET / HTTP/1.1" 400 -
Fiware/OrionからContext Consumerに対する通知はPOSTなのですが、ブラウザからはGETでリクエストが発行されるので、Context Consumerはエラーを返却しています。とにかく、ちゃんとポート3000でメッセージが通知されたことは確認できました。



7.3.3 ネットワーク設定の追加
 今回は、fiware/orionから通知をWindows上のConsumerに送るので、そのための指定を追加します。ネットワークの構成は右以下の図となります。追加されるのは、太い赤字の部分です。



このうち、"hostpc"の部分がコンテナ起動時の指定となります。
まずは、Ubuntuから見たWindowsのIPアドレスを調べます。2.3節で説明したように、Ubuntuで以下のコマンド打ち、nameserverの右側のIPアドレスをメモします。筆者の環境では、172.27.144.1でした。

cat /etc/resolv.conf

次に、一旦fiware/orionのコンテナを停止します。以下の二つのコマンドが必要です。

docker stop fiware-orion
docker rm fiware-orion

コンテナが停止したら、前記のIPアドレスを指定して以下の様に打ち、コンテナを再起動します。

docker run -d --name fiware-orion --network=fiware_default --add-host=hostpc:172.27.144.1 -p 1026:1026 fiware/orion -dbhost mongo-db
harry@MSI:~$ docker run -d --name fiware-orion --network=fiware_default --add-host=hostpc:172.27.144.1 -p 1026:1026 fiware/orion -dbhost mongo-db
7bf51ff4b7419ea867140e7bad41c50246a145b8d7bd0ac2a70f5d04e438a034
harry@MSI:~$

このコマンドの意味は、"fiware/orion"というimageを、バックグランドで動作する"fiware-orion"という名前のContainerとして起動しています。その際に、コンテナが使うネットワークはfiware_defaultです。fiware/orionのコンテナのポート1026はDcokerの外部からも1026で通信可能とします。逆にコンテナ側から外部(Windows)への通信時には、172.27.144.1というIPアドレスを指定する代わりに"hostpc"という名前で呼び出す事を可能とする指定です。また、Orionに必要なDBは"mongo-db"というコンテナを使うように指定しています。
 ここで、fiware/orionのContainerからWindowsに通信ができる事を確認しましょう。まずはContainer内からcUrlコマンドを動かせるように、以下のコマンドをUbuntuから発行します。

docker exec -it fiware-orion bash
そうすると、fiware/orionのContainerのBashが起動します。そこで、以下のcUrlコマンドを発行します。これは、hostpcで指定したIPアドレスのポート番号3000番に対してPOSTの要求をするのですが、その際に7.6節で説明するResistrationに応じてFiware/OrionからContext Providerに対して発行される要求を真似してデータを送信しています。

curl -X POST -H 'Content-Type: application/json' http://hostpc:3000/subscriptions/temperature-change -d '{"data":[{"id":"aaa","type": "Room","name":{"type":"StructuredValue","value":{"type":"Text","value": "bbb"}},"temperature":{"type":"StructuredValue","value":100}}]}'
harry@MSI:~$ docker exec -it fiware-orion bash
bash-4.4$ curl -X POST -H 'Content-Type: application/json' http://hostpc:3000/subscriptions/temperature-change -d '{"data":[{"id":"aaa","type": "Room","name":{"type":"StructuredValue","value":{"type":"Text","value": "bbb"}},"temperature":{"type":"StructuredValue","value":100}}]}'
bash-4.4$
そうすると、Providerのプログラムには以下の様なメッセージが表示されれば、正常に通信が行われていることが分かります。
部屋 : bbb
室温 : 100
172.22.32.1 - - [07/May/2022 15:14:37] "POST /subscriptions/temperature-change HTTP/1.1" 200 -