IronPython で WebDAV を使ってファイルをアップロード

以下のように .NET の WebClient や WebRequest クラスを使えば、WebDAV の操作が行える。

コレクションの作成

WebClient の UploadString 等を使って URI、MKCOL を指定するだけなので非常に簡単

http://localhost/webdav/ に test1 フォルダを作成する例
from System import *
from System.Net import *

wc = WebClient()
wc.BaseAddress = "http://localhost/webdav/"
wc.UploadString("test1", "MKCOL", "")

リソースの作成

WebClient の UploadFile を使って URI、PUT、ファイル名を指定するだけなので非常に簡単

../../data1.txt ファイルを http://localhost/webdav/test1/data1.txt というリソースとして作成する例
from System import *
from System.Net import *

wc = WebClient()
wc.BaseAddress = "http://localhost/webdav/"
wc.UploadFile("test1/data1.txt", "PUT", "../../data1.txt")

大きなデータをアップロードする際の注意点

実は、HttpWebRequest(WebClient は内部で使用) を使ってサイズの大きなファイルをアップロードしたり WebDAV に POST・PUT する場合に out-of-memory やタイムアウトが発生して失敗することがある。

これは以下の仕様に起因するもので、

  • HttpRequest クラスはデフォルトでデータをバッファリングする

回避するためには、以下のようにバッファリングを停止する必要がある。

  • AlowWriteStreamBuffering プロパティに False を設定

ただし、上記の回避策を認証機能と共に利用すると ProtocolError - 「この要求には、データのバッファが必要です」というエラーが発生する。

そのため、これを回避するには以下のような回避策を施す。

  1. PreAuthenticate に False を設定し最小限のリクエスト送信(#1)で認証を終える
  2. PreAuthenticate と AllowWriteStreamBuffering に False を設定し、巨大なデータを POST や PUT する

(#1)は基本的に認証が終了するアクセスであれば何でも良く、通常は HEAD メソッドを使用すればよいが、その場合は指定する URL が予め存在している必要がある。

IronPython でサイズの大きいファイルを WebDAV を使って登録するサンプル
from System import *
from System.IO import *
from System.Net import *

url = "http://localhost/test/"
postUrl = url + "default_dest.wmv"
file = "default.wmv"

req = WebRequest.Create(postUrl)

ccache = CredentialCache()
ccache.Add(Uri(url), "Digest", NetworkCredential("test", "testpass"))
req.Credentials = ccache

req.Method = "PUT"
req.PreAuthenticate = True

#存在しない URL に HEAD メソッドは使えないため、
#PUT で空のファイルを作成して認証を終える
#(ファイルは何でも良い)
req.PreAuthenticate = True
req.Method = "PUT"
req.GetRequestStream().Close()
req.GetResponse().Close()

#一度 書き込みのストリームを開いた HttpWebRequest オブジェクトでは
# AllowWriteStreamBuffering の設定が変更できないため再インスタンス化
req = WebRequest.Create(postUrl)
req.Credentials = ccache

#AllowWriteStreamBuffering を false にして有効に通信するためには
#PreAuthenticate で事前認証済みでなければならない
req.AllowWriteStreamBuffering = False
req.PreAuthenticate = True
req.Method = "PUT"

#ファイルアップロード
try:
    fi = FileInfo(file)
    req.ContentLength = fi.Length

    fs = fi.OpenRead()
    st = req.GetRequestStream()

    buf = Array.CreateInstance(Byte, 1024 * 100);
    len = 0

    try:
        while True:
            len = fs.Read(buf, 0, buf.Length)

            if len > 0:
                st.Write(buf, 0, len)
                st.Flush()

            else:
                break
    finally:
        fs.Close()
        fs.Dispose()

    st.Close()
    req.GetResponse().Close()

except WebException, ex:
    print "%s, %s" % (ex.Status, ex.Message)