1

我正在尝试将 Microsoft Graph API 的createUploadSession端点与 JavaScript SDK 一起使用,并尝试使用刚刚超过 3MB 的小文件来测试上传。每当发送PUT请求时,我都会收到以下错误响应:

{
  "error": {
     "code": "InvalidContentRangeHeader",
     "message": "Invalid Content-Range header."
  }
}

我正在尝试一次发送整个文件,因此请求如下所示:

  const uploadSession = await client
    .api(`/me/messages/${messageId}/attachments/createUploadSession`)
    .version('beta')
    .post({
      AttachmentItem: {
        attachmentType: 'file',
        name: attachment.name,
        size: attachment.size,
      },
    });

  const res = await fetch(uploadSession.uploadUrl, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Range': `bytes 0-${attachment.size - 1}/${attachment.size}`,
      'Content-Length': attachment.size,
    },
    body: attachment.contentBytes,
  });

  const json = await res.json();
  return json;

这里有什么我想念的吗?如果我理解正确,给出完整的范围应该传递整个文件。

4

1 回答 1

0

该问题可能已过时,因为您使用的是 beta 版本,并且 UploadSessions 现在是核心功能的一部分:https ://docs.microsoft.com/en-us/graph/outlook-large-attachments 。

无论如何,我仍然决定在 JS 中分享我的工作实现(TypeScript,更正确),因为关于这个主题的大多数问题都是针对C#.

我没有尝试在创建后立即上传整个文件uploadSession(尽管文档说明这是可能的),但决定PUT按照他们的建议将整个文件分成 4MB 块。

const BYTE_RANGE = 4e6

...

// Send large attachments using an uploadSession
private async _uploadLargeAttachments(
    attachments: IMSLargeFileAttachment[],
    messageId: string,
    clientInstance: Client
): Promise<string[]> {
    return Promise.all(
        attachments.map(async (attachment) => {
            const { attachmentType, name, size } = attachment
            const uploadSession: UploadSession = await clientInstance
                .api(`/me/messages/${messageId}/attachments/createUploadSession`)
                .post({
                    AttachmentItem: { attachmentType, name, size },
                })

                if (!uploadSession.uploadUrl) {
                    // You may also want to throw an error here
                    return null
                }

            const fragmentsTotal = Math.ceil(size / BYTE_RANGE)
            let i = 0
            let bytesRemaining = size

            while (i < fragmentsTotal) {
                const step = (i + 1) * BYTE_RANGE
                const from = i * BYTE_RANGE
                const to = bytesRemaining > BYTE_RANGE ? step - 1 : size - 1
                bytesRemaining = Math.max(size - step, 0)

                const res = await fetch(uploadSession.uploadUrl, {
                    method: 'PUT',
                    headers: {
                        'Content-Type': 'application/octet-stream',
                        'Content-Length': `${to - from + 1}`,
                        'Content-Range': `bytes ${from}-${to}/${size}`,
                    },
                    body: attachment.body.slice(from, to + 1), // the 2nd slice arg is not inclusive
                })

                // The final response should have the 'Location' header with a long URL,
                // from which you can extract the attachment ID
                if (res.status === 201 && res.headers.has('Location')) {
                    return res.headers.get('Location')
                }
                i++
            }
        }).filter((it) => Boolean(it))
    )
}

希望这可以帮助任何为此苦苦挣扎的人,因为我要更快地达到他们的目标。

于 2020-11-19T19:38:40.190 回答