我正在尝试使用Microsoft Graph API使用Ajax调用上传大文件。
根据documentation,您首先必须创建一个上传会话,我可以使用我的代码成功完成。当我开始上传到返回的uploadUrl
时出现问题。我收到以下错误:
{
code: "invalidRequest",
message: "The Content-Range header length does not match the provided number of bytes."
}
因此,当我在Fiddler中检查实际请求时,我可以看到Content-Length
标头设置为0
。
所以我尝试将Content-Length
标题设置为我发送的ArrayBuffer
的大小,但是我收到错误(Chrome),其中包含:
Refused to set unsafe header "Content-Length"
我一直在努力奋斗这整整2天,而我现在已经结束了。关于Microsoft Graph API的文档很少,甚至更少的示例似乎适合我正在尝试做的事情。
我无法想象我是唯一一个试图这样做的人,我认为这是一个相当普遍的想法?
以下是我正在使用的代码。我在其他地方获得AccessToken
和URL
,但它们看起来很好,因为我可以在控制台中查询它们。
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);
},
});
};
返回发送数据缓冲区的函数
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);
};
};
最后,chunkUpload函数:
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 :(得分:0)
我能够找到问题的答案,所以我也会在这里发布最终代码以及其他任何有问题的人,因为我可以在网上找到很少的例子。这样做:
首先,在任何浏览器(IE,Mozilla或Chrome)中发送一个裸ArrayBuffer都没有将Content-Length设置为0以外的任何值,至少对我来说不是这样。 但是,如果我将ArrayBuffer转换为新的uInt8Array,浏览器确实会选择Content-Length并正确设置它。
我发现的另一个问题是Microsoft Graph文档。我不知道您必须将新文件名放在上传会话请求URL中 - 您不清楚您是否需要在文档中执行此操作。请参阅下面的代码,它的格式正确且运行良好。
最后,我的chunkedUpload函数需要进行相当多的更改,最明显的是将xhr.send(文件)调整为xhr.send(fileChunk)&lt; - 这是我最初错过的一个大问题。我还包含了一个用于文件上传的Progress回调,它与我的bootstrap ProgressBar非常吻合。
到工作代码:
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);
}