2010-01-16
Google App Engineの開発サーバをApacheのVirtualHostにマッピングする
Google App Engine, Python, Apache
Google App Engineの開発サーバはdev_appserver.pyを起動してなくちゃいけなくて面倒。
デフォルトでは、ポート8080で127.0.0.1に対してバインドするようになってる。
Tomcatの動いてるサーバを使って外から開発したい場合、portとaddressを指定しなくちゃいけなくて更に面倒。
ポートはアプリ1つ1つ変えないといけなくて面倒。
ブラウザからアクセスする時も、URLはhttp://example.com:8081/とポートも打たなきゃいけなくて面倒。
python dev_appserver.py --port=8081 --address=0.0.0.0 /var/www/html/gae
理想は、80番ポートで待ち受けてて、VirtualHostを1つ割り当てる事。
調べたけどそういう事は出来なそうだったから、Apacheのmod_proxyで出来ないかな、と思ってやってみた。
リバースプロキシで、1つのVirtualHostへのアクセスをhttp://127.0.0.1:8081/に転送するようにしたら動いた。
でも、常に起動しっぱなしにする場合、dev_appserver.pyはnohupで起動させたりしなくちゃいけない。
それもまた面倒だったから、設定と管理が出来るスクリプトを書いてみた。
とりあえず名前はGAEPorxy。
Pythonの流儀は解らないけど、一応動いてる。
今回初めてPythonやってみたけど、個人的にはあの三項演算子はないと思う。
dev_appserver はローカル テスト用に設計されており、外部接続はデフォルトでは許可されません。実行するときに -a <hostname> フラグを使用して上書きできますが、このようにすると SDK のセキュリティが強化されず、脆弱性が生じることがあるため、推奨しません。
Google App Engine に関する一般的な質問 - Google App Engine — Google Developers
GAEProxyの使い方
ヘルプ表示
# service gaeproxy help
usage: /etc/init.d/gaeproxy subcommand
subcommand(config):
list show domain list
add add domain
/etc/init.d/gaeproxy add [domain dir]
del delete domain
/etc/init.d/gaeproxy del [id]
mod modify domain
/etc/init.d/gaeproxy mod [id domain dir]
make make apache config
subcommand(run):
start start server
stop stop server
fstop stop server (force)
restart restart server
subcommand(help):
help show this message
プロキシを立てたいドメインとGAE開発ディレクトリと登録
service gaeproxy add service gaeproxy add gae.example.com /var/www/html/gae
登録内容削除
2つ目の引数はID。
IDはlistで確認。
service gaeproxy del service gaeproxy del 1
登録内容編集
1つ目の引数は、編集するデータのID。
2つ目の引数は、編集後のドメイン。
3つ目の引数は、編集後のディレクトリ。
service gaeproxy mod service gaeproxy mod 1 gaegae.example.com /var/www/html/gaegae
登録した内容を確認
service gaeproxy list
登録した内容でhttpd.confを作成・Apacheリロード
add、del、modをしたらこれをする。
service gaeproxy make
GAE開発サーバ起動
使用ポートは、8100+ID。
service gaeproxy start
GAE開発サーバ停止
service gaeproxy stop
GAE開発サーバ強制停止
実行してPythonのエラーが出たらまずこれ。
service gaeproxy fstop
GAE開発サーバ再起動
中身は、stop startをやってる。
service gaeproxy restart
GAE開発サーバの起動状態
service gaeproxy status
インストール
gaeproxyのファイル群一式は/usr/local/gaeproxyに保存。
Google App EngineのSDKは/usr/local/google_appengineにあると仮定。
chmod +x /usr/local/gaeproxy/gaeproxy.py ln -s /usr/local/gaeproxy/gaeproxy.py /etc/rc.d/init.d/gaeproxy # 環境変数でプロキシを使ってる事を悟られないようにするパッチ patch /usr/local/google_appengine/google/appengine/tools/dev_appserver.py < /usr/local/gaeproxy/dev_appserver.patch chkconfig --add gaeproxy
動作環境
# python -V Python 2.6.1 # python -V Python 2.5.1 # sqlite3 -version 3.3.6 # httpd -version Server version: Apache/2.2.3 Server built: Nov 12 2009 18:43:47
ハマった所
ブート時はホームディレクトリが/になってる
1発目の起動でこんなの聞かれる。
ブート時にinit.d内のスクリプトを叩いてるのはrootだけど、ホームディレクトリは/になってるっぽい。
chkconfigに登録して使う場合は、/.appcfg_nagが必要だった。
Allow dev_appserver to check for updates on startup? (Y/n):
CentOSの場合だいたいPython2.4をみてる
Yumのせいで。
PATH上にある「python」という名のpython2.4を全て消して、使いたいバージョンのpythonにシンボリックリンクを貼る。
Yumの1行目はpython2.4を直接指定。
これで2.4がデフォルトのpythonにならなくなる。
ソース
gaeproxy.py
#!/usr/bin/env python # # coding: utf-8 # chkconfig: 2345 99 01 # description: GAEProxy is a Google App Engine development server manager. # processname: gaeproxy import os import sys import pwd import subprocess import re import time import sqlite3 class GAEProxy(object): appserver = '/usr/local/google_appengine/dev_appserver.py' piddir = '/var/run/gaeproxy' logdir = '/var/log/gaeproxy' apacheconf = '/etc/httpd/conf.d/gaeproxy.conf' port = 8100 def __init__(self): dir_path = os.path.dirname(os.path.realpath(__file__)) db_path = os.path.join(dir_path, 'config.db') home_path = os.path.expanduser('~/') nag_path = os.path.join(home_path, '.appcfg_nag') init_flag = False if not os.path.exists(db_path): init_flag = True self.con = sqlite3.connect(db_path) self.con.isolation_level = None if init_flag: self.db_init() if not os.path.exists(self.piddir): os.mkdir(self.piddir) if not os.path.exists(self.logdir): os.mkdir(self.logdir) if not os.path.exists(nag_path): open(nag_path, 'w').write('opt_in: false\ntimestamp: %f' % time.time()) def db_init(self): sql_drop = """ DROP TABLE IF EXISTS appdata; """ sql_create = """ CREATE TABLE appdata ( id INTEGER PRIMARY KEY, domain VARCHAR(255), dir VARCHAR(255) ); """ self.con.execute(sql_drop) self.con.execute(sql_create) def command_list(self): sql_len = 'SELECT MAX(LENGTH(id)), MAX(LENGTH(domain)), MAX(LENGTH(dir)) FROM appdata;' id_len = 2 dom_len = 5 dir_len = 9 for id, dom, dir in self.con.execute(sql_len): id_len = max(id_len, id) dom_len = max(dom_len, dom) dir_len = max(dir_len, dir) sql_list = 'SELECT * FROM appdata;' list = self.con.execute(sql_list) print (' %-'+str(id_len)+'s | %-'+str(dom_len)+'s | %s') % ('id', 'domain', 'directory') print '-' + '-'*id_len + '-+-' + '-'*dom_len + '-+-' + '-'*dir_len + '-' for id, dom, dir in list: print (' %'+str(id_len)+'u | %-'+str(dom_len)+'s | %s') % (id, dom, dir) def command_add(self): domain = '' dir = '' ok = 'Y' if len(sys.argv)==4: domain = sys.argv[2] dir = sys.argv[3] else: while len(domain)==0: domain = raw_input('domain: ').strip() while len(dir)==0: dir = raw_input('dir: ').strip() print 'confirm' print ' type : add' print ' domain: %s' % domain print ' dir : %s' % dir ok = raw_input('ok? ').strip().upper() if ok=='Y' or ok == 'YES': insert_sql = 'INSERT INTO appdata (domain, dir) VALUES (?, ?);' result = self.con.execute(insert_sql, (domain, dir)).rowcount if result: print '%s rows added.' % (result) else: print 'Error!' else: print 'Cancel.' def command_del(self): id = 0 ok = 'Y' if len(sys.argv)==3: if sys.argv[2].isdigit(): id = int(sys.argv[2]) else: id2 = '' while not id2.isdigit(): id2 = raw_input('id: ').strip() id = int(id2) print 'confirm' print ' type: delete' print ' id : %u' % id ok = raw_input('ok? ').strip().upper() if ok=='Y' or ok=='YES': delete_sql = 'DELETE FROM appdata WHERE id=:id;' result = self.con.execute(delete_sql, {'id':id}).rowcount print '%s rows deleted.' % (result) else: print 'Cancel.' def command_mod(self): id = 0 domain = '' dir = '' ok = 'Y' if len(sys.argv)==5: if sys.argv[2].isdigit(): id = int(sys.argv[2]) domain = sys.argv[3] dir = sys.argv[4] else: id2 = '' while not id2.isdigit(): id2 = raw_input('id: ').strip() id = int(id2) while len(domain)==0: domain = raw_input('domain: ').strip() while len(dir)==0: dir = raw_input('dir: ').strip() print 'confirm' print ' type : modify' print ' id : %u' % id print ' domain: %s' % domain print ' dir : %s' % dir ok = raw_input('ok? ').strip().upper() if ok=='Y' or ok == 'YES': update_sql = 'UPDATE appdata SET domain=:domain, dir=:dir WHERE id=:id;' result = self.con.execute(update_sql, {'id':id, 'domain':domain, 'dir':dir}).rowcount if result: print '%s rows modified.' % (result) else: print 'Error!' else: print 'Cancel.' def command_start(self): sql_list = 'SELECT * FROM appdata;' list = self.con.execute(sql_list) for id, domain, dir in list: pidfile = os.path.join(self.piddir, domain) + '.pid' logfile = os.path.join(self.logdir, domain) + '.log' if os.path.exists(pidfile) and self.checkPID(pidfile): print 'Running: %s' % domain else: cmd = "nohup %s --port=%u '%s' > '%s' 2>&1 &" % (self.appserver, self.port+id, dir, logfile) subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE).wait() pid = self.getPID(pattern = (' --port=%u ' % (self.port+id))) if 0 < pid: open(pidfile, 'w').write(str(pid)) print 'Starting: %s(%u)' % (domain, pid) else: print 'Error: %s' % domain def command_stop(self): list = os.listdir(self.piddir) for item in list: pidfile = os.path.join(self.piddir, item) if os.path.exists(pidfile): pid = open(pidfile, 'r').read() cmd = 'kill %s' % pid ret = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE).wait() os.remove(pidfile) if ret==0: print 'Stopping: %s' % item else: print 'Error: %s' % item def command_fstop(self): cmd = "ps x | grep '%s' | grep -v grep | awk '{print $1}'" % self.appserver list = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE).stdout for pid in list.readlines(): if 0 < len(pid): cmd = 'kill %s' % pid ret = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE).wait() if ret==0: print 'Stopping: %s' % pid else: print 'Error: %s' % pid def command_restart(self): print '[Stopping]' self.command_stop() time.sleep(0.2) print '[Starting]' self.command_start() def command_status(self): sql_list = 'SELECT * FROM appdata;' list = self.con.execute(sql_list) for id, domain, dir in list: pidfile = os.path.join(self.piddir, domain) + '.pid' if os.path.exists(pidfile) and self.checkPID(pidfile): print 'Running: %s' % domain else: print 'Stopped: %s' % domain def command_make(self): sql_list = 'SELECT * FROM appdata;' list = self.con.execute(sql_list) conf = '' for id, domain, dir in list: conf += """ <VirtualHost *:80> DocumentRoot /tmp ServerName %s <Proxy *> Order deny,allow Allow from all </Proxy> ProxyPass / http://127.0.0.1:%u/ ProxyPassReverse / http://127.0.0.1:%u/ </VirtualHost> """ % (domain, self.port+id, self.port+id) conf = re.sub('\t\t\t\t', '', conf) print 'Writing %s...' % os.path.basename(self.apacheconf) open(self.apacheconf, 'w').write(conf) print 'Reload httpd...' cmd = 'service httpd reload' subprocess.Popen(cmd, shell =True, stdout = subprocess.PIPE, stderr = subprocess.PIPE).wait() print 'Finish' def command_help(self): help = """ usage: %s subcommand subcommand(config): list show domain list add add domain %s add [domain dir] del delete domain %s del [id] mod modify domain %s mod [id domain dir] make make apache config subcommand(run): start start server stop stop server fstop stop server (force) restart restart server subcommand(help): help show this message """ % (sys.argv[0], sys.argv[0], sys.argv[0], sys.argv[0]) help = re.sub('\t\t\t', '', help).strip() print help def getPID(self, pattern): cmd = "ps x | sed 's/^ *//' | grep '%s' | grep '%s' | grep -v grep | awk '{print $1}'" % (self.appserver, pattern) result = subprocess.Popen(cmd, shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE).stdout.readline() if 0 < len(result): return int(result) return 0 def checkPID(self, pidfile): pid = open(pidfile, 'r').read() return 0 < self.getPID(pattern = ('^%s ' % pid)) if __name__ == '__main__': if pwd.getpwuid(os.getuid())[0]=='root': gae = GAEProxy() sub = ['list', 'add', 'del', 'mod', 'start', 'stop', 'fstop', 'restart', 'status', 'make', 'help'] if 1 < len(sys.argv) and sys.argv[1].lower() in sub: exec 'gae.command_%s()' % sys.argv[1].lower() else: gae.command_help() else: print 'Permission denied'
dev_appserver.patch
--- google/appengine/tools/dev_appserver.py.backup 2010-01-16 03:52:53.000000000 +0900
+++ google/appengine/tools/dev_appserver.py 2010-01-16 03:57:35.000000000 +0900
@@ -697,6 +697,18 @@
infile.seek(0)
env['CONTENT_LENGTH'] = str(len(new_data))
+ if ('HTTP_X_FORWARDED_HOST' in env):
+ env['HTTP_HOST'] = env['HTTP_X_FORWARDED_HOST']
+ env.pop('HTTP_X_FORWARDED_HOST')
+
+ if ('HTTP_X_FORWARDED_FOR' in env):
+ env['REMOTE_ADDR'] = env['HTTP_X_FORWARDED_FOR']
+ env.pop('HTTP_X_FORWARDED_FOR')
+
+ if ('HTTP_X_FORWARDED_SERVER' in env):
+ env['SERVER_NAME'] = env['HTTP_X_FORWARDED_SERVER']
+ env.pop('HTTP_X_FORWARDED_SERVER')
+
return env
- 4 http://www.google.co.jp/search?hl=ja&source=hp&q=リポジトリルート+変更&lr=&aq=f&oq=
- 4 http://www.google.com/search?hl=ja&lr=lang_ja&ie=UTF-8&oe=UTF-8&q=SSL+Name+Based+Virtual+Host&num=50
- 3 http://reader.livedoor.com/reader/
- 3 http://www.google.co.jp/search?hl=ja&source=hp&q=Unable+to+initialize+module+Module+compiled&btnG=Google+検索&lr=lang_ja&aq=f&oq=
- 3 http://www.google.co.jp/search?q=Init:+You+should+not+use+name-based+virtual+hosts+in+conjunction+with+SSL!!&lr=lang_ja&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&client=firefox-a
- 3 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rlz=1T4GGLL_jaJP354JP355&q=Flex php+insert+配列
- 2 http://d.hatena.ne.jp/johzan/20100117/1263717830
- 2 http://pipes.yahoo.com/pipes/pipe.info?_id=02db597254ec68550537866a2fca2ce6
- 2 http://search.yahoo.co.jp/search?p=FlexでWaveファイルを再生するライブラリを作ってみた&search.x=1&fr=top_ga1_
- 2 http://www.google.co.jp/hws/search?hl=ja&q=535+5.7.0+Error:+authentication+failed:+authentication+failure&client=fenrir&adsafe=off&safe=off&lr=lang_ja
