我正在尝试使用Android中的驱动器API创建可恢复的上传会话。
根据文档,需要遵循的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]
}
我没有在响应头中找到Location URI来开始上传文件中指定的filedata,也没有找到任何Java样本来执行可恢复上传。
如何检索文档中指定的位置URI?
答案 0 :(得分:5)
我正在尝试一周的大部分时间,我终于获得了可恢复的上传。它没有按照我预期的方式工作,但确实有效。
据我所知,据我所知,Google Drive REST API无法真正进行分块上传。这可能是一个错误,也可能是设计错误。我也可能太傻了。
但让我想到的是我无法在任何地方看到代码示例 。每个人都在谈论Http
标题。所以这就是我们下面要做的。我们只使用标题。
以下是您使用Google Drive REST API和Android进行可恢复,分块上传的方式:
String accountName = "account_name";
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, Arrays.asList(SCOPES)).setBackOff(new ExponentialBackOff()).setSelectedAccountName(accountName);
遵循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();
最后,connect()
并等待回复。如果响应代码为200
,则表示您已成功启动了一个分块,可恢复的上传。现在将location
标头URI保存在某处(数据库,文本文件,等等)。你以后会需要它。
if (request.getResponseCode() == HttpURLConnection.HTTP_OK) {
String sessionUri = request.getHeaderField("location");
}
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))。
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();
在此之后,您将收到代码为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;
您可能会收到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();
然后,您将收到一个带有308
标题的range
,您可以从中读取上次上传的字节(就像我们上面所做的那样)。拿这个号码然后再开始循环。
我希望我可以帮助你们中的一些人。如果您还有其他问题,请在评论中提问,我将编辑答案。
答案 1 :(得分:4)
你不必关心所有这些逻辑。 documentation确实解释了完成可恢复上传的流程,但如果完成并且#34;手动"它很容易出错。
幸运的是,谷歌公开了一个专门的类来处理这种情况,即MediaHttpUploader
。
这段代码完成了驱动器上可恢复上传的工作(在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();
答案 2 :(得分:1)
也许这https://github.com/PiyushXCoder/google-drive-ResumableUpload/blob/master/ResumableUpload.java会帮助你。但是,它是为servlet编写的,但您可以轻松地为它修改它。
嗯,在收到评论后,让我加上一些额外的描述。
然而," ResumableUpload.java" github repo链接评论很好,它足以让你清楚如何在谷歌驱动器上执行此上传。而且,您实际上并不需要阅读这篇长篇描述。
如谷歌https://developers.google.com/drive/v3/web/resumable-upload所述,如何执行可恢复上传
public String requestUploadUrl(HttpServletRequest request, HttpServletResponse response, Credential credential, com.google.api.services.drive.model.File jsonStructure) throws MalformedURLException, IOException { URL url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable"); HttpURLConnection req = (HttpURLConnection) url.openConnection(); req.setRequestMethod("POST"); req.setDoInput(true); req.setDoOutput(true); req.setRequestProperty("Authorization", "Bearer " + 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)); OutputStream outputStream = req.getOutputStream(); outputStream.write(body.getBytes()); outputStream.close(); req.connect(); String sessionUri = null; if (req.getResponseCode() == HttpURLConnection.HTTP_OK) { sessionUri = req.getHeaderField("location"); } return sessionUri; }
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) throws MalformedURLException, IOException { URL url1 = new URL(sessionUri); HttpURLConnection req1 = (HttpURLConnection) url1.openConnection(); req1.setRequestMethod("PUT"); req1.setDoOutput(true); req1.setDoInput(true); req1.setConnectTimeout(10000); req1.setRequestProperty("Content-Type", jsonStructure.getMimeType()); req1.setRequestProperty("Content-Length", String.valueOf(uploadBytes)); req1.setRequestProperty("Content-Range", "bytes " + chunkStart + "-" + (chunkStart + uploadBytes -1) + "/" + jsonStructure.getSize()); OutputStream outstream = req1.getOutputStream(); byte[] buffer = new byte[(int) uploadBytes]; FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.getChannel().position(chunkStart); if (fileInputStream.read(buffer, 0, (int) uploadBytes) == -1); fileInputStream.close(); outstream.write(buffer); outstream.close(); req1.connect(); return 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); } }
这就是全部。
答案 3 :(得分:0)
如果您能够获得200 Http状态,它将提供Location
作为标题的一部分。但是,从我在System.print
上看到的情况来看,没有HttpResponse.getHeader
,这可能只是一个错字而你指的是HttpResponse.getHeaders
。
如果是这种情况,我建议首先确定您是否获得了200 OK
Http状态代码,并循环getAllheaders
以确定是否列出了Location
标头。< / p>
希望这有帮助!