2010-10-10

開発中の Google App Engine 用アプリに iPhone/iPad から接続する

Mac 版 Google App Engine SDK の GoogleAppEngineLauncher.app から開発中のアプリを起動した場合、localhost 以外からは接続できない。Mac や PC から(もちろんブラウザで)使うためのアプリであれば問題にはならない。しかし、開発しているアプリが iPhone や iPad をターゲットとしているものだったら?

開発中の GAE アプリに iOS デバイスから接続したい場合、上記の問題への対処方法は 2 つある。

  1. iPhone シミュレータを使う
  2. 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 なんかを作りたくない、というなら以下の場所に実体がある。

/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/dev_appserver.py

さて、この 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」にオプションを記述すれば良い。

GoogleAppEngineLauncher.app のアプリ設定編集パネル

ターミナルから 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.pydev_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)

つまり、TCPServerserver_bind 内で呼び出されている self.socket.bind(self.server_address) は、_socket モジュールからインポートされた socket (インポート直後に _realsocket にリネームされている) の bind メソッドだと言うことになる。で、ここから先は C で記述されているモジュールになる。

C のソースは簡単にしか見ていないが、Modules/socketmodule.csock_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 としてもアクセスできる。

参考文献

UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI
W.リチャード スティーヴンス
ピアソンエデュケーション ( 2000-04 )
ISBN: 9784894712058
おすすめ度:アマゾンおすすめ度

関連リンク

関連記事

0 件のコメント:

コメントを投稿