Python でダミーサーバーを立てよう!
2015年一発目の記事です!
今年もよろしくお願いします
さて、開発していく中で外部APIとの連携、メール送信などのテストのためにダミーのサーバーが必要になる場面はよくあると思います
最近仕事でまた必要になったんですが、わざわざ専用のサーバーを入れるほどでもなかったので Python で動くスタブのサーバーをつくりました
いろいろインストールして準備する必要がないように CentOS 6.4 にデフォルトで入っている Python 2.6.6 上で動かせるようにします
この記事では
1. ダミーサーバーの立て方
2. ダミーサーバーをカスタムする
3. ダミーサーバーをデーモン化する
4. まとめ
について説明します
1. ダミーサーバーの立て方
今回は以下の2つのダミーサーバーを用意します
まずは、一番簡単にできそうな方法を調べました
Web サーバー
Python には標準で用意されている Web サーバー機能があります
単純に静的ファイル をホストするだけの Web サーバーなら以下のコマンドで起動できます
python -u -m SimpleHTTPServer 8000
引数にはポート番号を指定します
1000番以下はルート権限で実行する必要があるので sudo をつけましょう
SimpleHTTPServer は GET、HEAD のメソッドに対応していますが、POST や PUT の機能には対応していません
なので API のダミーとして使うのは厳しいですね
SMPT サーバー
Python には標準で用意されているテスト用の SMTP サーバー機能があります
受け取ったメールの内容を標準出力に吐く機能だけを持ったテスト用の SMTP サーバーであれば以下のコマンドで起動できます
python -u -m smtpd -c DebuggingServer :8025
引数にポート番号を指定しますが、SimpleHTTPServer と違ってポート番号の前にコロンが必要です
DebuggingServer には base64 デコードする機能がついていないので、メール本文が base64 エンコードされている場合に base64 エンコードされた文字列がそのまま出力されてしまいます
メールを受信できるのはいいものの、base64 エンコードされた文字列をコピペしてデコードしないと中身が確認できません
SimpleHTTPServer、smtpd.DebuggingServer をそのまま使っても今回欲しかった機能にちょっと足りないです
そんな時に、欲しい機能を追加したダミーサーバーをサクっと作りたいですよね
どんな感じでカスタムできるのかを見ていきましょう
2. ダミーサーバーをカスタムする
WebAPI のダミーとするには POST や PUT に対応した Web サーバーが必要です
base64 エンコードされたメールの中身も確認したいので、 base64 デコードの機能を SMTP サーバーに付けたいです
また、どちらも受け取ったリクエストの内容を確認したいので、データをログに記録する機能も欲しいですね
そんな機能を追加したサーバーを作りました
実装したソースは GitHub にアップしています
GitHub - Chanmoro/python: python sample code
こんな感じで起動します
# Web サーバー $ python dummy_webapi.py # SMTP サーバー $ python dummy_smtp.py
ログは python ファイルと同じディレクトリに server.log という名前で出力するようにしています
Web サーバー
python/dummy_webapi.py at master · Chanmoro/python · GitHub
以下の機能を追加しています
- GET と POST に対応した固定の JSON を返す
- ログファイル出力
BaseHTTPServer.BaseHTTPRequestHandler を継承して、各 HTTP リクエストのメソッドに対応した関数を実装します
POST メソッドに対応した処理を追加したければ do_POST メソッドを実装する、というような命名規則になってます
今回はこんな感じで実装しました
def do_POST(self): f = open("post_response.json") response_body = f.read() self.send_response(200) self.send_header('Content-type', 'application/json; charset=UTF-8') self.send_header('Content-length', len(response_body)) self.end_headers() self.wfile.write(response_body) logging.info('[Request method] POST') logging.info('[Request headers]\n' + str(self.headers)) content_len = int(self.headers.getheader('content-length', 0)) post_body = self.rfile.read(content_len) logging.info('[Request doby]\n' + post_body)
ちなみにルーティングの機能は用意されていないので、もし複数の API を用意する必要があればリクエスト URL 毎に振り分ける処理を書かないといけないです
自分でルーティングの機能を追加して MVC を実装するサンプルが以下の記事に載っていました
Aventures Logicielles: A very simple HTTP server with basic MVC in Python
正直、ここまでやるなら Django を入れた方が良いんじゃないかと思いますが・・・
SMPT サーバー
python/dummy_smtp.py at master · Chanmoro/python · GitHub
以下の機能を追加しています
- Content-Transfer-Encoding の値を見てメール本文をデコードする
- ログファイル出力
DebuggingServer の実装を見てみると以下のようになっていて、SMTPServer の process_message をオーバーライドしてるだけでした
class DebuggingServer(SMTPServer): # Do something with the gathered message def process_message(self, peer, mailfrom, rcpttos, data): inheaders = 1 lines = data.split('\n') print '---------- MESSAGE FOLLOWS ----------' for line in lines: # headers first if inheaders and not line: print 'X-Peer:', peer[0] inheaders = 0 print line print '------------ END MESSAGE ------------'
なので、メールをコンソールに出力するだけじゃなくて何かの処理もカマしたい!という時は、これと同じように SMTPServer を継承して process_message をオーバーライドすれば作れます
※Python 2.7 系のソースですが DebuggingServer の部分は 2.6 系と同じです
releasing/2.7.9: 753a8f457ddc Lib/smtpd.py
ついでにテストメールを送信するコードもつくりました
特筆することはありませんが smtplib.SMTP( ) のところに接続先のメールサーバーを書きます
python/test_mail_sender.py at master · Chanmoro/python · GitHub
# Test mail send sample. import smtplib import email.utils from email.mime.text import MIMEText mailTo = 'test_to@example.com' mailFrom = 'test_from@example.com' mail = MIMEText('This is the body of the test message.') mail['To'] = mailTo mail['From'] = mailFrom mail['Subject'] = 'Test message' server = smtplib.SMTP('localhost', 8025) server.set_debuglevel(True) try: server.sendmail(mailTo , [mailFrom], mail.as_string()) finally: server.quit()
いろいろな文字コード、エンコードで送信してみるのは以下の記事が参考になります
Pythonで日本語メールを送る方法をいろいろ試した
3. ダミーサーバーをデーモン化する
テストしたい時だけ Python コードを都度実行するというのでもいいんですが、意識せずに起動しっぱなしにしておきたい!というニーズもあると思います
処理をデーモン化するやり方を調べたところ Upstart が簡単にできそうでした
conf ファイルを置くだけでサービスとして認識してくれます
例としてダミーの SMTP サーバーをデーモン化するやり方を見てみましょう
まず /etc/init/dummy_smtp.conf ファイルを作成し以下の内容を記述します
script 〜 end script の間にデーモン化したい処理を書きます
## Upstart Job Defines description "Dummy SMTP Server" script python -u dummy_smtp.py end script
conf ファイルを配置したらすぐ動かせるので、以下のようにして操作します
# conf ファイルの設定反映 initctl reload-configuration # サービスの起動 $ initctl start dummy-smtpd dummy-smtpd start/running, process 29022 # サービスの停止 $ initctl stop dummy-smtpd dummy-smtpd stop/waiting # サービスの状態確認 $ initctl list 〜省略〜 dummy-smtpd start/running, process 29022 〜省略〜
Upstart を試しているときにちょっとした問題がありました
以下のように標準出力をリダイレクトする処理を設定した時、なぜかファイルに出力されるまでの間にタイムラグが発生していて出力をうまく確認できませんでした
python -m smtpd -c DebuggingServer :8025 >> /var/log/dummy-smtpd.log 2>&1
結局、Python が標準出力のバッファリング機能を持っているのが原因で、バックグラウンドで動いたときにバッファのせいでタイムラグが発生していました
-u オプションをつけることで Python のバッファリングが無効になりすぐにファイルに出力されるようになりました
python -u -m smtpd -c DebuggingServer :8025 >> /var/log/dummy-smtpd.log 2>&1
ちなみに、バージョンが 1.4 以降の Upstart が入っていれば /var/log/upstart 以下に標準出力のログが吐かれるそうです
CentOS に入っている Upstart はバージョン 0.6.5 というものすごく古いのが入っているので、ログの機能は使えないバージョンでした
# Upstart のバージョン確認 initctl --version
本番用のサービスは init スクリプトで定義したほうがいいですね
CentOS 7 では systemd を主に使う方向になっているそうです