address Logo

WebDAV と OSX クライアント

目次

2009/01/30 追加

OSX 10.5 クライアント

2009/01/30

大きなデータをアップロードできない!

さて Lua による WebDAV スクリプトもほぼ完成しつつあるので、OSX クライアントでマウントして、クライアントのデータのアップロードを試みた。この場合 WebDAV では PUT メソッドが使われる。大きな画像データを送ろうとしたが、うまく行かない。
ログを見て驚いた。その時の OSX からのリクエストヘッダを示す。

PUT /~alice/dav/P7131744.JPG HTTP/1.1
User-Agent: WebDAVFS/1.7 (01708000) Darwin/9.5.0 (i386)
Accept: */*
X-Expected-Entity-Length: 6063177
If: (<0.77059236426893>)
Connection: close
Host: io
Transfer-Encoding: Chunked

つまり転送データのサイズが

    X-Expected-Entity-Length

で示されているのだ。こんなの見た事無い! もちろん僕のサーパがサポートしている訳は無い。
Google で検索してみると近頃結構このテーマが話題になっている。どれも OSX 10.5 に関する記事である。

OSX はデータを転送する前にサイズが 0 のファイルを作る。つまり以下のリクエストが先に実行される。

PUT /~alice/dav/P7131744.JPG HTTP/1.1
User-Agent: WebDAVFS/1.7 (01708000) Darwin/9.5.0 (i386)
Accept: */*
Content-Length: 0
Connection: keep-alive
Host: io

そして成功すれば次の応答を返す。

HTTP/1.1 201 Created
Content-Length: 0
Location: http://io/~alice/dav/P7131744.JPG
MS-Author-Via: DAV

まあ、これは妥当な手順である。大きなファイルを送ってしまってからアクセス権が無いと言われるよりも、アクセス権を確認して送る方が賢明と言う訳だ。

次に問題の

    X-Expected-Entity-Length

の話に入る訳だが、その前に chunk 形式を解説しておかなくてはならない。

chunk 形式

rfc2616 に説明されている chunk 形式のシンタックスは(多少の省略はあるが)次の通りである。

       Chunked-Body   = *chunk
                        last-chunk
                        trailer
                        CRLF
       chunk          = chunk-size [ chunk-extension ] CRLF
                        chunk-data CRLF
       chunk-size     = 1*HEX
       last-chunk     = 1*("0") [ chunk-extension ] CRLF
       trailer        = *(entity-header CRLF)

これは具体的には何を意味するか? 次のサイトが分かりやすい例を示している。

ここには次の例が載っている。 HTTP/1.1 で始まっているのでこの例はサーバー側の応答である。

    HTTP/1.1 200 OK
    Date: Fri, 31 Dec 1999 23:59:59 GMT
    Content-Type: text/plain
    Transfer-Encoding: chunked

    1a; ignore-stuff-here
    abcdefghijklmnopqrstuvwxyz
    10
    1234567890abcdef
    0
    some-footer: some-value
    another-footer: another-value
    [blank line here]

ここに [blank line here] は HTTP ヘッダ部と HTTP ボディ部の仕切りであるから、転送目的のデータ

    abcdefghijklmnopqrstuvwxyz1234567890abcdef

は全て HTTP ヘッダ部に置かれていることになる。

chunk が HTTP ヘッダ部の一部である事は chunk の仕様を見ても分かる。改行ポイントは CRLF になっている。

この例は次と等価である。

    HTTP/1.1 200 OK
    Date: Fri, 31 Dec 1999 23:59:59 GMT
    Content-Type: text/plain
    Content-Length: 42
    some-footer: some-value
    another-footer: another-value

    abcdefghijklmnopqrstuvwxyz1234567890abcdef

そしてもちろんこれは

    HTTP/1.1 200 OK
    Date: Fri, 31 Dec 1999 23:59:59 GMT
    Content-Type: text/plain
    some-footer: some-value
    another-footer: another-value
    Transfer-Encoding: chunked

    2a
    abcdefghijklmnopqrstuvwxyz1234567890abcdef
    0

と同じである。

現在の Pegasus は I/O 回りに関しては Plan 9 標準の httpd を幾分改善したに過ぎない。標準の httpd は HEADGETOPTIONS ぐらいしかサポートされていない。POST メソッドすらサポートされていないのである。つまり大きなデータがクライアントからやってくる事は想定されていない。Pegasus で僕は POST を含めて一通りの機能をサポートしたのであるが、僕はその時についでに chunk 形式の取り込みもサポートしたのか確認の必要がある。(記憶がないから多分していない)

ところで chunk の末尾に trailer を許す RFC の仕様は問題ではないか? この部分は chunk の前に移動可能な部分だと思う。
chunk data の後に HTTP ヘッダの一部が現れるかも知れないと言う事は嫌な気分になる。chunk の処理に慎重にならざるを得ない。仮に次のように考えてみたら良い。Chunked-Transfer-Encoding なるものが存在しなければ、サーバはクライアントからの送信データの body 部を受け取る前に全ての HTTP ヘッダが手に入る事が保証される。そして、body 部の受け取りは後回しに出来たのである。ところが Chunked-Transfer-Encoding の仕様がサーバにこの態度を許さなくしている!

もちろん Chunked-Transfer-Encoding には良い面が沢山ある。例えばデータサイズを前もって知らなくても、持続的接続による HTTP 通信が可能になる。trailer さえあんな所に許さなければもっと使いやすかったのではないかと思えるが、僕の勘違いか?

trailer の中に含まれる entity-header とは何だかよく分からない。
次の記事が結構親切であるがそれでもよく分からない。

X-Expected-Entity-Length

それでは本題に戻る。"X-Expected-Entity-Length" は Apple による拡張ヘッダで、この言葉の意味から推測するに、chunk をまとめた時に期待されるサイズであろう。もちろんそのような情報が(添えられる場合には)添えた方が親切である。添えられていない場合には、データを受け取る時に、それをメモリに保存すべきか、それとも HDD に保存すべきかの区別が付かない。Apple は、この拡張ヘッダによる応答も規則化したはずではあるが、その情報は得られない。

サーバは、自分の知らないヘッダを無視することができる。もちろん Apple は、サーバが "X-Expected-Entity-Length" を知らなくても大丈夫なように設計したはずである。問題なのは

    Transfer-Encoding: Chunked

だ。OSX 10.4 までは chunk 形式によるデータのアップロードは行われて居なかった。OSX 10.5 から chunk 形式が使われるようになったのだ。

さて僕のサーバが OSX 10.5 で旨く行かない原因を探って行くと、 chunk 形式のアップロードでバグっていた。 標準 httpd で使用されているライブラリでは chunk 形式のデータの取り込みも一応はサポートされていたのだが、それを Pegasus で使う時にデバッグされていなかったのだ。その問題が OSX 10.5 で露に出て来たのである。結局コードを2カ所(2行)追加して解決。「OSX 10.5 で webdav が働かない!」と言うあちこちからの悲鳴は、僕のサーバと良く似たサーバが沢山あると言う事であろう。

さて OSX 10.5 付属の Apache はこの点でどうであろうか? 僕の実験した限りでは OK である。詳しくは
http://ar.aichi-u.ac.jp/osx/apache.html
に解説されている。

遅さの秘密

OSX クライアントから WebDAV サーバーを使うと遅いのが気になる。特に僕の家庭で使っているサーバは非力であるからなおさらである。また WebDAV をインターネットレベルで使うと、通信速度の問題が絡んでいるので、もっと酷いことになると思う。なぜ遅いのか? いくつかの原因がある。

無駄なリクエストに関しては既に OSX 10.4 クライアントの記事で解説したが、今回はさらに次の問題を指摘する。
PROPFIND で得た情報を十分に活用していない。具体的に言えば、Depth:1 の後に、コレクションの個々の要素に対して PROPFINDDepth:0 で再確認している。つまり Depth:1 の情報は名前しか活用していないのである。これはコレクションの要素数が多い場合には処理速度を大きく落とす。

最後の問題に関しては、OSX クライアントはディレクトリの COPY や MOVE に対して、GET メソッドと PUT メソッドを繰り返している。その結果、ディレクトリの中に 30 個のファイルがあれば、30 個のファイルのダウンロードとアップロードの繰り返しが発生するのだ。OSX クライアントが表示する、ユーザフレンドリなメッセージ(「あと XX 個のファイルを...しています」)はこの非効率な処理の賜物である。

Resource Fork の問題

OSX クライアントが作成するリソースフォークも処理を大きく落とす。OSX クライアントはサーバー上にファイル毎に 4096B のリソースフォークを作りそのデータをアップロードする。しかし僕にはこのデータが僕にとって役に立っているようには思えない。作成を止める方法はないか? どうやら NO である。

なお Resource fork に関する詳しい解説が Wikipedia にある。
http://en.wikipedia.org/wiki/Resource_fork

".DS_Store" の問題

".DS_Store" ファイルの作成はコマンド

    defaults write com.apple.desktopservices DSDontWriteNetworkStores true

で止めることができる。

奇怪な OSX クライアントからのリクエスト

2009/01/31

以下に述べる現象は OSX 10.5 以前から観測されている。

今回も改めて PROPFIND のリクエストを調べると、以下のようなものが見つかる。

PROPFIND /dav/.metadata_never_index HTTP/1.1
PROPFIND /dav/Backups.backupdb HTTP/1.1
PROPFIND /dav/.Spotlight-V100 HTTP/1.1
PROPFIND /dav/mach_kernel HTTP/1.1

これらについては次の URL の中で触れられている。

しかし結論から言えば、良く分からないらしい。

驚くのは

    PROPFIND /dav/Backups.backupdb HTTP/1.1

    PROPFIND /dav/mach_kernel HTTP/1.1

が観測される事である。これらはルートディレクトリに置かれているファイルである。これらの情報が何に使われるのか全く理解できない。そもそもルートディレクトリに WebDAV からアクセスできるセキュリティに無頓着なサーバーが存在すること自体がおかしい。まさか Apple のサーバーは...

バックアップファイルの置き場所

2009/02/02

今回気がついた事だが OSX 10.5 は、「テキストエディト」でファイル "abc" を編集していると、

    PUT /dav/.TemporaryItems/folders.502/TemporaryItems/%EF%BC%88....%EF%BC%89/abc HTTP/1.1

のようなリクエストを出す。ここに "...." は、あまりにも長ったらしいので、途中を省略した事を意味している。
この

    %EF%BC%88....%EF%BC%89

の部分をデコードすると

    (テキストエディット で保存中の書類)

である。この中に "abc" が出来たのは(クライアントから見て) /dev/abc を編集していたからだろう。
OSX はバックアップファイルをこんな所に作るんだねー。

スピードを上げるには

2009/02/17

「Finder 表示オプション」をシンプルにする事。OSX 10.5 のアイコンは、ファイルをダウンロードをしなくては表示できない。そこで...

finder-option

OSX 10.5 の 「Finder 表示オプション」

少なくとも、これでファイル一覧の表示にイライラするのはかなり軽減される。

OSX 10.4 クライアント

石橋を叩いて渡る OSX

2007/02/06

WebDAV における Mac/OSX クライアントの動作は遅い。その原因を探るために Mac/OSX 10.4.8 のクライアントにおけるアップロードのプロセスを調べてみた。サーバは筆者の自宅のもので、アップロードで実験したファイルは 03m3180.pdf である。
サーバの URI を

    http://pc/pcdav

としているので、基本的には

    PUT /pcdav/03m3180.pdf

で済むのであるが、OSX は実に多くの事を行っている。合理的な動作もあれば、筆者にとっては理解しがたい動作もある。(筆者のサーバに何か欠陥があるのかも知れない...)

以下にアップロードが完了するまでのクライアントとサーバーのやりとりを載せる。長いので小分けにして解説しているが、連続した動作である。

PROPFIND /pcdav/ HTTP/1.1

PROPFIND /pcdav/03m3180.pdf HTTP/1.1

# Reply: 404 Not Found

まずは PROPFIND で様子を見ている。これは当然であろう。既に存在するのであればユーザにそのことを知らせ、止めるか続けるかの選択をさせないとならない。"#" の部分はサーバーの応答である。

しかし「存在しない」と言っているのに再度 PROPFIND を試す。答えは当然「存在しない」である。

PROPFIND /pcdav/03m3180.pdf HTTP/1.1

# Reply: 404 Not Found

PUT /pcdav/03m3180.pdf HTTP/1.1
Content-Length: 0

# Reply: 201 Created

OSX は慎重である。ファイルの本体をいきなり作らずに、まずは空のファィルを作り、作成可能である事を確認するのである。(以下 HTTP/1.1 を省略する)

その後、

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

リソースフォークが存在しない事を確認し(3回も!)、その後に空のリソースフォークを作り

PUT /pcdav/._03m3180.pdf
Content-Length: 0

# Reply: 201 Created

作成に成功したら Lock を掛けて

LOCK /pcdav/._03m3180.pdf
Content-Length: 229

PUT /pcdav/._03m3180.pdf
Content-Length: 82

# Reply: 204 No Content

内容を書き込む。そして PROPFIND で確認し

PROPFIND /pcdav/._03m3180.pdf

UNLOCK /pcdav/._03m3180.pdf

Lock を外す。そしてまた Lock を掛けて

LOCK /pcdav/._03m3180.pdf

GET /pcdav/._03m3180.pdf

UNLOCK /pcdav/._03m3180.pdf

中身を確認する。何故 PROPFIND の後に続けて GET ではないのか分からない。そして DELETE だ。

DELETE /pcdav/._03m3180.pdf

# Reply: 204 No Content

これでは何のためにリソースフォークを作ったのか分からない!

こうした一連の動作の後に

LOCK /pcdav/03m3180.pdf

GET /pcdav/03m3180.pdf

PUT /pcdav/03m3180.pdf
Content-Length: 410931

# Reply: 204 No Content

PROPFIND /pcdav/03m3180.pdf

UNLOCK /pcdav/03m3180.pd

# Reply: 204 No Content

ようやくアップロードするファイルの内容がサーバーに届けられる。ここでもまた PUT の前に GET を行い、03m2180 が空ファイルである事を確認して PUT が行われる。そして PUT に成功すれば PROPFIND で再確認する。

不可思議なのは次の一連の PROPFIND である。"._03m3180.pdf" を消した事を忘れたのか?!

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

PROPFIND /pcdav/._03m3180.pdf

# Reply: 404 Not Found

8 回も無駄な PROPFIND を実行して、ようやく意味のある PROPFIND が実行される。

PROPFIND /pcdav/
Depth: 1

iDisk で実験

2007/02/07

Mac/OSX クライアントの異常な振る舞いの背景には、筆者のサーバに何らかの欠陥が含まれている可能性を否定できないので、Apple の純正サーバを試してみた。と言っても買える訳ではないので、iDisk の「お試し」を利用した。
サーバのログは見れないので、代わりに tcpdump を利用した。tcpdump は OSX に標準装備されている。使い方は

    tcpdump	-nX '(host idisk.mac.com) and (port 80)'

である。
ここでも 03m3180.pdf をアップロードする。
tcpdump のログの先頭部分は次のようになっている。

17:17:48.829214 IP 192.168.1.101.50772 > 17.250.248.77.80: P 1940885603:1940886128(525) ack 3540220645 win 65535 <nop,nop,timestamp 785932766 767730340>
        0x0000:  4500 0241 f312 4000 4006 794f c0a8 0165  E..A..@.@.yO...e
        0x0010:  11fa f84d c654 0050 73af 9063 d303 7ae5  ...M.T.Ps..c..z.
        0x0020:  8018 ffff ce88 0000 0101 080a 2ed8 61de  ..............a.
        0x0030:  2dc2 a2a4 5052 4f50 4649 4e44 202f 6172  -...PROPFIND./ar
        0x0040:  6973 6177 6132 2f20 4854 5450 2f31 2e31  isawa2/.HTTP/1.1
        0x0050:  0d0a                                     ..
17:17:48.829293 IP 192.168.1.101.50772 > 17.250.248.77.80: P 525:686(161) ack 1 win 65535 <nop,nop,timestamp 785932766 767730340>
        0x0000:  4500 00d5 f313 4000 4006 7aba c0a8 0165  E.....@.@.z....e
        0x0010:  11fa f84d c654 0050 73af 9270 d303 7ae5  ...M.T.Ps..p..z.
        0x0020:  8018 ffff cd1c 0000 0101 080a 2ed8 61de  ..............a.
        0x0030:  2dc2 a2a4 3c3f 786d 6c20 7665 7273 696f  -...<?xml.versio
        0x0040:  6e3d 2231 2e30 2220 656e 636f 6469 6e67  n="1.0".encoding
        0x0050:  3d22                                     ="

ここにはパケットヘッダなどが含まれているので分かりにくいが、パケットのデータが顔を見せている。以下に本質的な部分を取り出したものを載せる。見て分かるように、基本的な振る舞いは筆者のサーバと同じである。
(今回は観測された全てのレスポンスを # で記した。)

PROPFIND /arisawa2/

PROPFIND /arisawa2/

# 207 Multi-Status

PROPFIND /arisawa2/.DS_Store

# 404 Not Found

PROPFIND /arisawa2/

# 207 Multi-Status

PROPFIND /arisawa2/.DS_Store

# 404 Not Found

PROPFIND /arisawa2/03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/03m3180.pdf

# 404 Not Found

PUT /arisawa2/03m3180.pdf

# 201 Created

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PUT /arisawa2/._03m3180.pdf

# 201 Created

LOCK /arisawa2/._03m3180.pdf

# 200 OK

PUT /arisawa2/._03m3180.pdf

# 204 No Content

UNLOCK /arisawa2/._03m3180.pdf

# 204 No Content

PROPFIND /arisawa2/

# 207 Multi-Status

LOCK /arisawa2/._03m3180.pdf

# 200 OK

UNLOCK /arisawa2/._03m3180.pdf

# 204 No Content

DELETE /arisawa2/._03m3180.pdf

# 204 No Content

LOCK /arisawa2/03m3180.pdf

# 200 OK

GET /arisawa2/03m3180.pdf

# 200 OK

PUT /arisawa2/03m3180.pdf

# 204 No Content

UNLOCK /arisawa2/03m3180.pdf

# 204 No Content

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/

# 207 Multi-Status

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/._03m3180.pdf

# 404 Not Found

PROPFIND /arisawa2/

# 207 Multi-Status

PROPFIND /arisawa2/Documents/.localized

# 404 Not Found

PROPFIND /arisawa2/Library/.localized

# 404 Not Found

PROPFIND /arisawa2/Movies/.localized

# 404 Not Found

PROPFIND /arisawa2/Music/.localized

# 404 Not Found

PROPFIND /arisawa2/Pictures/.localized

# 404 Not Found

PROPFIND /arisawa2/Public/.localized

# 404 Not Found

PROPFIND /arisawa2/Sites/.localized

# 404 Not Found

PROPFIND /arisawa2/

# 207 Multi-Status

PROPFIND /arisawa2/Documents/.localized

# 404 Not Found

PROPFIND /arisawa2/Library/.localized

# 404 Not Found

PROPFIND /arisawa2/Movies/.localized

# 404 Not Found

PROPFIND /arisawa2/Music/.localized

# 404 Not Found

PROPFIND /arisawa2/Pictures/.localized

# 404 Not Found

PROPFIND /arisawa2/Public/.localized

# 404 Not Found