WSGIで作る簡単ローカルHTTP Proxyサーバ
前提としては、
- 社内のWiki(仮にintra.example.comとする)には外部から直接アクセスできない
- ゲートウェイ(仮にgw.example.comとする)にはssh接続できる
- ノートPCを使っていて、頻繁にLANの内部と外部を行き来する
という状況で、どこからでも社内のWikiが見られるようにしたい。
SSHのポートフォワーディングだけだと、http://localhost:8088/みたいなURLでアクセスすることになるので、リンクを辿れなかったり、VirtualHostを使っている場合はhostsを書き換える必要があったりして面倒。
SOCKSとかDynamicDNSとか調べたけどよくわからず、既存のHTTP Proxyサーバではちょうど良いものがなかったので、自分で作ることにした。
幸いWSGIには必要な部品は揃っているので、あとはそれを組み合わせるだけ。
virtualenv環境の構築とプロジェクトのひな形生成
$ python virtualenv.py --no-site-packages TinyWSGIProxy $ cd TinyWSGIProxy $ . bin/activate (TinyWSGIProxy)$ easy_install Paste (TinyWSGIProxy)$ easy_install PasteScript (TinyWSGIProxy)$ paster create -t paste_deploy TinyWSGIProxy Selected and implied templates: PasteScript#basic_package A basic setuptools-enabled package PasteDeploy#paste_deploy A web application deployed through paste.deploy Variables: egg: TinyWSGIProxy package: tinywsgiproxy project: TinyWSGIProxy Enter version (Version (like 0.1)) ['']: Enter description (One-line description of the package) ['']: Enter long_description (Multi-line description (in reST)) ['']: Enter keywords (Space-separated keywords/tags) ['']: Enter author (Author name) ['']: Enter author_email (Author email) ['']: Enter url (URL of homepage) ['']: Enter license_name (License name) ['']: Enter zip_safe (True/False: if the package can be distributed as a .zip file) [False]: Creating template basic_package Creating directory ./TinyWSGIProxy Recursing into +package+ Creating ./TinyWSGIProxy/tinywsgiproxy/ Copying __init__.py to ./TinyWSGIProxy/tinywsgiproxy/__init__.py Copying setup.cfg to ./TinyWSGIProxy/setup.cfg Copying setup.py_tmpl to ./TinyWSGIProxy/setup.py Creating template paste_deploy Recursing into +package+ Copying sampleapp.py_tmpl to ./TinyWSGIProxy/tinywsgiproxy/sampleapp.py Copying wsgiapp.py_tmpl to ./TinyWSGIProxy/tinywsgiproxy/wsgiapp.py Recursing into docs Creating ./TinyWSGIProxy/docs/ Copying devel_config.ini_tmpl to ./TinyWSGIProxy/docs/devel_config.ini Updating ./TinyWSGIProxy/setup.py Updating ./TinyWSGIProxy/setup.py ************************************************************************ * Run "paster serve docs/devel_config.ini" to run the sample application * on http://localhost:8080 ************************************************************************ Running /Users/nozom/work/TinyWSGIProxy/bin/python setup.py egg_info Adding PasteDeploy to paster_plugins.txt (TinyWSGIProxy)$ cd TinyWSGIProxy
tinywsgiproxy/wsgiapp.py
from paste.proxy import TransparentProxy from sqlalchemy import engine_from_config import model class ProxyAdmin(object): def __call__(self, environ, start_response): query = model.HostNameMapping.query.filter_by(enabled=True) mapping = query.filter_by(hostname=environ["HTTP_HOST"]).first() if mapping: proxy = TransparentProxy(force_host=mapping.mapped) else: proxy = TransparentProxy() return proxy(environ, start_response) def make_app(global_conf, **kw): # Here we merge all the keys into one configuration # dictionary; you don't have to do this, but this # can be convenient later to add ad hoc configuration: conf = global_conf.copy() conf.update(kw) # Setup model model.metadata.bind = engine_from_config(conf, 'sqlalchemy.') model.setup_all() # model.create_all() # This is a WSGI application: app = ProxyAdmin() return app
tinywsgiproxy/model.py
from elixir import * __all__ = ["HostNameMapping"] class HostNameMapping(Entity): using_options(shortnames=True) hostname = Field(String(255), unique=True) mapped = Field(String(255)) enabled = Field(Boolean)
development.ini
[DEFAULT] [filter-app:main] # This puts the interactive debugger in place: use = egg:Paste#evalerror next = devel [app:devel] # This application is meant for interactive development use = egg:TinyWSGIProxy sqlalchemy.url = sqlite:////%(here)s/data.db filter-with = log [filter:log] use = egg:Paste#translogger [app:test] # While this version of the configuration is for non-iteractive # tests (unit tests) use = devel [server:main] use = egg:Paste#http # Change to 0.0.0.0 to make public: host = 127.0.0.1 port = 8088
- DBのセットアップ
(TinyWSGIProxy)$ easy_install Elixir (TinyWSGIProxy)$ easy_install pysqlite (TinyWSGIProxy)$ python >>> from sqlalchemy import create_engine >>> import tinywsgiproxy.model >>> tinywsgiproxy.model.metadata.bind = create_engine('sqlite:///data.db') >>> tinywsgiproxy.model.setup_all() >>> tinywsgiproxy.model.create_all()
$ sqlite3 sqlite> insert into hostnamemapping (id, hostname, mapped, enabled) values (1, "intra.example.com", "localhost:40080", 1); sqlite> .quit
実行
(TinyWSGIProxy)$ paster serve --reload development.ini
別のターミナルで
$ ssh -L 40080:intra.example.com:80 gw.example.com
を実行しておいて、ブラウザのプロキシ設定をlocalhost:8088に変更する。
http://intra.example.com/にアクセスすると、プロキシ経由でページが表示されるようになります。