2017-09-26 1 views
1

Ich versuche, die Microsoft Graph API zu verwenden, um große Dateien mit einem Ajax-Aufruf hochzuladen.Hochladen von großen Dateien mit Ajax-Aufruf

Nach der documentation müssen Sie zuerst eine Upload-Sitzung erstellen, die ich mit meinem Code erfolgreich durchführen kann. Das Problem tritt auf, wenn ich meinen Upload auf die zurückgegebene uploadUrl starte. Ich erhalte die folgende Fehlermeldung:

{ 
    code: "invalidRequest", 
    message: "The Content-Range header length does not match the provided number of bytes." 
} 

Also, wenn ich die aktuelle Anfrage in Fiddler überprüfen, kann ich sehen, dass die Content-Length Header 0 gesetzt.

Also versuchte ich meine Content-Length Header auf die Größe der ArrayBuffer Einstellung, die ich schicke, aber ich erhalte eine Fehlermeldung (Chrom), der sagt:

Refused to set unsafe header "Content-Length"

Ich habe mit diesem zu kämpfen seit 2 vollen Tagen jetzt und ich bin am Ende meines Wissens. Es gibt sehr wenig Dokumentation über die Microsoft Graph-API und noch weniger Beispiele, die zu dem passen, was ich versuche zu machen.

Ich kann mir nicht vorstellen, dass ich der einzige bin, der versucht, dies zu tun, ich würde denken, dass es eine ziemlich allgemeine Idee wäre?

Unten ist der Code, den ich verwende. Ich bekomme meine AccessToken und URL woanders hin, aber sie scheinen in Ordnung zu sein, da ich sie über die Konsole abfragen kann.

this.UploadLargeFileToFolderID = function (FolderID, 
    FileObject, 
    ShowLoadingMessage, 
    SuccessFunction, 
    ErrorFunction, 
    CompleteFunction) { //will upload a file up to 60Mb to folder. 

    //shows the loading messag 
    ShowLoadingMessage && ThisRepository.LoadingMessage.Show(); 

    //cleans the file name to something acceptable to SharePoint 
    FileObject.name = CleanOneDriveFileName(FileObject.name); 

    var UploadSessionURL = FolderID ? 
     ThisRepository.RepositoryRootURL + '/drive/items/' + FolderID + '/createUploadSession' : 
     ThisRepository.RepositoryRootURL + '/drive/root/createUploadSession'; 

    //First, get the Upload Sesion. 
    $.ajax({ 
     url: UploadSessionURL, 
     method: 'POST', 
     headers: { 
      authorization: "Bearer " + ThisRepository.AccessToken 
     }, 
     success: function (data, textStatus, jqXHR) { 
      //successfully got the upload session.    
      console.log('Session:'); 
      console.log(data); 

      //Create the ArrayBuffer and upload the file. 
      ReturnArrayBufferFromFile(FileObject, function (ArrayBuffer) { 
       console.log('Array Buffer:'); 
       console.log(ArrayBuffer); 
       var MaxChunkSize = 327680; 
       var ChunkSize = ArrayBuffer.byteLength < MaxChunkSize ? 
        ArrayBuffer.byteLength : 
        MaxChunkSize; 

       chunkedUpload(data.uploadUrl, ArrayBuffer, ChunkSize, 0, null, 
        null, null, null, 
        function (response) { 
         console.log(response); 
         !SuccessFunction && console.log(response); 
         typeof SuccessFunction === 'function' && SuccessFunction(response); 
        }); 

      }); 

     }, 
     error: function (jqXHR, textStatus, errorThrown) { 
      console.log(jqXHR); 
      typeof ErrorFunction === 'function' && ErrorFunction(jqXHR); 
     }, 
     complete: function (jqXHR, textStatus) { 
      ThisRepository.LoadingMessage.Remove(); 
      typeof CompleteFunction === 'function' && CompleteFunction(jqXHR); 
     }, 
    }); 

}; 

Funktion zur Rückführung des Array Buffer

function ReturnArrayBufferFromFile(InputFile, CallBackFunction) { 
    console.log('Input File'); 
    console.log(InputFile); 
    var FileName = CleanOneDriveFileName(InputFile.name); 
    var FileUploadReader = new FileReader(); 

    if (InputFile.type.match('image.*')) { 
     // if the file is an image, we want to make sure 
     // it's not too big before we return it. 
     FileUploadReader.onloadend = function (e) { 
      var img = new Image(); 

      //will resize an image to a maximum of 2 megapixels. 
      img.onload = function() { 
       var MAX_HEIGHT = 2048; //max final height, in pixels 
       var MAX_WIDTH = 2048; //max final width, in pixels 
       var height = img.height; 
       var width = img.width; 

       //do the resizing 
       if (width > height) { //landscape image 
        if (width > MAX_WIDTH) { 
         height *= MAX_WIDTH/width; 
         width = MAX_WIDTH; 
        }; 
       } else { //portrait image 
        if (height > MAX_HEIGHT) { 
         width *= MAX_HEIGHT/height; 
         height = MAX_HEIGHT; 
        }; 
       }; 

       //Create a new canvas element, correctly sized with the image 
       var canvas = document.createElement("canvas"); 
       canvas.width = width; 
       canvas.height = height; 
       canvas.getContext('2d').drawImage(this, 0, 0, width, height); 

       //Create the new file reader for the upload function.     
       var ConvertedFile = canvas.toBlob(function (blob) { 
        var ConvertedFileReader = new FileReader(); 

        ConvertedFileReader.onloadend = function (loadendevent) { 
         //return loadendevent.target.result; 
         var result = loadendevent.target.result; 
         var Rawresult = result.split(',')[1]; 
         CallBackFunction(loadendevent.target.result); 
        }; 

        ConvertedFileReader.readAsArrayBuffer(blob); 

       }, 'image/jpeg', 0.90); 
      }; 

      img.src = e.target.result; 
     }; 

     FileUploadReader.readAsArrayBuffer(InputFile); 
    } else { 
     //File is not an image. No pre-work is required. Just upload it. 
     FileUploadReader.onloadend = function (e) { 
      CallBackFunction(e.target.result); 
     }; 
     FileUploadReader.readAsArrayBuffer(InputFile); 
    }; 
}; 

Und schließlich die chunkUpload Funktion senden:

function chunkedUpload(url, file, chunkSize, chunkStart, 
    chunkEnd, chunks, chunksDone, fileChunk, CompleteCallBack) { 

    var filesize = file.byteLength; 

    chunkSize = chunkSize ? chunkSize : 327680; 
    chunkStart = chunkStart ? chunkStart : 0; 
    chunkEnd = chunkEnd ? chunkEnd : chunkSize; 
    chunks = chunks ? chunks : filesize/chunkSize; 
    chunksDone = chunksDone ? chunksDone : 0; 
    fileChunk = fileChunk ? fileChunk : file.slice(chunkStart, chunkEnd); 

    var req = new XMLHttpRequest(); 

    req.open("PUT", url, true); 
    //req.setRequestHeader("Content-Length", file.size.toString()); 
    req.setRequestHeader("Content-Range", "bytes " + chunkStart + "-" + 
     (chunkEnd - 1) + "/" + filesize); 

    req.onload = (e) => { 
      let response = JSON.parse(req.response); 
      console.log(response); 
      if (response.nextExpectedRanges) { 
       let range = response.nextExpectedRanges[0].split('-'), 
        chunkStart = Number(range[0]), 
        nextChunk = chunkStart + chunkSize, 
        chunkEnd = nextChunk > file.byteLength ? file.byteLength : nextChunk; 
       console.log(((chunksDone++/ chunks) * 100), '%'); 
          chunkedUpload(url, file, chunkSize, chunkStart, 
           chunkEnd, chunks, chunksDone, CompleteCallBack); 
         } 
         else { 
          console.log("upload session complete"); 
          typeof CompleteCallBack === 'function' && 
           CompleteCallBack(response); 
         } 
        }; req.send(file); 
       } 
+0

Hat Ihr 'PUT' Anfrage einen' Transfer Encoding' Header? Wenn ja, was ist der Wert? – Brad

+0

Nein, ich habe keinen Transfer-Encoding-Header. Ich habe dafür keine Anforderung in der MS Graph-API-Dokumentation erhalten. Soll ich einen haben? –

+0

Nein, aber ich wollte sicherstellen, dass die Chunked-Codierung nicht ins Bild kommt. Der Browser sollte den 'Content-Length'-Header richtig setzen, daher ist die Tatsache, dass es 0 ist, interessant. – Brad

Antwort

0

ich war in der Lage, eine Antwort auf das Problem, herauszufinden, so dass ich Ich werde den endgültigen Code hier auch für jeden anderen veröffentlichen, der ein Problem damit hat, da es sehr kleine Beispiele gibt, die ich online finden könnte, um dies zu tun:

Erstens, Senden eines nackten ArrayBuffer in einem Browser (IE, Mozilla oder Chrome) nicht die Content-Length auf alles außer 0, zumindest würde es nicht für mich. Wenn ich den ArrayBuffer jedoch in ein neues uInt8Array konvertiert habe, haben die Browser die Inhaltslänge übernommen und richtig gesetzt.

Ein anderes Problem, das ich fand, war in der Microsoft Graph Dokumentation. Mir war nicht bewusst, dass Sie den neuen Dateinamen in die URL für die URL für die Upload-Sitzung eingeben mussten. Es ist nicht klar, dass Sie dies in der Dokumentation tun müssen. Siehe meinen Code unten. Er ist korrekt formatiert und funktioniert gut.

Schließlich brauchte meine chunkedUpload-Funktion einige Änderungen, vor allem die Anpassung der xhr.send (Datei) an xhr.send (fileChunk) < - das war eine große, die ich ursprünglich vermisste. Ich habe auch einen Progress-Callback für den Datei-Upload eingefügt, der sehr gut mit meiner Bootstrap ProgressBar funktioniert.

auf den Arbeitscode:

this.UploadLargeFileToFolderID = function (FolderID, FileObject, ShowLoadingMessage, SuccessFunction, ErrorFunction, CompleteFunction, ProgressFunction) {//will upload a file up to 60Mb to folder. 
    ShowLoadingMessage && ThisRepository.LoadingMessage.Show(); //shows the loading message 

    FileObject.name = CleanOneDriveFileName(FileObject.name); //cleans the file name to something acceptable to SharePoint 
    var NewFileName = CleanOneDriveFileName(FileObject.name); 
    var UploadSessionURL = FolderID ? ThisRepository.RepositoryRootURL + '/drive/items/' + FolderID + ':/' + NewFileName + ':/createUploadSession' : ThisRepository.RepositoryRootURL + '/drive/root:/' + NewFileName + ':/createUploadSession'; 
    var PathToParent = FolderID ? ThisRepository.RepositoryRootURL + '/drive/items/' + FolderID + ':/' + NewFileName + ':/' : ThisRepository.RepositoryRootURL + '/drive/root:/' + NewFileName + ':/'; //used if we have a naming conflict and must rename the object. 

    var UploadSessionOptions = { 
     item: { 
      //"@microsoft.graph.conflictBehavior": "rename", 
      "@microsoft.graph.conflictBehavior": "replace", 
     } 
    }; 

    //First, get the Upload Sesion. 
    $.ajax({ 
     url: UploadSessionURL, 
     method: 'POST', 
     headers: { authorization: "Bearer " + ThisRepository.AccessToken, 'Content-Type': 'application/json', 'accept': 'application/json'}, 
     data: JSON.stringify(UploadSessionOptions), 
     success: function (SessionData, textStatus, jqXHR) { //successfully got the upload session. 
      //Create the ArrayBuffer and upload the file. 
      ReturnArrayBufferFromFile(FileObject, 
        function (ArrayBuffer) { 
        var uInt8Array = new Uint8Array(ArrayBuffer); 
        var FileSize = uInt8Array.length; 
        var MaxChunkSize = 3276800; //approx 3.2Mb. Microsoft Graph OneDrive API says that all chunks MUST be in a multiple of 320Kib (327,680 bytes). Recommended is 5Mb-10Mb for good internet connections. 
        var ChunkSize = FileSize < MaxChunkSize ? FileSize : MaxChunkSize; 
        var DataUploadURL = SessionData.uploadUrl; 

        chunkedUpload(DataUploadURL, uInt8Array, ChunkSize, 0, null, null, null, 
         function (progress) { //progress handler 
          ProgressFunction(progress); 
         }, 
         function (response) { //completion handler 
        if (response.StatusCode == 201 || response.StatusCode == 200) {//success. 201 is 'Created' and 200 is 'OK' 

         typeof SuccessFunction === 'function' && SuccessFunction(response); 

         ThisRepository.LoadingMessage.Remove(); 
         typeof CompleteFunction === 'function' && CompleteFunction(response); 

        } else if (response.StatusCode == 409) { //naming conflict? 

         //if we had a renaming conflict error, per Graph Documentation we can make a simple PUT request to rename the file 

         //HAVE NOT SUCCESSFULLY TESTED THIS... 
         var NewDriveItemResolve = { 
          "name": NewFileName, 
          "@microsoft.graph.conflictBehavior": "rename", 
          "@microsoft.graph.sourceUrl": DataUploadURL 
         }; 

         $.ajax({ 
          url: PathToParent, 
          method: "PUT", 
          headers: { authorization: "Bearer " + ThisRepository.AccessToken, 'Content-Type': 'application/json', accept: 'application/json' }, 
          data: JSON.stringify(NewDriveItemResolve), 
          success: function (RenameSuccess) { 
           console.log(RenameSuccess); 
           typeof SuccessFunction === 'function' && SuccessFunction(response); 

           ThisRepository.LoadingMessage.Remove(); 
           typeof CompleteFunction === 'function' && CompleteFunction(response); 

          }, 
          error: function (RenameError) { 
           console.log(RenameError); 

           var CompleteObject = { StatusCode: RenameError.status, ResponseObject: RenameError, Code: RenameError.error ? RenameError.error.code : 'Unknown Error Code', Message: RenameError.error ? RenameError.error.message : 'Unknown Error Message' }; 
           var Status = CompleteObject.StatusCode; 
           var StatusText = CompleteObject.Code; 
           var ErrorMessage = CompleteObject.Message; 

           var ErrorMessage = new Alert({ Location: ThisRepository.LoadingMessage.Location, Type: 'danger', Text: "Status: " + Status + ': ' + StatusText + "<br />Error: " + ErrorMessage + '<br />Rest Endpoint: ' + data.uploadUrl }); 
           ErrorMessage.ShowWithoutTimeout(); 

           typeof ErrorFunction == 'function' && ErrorFunction(response); 

           ThisRepository.LoadingMessage.Remove(); 
           typeof CompleteFunction === 'function' && CompleteFunction(response); 

          }, 
          complete: function (RenameComplete) { /* Complete Function */ } 

         }); 


        } else { //we had an error of some kind. 

         var Status = response.StatusCode; 
         var StatusText = response.Code; 
         var ErrorMessage = response.Message; 

         var ErrorMessage = new Alert({ Location: ThisRepository.LoadingMessage.Location, Type: 'danger', Text: "Status: " + Status + ': ' + StatusText + "<br />Error: " + ErrorMessage + '<br />Rest Endpoint: ' + data.uploadUrl }); 
         ErrorMessage.ShowWithoutTimeout(); 

         //CANCEL THE UPLOAD SESSION. 
         $.ajax({ 
          url: UploadSessionURL, 
          method: "DELETE", 
          headers: { authorization: "Bearer " + ThisRepository.AccessToken }, 
          success: function (SessionCancelled) { console.log('Upload Session Cancelled');}, 
          error: function (SessionCancelError) { /* Error Goes Here*/}, 
         }); 

         typeof ErrorFunction == 'function' && ErrorFunction(response); 

         ThisRepository.LoadingMessage.Remove(); 
         typeof CompleteFunction === 'function' && CompleteFunction(response); 
        };      
       }); 
       } 
      ); 
     }, 
     error: function (jqXHR, textStatus, errorThrown) { 
      console.log('Error Creating Session:'); 
      console.log(jqXHR); 
      //WE MAY HAVE A CANCELLED UPLOAD...TRY TO DELETE THE OLD UPLOAD SESSION, TELL THE USER TO TRY AGAIN. 
      //COULD OPTIONALLY RUN A "RENAME" ATTEMPT HERE AS WELL 
      $.ajax({ 
       url: PathToParent, 
       method: "DELETE", 
       headers: { authorization: "Bearer " + ThisRepository.AccessToken }, 
       success: function (SessionCancelled) { console.log('Upload Session Cancelled'); }, 
       error: function (SessionCancelError) { console.log(SessionCancelError); }, 
      }); 

      typeof ErrorFunction === 'function' && ErrorFunction(jqXHR); 
     }, 
     complete: function (jqXHR, textStatus) { /* COMPLETE CODE GOES HERE */}, 
    }); 
}; 

function ReturnArrayBufferFromFile(InputFile, CallBackFunction) { 
var FileName = InputFile.name; 
var FileUploadReader = new FileReader(); 

//Check the file type. If it's an image, we want to make sure the user isn't uploading a very high quality image (2 megapixel max for our purposes). 

if (InputFile.type.match('image.*')) { // it's an image, so we will resize it before returning the array buffer... 
    FileUploadReader.onloadend = function (e) { 
     var img = new Image(); 

     img.onload = function() { //will resize an image to a maximum of 2 megapixels. 

      var MAX_HEIGHT = 2048;//max final height, in pixels 
      var MAX_WIDTH = 2048; //max final width, in pixels 
      var height = img.height; 
      var width = img.width; 

      //do the resizing 
      if (width > height) {//landscape image 
       if (width > MAX_WIDTH) { 
        height *= MAX_WIDTH/width; 
        width = MAX_WIDTH; 
       }; 
      } 
      else { //portrait image 
       if (height > MAX_HEIGHT) { 
        width *= MAX_HEIGHT/height; 
        height = MAX_HEIGHT; 
       }; 
      }; 

      //Create a new canvas element, correctly sized with the image 
      var canvas = document.createElement("canvas"); 
      canvas.width = width; 
      canvas.height = height; 
      canvas.getContext('2d').drawImage(this, 0, 0, width, height); 

      //Create the new file reader for the upload function.     
      var ConvertedFile = canvas.toBlob(function (blob) { 
       var ConvertedFileReader = new FileReader(); 

       ConvertedFileReader.onloadend = function (loadendevent) { //return the ArrayBuffer 
        CallBackFunction(loadendevent.target.result); 
       }; 

       ConvertedFileReader.readAsArrayBuffer(blob); 
       //ConvertedFileReader.readAsDataURL(blob); 


      }, 'image/jpeg', 0.90); 
     }; 

     img.src = e.target.result; 
    }; 

    FileUploadReader.readAsDataURL(InputFile); 
} 
else { 
    FileUploadReader.onloadend = function (e) {//File is not an image. No pre-work is required. Just return as an array buffer. 
     CallBackFunction(e.target.result); 
    }; 
    FileUploadReader.readAsArrayBuffer(InputFile); 
}; 
}; 

function chunkedUpload(url, file, chunkSize, chunkStart, chunkEnd, chunks, 
    chunksDone, ProgressCallBack, CompleteCallBack) { 

var filesize = file.length; 


chunkSize = chunkSize ? chunkSize : 3276800; //note: Microsoft Graph indicates all chunks MUST be in a multiple of 320Kib (327,680 bytes). 
chunkStart = chunkStart ? chunkStart : 0; 
chunkEnd = chunkEnd ? chunkEnd : chunkSize; 
chunks = chunks ? chunks : Math.ceil(filesize/chunkSize); 
chunksDone = chunksDone ? chunksDone : 0; 
console.log('NOW CHUNKS DONE = ' + chunksDone); 
fileChunk = file.slice(chunkStart, chunkEnd); 

var TotalLoaded = chunksDone * chunkSize; 

var req = new XMLHttpRequest(); 

req.upload.addEventListener('progress', function (progressobject) { 
    var ThisProgress = progressobject.loaded ? progressobject.loaded : 0; 
    var OverallPctComplete = parseFloat((TotalLoaded + ThisProgress)/filesize); 
    ProgressCallBack({ PercentComplete: OverallPctComplete }); 
}, false); 

req.open("PUT", url, true); 
req.setRequestHeader("Content-Range", "bytes " + chunkStart + "-" + (chunkEnd - 1) + "/" + filesize); 

req.onload = function (e) { 
    var response = JSON.parse(req.response); 
    var Status = req.status; 
    var CallBackObject = { 
     StatusCode: Status, 
     ResponseObject: req, 
    }; 

    if (Status == 202) { //response ready for another chunk. 
     var range = response.nextExpectedRanges[0].split('-'), 
      chunkStart = Number(range[0]), 
      nextChunk = chunkStart + chunkSize, 
      chunkEnd = nextChunk > filesize ? filesize : nextChunk; 

     chunksDone++; 
     TotalLoaded = chunksDone * chunkSize; 

     CallBackObject.Code = "Accepted", 
     CallBackObject.Message = "Upload Another Chunk"; 

     chunkedUpload(url, file, chunkSize, chunkStart, chunkEnd, chunks, chunksDone++, ProgressCallBack, CompleteCallBack); 

    } else {//we are done 
     if (Status == 201 || Status == 200) {//successfully created or uploaded 
      CallBackObject.Code = 'Success'; 
      CallBackObject.Message = 'File was Uploaded Successfully.'; 
     } else { //we had an error. 
      var ErrorCode = response.error ? response.error.code : 'Unknown Error Code'; 
      var ErrorMessage = response.error ? response.error.message : 'Unknown Error Message'; 

      CallBackObject.Code = ErrorCode; 
      CallBackObject.Message = ErrorMessage; 
     }; 
     CompleteCallBack(CallBackObject); 
    }; 


}; 

req.send(fileChunk); 
} 
Verwandte Themen