meinheldをリリースした
こんにちわ、闇金業者です。
Cの練習とPEP333の実装として作っていたpicowsですが、せっかくなのでpypiにあげることしました。
名前も変えてmeinheldにしています。
meinheld · PyPI
あとbitbucketもなんだか寂しい気がしてきたのでgithubに移動しました。
http_parserの変更など中身は大分変わっています。
明らかに過度のチューニングです。
hello_worldでabでベンチをとるといかに過度のチューニングであるかわかります。
使いかた
使い方は特に難しくありません。
from meinheld import server def hello_world(environ, start_response): status = '200 OK' res = "Hello world!" response_headers = [('Content-type','text/plain'),('Content-Length',str(len(res)))] start_response(status, response_headers) return [res] server.listen(("0.0.0.0", 8000)) server.run(hello_world)
シングルスレッドの非同期サーバなので並列処理をしたい場合はforkして下さい。
(その他、exampleを見てください。)
一応、プロセス名も変えれるクチも用意しています。
(multiprocessing のexampleがあるのでそれを参考にしてください)
運用(gunicorn)
運用用にworkerプロセスなどを管理するツールも書こうと思ったのですが、めんどくさくなってやめました。
代わりにgunicornのworkerを提供しています。
「今時gunicornで動かせないとか情弱すぎるww」と言われそうだったので作りました。
gunicornはunicornのpython版です。
Gunicorn - Python WSGI HTTP Server for UNIX
簡単な例
gunicorn --workers=2 --worker-class="meinheld.gmeinheld.MeinheldWorker" gunicorn_test:app
worker class にmeinheld.gmeinheld.MeinheldWorkerをすればmeinheldで動作します。
unix domain socketもいけるのでnginxと同マシンで動作させる場合は使用した方がいいです。
workerの監視は毎秒親プロセスへの通知するとなんだかもったいない気がしたので10秒毎ぐらいになっています。
gunicornのタイムアウト値が短すぎるとkillされて新しいプロセスが立ち上がるので少し長めにしてください。
(30〜ぐらい)
I/O戦略
readに関してはread_callbackで毎回readで読んでるだけで普通です。
writeの方は今まで自前でバッファ連結してたのですが、writevを使うようにしています。
writevで書けるだけ書いて残があればwrite_callbackを仕掛けて残りもwritevで送ります。
送信時には一応TCP_CORKも立ててます。
セキュリティ
Long Header DoSでやられないようにfield-valueの最大値は8kになっています。
(長ければ400を返す。ヘッダ数自身も制限)
またreadが遅いクライアントは積極的にバッサリクローズします。
なのでslowlorisをかましても返ってこなくなることはないです。
クソでかいデータが来ると困るのでContent-Lengthが16M(デフォルト)以上は413を返します。
(set_max_content_lengthで変更できる)
また512k以上の場合はメモリではなくtmpfileにデータを吐き出してメモリのドカ食いを防ぎます。
なんだかんだで割とまともなモノができたかなあと思います。