Drive Rest API V3 中的可恢复上传

2022-01-10 00:00:00 google-drive-api android java

我正在尝试使用 Android 中的 Drive Rest API 创建可恢复的上传会话.

I am trying to create a resumable upload session using drive rest API in Android.

根据文档,需要遵循的 3 个步骤是

As per the documentation the 3 steps needed to be followed are

  1. 开始一个可恢复的会话
  2. 保存可恢复的会话 URI
  3. 上传文件

第 1 步:我使用以下代码启动可恢复会话.

Step 1 : I use the following code to start the resumable session.

File body = new File();
body.setName(fileName);
body.setMimeType(mimeType);
body.setCreatedTime(modifiedDate);
body.setModifiedTime(modifiedDate);
body.setParents(Collections.singletonList(parentId));

HttpHeaders header = new HttpHeaders();
header.setContentLength(0L);
header.setContentType("application/json; charset=UTF-8");
header.set("X-Upload-Content-Type","image/jpeg");

HttpResponse response= driveObject
                     .files()
                     .create(body)
                     .setRequestHeaders(header)
                     .set("uploadType","resumable")
                     .buildHttpRequest()
                     .execute();

第 2 步:执行完成后,我将打印请求的响应标头以查看位置 URI

Step 2: Once the execution is complete, I'm printing the response header of the request to see the Location URI

System.out.println(response.getHeader().toString());

输出如下

{
    cache-control=[no-cache, no-store, max-age=0, must-revalidate], 
    content-encoding=[gzip], 
    content-type=[application/json; charset=UTF-8], 
    date=[Thu, 06 Oct 2016 02:20:18 GMT], 
    expires=[Mon, 01 Jan 1990 00:00:00 GMT], 
    alt-svc=[quic=":443"; ma=2592000; v="36,35,34,33,32"], 
    pragma=[no-cache], 
    server=[GSE], 
    transfer-encoding=[chunked], 
    vary=[Origin, X-Origin], 
    x-android-received-millis=[1475720421761], 
    x-android-response-source=[NETWORK 200], 
    x-android-sent-millis=[1475720420804], 
    x-content-type-options=[nosniff], 
    x-frame-options=[SAMEORIGIN], 
    x-xss-protection=[1; mode=block]
}

我没有在响应标头中找到开始上传文档中指定的文件数据的位置 URI,也没有找到任何 Java 示例来执行可恢复上传.

I don't find the Location URI in the response header to start uploading filedata as specified in the documentation nor I find any Java samples to perform resumable upload.

如何检索文档中指定的位置 URI?

How do I retrieve Location URI as specified in documentation?

推荐答案

我已经尝试了一周的大部分时间,终于可以运行可恢复的上传.它不像我预期的那样工作,但它确实有效.

I was trying for the better part of a week now and I finally got the resumable uploads to run. It does not work how I expected it would, but it does work.

我了解到,据我所知,Google Drive REST API 并不能真正进行分块上传.这可能是一个错误,也可能是设计使然.我也可能太傻了.

What I learned is that the Google Drive REST API is, as far as I know, not really capable of making chunked uploads. This may be a bug or it may be by design. I may also be too stupid.

但让我想到的是,我在任何地方都看不到代码示例.每个人都一直在谈论 Http 标头.所以这就是我们下面要做的.我们将只使用标题.

But what got me thinking was that I could not see code examples anywhere. Everybody just talked about Http headers all the time. So this is what we're gonna do below. We'll use just the headers.

以下是使用 Google Drive REST API 和 Android 进行可恢复、分块上传的方法:

So here is how you do resumable, chunked uploads with the Google Drive REST API and Android:

String accountName = "account_name";
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, Arrays.asList(SCOPES)).setBackOff(new ExponentialBackOff()).setSelectedAccountName(accountName);

1) 启动可恢复会话

遵循 Google 在本文档

POST /upload/drive/v3/files?uploadType=resumable HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer your_auth_token
Content-Length: 38
Content-Type: application/json; charset=UTF-8
X-Upload-Content-Type: image/jpeg
X-Upload-Content-Length: 2000000

{
  "name": "My File"
}

设置所有标题字段,就像在 Google 的示例中一样.将其作为 POST 请求发送.使用您的 credential 变量来获取授权令牌.X-Upload-Content-Type 的 mime 类型不是那么重要,没有它也可以工作(thisSO answer 提供了一个很好的函数来从路径中检索它).将 X-Upload-Content-Length 设置为文件的总长度.将 Content-Type 设置为 JSON 格式,因为我们的正文将以 JSON 格式为 Google 提供元数据.

Set all the header fields just like in Google's example. Send it as a POST request. Use your credential variable to get the authorization token. The mime type for X-Upload-Content-Type is not so important, it works without it too (this SO answer provides a nice function to retrieve it from a path). Set the X-Upload-Content-Length to the total length of your file. Set Content-Type to JSON format, since our body will provide the metadata for Google in the JSON format.

现在创建您的元数据正文.我输入了文件名和父级.将 Content-Length 设置为 body 的长度(以字节为单位).然后将你的 body 写入 request.getOutputStream() 输出流.

Now create your metadata body. I put in a file name and a parent. Set the Content-Length to the length of your body in bytes. Then write your body to the request.getOutputStream() output stream.

URL url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setRequestMethod("POST");
request.setDoInput(true);
request.setDoOutput(true);
request.setRequestProperty("Authorization", "Bearer " + credential.getToken());
request.setRequestProperty("X-Upload-Content-Type", getMimeType(file.getPath()));
request.setRequestProperty("X-Upload-Content-Length", String.format(Locale.ENGLISH, "%d", file.length()));
request.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
String body = "{"name": "" + file.getName() + "", "parents": ["" + parentId + ""]}";
request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", body.getBytes().length));
OutputStream outputStream = request.getOutputStream();
outputStream.write(body.getBytes());
outputStream.close();
request.connect();

2) 保存可恢复会话 URI

最后,connect() 并等待响应.如果响应代码为 200,则您已成功启动分块可恢复上传.现在将 location 标头 URI 保存在某处(数据库、文本文件等).你以后会需要它的.

2) Save the Resumable Session URI

Finally, connect() and wait for a response. If the response code is 200, you have successfully initiated a chunked, resumable upload. Now save the location header URI somewhere (database, text file, whatever). You're gonna need it later.

if (request.getResponseCode() == HttpURLConnection.HTTP_OK) {
    String sessionUri = request.getHeaderField("location");
}

3) 上传文件

PUT {session_uri} HTTP/1.1
Host: www.googleapis.com
Content-Length: 524288
Content-Type: image/jpeg
Content-Range: bytes 0-524287/2000000

bytes 0-524288

将以下代码放入循环中,直到上传整个文件.在每个块之后,您将收到带有代码 308range 标头的响应.从这个 range 标头中,您可以读取下一个块开始(参见(4)).

Put the following code in a loop, until the entire file is uploaded. After every chunk, you will get a response with code 308 and a range header. From this range header, you can read the next chunk start (see (4)).

Content-Type 将再次成为 mime 类型.Content-Length 是您在此块中上传的字节数.Content-Range 需要采用 bytes startByte-EndByte/BytesTotal 的形式.你把它放在 PUT 请求中.

Content-Type is going to be the mime type again. Content-Length is the number of bytes you upload in this chunk. Content-Range needs to be of the form bytes startByte-EndByte/BytesTotal. You put this in a PUT request.

然后您创建一个 FileInputStream 并将位置设置为您的起始字节(您从上一个响应 range 标头中获得)并将另一个块读入您的缓冲区.然后将此缓冲区写入连接输出流.最后,connect().

Then you create a FileInputStream and set the position to your start byte (which you got from your last response range header) and read another chunk into your buffer. This buffer is then written to the connection output stream. Finally, connect().

URL url = new URL(sessionUri);
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setRequestMethod("PUT");
request.setDoOutput(true);
request.setConnectTimeout(10000);
request.setRequestProperty("Content-Type", getMimeType(file.getPath()));
long uploadedBytes = chunkSizeInMb * 1024 * 1024;
if (chunkStart + uploadedBytes > file.length()) {
    uploadedBytes = (int) file.length() - chunkStart;
}
request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", uploadedBytes));
request.setRequestProperty("Content-Range", "bytes " + chunkStart + "-" + (chunkStart + uploadedBytes - 1) + "/" + file.length());
byte[] buffer = new byte[(int) uploadedBytes];
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.getChannel().position(chunkStart);
if (fileInputStream.read(buffer, 0, (int) uploadedBytes) == -1) { /* break, return, exit*/ }
fileInputStream.close();
OutputStream outputStream = request.getOutputStream();
outputStream.write(buffer);
outputStream.close();
request.connect();

4) 处理响应

在此之后,您将收到代码 308 的响应(如果成功).此响应包含一个 range 标头(已提及).

4) Handle Response

After this you will get a response with code 308 (if successful). This response contains a range header (mentioned).

HTTP/1.1 308 Resume Incomplete
Content-Length: 0
Range: bytes=0-524287

您将其拆分并获取新的块起始字节.

You split this up and obtain your new chunk start byte.

 String range = chunkUploadConnection.getHeaderField("range");
    int chunkPosition = Long.parseLong(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1;

5) 响应码不是308?!

您可能会收到 5xx 响应.您的互联网连接可能会失败,文件可能会在上传过程中被删除/重命名,等等.别担心.只要您保存会话 URI 和块起始字节,您就可以随时恢复上传.

5) The Response Code Is Not 308?!

It can happen that you get a 5xx response. Your internet connection could fail, the file could be deleted/renamed during upload, etc. etc. Don't worry. As long as you save your session URI and your chunk start byte, you can resume the upload anytime.

为此,请发送以下格式的标头:

In order to do that, send a header of the following form:

PUT {session_uri} HTTP/1.1
Content-Length: 0
Content-Range: bytes */TotalFileLength


URL url = new URL(sessionUri);
HttpURLConnection request = (HttpURLConnection) url.openConnection();
request.setRequestMethod("PUT");
request.setDoOutput(true);
request.setConnectTimeout(10000);
request.setRequestProperty("Content-Length", "0");
request.setRequestProperty("Content-Range", "bytes */" + file.length());
request.connect();

然后您将收到一个带有 range 标头的 308,您可以从中读取最后上传的字节(就像我们在上面所做的那样).取这个数字并重新开始循环.

You will then receive a 308 with a range header, from which you can read the last uploaded byte (just as we did above). Take this number and start to loop again.

我希望我能帮助你们中的一些人.如果您还有其他问题,请在评论中提出,我会编辑答案.

I hope I could help some of you. If you have any more questions, just ask in the comments and I will edit the answer.

相关文章