Mac 版 Google App Engine SDK の GoogleAppEngineLauncher.app から開発中のアプリを起動した場合、localhost 以外からは接続できない。Mac や PC から(もちろんブラウザで)使うためのアプリであれば問題にはならない。しかし、開発しているアプリが iPhone や iPad をターゲットとしているものだったら?
開発中の GAE アプリに iOS デバイスから接続したい場合、上記の問題への対処方法は 2 つある。
- iPhone シミュレータを使う
- GAE アプリをオプションつき(サーバのアドレスを指定する)で起動する
1. については特に言うことはない。ただ iPhone シミュレータを起動して Mac の Safari で使うのと同じアドレスを開くだけ。
iPhone や iPad の実機を使いたい場合には 2. の方法になる。以降では、この方法について説明する。
GAE アプリをオプションつきで起動する
dev_appserver.py を直接起動する
dev_appserver.py は、その名の通り開発用のアプリケーションサーバだ。引数には起動したい GAE アプリのディレクトリ(app.yaml が置かれているディレクトリ)を指定する。
GAE Launcher の初回起動時に「シンボリックリンクを張るか?」という問いに「Yes」と答えていれば、/usr/local/bin に(シボリックリンクが)置かれている。「No」と答えていれば、GAE Launcher のメニューから「GoogleAppEngineLauncher」>「Make Symlinks...」を実行すれば後からでもシンボリックリンクを張ることができる。/usr/local なんかを作りたくない、というなら以下の場所に実体がある。
さて、この dev_appserver.py には、アプリサーバのアドレスやポート番号を指定するオプションがある。このアドレスを指定するオプション(--address
)に Mac の LAN 上で有効な IP アドレスを指定することで、localhost (IP アドレスで書くと 127.0.0.1) 以外のクライアントからのアクセスできるようになる。無線 LAN でアクセスできれば、iPhone や iPad からでも OK。
[imac] mnbi% /usr/local/bin/dev_appserver.py --port=8080 --address=192.168.0.1 /somewhere/helloworld
iPhone や iPad から名前解決できるのであれば(LAN 内にプライベートな DNS サーバが稼動している等)、アドレスとしてホスト名を指定しても良い。
[imac] mnbi% /usr/local/bin/dev_appserver.py --port=8080 --address=imac.private /somewhere/helloworld
Mac に割り当てられている IP アドレスがわからない(調べるのがメンドウ)、さらには名前解決もできないというなら、「ワイルドカード」アドレスを指定することもできる。
[imac] mnbi% /usr/local/bin/dev_appserver.py --port=8080 --address=0.0.0.0 /somewhere/helloworld
Python のバージョンが気になるなら、明示的にインタープリタを指定して dev_appserver.py を起動すれば良い。まとめると、iOS デバイス(実機)で接続したい場合の GAE アプリの起動方法は以下のようになる。
[imac] mnbi% python2.5 /usr/local/bin/dev_appserver.py --port=8080 --address=0.0.0.0 /somewhere/helloworld
ちなみに、dev_appserver.py にはこの他にもデバッグ用のロギングを有効にする --debug
オプションもある。これはデフォルトでは無効になっているが、有効にすれば自分で書いたアプリ内のデバッグログが表示されるとともに、リクエストに関する詳細な情報が表示されるようになる。
GAE Launher でアプリ起動時のオプションを指定する
実は GAE Launcher でも、上述のオプションを指定することができる。そのためには Launcher でオプションを指定したいアプリを選び、メニューから「Edit」>「Application Settings...」を実行する。以下のパネルが現れるので、下部にある「Lauche Settings」の「Extra Flags」にオプションを記述すれば良い。
ターミナルから dev_appserver.py を直接起動するより、こちらの方が簡単か。
アドレス指定の謎
ところで、dev_appserver.py に --address
オプション経由でわたされたアドレスはどう使われるのだろうか? もっとはっきり言えば、サーバのアドレスを指定することで何が変わって、LAN につながった他の機器からの接続を受け付けるようになるのか。疑問に思ったので、dev_appserver.py からライブラリ(Python 2.5 用)のソースをたどってみた。その過程でわかったことだが、--address
オプションを指定しない場合、デフォルト値として "localhost"
が使われる。
dev_appserver.py (dev_appserver_main.py が実行される) google/appengine/tools/dev_appengine_main.py ParseArguments(argv): [...snip...] if option in ('-a', '--address'): option_dict['ARG_ADRESS'] = value [...snip...] main(argv): args, option_dict = ParseArguments(argv) [...snip...] serve_address = option_dict[ARG_ADDRESS] [...snip...] http_server = dev_appserver.CreateServer(..., serve_address,...) [...snip...] google/appengine/tools/dev_appserver.py CreateServer(..., serve_address,...): [...snip...] server = HTTPServerWithScheduler((serve_address, port), handler_class) [...snip...] class HTTPServerWithScheduler(BaseHTTPServer.HTTPServer): [...snip...] def __init__(self, server_address, ...): [...snip...] BaseHTTPServer.HTTPServer.__init__(self, server_address, request_handler_class) [...snip...]
ざっと流れをたどると、dev_appserver.py にわたされた --address
オプションの値は、dev_appserver_main.py、dev_appserver.py (最初のとは別物) を経て、ビルトインライブラリにわたる。
-- built-in libs -- BaseHTTPServer.py class HTTPServer(SocketServer.TCPServer): (no __init__ definition) SocketServer.py class TCPServer(BaseServer): [...snip...] def __init__(self, server_address, RequestHandlerClass): (BaseServer.__init__ で self.server_address に代入される) [...snip...] self.socket = socket.socket(self.address_family, self.socket_type) self.server_bind() [...snip...] def server_bind(self): [...snip...] self.socket.bind(self.server_address) [...snip...]
ビルトインライブラリに入ってからは、BaseHTTPServer.HTTPServer
の親クラスである SocketServer.TCPServer
にわたり、そこで socket
オブジェクトの bind
メソッドにわたる。
-- built-in libs -- socket.py [...snip...] import _socket from _socket import * [...snip...] _realsocket = socket [...snip...] _socketmethods = ( 'bind', [...snip...]) [...snip...] class _socketobject(object): [...snip...] def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, _sock=None): if _sock is None: _sock = _realsocket(family, type, proto) self._sock = _sock [...snip...] _s = ("def %s(self, *args): return self._sock.%s(*args)\n\n" "%s.__doc__ = _realsocket.%s.__doc__\n") for _m in _socketmethods: exec _s % (_m, _m, _m, _m) [...snip...] socket = SocketType = _socketobject
_socketobject
の __init__
の定義中の exec
によって bind
の定義として以下のコードが実行される。
def bind(self, *args): return self._sock.bind(*args)
つまり、TCPServer
の server_bind
内で呼び出されている self.socket.bind(self.server_address)
は、_socket
モジュールからインポートされた socket (インポート直後に _realsocket
にリネームされている) の bind
メソッドだと言うことになる。で、ここから先は C で記述されているモジュールになる。
C のソースは簡単にしか見ていないが、Modules/socketmodule.c の sock_bind
がどうやらそれらしい。この中では bind
(2) が呼ばれている。最初にわたされたサーバのアドレスは最終的にこのシステムコールにわたるわけだ。
以上をまとめると、デフォルトでは "localhost"
が、--address
を指定した場合にはその値が bind
(2) にわたる、ということだ。
(「UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI」p.89 より)
プロセスは、ソケットに特定の IP アドレスをbind
することができる。bind
する IP アドレスは、そのホストのインタフェースに属するものでなければならない。[...snip...] TCP サーバでは、この操作により、そのソケットではバインドした IP アドレスに向けたクライアントからのコネクション要求のみを受信するようになる。
要は、localhost (127.0.0.1)と LAN につながっているネットワークインタフェースとは別物だってこと。IP アドレスが違うんだから当然と言えば当然か。
bind
(2) はわたされたアドレスをサーバ用のソケットに束縛するため、デフォルトでは localhost (127.0.0.1)だけがサーバ用のアドレスとなるわけだ。Mac の LAN につながっているアドレスの方は無視される。だから、LAN 側からのアクセスができないのだ。
一方、明示的に LAN につながっているアドレスを指定すれば、そちらをサーバ用のアドレスとして束縛するため、きちんと LAN 側からアクセスできる。逆にこのときは Mac からであっても localhost としてアクセスできなくなる。
(「UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI」p.111 より)
ワイルドカードアドレスをバインドすることによって、マルチホームシステムでは、どのローカルインターフェースにあてたコネクションでも受け付けることをシステムに指示することになる。
また、アドレスとしてワイルドカード(0.0.0.0)を指定すれば、LAN 側からも、localhost としてもアクセスできる。
参考文献
関連リンク
- Google App Engineをlocalhost以外から使う方法 (Reinvention of the Wheel; 追記として
--address
を使う方法が書かれている) - GAE/Jの開発サーバをlocalhost以外からアクセス (Android Zaurusの日記; Java版用の内容だけど
--address=0.0.0.0
は Python 版でも使用可) - bind(2) (FreeBSD Man Pages より)
0 件のコメント:
コメントを投稿