pythonでping監視 〜〜その2〜〜
前回、あるNWセグメント内のクライアントにpingを送って常時監視することを試みました。
その別バージョンです。xargs
で並列処理をしています。
# ping_xargs.py import subprocess, requests, sys def is_connectable(host): # ping = subprocess.Popen(["ping", "3", "-c", "1", host], stdin=subprocess.PIPE, stdout=subprocess.PIPE) ping = subprocess.Popen(["ping", "-W", "1", "-c", "1", host], stdin=subprocess.PIPE, stdout=subprocess.PIPE) ping.communicate() return ping.returncode == 0 ipaddress = sys.argv[2] num = sys.argv[1] result = is_connectable(ipaddress) print(ipaddress) if result is not True: url = "https://my-line-bot-url.com/alert" + num requests.get(url)
$ while true; do sed -e 's/,/ /g' IPAdressTable.csv | xargs -L 1 -P20 python ping_xargs.py; done;
特に効率的になったわけではないですが。
しかも今回監視をしたいと思っていたNW設計を聞いたら一番上のL3SWではポート間のICMPを禁止しているらしい。。。
L3SWにぶら下がってるHUB間で全然疎通しないからなんでかなと思っていたら、そもそもpingで監視が出来るNW環境じゃなかった。。
pythonでping監視
はじめに
- あるプライベートネットワークの中に存在するクライアントにpingを打ち、常時監視するツールが欲しかった。
- メールでの通知とかも分かりづらいのでいろんな通知の方法が使いたかった(今回はLine Botで通知)。
- 良さげなツールが無かったので自分でプログラムを書いて実現することにした
PythonでPingを打つ
pythonではsubprocess
を使えばpingを打てるらしいです。
この関数をお借りしてpingを打ちます。
import subprocess def is_connectable(host): ping = subprocess.Popen(["ping", "-w", "3", "-c", "1", host], stdin=subprocess.PIPE, stdout=subprocess.PIPE) ping.communicate() return ping.returncode == 0
今回監視したい対象は、あるセグメントの中で固定IPを振られて存在しているクライアントが対象なので、クライアントのIPアドレスをcsvファイルから読み込みます。
取得したIPアドレスのリストをforループで回して全てにpingを打ち、NGだった場合は通知を送ります。
(今回はLine Botに通知させたいのでBotサーバのURLにリクエストしています。)
1,192.168.101.1 2,192.168.101.2 3,192.168.101.3 4,192.168.101.4 : :
import csv, subprocess, requests def is_connectable(host): ping = subprocess.Popen(["ping", "-W", "3", "-c", "1", host], stdin=subprocess.PIPE, stdout=subprocess.PIPE) ping.communicate() return ping.returncode == 0 while True: f = open('IPAdressTable.csv', 'r') reader = csv.reader(f) for index, row in enumerate(reader): print(row) result = is_connectable(row[1]) if result is not True: url = "https://my-line-bot-url.com/alert" + row[0] requests.get(url) f.close()
これで、まずは機能としては完成ですが、同セグメント内に存在する100個近くのクライアントにpingを打っていると、一周するのも時間がかかってしまい、あまり常時監視している感じがしなくなってしまいます。
そこで並列処理を導入して、複数スレッドで処理させようと思います。
joblibで並列処理
pythonの並列処理にはjoblib
が便利みたいです。
ここを参考にループ処理の部分を書き換えます。
import csv, subprocess, requests from joblib import Parallel, delayed def is_connectable(host): ping = subprocess.Popen(["ping", "-W", "3", "-c", "1", host], stdin=subprocess.PIPE, stdout=subprocess.PIPE) ping.communicate() return ping.returncode == 0 def send_connection_status(host, number): print(host) result = is_connectable(host) if result is not True: url = "https://my-line-bot-url.com/alert" + row[0] requests.get(url) while True: f = open('IPAdressTable.csv', 'r') reader = csv.reader(f) header = next(reader) Parallel(n_jobs=-1)( [delayed(send_connection_status)(row[1], row[0]) for index, row in enumerate(reader)] ) f.close()
これで複数スレッド立てて処理が出来るようです。
とりあえず実装しただけで細かいことはまだよくわかりませんが。
時間があるときにもう少し勉強したいと思います。
Flask-AskでAlexaのカスタムスキルを作成
はじめに
昨年末にAmazon Echo Dotを購入したので、カスタムスキルを作ろうと思い、その方法を備忘としてまとめます。
Alexaのカスタムスキルは以前作ったことがあり、そのときに使用したFlask-Askを使用して実装したいと思います。
Flask-Askをインストールする際の注意点はこちらの記事にまとめています。
環境構築
DockerでPython環境の構築
今回は作ったプログラムと環境をHerokuにデプロイするので、コンテナをそのままデプロイ出来るようDockerで環境を作ります。
まずは簡単に以下のようなDockerfileを作ります。
FROM python:3.7.1-alpine3.8 ADD . /app WORKDIR /app RUN apk update && apk add git vim
以下コマンドでDockerコンテナを起動します。
$ docker build . -t alexa_env $ docker run -it -v $PWD:/app -p 8000:8000 alexa_env /bin/ash
今回はalpineのDocker imageを使用しているので、/bin/bash
ではなくbin/ash
であることに注意。
Dockerコンテナに入ったら入ったら、flask
とgunicorn
インストールします。
/app $ pip install flask gunicorn
動作確認のためのapp.py
を作成します。
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run()
gunicornを起動します。
/app $ gunicorn app:app -b 0.0.0.0:8000
ブラウザからhttp://localhost:8000
を開いて"Hello World!"が表示されればOKです。
Herokuにデプロイ
続いてDockerfileを編集してHerokuにデプロイ出来る環境を作っていきます。
事前にコンテナ内でpip freeze > requirements.txt
を実行しrequirement.txt
を作成しておきます。
また前回の記事にて実施したFlask-Askのパッケージのローカルへのダウンロードと編集したパッケージを/src
配下に準備するとともに、build-base
,libffi-dev
,openssl-dev
のインストールを追記しておきます。
最終的なDockerfileは以下の通り。
FROM python:3.7.2-alpine3.8 ADD . /app WORKDIR /app RUN apk update && \ apk add git vim build-base libffi-dev openssl-dev && \ pip install --upgrade pip && \ pip install -r requirements.txt && \ pip install src/Flask-Ask-0.9.8.tar.gz EXPOSE 8000 ENV PORT 8000 CMD gunicorn app:app -b 0.0.0.0:$PORT
以前の記事に書いたように、以下コマンドでherokuにデプロイします。
$ heroku login # herokuにログイン $ heroku container:login # Heroku 上の Container Registry へログイン $ heroku create alexacustomskil # Heorkuアプリの作成 $ heroku container:push web --app yourappname $ heroku container:release web --app yourappname
これでHerokuにデプロイされ、デプロイされたURLにアクセスし先ほどと同様"hello World!"が表示されればOKです。
スキル作成
いよいよここからはカスタムスキルを作成していきます。
基本的にはこちらの開発ブログに掲載されているチュートリアルに従って実装していきます。
Flask-Ask: A New Python Framework for Rapid Alexa Skills Kit Development : Alexa Blogs
こちらのチュートリアルでは、数字が3つ読み上げられるのでその数字を覚えて逆から読むというゲームを作っています。
Alexa Developer Console上での設定と、Flask-Askでのサーバ側の処理実装が必要になります。
Amazon Developper Consoleで設定
チュートリアルとは少し画面構成が違いますが、Console上で設定するものは以下のものです。
- スキルの作成
- スキルの名前を設定 「アレクサ、〇〇を開いて」のときの名前
- スキルのインテントを設定
インテントとはユーザーがアレクサに対して応答する言葉のカテゴリのようなもの
そのユーザー応答が何を意味するのかを定義します。
今回登録するのは以下の2つ- YesIntent ゲーム開始時に「始めてもいいですか?」の問に対して、ユーザーがOKを場合の発話を定義
- AnswerIntent 出された問題に対してユーザーが解答する場合の発話を定義
- それぞれのインテントのスロットを設定 インテントにて定義する発話の中で使用する、変数の器のようなもの 今回のAnswerIntentでは発話の中で数字を読み上げるので、固定値で発話パターンを定義出来ないので、変数としてスロットを定義したうえで発話を定義します。
まずはAlexa Developer ConsoleにAmazonアカウントでログインします。
開発者アカウントにするために追加で情報入力が必要な際は入力します。
https://developer.amazon.com/alexa/console/ask
開発者コンソールが開いたらスキルの作成をクリックします。
新しいスキルの登録画面で、カスタムスキルを作成します。
必要情報を入力すると、スキル編集画面にきます。
まずはインテントを登録します。
今回は上で説明したとおり、以下の2つを登録します。
- YesIntent ゲーム開始時に「始めてもいいですか?」の問に対して、ユーザーがOKを場合の発話を定義
- AnswerIntent 出された問題に対してユーザーが解答する場合の発話を定義
まずはYesIntentを登録します。
また、YesIntentのサンプルを登録し、このIntentがどういった発話パターンを取りうるのかAlexaに知ってもらいます。
次にAnswerIntentを登録します。
AnserIntentについては、発話パターンのサンプルを登録する前に、発話パターンの中で使用されるスロット(変数、器のようなもの)を登録します。
今回は3つの数字のスロットを使用しますので、それぞれfirst、second、thirdの3個を登録します。 スロットのタイプはAMAZON.numberとします。
次にこのスロットを使用してAnswerIntentの発話パターンを登録します。 今回は以下の2パターンを登録しました。
次に、このスキルを呼び出した際にそれを処理するサーバーのURLを設定します。 先程デプロイしたherokuのURLを設定します。
最後に今回のスキルを呼び出すためのスキル名を設定します。 設定を保存し、ビルドします。
これで開発コンソールでの設定は完了です。
左メニューのJSONエディター上は以下のような表示になります。
このJSONをそのまま編集してもOKです。
{ "interactionModel": { "languageModel": { "invocationName": "メモリーゲーム", "intents": [ { "name": "AMAZON.CancelIntent", "samples": [] }, { "name": "AMAZON.HelpIntent", "samples": [] }, { "name": "AMAZON.StopIntent", "samples": [] }, { "name": "AMAZON.NavigateHomeIntent", "samples": [] }, { "name": "YesIntent", "slots": [], "samples": [ "うん", "はい", "もちろん", "OKです", "OK", "了解" ] }, { "name": "AnswerIntent", "slots": [ { "name": "first", "type": "AMAZON.NUMBER" }, { "name": "second", "type": "AMAZON.NUMBER" }, { "name": "third", "type": "AMAZON.NUMBER" } ], "samples": [ "{first} と {second} と {third}", "{first} {second} {third}" ] } ], "types": [] } } }
Flask-ASKで実装
サーバ側の処理はFlask-Askで実装していきます。
Welcome to Flask-Ask — Flask-Ask documentation
チュートリアルの通り、app.pyを以下のように書き換えます。
import logging from random import randint from flask import Flask, render_template from flask_ask import Ask, statement, question, session app = Flask(__name__) ask = Ask(app, "/") logging.getLogger("flask_ask").setLevel(logging.DEBUG) @ask.launch ``def new_game(): welcome_msg = render_template('welcome') return question(welcome_msg) @ask.intent("YesIntent") def next_round(): numbers = [randint(0, 9) for _ in range(3)] round_msg = render_template('round', numbers=numbers) session.attributes['numbers'] = numbers[::-1] # reverse return question(round_msg) @ask.intent("AnswerIntent", convert={'first': int, 'second': int, 'third': int}) def answer(first, second, third): winning_numbers = session.attributes['numbers'] if [first, second, third] == winning_numbers: msg = render_template('win') else: msg = render_template('lose') return statement(msg) if __name__ == '__main__': app.run(debug=True)
templateファイルの中にはAlexaが話すセリフを記載します。
welcome: メモリーゲームへようこそ! これから5つの数字を読み上げるので、アタナはその数字を逆の順番で答えてください。準備はいいですか? round: 今回の数字は {{ numbers|join(", ") }} です。反対の順番で読み上げてください。 win: 正解です! lose: 不正解です。
サーバー側の処理はこれでOKなので、再度Herokuにデプロイします。
そして開発用アカウントに紐づけたEcho端末や、アレクサシミュレーターで「アレクサ、メモリーゲームを開いて」と話しかけてゲームがスタートすれば成功です。
が、返事が返ってきません。
Herokuのログを確認すると以下のようなエラーが出ていました。
2019-01-24T15:02:16.836079+00:00 app[web.1]: File "/usr/local/lib/python3.7/site-packages/OpenSSL/crypto.py", line 740, in _subjectAltNameString 2019-01-24T15:02:16.836081+00:00 app[web.1]: method = _lib.X509V3_EXT_get(self._extension) 2019-01-24T15:02:16.836082+00:00 app[web.1]: AttributeError: module 'lib' has no attribute 'X509V3_EXT_get'
調べてみるとpyOpenSSLを再インストールする必要があるようです。
記事を参考にDockerfileを以下のように修正します。
FROM python:3.7.2-alpine3.8 ADD . /app WORKDIR /app RUN apk update && \ apk add git vim build-base libffi-dev openssl-dev && \ pip install --upgrade pip && \ pip install -r requirements.txt && \ pip install src/Flask-Ask-0.9.8.tar.gz && \ pip uninstall --yes pyOpenSSL && \ yes | pip install pyOpenSSL EXPOSE 8000 ENV PORT 8000 CMD gunicorn app:app -b 0.0.0.0:$PORT
再度Herokuにデプロイすると無事動作するようになりました。
まとめ
前回の続きで、Flask-Askを使用してAlexaのカスタムスキルを作成してみました。
Flaskを使って簡単に実装できるので、アイデア次第でいろんなスキルが作れそうです。
Dockerのalpine上 Flask-Askをpipでインストールしようとするとエラー
はじめに
年末にEcho dotを購入したので、Alexaのカスタムスキルを作ろうと思いました。
Alexaのカスタムスキルは以前作成したことがあったのでその際に使用したFlask_Askを使って作成しようと思い取り掛かりました。
ところがFlask_Askはしばらくメンテナンスされていないようで、pipでインストールする際にエラーが出てしまったのでその対処を備忘としてまとめます。
pip installでエラー
今回はDocker上に環境を作っており、Python3.7.2-alpine3.8のDockerimageを使用しました。
早速開発しようとFlask_Askをpipでインストールしようとしたらエラーになりました。
$ pip install flask_ask Collecting flask_ask Using cached https://files.pythonhosted.org/packages/6a/f5/d4709ae94584a0b1541e9b52b2d25a8a1bdb6e2da9d6870f23fdd0523a30/Flask-Ask-0.9.8.tar.gz Complete output from command python setup.py egg_info: Traceback (most recent call last): File "<string>", line 1, in <module> File "/tmp/pip-install-la1z83pq/flask-ask/setup.py", line 8, in <module> from pip.req import parse_requirements ModuleNotFoundError: No module named 'pip.req' ---------------------------------------- Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-la1z83pq/flask-ask/
'pip.req'が無いと言われています。
いろいろ調べてみると既知のエラーらしく、コミュニティでも話題になっていました。
どうやらsetup.pyを編集すれば解消するようです。
pip downloadでダウンロードしてからオフラインでインストール
setup.pyを編集するためには、まずはソースをダウンロードしなくてはなりません。
こちらを参考に以下の手順で実施しました。
まずはsrc
ディレクトリを作成し、そこにパッケージをダウンロードします。
$ mkdir src $ pip download -d src --no-binary :all: flask_ask Collecting flask_ask Downloading https://files.pythonhosted.org/packages/6a/f5/d4709ae94584a0b1541e9b52b2d25a8a1bdb6e2da9d6870f23fdd0523a30/Flask-Ask-0.9.8.tar.gz (40kB) 100% |████████████████████████████████| 40kB 2.8MB/s Saved ./src/Flask-Ask-0.9.8.tar.gz Complete output from command python setup.py egg_info: Traceback (most recent call last): File "<string>", line 1, in <module> File "/tmp/pip-download-on3tlt2h/flask-ask/setup.py", line 8, in <module> from pip.req import parse_requirements ModuleNotFoundError: No module named 'pip.req' ---------------------------------------- Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-download-on3tlt2h/flask-ask/ $ ls -l src/ total 40 -rw-r--r-- 1 root root 40142 Jan 8 07:03 Flask-Ask-0.9.8.tar.gz
何やらまたsetup.pyでエラーが出ていますが、ちゃんとダウンロード出来ているようです。
setup.pyの編集
ダウンロードしたtar.gzファイルを解凍し、setup.pyを編集します。
$ cd src/ $ tar -zxvf Flask-Ask-0.9.8.tar.gz $ ls -l total 40 drwxr-xr-x 15 501 wheel 480 Jan 8 07:05 Flask-Ask-0.9.8 -rw-r--r-- 1 root root 40142 Jan 8 07:03 Flask-Ask-0.9.8.tar.gz $ cd Flask-Ask-0.9.8 $ vim setup.py
setup.pyを以下のように編集します。
""" Flask-Ask ------------- Easy Alexa Skills Kit integration for Flask """ from setuptools import setup # from pip.req import parse_requirements try: # for pip >= 10 from pip._internal.req import parse_requirements except ImportError: # for pip <= 9.0.3 from pip.req import parse_requirements setup( name='Flask-Ask', version='0.9.8', url='https://github.com/johnwheeler/flask-ask', license='Apache 2.0', author='John Wheeler', author_email='john@johnwheeler.org', description='Rapid Alexa Skills Kit Development for Amazon Echo Devices in Python', long_description=__doc__, packages=['flask_ask'], zip_safe=False, include_package_data=True, platforms='any', install_requires=[ str(item.req) for item in parse_requirements('requirements.txt', session=False) ], test_requires=[ 'mock', 'requests' ], test_suite='tests', classifiers=[ 'License :: OSI Approved :: Apache Software License', 'Framework :: Flask', 'Programming Language :: Python', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ] )
setup.pyを編集したパッケージフォルダを再度tarで固めます。
$ mv Flask-Ask-0.9.8.tar.gz Flask-Ask-0.9.8_bk.tar.gz $ tar -zcvf Flask-Ask-0.9.8.tar.gz Flask-Ask-0.9.8 $ ls -l total 80 drwxr-xr-x 15 501 wheel 480 Jan 8 07:06 Flask-Ask-0.9.8 -rw-r--r-- 1 root root 40284 Jan 8 07:08 Flask-Ask-0.9.8.tar.gz -rw-r--r-- 1 root root 40142 Jan 8 07:03 Flask-Ask-0.9.8_bk.tar.gz
alpineにgccのインストール
pipでインストールを実行したところ、何やら別のエラーが出ました。
$ pip install Flask-Ask-0.9.8.tar.gz Processing ./Flask-Ask-0.9.8.tar.gz Collecting aniso8601==1.2.0 (from Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/5b/fb/251a0dd2f4710e60664ddd8bd3485bd8362530f47af9e88f4061fe589ebf/aniso8601-1.2.0.tar.gz (59kB) 100% |████████████████████████████████| 61kB 2.2MB/s Collecting Flask==0.12.1 (from Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/f4/43/fb2d5fb1d10e1d0402dd57836cf9a78b7f69c8b5f76a04b6e6113d0d7c5a/Flask-0.12.1-py2.py3-none-any.whl (82kB) 100% |████████████████████████████████| 92kB 5.1MB/s Collecting pyOpenSSL==17.0.0 (from Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/8c/b7/0048adbf09ad93f571e70fb3100e696d4711cdcbfe38161690d716a5ed35/pyOpenSSL-17.0.0-py2.py3-none-any.whl (51kB) 100% |████████████████████████████████| 61kB 3.1MB/s Collecting PyYAML==3.12 (from Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/4a/85/db5a2df477072b2902b0eb892feb37d88ac635d36245a72a6a69b23b383a/PyYAML-3.12.tar.gz (253kB) 100% |████████████████████████████████| 256kB 7.0MB/s Collecting six==1.11.0 (from Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl Collecting python-dateutil (from aniso8601==1.2.0->Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/74/68/d87d9b36af36f44254a8d512cbfc48369103a3b9e474be9bdfe536abfc45/python_dateutil-2.7.5-py2.py3-none-any.whl (225kB) 100% |████████████████████████████████| 235kB 5.4MB/s Requirement already satisfied: click>=2.0 in /usr/local/lib/python3.7/site-packages (from Flask==0.12.1->Flask-Ask==0.9.8) (7.0) Requirement already satisfied: Jinja2>=2.4 in /usr/local/lib/python3.7/site-packages (from Flask==0.12.1->Flask-Ask==0.9.8) (2.10) Requirement already satisfied: itsdangerous>=0.21 in /usr/local/lib/python3.7/site-packages (from Flask==0.12.1->Flask-Ask==0.9.8) (1.1.0) Requirement already satisfied: Werkzeug>=0.7 in /usr/local/lib/python3.7/site-packages (from Flask==0.12.1->Flask-Ask==0.9.8) (0.14.1) Collecting cryptography>=1.7 (from pyOpenSSL==17.0.0->Flask-Ask==0.9.8) Downloading https://files.pythonhosted.org/packages/f3/39/d3904df7c56f8654691c4ae1bdb270c1c9220d6da79bd3b1fbad91afd0e1/cryptography-2.4.2.tar.gz (468kB) 100% |████████████████████████████████| 471kB 6.3MB/s Installing build dependencies ... error Complete output from command /usr/local/bin/python -m pip install --ignore-installed --no-user --prefix /tmp/pip-build-env-x0_m8e40 --no-warn-script-location --no-binary :none: --only-binary :none: -i https://pypi.org/simple -- setuptools>=18.5 wheel "cffi>=1.7,!=1.11.3; python_implementation != 'PyPy'": Collecting setuptools>=18.5 Downloading https://files.pythonhosted.org/packages/37/06/754589caf971b0d2d48f151c2586f62902d93dc908e2fd9b9b9f6aa3c9dd/setuptools-40.6.3-py2.py3-none-any.whl (573kB) Collecting wheel Downloading https://files.pythonhosted.org/packages/ff/47/1dfa4795e24fd6f93d5d58602dd716c3f101cfd5a77cd9acbe519b44a0a9/wheel-0.32.3-py2.py3-none-any.whl Collecting cffi!=1.11.3,>=1.7 Downloading https://files.pythonhosted.org/packages/e7/a7/4cd50e57cc6f436f1cc3a7e8fa700ff9b8b4d471620629074913e3735fb2/cffi-1.11.5.tar.gz (438kB) Complete output from command python setup.py egg_info: No working compiler found, or bogus compiler options passed to the compiler from Python's standard "distutils" module. See the error messages above. Likely, the problem is not related to CFFI but generic to the setup.py of any Python package that tries to compile C code. (Hints: on OS/X 10.8, for errors about -mno-fused-madd see http://stackoverflow.com/questions/22313407/ Otherwise, see https://wiki.python.org/moin/CompLangPython or the IRC channel #python on irc.freenode.net.) ---------------------------------------- Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-zydf2ff8/cffi/ ---------------------------------------- Command "/usr/local/bin/python -m pip install --ignore-installed --no-user --prefix /tmp/pip-build-env-x0_m8e40 --no-warn-script-location --no-binary :none: --only-binary :none: -i https://pypi.org/simple -- setuptools>=18.5 wheel "cffi>=1.7,!=1.11.3; python_implementation != 'PyPy'"" failed with error code 1 in None
コンパイラが見つからないよーというエラーが出ています。
調べると対処方法が見つかりました。
alpineにgccが含まれていない事が原因のようです。
早速alpine上でパッケージをインストールしてから再試行しますと、また別のエラーが、、、
$ apk add build-base libffi-dev $ pip install Flask-Ask-0.9.8.tar.gz 〜〜〜〜略〜〜〜〜 build/temp.linux-x86_64-3.7/_openssl.c:498:30: fatal error: openssl/opensslv.h: No such file or directory #include <openssl/opensslv.h> ^ compilation terminated. error: command 'gcc' failed with exit status 1 ---------------------------------------- Failed building wheel for cryptography 〜〜〜〜略〜〜〜〜 build/temp.linux-x86_64-3.7/_openssl.c:498:30: fatal error: openssl/opensslv.h: No such file or directory #include <openssl/opensslv.h> ^ compilation terminated. error: command 'gcc' failed with exit status 1 ---------------------------------------- Command "/usr/local/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pip-install-z1_hjgoi/cryptography/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-record-rfu9q4ne/install-record.txt --single-version-externally-managed --compile" failed with error code 1 in /tmp/pip-install-z1_hjgoi/cryptography/
今度はopensslが無い!と怒られてしまっています。インストールしてから再度pipでインストール実行します。
$ apk add openssl-dev $ pip install Flask-Ask-0.9.8.tar.gz 〜〜〜〜略〜〜〜〜 Successfully built Flask-Ask cryptography Installing collected packages: cryptography, pyOpenSSL, PyYAML, Flask-Ask Successfully installed Flask-Ask-0.9.8 PyYAML-3.12 cryptography-2.4.2 pyOpenSSL-17.0.0
無事インストール出来ました。
$ python Python 3.7.1 (default, Dec 21 2018, 03:21:42) [GCC 6.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from flask_ask import Ask, statement, question, session >>> >>>
ちゃんとimportも出来るようになりました。
これでFlask_Askを使用できるようになったので、これからカスタムスキルを開発していきたいと思います。
locustでLINEメッセージ受信サーバに負荷試験
はじめに
業務でLINE Messeging APIを使用しているのですが、先日突然メッセージ受信サーバ(LINEからのWebhookを受け付けるサーバ)に対して、負荷試験を実施してほしいと先輩に依頼されました。
負荷試験ツールではjMeterが有名ですがノウハウがなく、もっと簡単に実施できるものが無いか調べたところ、 locustというPythonで記述できるツールがあることを知りました。
その内容を、自分用メモレベルですが備忘としてまとめます。
locustのインストール
公式サイト
Locust - A modern load testing framework
LocustはPython3.6までにしか対応していないので、Python3.6系の環境をあらかじめpyenvなどで準備しておきます。
Locust supports Python 2.7, 3.4, 3.5, and 3.6.
Macにてインストールする場合、libevが必要なので先にインストールしておきます。
$ brew install libev $ pip install locustio
基本的な使い方は上記公式サイトや以下サイトを参照しました。
シナリオ作成
LINEから送られるWebhookを模してJSONのhttpヘッダーにx-line-signature
パラメータを追加して送っています。
リクエスト先はLine developerで設定しているcallbackURLのパスを指定しています。(今回は/callback
)
メッセージIDは、受け付けるサーバ側(今回負荷をかけるサーバ)でユニークなものでないと処理できない仕様になっていたようなので、タイムスタンプを設定して送っています。
Messaging API - LINE Developers
from locust import HttpLocust, TaskSet, task import base64 import hashlib import hmac import datetime import json channel_secret = ' channnel secret ' class UserBehavior(TaskSet): @task(1) def index(self): payload = { "events": [ { "replyToken": "0f377", "type": "message", "timestamp": 1462629479859, "source": { "type": "user", "userId": "Ue636d0*****************" }, "message": { "id": str(int(datetime.datetime.now().timestamp()*1000000)), "type": "text", "text": "性能試験実施中" } } ], "destination":"U6415f13f***********" } hash = hmac.new(channel_secret.encode('utf-8'),json.dumps(payload).encode('utf-8'), hashlib.sha256).digest() signature = base64.b64encode(hash) headers = {'content-type': 'application/json', 'x-line-signature': signature} r = self.client.post("/callback", data=json.dumps(payload), headers=headers, catch_response=True) print(r) class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 1000 max_wait = 1000
以下コマンドで起動します。
$ locust -H http://"負荷をかけるサーバURL"
画面が起動して
- Number of users to simulate:
何クライアント作成するか - Hatch rate:
クライアントの作成スピード(毎秒)
Vue.jsとFlaskでフルスタックなWebアプリの開発環境を構築 その3〜〜ログイン認証追加〜〜
はじめに
今回は、前回作成したフロントのUIがVue.js
、バックエンドのAPIがFlask
のWebアプリケーションに対してFlask-Login
を使用してログイン機能を作成します。
Flask-Login
↓前々回 kittagon.hateblo.jp
まずはFlask-Login
とログインフォームの作成に使用するFlask-WTF
を以下コマンドでインストールします。
$ pip install flask-login flask_wtf
ユーザーモデルの追加
まずはログインユーザーとなるユーザーモデルをmodels.py
に記述します。
公式サイトにも以下のように記載されており、UserクラスはUserMixin
を継承する形で記述すると簡単なようです。
To make implementing a user class easier, you can inherit from UserMixin, which provides default implementations for all of these properties and methods. (It’s not required, though.)
また、ログインフォームはとあえず簡単にflask_wtfを使いLoginForm
クラスを作成しておきます。
# backend/models.py from flask_login import UserMixin from flask_wtf import FlaskForm from wtforms import TextField, PasswordField, validators from sqlalchemy.orm import synonym from werkzeug import check_password_hash, generate_password_hash from backend import db class LoginForm(FlaskForm): email = TextField('email', validators=[validators.Required()]) password = PasswordField('Password', validators=[validators.Required()]) def __str__(self): return ''' <form action="/-/login" method="POST"> <p>%s: %s</p> <p>%s: %s</p> %s <p><input type="submit" name="submit" /></p> </form> ''' % (self.email.label, self.email, self.password.label, self.password, self.csrf_token) class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), unique=True, nullable=False) email = db.Column(db.String(255), unique=True, nullable=False) _password = db.Column('password', db.String(255), nullable=False) active = db.Column(db.Boolean(), default=True) def _get_password(self): return self._password def _set_password(self, password): if password: password = password.strip() self._password = generate_password_hash(password) password_descriptor = property(_get_password, _set_password) password = synonym('_password', descriptor=password_descriptor) # フォームで送信されたパスワードの検証 def check_password(self, password): password = password.strip() if not password: return False return check_password_hash(self.password, password) # 認証処理 @classmethod def auth(cls, query, email, password): user = query(cls).filter(cls.email==email).first() if user is None: return None, False return user, user.check_password(password) class Task(db.Model): __tablename__ = 'tasks' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.Text) text = db.Column(db.Text) def to_dict(self): return dict( id=self.id, title=self.title, text=self.text ) def __repr__(self): return '<Entry id={id} title={title!r}>'.format( id=self.id, title=self.title) def init(): db.create_all()
ログイン処理の追加
次にFlask側の処理の中に実際のログイン処理を記述していきます。
__init__.py
でFlask-Login
の初期化
view.py
でログイン画面のルーティングとログイン処理を実装します。
このあたりを参考。
Flask-Loginの使い方 - Qiita
Python Flaskでつくる LDAPログインページ - AAA Blog
Flaskで認証 - Kensuke Kousaka's Blog
config.py
# backend/config.py import os class BaseConfig(object): DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:///backend.db' # cookieを暗号化する秘密鍵 SECRET_KEY = os.urandom(24) # csrf token用秘密鍵 WTF_CSRF_SECRET_KEY = os.urandom(24)
init.py
Flask-login
を使うとcurrent_user
、login_user
、logout_user
という変数が払い出されて払い出されていい感じにログイン状態の確認ができます。
また、後述するview.py
に記載するルーティングを-
というプレフィックスをつけて取り込んでいます。
# backend/__init__.py from flask import Flask, request, redirect, url_for from flask_cors import CORS import requests from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, current_user, login_user, logout_user, login_required # ここを追記 app = Flask('FLASK-VUE', static_folder = "./dist/static", template_folder = "./dist") app.config.from_object('backend.config.BaseConfig') db = SQLAlchemy(app) ######ここから追記###### login_manager = LoginManager() login_manager.init_app(app) from backend.models import User @login_manager.user_loader def load_user(user_id): return User.query.get(user_id) # 未認証の際のリダイレクト先を設定 @login_manager.unauthorized_handler def unauthorized_callback(): return redirect(url_for('app.login')) ############# route ############## from backend.view import view app.register_blueprint(view, url_prefix="/-") ######ここまで追記###### from backend.api import api app.register_blueprint(api, url_prefix="/api") cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) @app.route('/', defaults={'path': ''}) @app.route('/<path:path>') @login_required # ここを追記 def catch_all(path): if app.debug: return requests.get('http://localhost:8080/{}'.format(path)).text return render_template("index.html")
view.py
from flask import Blueprint, redirect, request from flask_login import login_user, logout_user from backend import db from backend.models import User, LoginForm view = Blueprint('app', __name__, template_folder='templates', static_folder='templates/static') @view.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm(request.form) if form.validate_on_submit(): user, authenticated = User.auth(db.session.query, form.email.data, form.password.data) if authenticated: login_user(user, remember=True) print('Login successfully.') return redirect('/tasks') else: print('Login failed.') return str(form) @view.route('/logout', methods=['POST']) def logout(): logout_user() return redirect('/login')
ログアウトボタンの追加
ログインした後のタスク画面にログアウトボタンを追加します。
以下のボタンをTasks.vue
の中に記述します。
場所はどこでも結構です。
// frontend/src/component/Tasks.vue <b-form v-if="show" action="/-/logout" method="post" > <b-button type="submit">ログアウト</b-button> </b-form>
ログインユーザーの作成
まだログインユーザーを作成する画面を作っていないので、
Pythonのインタラクティブモードからユーザーを作成し、動作確認をします。
$ python >>> from backend.models import User, db >>> User.query.all() [] >>> user = User(name='test_user', email='test@example.com') >>> user._set_password('password') >>> user <User (transient 4334868296)> >>> db.session.add(entry) >>> db.session.commit() >>> User.query.all() [<User 1>]
http://localhost:5000/-/login
を開いてログイン画面が表示され、
'test@example.com'と'password'でログインできればOKです。
また、ログインしていない状態でhttp://localhost:5000/tasks
を表示しようとすると
ログイン画面にリダイレクトされます。
ログアウトボタンでログアウトできます。
ログイン画面修正
最初に作成したのは最低限の要素のみ記述したので、もう少しきれいなレイアウトに変更します。
現状は↓のようにフォームを文字列で定義しておき、それを表示しているだけだったので、
画面をテンプレートから呼び出せるよう変更します。
def __str__(self): return ''' <form action="/-/login" method="POST"> <p>%s: %s</p> <p>%s: %s</p> %s <p><input type="submit" name="submit" /></p> </form> ''' % (self.email.label, self.email, self.password.label, self.password, self.csrf_token)
テンプレートの作成には、Vue.js側で作成している画面と揃えるために、Flask-Bootstrap
を使用します。
まずは以下コマンドでインストールします。
$ pip install flask-bootstrap
__init__.py
でflask-bootstrapの初期化をします。
# backend/__init__.py from flask import Flask, request, redirect, url_for from flask_cors import CORS import requests from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, current_user, login_user, logout_user from flask_bootstrap import Bootstrap # ここを追記 app = Flask('FLASK-VUE', static_folder = "./dist/static", template_folder = "./dist") app.config.from_object('backend.config.BaseConfig') db = SQLAlchemy(app) bootstrap = Bootstrap(app) # ここを追記 #############以下略#############
view.py
のログイン画面表示を修正します。
テンプレートの呼び出しに加えて、action
をFlaskテンプレートの変数としてurl_for
を渡すように修正しています。
# backend/view.py from flask import Blueprint, redirect, request, render_template, url_for # render_template, url_forを追加 #############略############# @view.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm(request.form) if form.validate_on_submit(): user, authenticated = User.auth(db.session.query, form.email.data, form.password.data) if authenticated: login_user(user, remember=True) print('Login successfully.') return redirect('/tasks') else: print('Login failed.') # return str(form) ## ここを修正 return render_template('login.html', form=form, action=url_for('app.login')) #############略#############
models.py
からstr要素を削除します。
また、Loginform
内に新たにsubmit要素を定義します。
# backend/models.py from flask_login import UserMixin from flask_wtf import FlaskForm from wtforms import TextField, PasswordField, validators, SubmitField # SubmitFieldの追加 from sqlalchemy.orm import synonym from werkzeug import check_password_hash, generate_password_hash from backend import db class LoginForm(FlaskForm): email = TextField('email', validators=[validators.Required()]) password = PasswordField('Password', validators=[validators.Required()]) submit = SubmitField('ログイン') # submit追加 # def __str__(self):の削除
次にログインテンプレートを作成します。
テンプレートフォルダはview.py
にて以下のように定義されています。
view = Blueprint('app', __name__, template_folder='templates', static_folder='templates/static')
view.py
と同階層にtemplates
フォルダを作成します。
app_dir ┣manage.py ┣frontend/ ┗backend/ ┣__init__.py ┣api.py ┣view.py ┣models.py ┣config.py ┗templates/
templates
フォルダ内にログイン画面用テンプレートを作成します。
<!-- backend/templates/login.html--> {% import "bootstrap/wtf.html" as wtf %} {% extends "bootstrap/base.html" %} {% block title %}This is a login page{% endblock %} {% block content %} <div class="container"> {{ wtf.quick_form(form, action=action, method="post") }} </div> {% endblock %}
またまた画面デザインは適当ですが、Bootstrap
により良い感じのフォームができました。
まとめ
これで認証までも含めたWebアプリができました。
基本的な機能はほぼ実装することができたので、今後はこれをベースにいろんな機能を実装していきたいと思います。
参考サイト
vue-cliのnpm run devでwebpack-dev-serverのエラー
はじめに
vue-cli
を使ってVue.jsの開発をしていたら、あるときからnpm run dev
実行時に↓のようなエラーが出てしまい開発用サーバが立ち上がらなくなりました。
(なんの操作をしていたときか忘れてしまいました。。)
$ npm run dev > frontend@1.0.0 dev /Users/hogehoge/Develop/practice-flask-vue/frontend > webpack-dev-server --inline --progress --config build/webpack.dev.conf.js The CLI moved into a separate package: webpack-cli Please install 'webpack-cli' in addition to webpack itself to use the CLI -> When using npm: npm i -D webpack-cli -> When using yarn: yarn add -D webpack-cli internal/modules/cjs/loader.js:583 throw err; ^ Error: Cannot find module 'webpack-cli/bin/config-yargs' at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15) at Function.Module._load (internal/modules/cjs/loader.js:507:25) at Module.require (internal/modules/cjs/loader.js:637:17) at require (internal/modules/cjs/helpers.js:20:18) at Object.<anonymous> (/Users/hogehoge/Develop/practice-flask-vue/frontend/node_modules/webpack-dev-server/bin/webpack-dev-server.js:84:1) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! frontend@1.0.0 dev: `webpack-dev-server --inline --progress --config build/webpack.dev.conf.js` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the frontend@1.0.0 dev script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! /Users/hogehoge/.npm/_logs/2018-11-19T11_12_55_011Z-debug.log
webpack-cliのインストール
しばらくパッケージをアップデートしていないので、なぜ今のタイミングで
The CLI moved into a separate package: webpack-cli Please install 'webpack-cli' in addition to webpack itself to use the CLI -> When using npm: npm i -D webpack-cli -> When using yarn: yarn add -D webpack-cli
が出るのか不明ですが、ひとまず指示どおりにインストールします。
$ npm i -D webpack-cli npm WARN url-loader@1.1.0 requires a peer of webpack@^4.0.0 but none is installed. You must install peer dependencies yourself. npm WARN webpack-dev-middleware@3.4.0 requires a peer of webpack@^4.0.0 but none is installed. You must install peer dependencies yourself. npm WARN webpack-dev-server@3.1.10 requires a peer of webpack@^4.0.0 but none is installed. You must install peer dependencies yourself. npm WARN webpack-cli@3.1.2 requires a peer of webpack@^4.x.x but none is installed. You must install peer dependencies yourself. + webpack-cli@3.1.2 added 25 packages from 7 contributors and audited 34200 packages in 37.642s found 0 vulnerabilities
インストールが完了しました。
webpack@^4.0.0
をインストールせよと言われてしまいます。
$ npm run dev > frontend@1.0.0 dev /Users/hogehoge/Develop/practice-flask-vue/frontend > webpack-dev-server --inline --progress --config build/webpack.dev.conf.js TypeError: Cannot destructure property `compile` of 'undefined' or 'null'. at addHooks (/Users/hogehoge/Develop/practice-flask-vue/frontend/node_modules/webpack-dev-server/lib/Server.js:114:49) at new Server (/Users/hogehoge/Develop/practice-flask-vue/frontend/node_modules/webpack-dev-server/lib/Server.js:127:5) at startDevServer (/Users/hogehoge/Develop/practice-flask-vue/frontend/node_modules/webpack-dev-server/bin/webpack-dev-server.js:355:14) at processOptions (/Users/hogehoge/Develop/practice-flask-vue/frontend/node_modules/webpack-dev-server/bin/webpack-dev-server.js:309:5) at process._tickCallback (internal/process/next_tick.js:68:7) at Function.Module.runMain (internal/modules/cjs/loader.js:745:11) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
やっぱりまだだめみたいです。
webpack-dev-serverのダウングレード
TypeError: Cannot destructure property `compile` of 'undefined' or 'null'.
そこでこちらのエラーについて更にいろいろと調べてみると、同じようなissueがありました。
1 - Upgrade webpack to v4, you must change the webpack.config.js file for compatibility. or
2 - Downgrade webpack-dev-server to exactly v3.0.0 as @Nufeen said.
やはり上で突っ込まれているように、v4以上にアップグレードするか、v3にダウングレードすれば直るようです。
webpack
の知識はほぼ無く、webpack.config.js
を編集するのもハードルが高そうだったので今回はダウングレードして対応することとしました。
$ npm install -D webpack-dev-server@3.0.0 npm WARN webpack-cli@3.1.2 requires a peer of webpack@^4.x.x but none is installed. You must install peer dependencies yourself. npm WARN webpack-dev-server@3.0.0 requires a peer of webpack@^4.0.0-beta.1 but none is installed. You must install peer dependencies yourself. + webpack-dev-server@3.0.0 added 37 packages from 16 contributors, removed 28 packages, updated 10 packages and audited 35284 packages in 30.206s found 1 high severity vulnerability run `npm audit fix` to fix them, or `npm audit` for details
インストール出来たので再度試してみます。
$ npm un dev
DONE Compiled successfully in 26990ms 8:59:34 PM
I Your application is running here: http://localhost:8080
無事開発用サーバが起動しました。