【问题标题】:Resumable upload in Drive Rest API V3Drive Rest API V3 中的可恢复上传
【发布时间】:2017-02-14 16:27:33
【问题描述】:

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

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

  1. 启动可恢复会话
  2. 保存可恢复会话 URI
  3. 上传文件

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

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

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 示例来执行可恢复上传。

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

【问题讨论】:

  • 您能编辑/指定您的明确问题吗?
  • @Nick Bell,完成!!!

标签: java android google-drive-api


【解决方案1】:

如果您能够获得 200 Http 状态,它将提供 Location 作为标头的一部分。但是从我在您的System.print 上看到的,没有HttpResponse.getHeader,这可能只是一个错字,您指的是HttpResponse.getHeaders

如果是这种情况,我建议先确定你是否有200 OK Http 状态码,然后循环getAllheaders 以确定是否列出了Location 标头。

希望这会有所帮助!

【讨论】:

【解决方案2】:

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

不要对所有东西都使用 Drive REST API

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

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

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

0) 初始化

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

1) 启动可恢复会话

遵循 Google 在this document 中列出的规则:

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 类型并不那么重要,没有它也可以工作(this SO answer 提供了一个很好的函数来从路径中检索它)。将X-Upload-Content-Length 设置为文件的总长度。将Content-Type 设置为 JSON 格式,因为我们的正文将以 JSON 格式为 Google 提供元数据。

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

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 保存在某处(数据库、文本文件等)。你以后会需要它的。

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))。

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

然后您创建一个FileInputStream 并将位置设置为您的起始字节(您从上一个响应range 标头中获得)并将另一个块读入您的缓冲区。然后将此缓冲区写入连接输出流。最后,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 标头(已提及)。

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

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

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

5) 响应码不是308?!

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

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

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,您可以从中读取最后上传的字节(就像我们上面所做的那样)。取这个数字并重新开始循环。

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

【讨论】:

  • 我收到响应 400 和 chunkUploadConnection.getHeaderField("range");正在返回 null。请帮忙
  • Response 400 "Bad Request" 意味着您的请求在某种程度上是无效的。你确定你正确设置了所有的标题字段吗?
  • @Ali 我对最后一个块也有 400 Bad request,因为最后一个块的 Content-Range 不正确。我没有注意到file.length() 返回字节数,所以最后一个字节的数字将是file.length() -1。希望这条评论有一天会对某人有所帮助)
  • 嗨@Marco7757。您的回答很好,因为您确实使用显式 Java 代码解释了可恢复上传的所有流程。但是,我们可以将其视为容易出错,因为是“手动”完成的。我在回答中建议使用 Google 提供的 MediaHttpUploader 并实现您描述的所有逻辑。希望能帮助到你。加油!
【解决方案3】:

也许这个https://github.com/PiyushXCoder/google-drive-ResumableUpload/blob/master/ResumableUpload.java 可以帮助你。但是,它是为 servlet 编写的,但您可以轻松地为 android 修改它。

好吧,在拿到 cmets 之后,让我补充一些说明。

然而,“ResumableUpload.java”github repo 链接有很好的注释,足以让您清楚如何在 google drive 上执行此上传。而且,您实际上不需要阅读这么长的说明。

正如谷歌在https://developers.google.com/drive/v3/web/resumable-upload 中描述的关于如何执行可恢复上传的内容

  • 我们需要发出一个 POST 请求来通知服务器有关此上传的信息,并获取我们将向其发送文件数据块的会话 URI。是的,我们需要访问令牌来执行这个请求(这里,凭证的对象有访问令牌,我们将使用它)。这个请求是通过这个方法执行的:
public String requestUploadUrl(HttpServletRequest request, HttpServletResponse response, Credential credential, com.google.api.services.drive.model.File jsonStructure) throws MalformedURLException, IOException { URL url = 新 URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable"); HttpURLConnection 请求 = (HttpURLConnection) url.openConnection(); req.setRequestMethod("POST"); req.setDoInput(true); req.setDoOutput(true); req.setRequestProperty("授权", "承载" + credential.getAccessToken()); req.setRequestProperty("X-Upload-Content-Type", jsonStructure.getMimeType()); req.setRequestProperty("X-Upload-Content-Length", String.valueOf(jsonStructure.getSize())); req.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); String body = "{ \"name\": \""+jsonStructure.getName()+"\" }"; req.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", body.getBytes().length)); 输出流输出流 = req.getOutputStream(); outputStream.write(body.getBytes()); outputStream.close(); req.connect(); 字符串 sessionUri = null; if (req.getResponseCode() == HttpURLConnection.HTTP_OK) { sessionUri = req.getHeaderField("位置"); } 返回会话Uri; }
  • 现在,当我们获得会话 URI 后,我们可以继续发送请求文件的数据,这是明智的。而且,让我们为每个块执行 PUT 请求。每个卡盘的大小应为 256KB 的倍数。以下方法可用于每个块。
public int uploadFilePacket(HttpServletRequest request, HttpServletResponse response, String sessionUri, com.google.api.services.drive.model.File jsonStructure, java.io.File file, long chunkStart, long uploadBytes) 抛出 MalformedURLException, IOException { URL url1 = 新 URL(sessionUri); HttpURLConnection req1 = (HttpURLConnection) url1.openConnection(); req1.setRequestMethod("PUT"); req1.setDoOutput(true); req1.setDoInput(true); req1.setConnectTimeout(10000); req1.setRequestProperty("内容类型", jsonStructure.getMimeType()); req1.setRequestProperty("内容长度", String.valueOf(uploadBytes)); req1.setRequestProperty("Content-Range", "bytes" + chunkStart + "-" + (chunkStart + uploadBytes -1) + "/" + jsonStructure.getSize()); OutputStream outstream = req1.getOutputStream(); 字节[] 缓冲区 = 新字节[(int) uploadBytes]; FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.getChannel().position(chunkStart); if (fileInputStream.read(buffer, 0, (int) uploadBytes) == -1); 文件输入流.close(); outstream.write(缓冲区); outstream.close(); req1.connect(); 返回 req1.getResponseCode(); }

以下方法上传一个文件,将其分成块。

public void uploadFile(HttpServletRequest request, HttpServletResponse response, Credential credential, com.google.api.services.drive.model.File jsonStructure, java.io.File file) throws IOException, UploadFileException { String sessionUrl = requestUploadUrl(request, response, credential, jsonStructure); for(long i = 1, j = CHUNK_LIMIT;i = jsonStructure.getSize()) { j = jsonStructure.getSize() - i + 1; } int responseCode = uploadFilePacket(request, response, sessionUrl, jsonStructure, file, i-1, j); if(!(responseCode == OK || responseCode == CREATED || responseCode == INCOMPLETE)) throw new UploadFileException(responseCode); } }

就是这样。

【讨论】:

【解决方案4】:

您不必关心所有这些逻辑。 documentation 确实解释了完成可恢复上传的流程,但如果“手动”完成,则容易出错。
幸运的是,谷歌公开了一个专门的类来处理这种情况,即MediaHttpUploader

这段代码的 sn-p 完成了在驱动器上可恢复上传的工作(在 GCS 上可以实现同样的事情):

public class Main {

private static final JacksonFactory JSON_FACTORY = new JacksonFactory();
private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final MemoryDataStoreFactory DATA_STORE = new MemoryDataStoreFactory();

public static void main(String... args) throws IOException {


    Credential credential = authorize();

    MediaHttpUploader mediaHttpUploader = new MediaHttpUploader(new FileContent("application/json", Paths.get("/path/to/foo.json").toFile()), HTTP_TRANSPORT, credential);
    mediaHttpUploader.setProgressListener(uploader -> System.out.println("progress: " + uploader.getProgress()));
    GenericUrl genericUrl = new GenericUrl(new URL("https://www.googleapis.com/upload/drive/v3/files?name=toto"));
    GenericJson data = new GenericJson();
    data.put("name", "title");


    JsonHttpContent jsonHttpContent = new JsonHttpContent(JSON_FACTORY, data);
    mediaHttpUploader.setMetadata(jsonHttpContent).upload(genericUrl);

    System.out.println("Finished");
}

private static Credential authorize() throws IOException {
    // load client secrets
    try (BufferedReader br = Files.newBufferedReader(Paths.get(Resources.getResource("client_secret.json").getPath()))) {
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, br);

        // set up authorization code flow
        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets,
                Collections.singleton(DriveScopes.DRIVE))
                .setAccessType("offline")
                .setDataStoreFactory(DATA_STORE).build();
        // authorize
        return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");

    }
}

}

请注意,我们没有提到位置。所有逻辑都隐藏在MediaHttpUploader 类中。
所以我并没有真正回答这个问题(在哪里可以找到“位置”),但我指出了这样一个事实,即在使用 Google 库中的类时并不需要这样做(而且我很确定存在其他第三方库来做同样的工作) .

更新: mediaHttpUploader 是 Drive v3 客户端在后台使用的。 所以我们可以考虑这样的事情:

      File fileMetadata = new File();
    fileMetadata.setName(UPLOAD_FILE.getName());

    FileContent mediaContent = new FileContent("image/jpeg", UPLOAD_FILE);

    Drive.Files.Create insert = drive.files().create(fileMetadata, mediaContent);
    MediaHttpUploader uploader = insert.getMediaHttpUploader();
    uploader.setDirectUploadEnabled(false);
    uploader.setProgressListener(new FileUploadProgressListener());
    return insert.execute();

【讨论】:

  • 我认为这是最好的方法,我使用另一个 MediaHttpUploader - 答案组合。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-13
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多