![]() |
![]() |
X-Expected-Entity-Length
.DS_Store" の問題
2009/01/30 追加
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 形式を解説しておかなくてはならない。
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 は HEAD、GET、OPTIONS ぐらいしかサポートされていない。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 の後に、コレクションの個々の要素に対して PROPFIND を Depth:0 で再確認している。つまり Depth:1 の情報は名前しか活用していないのである。これはコレクションの要素数が多い場合には処理速度を大きく落とす。
最後の問題に関しては、OSX クライアントはディレクトリの COPY や MOVE に対して、GET メソッドと PUT メソッドを繰り返している。その結果、ディレクトリの中に 30 個のファイルがあれば、30 個のファイルのダウンロードとアップロードの繰り返しが発生するのだ。OSX クライアントが表示する、ユーザフレンドリなメッセージ(「あと XX 個のファイルを...しています」)はこの非効率な処理の賜物である。
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
で止めることができる。
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 のアイコンは、ファイルをダウンロードをしなくては表示できない。そこで...

OSX 10.5 の 「Finder 表示オプション」
少なくとも、これでファイル一覧の表示にイライラするのはかなり軽減される。
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
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