Drive Rest API V3 中的可恢复上传
我正在尝试使用 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
- 开始一个可恢复的会话
- 保存可恢复的会话 URI
- 上传文件
第 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
将以下代码放入循环中,直到上传整个文件.在每个块之后,您将收到带有代码 308
和 range
标头的响应.从这个 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.
相关文章