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
http://code.google.com/intl/ja/appengine/kb/general.html#sdkフラグを使用して上書きできますが、このようにすると SDK のセキュリティが強化されず、脆弱性が生じることがあるため、推奨しません。
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
GAE開発サーバ起動
使用ポートは、8100+ID。
service gaeproxy start
GAE開発サーバ停止
service gaeproxy stop
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
ハマった所
ソース
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