草庐IT

java - Drive Rest API V3 中的可恢复上传

coder 2023-11-22 原文

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

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

  1. 开始可续会
  2. 保存可恢复 session URI
  3. 上传文件

第 1 步:我使用以下代码启动可恢复 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 步:执行完成后,我将打印请求的响应 header 以查看 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]
}

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

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

最佳答案

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

不要对一切都使用 Drive REST API

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

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

下面是如何使用 Google Drive REST API 和 Android 进行可恢复的分块上传:

0) 初始化

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

1) 启动可恢复 session

遵循 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 变量获取授权 token 。 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) 保存可恢复 session URI

最后,connect() 并等待响应。如果响应代码为 200,则您已成功启动分块可续传上传。现在将 location header 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

将以下代码循环,直到上传整个文件。在每个 block 之后,您将收到代码为 308range header 的响应。从这个 range header ,您可以读取下一个 block 开始(请参阅 (4))。

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

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

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

您将其拆分并获得新的 block 起始字节。

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

5)响应码不是308?!

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

为此,请发送以下形式的 header :

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

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

关于java - Drive Rest API V3 中的可恢复上传,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39887303/

有关java - Drive Rest API V3 中的可恢复上传的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  9. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

  10. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

随机推荐