DAAPクライアントに応答してみる
mDNSは、Zeroconf.pyでできるようになったので、これを入れた簡単なWebサーバーデーモンにしてみる。yasusiiさめが導入ドキュメントを書いてくれているtwistedを使いたいところだけど、フットプリントの削減と、勉強をかねて、標準モジュールのみでやってみることに。
yasusiiさめのtwistedドキュメントは、(2)で終わりだろうか。まだ先もやってほしいな。
サーバーの終了時には、Zeroconf.unregisterService()とZeroconf.close()をしたいのだけど、終了処理はどう書いたらいいのかな。SIGTERMされたときに実行できればいいのだけど。try/finallyでやってみたけどダメ。シグナルハンドラを使って処理してみた。とりあえずうまく動いているけど、複数リクエストで複数プロセスになっている場合、親をKILLしても子プロセスが終われないことがあるようだ。なんでだろ? これは棚上げ。
Webサーバーとしては動作するようになったので、DAAPサーバーへの改造をはじめる。
プロトコルはHTTPと似ているのだけど、ヘッダ情報などは規格外になる。今後いろいろ追加が必要になりそうなので、SimpleHTTPServerを改造して、DaapHTTPServerを作る。
クラス名や関数名などを書き換え。
応答用ヘッダの一部はBasicHTTPServerで作られていたので、それを中でオーバーライド。
content-typeは"application/x-dmap-tagged"を固定で返せばいいので、その部分は潰してハードコード。
urlparseがdaap://を認識してくれなかったので、リクエストに応答できない。標準ライブラリをいじるのは気が引けるので、daap_urlparseを作ってimportする。
セッションはDAAPではHTTP1.1でやるのが普通のようだけど、1.0でも通信はできるようだ。BasicHTTPServerって、1.1には対応できてたかな?
変更部分は、まだその程度。
これでヘッダ部分がまともに応答できるようになった。
#!/usr/bin/python #-*- coding: utf-8 -*- EXECNAME = "daapd.py" VERSION = "a0.0" import Zeroconf import socket import BaseHTTPServer import DaapHTTPServer import signal, os hostname = socket.gethostname() hostaddr = socket.gethostbyname(socket.gethostname()) daapZConfType = "_daap._tcp.local." daapZConfHost = hostname + "." + daapZConfType daapPort = 3689 r = Zeroconf.Zeroconf() info = Zeroconf.ServiceInfo( daapZConfType, daapZConfHost, socket.inet_aton(hostaddr), daapPort, 0, 0, '') r.registerService(info) print " daapd.py: regist mDNS." def handler(signum, frame): print 'Signal handler called with signal', signum print " daapd.py: remove mDNS." r.unregisterService(info) r.close() def daapServer(addr, HandlerClass = DaapHTTPServer.DaapHTTPRequestHandler, ServerClass = BaseHTTPServer.HTTPServer): HandlerClass.protocol_version = 'HTTP/1.0' httpd = ServerClass(addr, HandlerClass) sa = httpd.socket.getsockname() print "Serving HTTP on %s:%d" % (sa[0], sa[1]) httpd.serve_forever() if __name__ == '__main__': signal.signal(signal.SIGTERM, handler) addr = (hostaddr, daapPort) try: print " daapd.py: Server daemonized." daapServer(addr) except: print " daapd.py: Can't start Server."
クライアントからは、まず/server-infoと/content-codesに応答できれば良い。これらは、静的に送れれば通信はできるので、http://www.deleet.de/projekte/daap/からサンプルデータを落としてきて使う。
iTunesからは、「この共有された音楽ライブラリ[無題のサーバー]は、このバージョンのiTunesと互換性がありません。」と言われるので、とりあえずデータの中身は送信できているようだ。
中身をhexeditで見てもよくわからない。通信仕様を見ないとダメだね。
とりあえず、DAAPクライアントライブラリのpython-daapを使って、さくっと応答されている内容を見る。
>>> import daap >>> d = daap.DAAPClient() >>> d.connect('192.168.0.82', 3689) >>> p = d.request('/server-info') >>> p.printTree() dmap.serverinforesponse (msrv) c None dmap.status (mstt) i 200 dmap.protocolversion (mpro) v 1.0 daap.protocolversion (apro) v 1.0 dmap.itemname (minm) s ray’s music dmap.loginrequired (mslr) b 0 dmap.timeoutinterval (mstm) i 1800 dmap.supportsautologout (msal) b 0 dmap.authenticationmethod (msau) b 2 dmap.supportsupdate (msup) b 0 dmap.supportspersistentids (mspi) b 0 dmap.supportsextensions (msex) b 0 dmap.supportsbrowse (msbr) b 0 dmap.supportsquery (msqy) b 0 dmap.supportsindex (msix) b 0 dmap.supportsresolve (msrs) b 0
なるほど、こんな風になっているのね。4文字の名前とフラッグとデータみたいな作りなのか。
どうも、もらってきたデータでは、返しているバージョン情報が古すぎるようだ。たしか、iTunesは途中でv2以降にしか応答しなくなったのではなかったかな。
ついでに、iTunesとmt-daapdの応答も見ておく。
iTunes 6.0.5.20 の場合
dmap.serverinforesponse (msrv) c None dmap.status (mstt) i 200 dmap.protocolversion (mpro) v 2.2 daap.protocolversion (apro) v 3.2 None (aeSV) None dmap.itemname (minm) s kinneko の音楽 dmap.loginrequired (mslr) b 1 dmap.timeoutinterval (mstm) i 1800 dmap.supportsautologout (msal) b 1 None (msas) None dmap.supportsupdate (msup) b 1 dmap.supportspersistentids (mspi) b 1 dmap.supportsextensions (msex) b 1 dmap.supportsbrowse (msbr) b 1 dmap.supportsquery (msqy) b 1 dmap.supportsindex (msix) b 1 dmap.supportsresolve (msrs) b 1 dmap.databasescount (msdc) i 1
mt-daapdの場合
dmap.serverinforesponse (msrv) c None dmap.status (mstt) i 200 dmap.protocolversion (mpro) v 2.0 daap.protocolversion (apro) v 3.0 dmap.itemname (minm) s GLANTANK Music dmap.authenticationmethod (msau) b 0 dmap.timeoutinterval (mstm) i 1800 dmap.supportsextensions (msex) b 0 dmap.supportsindex (msix) b 0 dmap.supportsbrowse (msbr) b 0 dmap.supportsquery (msqy) b 0 dmap.supportsupdate (msup) b 0 dmap.databasescount (msdc) i 1
どうも、必須の情報とそうでない情報があるようだ。
つづく。