August, 2013

  • SharePoint Online/On-Premises 環境に対してプログラミングで大きなファイルをアップロードする

    こんにちは SharePoint サポートの森 健吾 (kenmori) です。今回の投稿では、SharePoint Online / On-Premises 環境において、ファイルをアップロードするプログラム開発で、よくあるご質問とその回答を記載させていただきます。

     

    1. CSOM (または REST) で 1.6 MB 以上のファイルをアップロードできない。


    質問事項

    CSOM を利用し、Office365 上 SharePoint Onlineへ 1.6 MB 程度のファイルをアップロードすると 「要求メッセージのサイズが大きすぎます。2097152 バイトを超えるメッセージはサーバーで許可されません。」とエラーが出力されます。

    なぜ、CSOM だと約 2 MBの制限が発生するのでしょうか。ドキュメント ライブラリのページからのアップロードは成功します。また、エラーが発生するファイルも、約1.6MBとエラーメッセージに記載されているバイト数よりも少ない容量となります。


    説明

    本現象は、クライアント サイド オブジェクト モデル (以下 CSOM) のサーバー側サービスとして公開されている client.svc において、サービス メッセージの最大サイズとして指定されたサイズ (*1 SPClientRequestServiceSettings.MaxParseMessageSize) を超えたメッセージを受信したためエラーが発生したことに起因します。

    CSOM ではなく、手動でファイルをアップロード際には、この client.svc を使用しないため本制限値の影響を受けません。


    コードと現象

    --- サンプルコード : ここから ---

    Dim ctx As SP.ClientContext = New SP.ClientContext("https://kenmori.sharepoint.com")
    Dim pwd As System.Security.SecureString = New Security.SecureString()
    For Each cpwd As Char In "password".ToArray()
        pwd.AppendChar(cpwd)
    Next
    ctx.Credentials = New SP.SharePointOnlineCredentials("user", pwd)

    Dim fileName As String = TextBox1.Text
    Dim loadFilePath As String = "C:\Shared\" & fileName
    Dim url As String = "https://kenmori.sharepoint.com/Shared%20Documents/"

    'アップロードファイルの読み込み
    Dim bFile As Byte() = System.IO.File.ReadAllBytes(loadFilePath)
    Dim fci As New SP.FileCreationInformation()
    fci.Content = bFile
    fci.Overwrite = True
    fci.Url = fileName

    'フォルダのURLから、Folderクラスを取得
    Dim oWebsite As SP.Web = ctx.Web
    Dim spfolder As SP.Folder = oWebsite.GetFolderByServerRelativeUrl(url)
    ctx.Load(spfolder)
    ctx.ExecuteQuery()

    'FolderへFileをアップロード
    Dim newFile As SP.File = spfolder.Files.Add(fci)
    ctx.Load(newFile)
    ctx.ExecuteQuery() 'ここでエラー!

     --- サンプルコード : ここまで ---

    上記のアップロードは、もちろん小さなファイルのアップロードでは問題なく動作します。
    この処理で SharePoint 2013 On-Premises 環境に対して CSOM でアップロードしても同じくエラーとなります。その場で診断ログを見ると以下のようなエラーが出力されています。

     08/22/2013 11:42:23.24  w3wp.exe (0x2A74)                        0x0CD8 SharePoint Foundation          CSOM                           afxv1 High     Microsoft.SharePoint.Client.InvalidClientQueryException: The request message is too big. The server does not allow messages larger than 2097152 bytes.     at Microsoft.SharePoint.Client.MaxSizeRequestStream.CheckTotalReadCount(Int64 totalReadCount)     at Microsoft.SharePoint.Utilities.ReadonlyWrapStream.Read(Byte[] buffer, Int32 offset, Int32 count)     at System.IO.StreamReader.ReadBuffer(Char[] userBuffer, Int32 userOffset, Int32 desiredChars, Boolean& readToUserBuffer)     at System.IO.StreamReader.Read(Char[] buffer, Int32 index, Int32 count)     at Microsoft.SharePoint.Client.ServerRuntime.TextPeekReader.Read(Char[] buffer, Int32 offset, Int32 count)     at System.Xml.XmlTextReaderImpl.ReadData()     at System.Xml.XmlTextReaderImpl.ParseText(Int32& startPos, Int32& endPos, Int32& out... f75a299c-6165-708a-27e3-1dd2ccc65d66

     

    対処方法

    On-Premises 環境では、以下の方法で、メッセージの最大サイズを変更することは可能です。
    ただし、根本対処策としては、HttpWebRequest クラスを使用してアップロードする方法となります。その方法は本投稿 "3. 大��なファイルをアップロードするための確実な方法"でまとめてご説明します。

     (手順)
     1. SharePoint 管理シェルを起動します。
     2. 以下のコマンド レットを実行します。
      [Microsoft.SharePoint.Administration.SPWebService]::ContentService
      $ws = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
      $ws.ClientRequestServiceSettings.MaxParseMessageSize = 2147483647
      $ws.Update()


    補足

    このSPClientRequestServiceSettings.MaxParseMessageSize の既定値は2097152 Byte です。ファイルは 1.5MB 程度なのになぜ SOAP メッセージは 2.0 MB を超えてしまうのでしょうか。

    その理由は Base64Binary (Base 64 エンコード) にあります。client.svc は HTTP 要求応答をベースとしたサービスとして公開されています。Web メソッドを実行する際には、XML などのテキスト ベースの情報で SOAP を構成し、メソッド名が引数などの情報を引き渡します。

    この SOAP 上にバイナリ データを記載する際に、バイナリをそのまま渡した場合プロトコルが正常機能しないため XML Web サービスの一般的な方法として Base64 エンコードを実施して Base64Binary 型のデータを引数として転送します。 

    - 例
    <Property Name="Content" Type="Base64Binary">YQ==</Property>

    このエンコードが実施された場合、バイナリ データを SOAP に影響しない範囲で定義された文字コードとして変換するため、データ サイズは大きくなります。このため、2 MB に満たないファイルを転送した場合においても、HTTP 要求上では 2097152 バイトを超えるデータ サイズとなったため、エラーが発生します。
    SharePoint 2010 REST サービスである ListData.svc を使用してファイルをアップロードしても、10485760 バイトを超えると同じエラーです。
    チャンク形式 (TransferEncodingChunked) として一度のトラフィックを減らしたとしても、全体のメッセージ サイズには影響しません。

    上記のため、大きなサイズのファイルを SOAP 経由でアップロードすることはトラフィックの増大につながるためお勧めできません。 


    2. 大きなファイルをアップロードしようとすると、クライアント環境で OutOfMemoryException が発生する。

     

    質問事項

    CSOM、REST API、WebClient クラスなどを利用し、SharePoint へ数百 MB の大きなファイルをアップロードするとOutOfMemoryException 例外が検出されます。
    小さなメモリを搭載している 32 ビットのクライアント PC なので、メモリが足らなくなったことは理解できるのですが、このようなマシンからもファイルをアップロードすることは可能でしょうか。

    説明

    HTTP を送信する際にバッファリング機能を無効化することで、ご要望を実現できる可能性があります。

    .NET Framework のライブラリには様々な Web クライアント機能を持ったクラスが存在します。Web 参照で生成される ASMX Web サービス プロキシ、WCF サービス 参照で生成される Web サービス クライアント、WebClient クラスなどがあります。
    これらのクラスは内部的に HttpWebRequest を使用してサーバーに要求を送信しています。

    以下のサポート技術情報にある通り、HttpWebRequest.AllowWriteStreamBuffering プロパティが true の場合、メモリ上にアップロードするファイルをバッファリングした上で、サーバーに送信する動作となります。この値は既定値が true であり、このクラスを Web 送信に内部利用する多くのクラスは AllowWriteStreamBuffering を false に設定するためのプロパティを持たないため、常にバッファリングが有効 (true) として動作します。この動作によりメモリを消費して OutOfMemoryException を生成する状況が発生します。

    HttpWebRequest を直接呼出し、AllowWriteStreamBuffering プロパティを false にすることで、このバッファリングを無効にし、クライアント端末のメモリを節約する方法があります。根本対処策は、1 の現象に対する対処策と併せて以下に記載いたします。

    タイトル : .NET Framework を実行しているコンピュータで HttpWebRequest クラスを使用して大量のデータを送信する場合に POST 要求または PUT 要求が失敗することがある
    アドレス : http://support.microsoft.com/kb/908573


    3. 大きなファイルをアップロードするための確実な方法

    上記の理由により、大きなファイルをアップロードする最も確実な方法はHttpWebRequest クラスを使用して、SharePoint Online / On-Premises 環境に PUT 要求を送信する方法となります。

    --- サンプルコード : ここから ---

    HttpWebRequest webReq;
    HttpWebResponse res;
    string user = "user@kenmori.onmicrosoft.com";
    string pwd = "password";
    string siteUrl = "https://kenmori.sharepoint.com/";

    //-----------------------------------
    // SPO 認証 Cookie の作成
    //  SharePointOnlineCredentials から認証 Cookie を取得
    // -----------------------------------
    CredentialCache myCredCache = new CredentialCache();
    System.Security.SecureString secpas = new System.Security.SecureString();
    foreach(char c in pwd.ToCharArray())
    {
        secpas.AppendChar(c);
    }

    // ClientContext であれば SharePointOnlineCredentials だけで良いが、HttpWebRequest に指定するために変換
    SharePointOnlineCredentials userCredentials = new Microsoft.SharePoint.Client.SharePointOnlineCredentials(user,secpas);
    CookieContainer cookiecontainer = new CookieContainer();
    Cookie authCookie = new Cookie("FedAuth", userCredentials.GetAuthenticationCookie(new Uri(siteUrl)).Replace("SPOIDCRL=", String.Empty));
    cookiecontainer.Add(new Uri(siteUrl), authCookie);

    //-----------------------------------
    // HEAD 要求を送信して接続を事前認証
    //  (KB 908573 Windows 認証 & AllowWriteStreamBufferint=false)
    //   SPO では不要なためコメントアウトしておきます。
    //-----------------------------------
    /*
    webReq = (HttpWebRequest)WebRequest.Create(siteUrl);
    webReq.PreAuthenticate = false;
    webReq.Method = "HEAD";
    CredentialCache myCredCache = new CredentialCache();
    myCredCache.Add(URL,"Negotiate",(NetworkCredential) CredentialCache.DefaultCredentials);          
    webReq.Credentials = myCredCache;
    webReq.PreAuthenticate = true;
    res = (HttpWebResponse)webReq.GetResponse();
    res.Close();
    */

    // -----------------------------------
    // PUT 要求を送信
    //-----------------------------------
    // アップロードするファイル URL を指定
    webReq = (HttpWebRequest)WebRequest.Create(siteUrl + "/Shared%20Documents/bigdata.txt");
    webReq.CookieContainer = cookiecontainer;
    //HTTP 要求するデータを一度メモリにバッファリングしない
    webReq.UnsafeAuthenticatedConnectionSharing = true; // 接続共有を true に設定
    webReq.AllowWriteStreamBuffering = false; // バッファリングを false に設定
    webReq.Method = WebRequestMethods.File.UploadFile;
    webReq.Timeout = int.MaxValue;

    // アップロードするファイルをオープンし、HttpWebRequestにサイズを設定
    FileStream inStrm = new FileStream(@"C:\bigdata.txt", FileMode.Open);
    webReq.ContentLength = inStrm.Length;

    // ファイルをバッファに読み込み・サーバーへ書き込み
    using (Stream outStrm = webReq.GetRequestStream())
    {
        int bufSize = 2048; // 2KBバッファ               
        byte[] buffer = new byte[bufSize];
        int bytesRead = 0;
        while ((bytesRead = inStrm.Read(buffer, 0, bufSize)) > 0)
            outStrm.Write(buffer, 0, bytesRead);
        inStrm.Close();
        outStrm.Flush();
        outStrm.Close();
    }

    // 結果取得
    res = (HttpWebResponse)webReq.GetResponse();
    res.Close();

    --- サンプルコード : ここから ---

    いかがでしたでしょうか。今回の投稿は以上になります。

  • 2013 年 8 月の CU がリリースされました

    2013 年 8 月の CU がリリースされましたのでお知らせします。

    以下のリンクよりダウンロードできます。

    注意点

    • SharePoint Server 2013 の CU 適用前には 2013 年 3 月の更新プログラムをインストールする必要があります。
    • SharePoint Server 2010、SharePoint Foundation 2010 の CU 適用前には SP1 をインストールする必要があります。
    • SharePoint Server 2013 の CU には SharePoint Foundation 2013 の CU が含まれます。SharePoint Server 2013 with Project Server の CU には SharePoint Server 2013、SharePoint Foundation 2013 の CU が含まれます。いずれか 1 つの製品ファミリに対応した CU を適用すれば問題ありません。
    • SharePoint Server 2010 の CU には SharePoint Foundation 2010 の CU が含まれます。SharePoint Server 2010 with Project Server の CU には SharePoint Server 2010、SharePoint Foundation 2010 の CU が含まれます。いずれか 1 つの製品ファミリに対応した CU を適用すれば問題ありません。
    • インストールの完了後 SharePoint 製品構成ウィザードを実行する必要があります。

    関連 KB

    • KB 2817594 - SharePoint Foundation 2010
    • KB 2825949 - SharePoint Server 2010
    • KB 2817573 - Project Server 2010
    • KB 2817517 - SharePoint Foundation 2013
    • KB 2817616 - SharePoint Server 2013
    • KB 2817615 - Project Server 2013
    • KB 2817521 - Office Web Apps Server 2013

    入手先

    関連サイト