使用Http将可断点续和多部分文件上传到Google云端硬盘。 最后一个块溢出错误



所以我根据谷歌文档的规范使用将文件恢复/分段上传到谷歌云端硬盘的准则

这里一切都很好,我很久以前就写了这段代码,它已经适用于这么多文件很长时间了,直到昨天我尝试使用相同的代码上传一个不是 256KB 倍数的示例文件[再次从谷歌文档中指定]

以下是相关类

1( URLMaker :- HttpUrlFactory 用于不断的块上传

/*A Utility Class for constantly creating URL connections for each chunk upload as an instance can be used to send only one request */
final class URLMaker 
{
static HttpURLConnection openConnection(String session, String method, String[] params, String[] values, boolean input, boolean output)throws IOException
{
HttpURLConnection request = (HttpURLConnection)new URL(session).openConnection();
request.setRequestMethod(method);
request.setDoInput(input);
request.setDoOutput(output);
request.setConnectTimeout(10000);
//params & values array are of equal length so just one->one key:value pass it as headers
for(int i=0;i<params.length;i++){request.setRequestProperty(params[i],values[i]);}
return request;
}
}

2( 会话 :- 用于检索我保存到文件中并在以后读取的可恢复 uri 会话

final class Session
{
private static final File SESSION=new File("E:/Session.txt");
private static String read()
{
try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream(SESSION)))
{
URISession session=(URISession)ois.readObject();
return session.uri;
}
catch(Exception ex){return null;}
}
static String makeSession(String token,File file)throws Exception
{
if(SESSION.exists()){return read();} //If I Previously saved an resumable URI reload that from file
String body = "{"name": "" + file.getName() + ""}";
String fields[]=new String[5];
fields[0]="Authorization";
fields[1]="X-Upload-Content-Type";
fields[2]="X-Upload-Content-Length";
fields[3]="Content-Type";
fields[4]="Content-Length";
String values[]=new String[5];
values[0]="Bearer "+token;
values[1]="*audio/mp3*";           //generic octet stream
values[2]=String.format(Locale.ENGLISH, "%d",file.length());
values[3]="application/json; charset=UTF-8";
values[4]=String.format(Locale.ENGLISH, "%d", body.getBytes().length);
HttpURLConnection request =URLMaker.openConnection("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable"
,"POST"
,fields,values, true, true);
try(OutputStream outputStream = request.getOutputStream()){outputStream.write(body.getBytes());}
request.connect();
if(request.getResponseCode()==HttpURLConnection.HTTP_OK)
{
String uri=request.getHeaderField("location");
SESSION.createNewFile();
write(uri);
return uri;
}
else
{
System.err.println(request.getResponseCode()+"/"+request.getResponseMessage());
System.out.println(new String(request.getErrorStream().readAllBytes()));
}
return null;
}
private static void write(String uri)
{
try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(SESSION)))
{
oos.writeObject(new URISession(uri));
}
catch(Exception ex){ex.printStackTrace(System.out);}
}
private static class URISession implements Serializable
{
private final String uri;
private URISession(String uri){this.uri=uri;}
}
}

我的问题所在的班级

3(上传者 :- 将数据从文件上传到 uri

final class Uploader 
{
private static final int
KB=1024,
_256KB=256*KB; 
public static void uploadFile(String session,File file)throws IOException
{
long uploadPosition,bytesDone,workLeft;
if((uploadPosition=getLastUploadedByte(session,file.length()))==-1){return;} //read the last uploaded byte position[0-length-1 value] if this upload previously failed due to network/server issues
bytesDone=uploadPosition;  //if +ve value it means my work is a little less
String
fields[]={"Content-Type","Content-Length","Content-Range"},
values[]=new String[]{"audio/mp3*","",""}; 
try(RandomAccessFile raFile = new RandomAccessFile(file,"r"))
{
raFile.seek(uploadPosition); 
byte buffer[]=new byte[_256KB];
while((workLeft=raFile.read(buffer))!=-1)  //read certain amount of work/or bytes to be uploaded
{
int code=0;
while(workLeft>0)// it is possible that google might not have uploaded all my bytes I have sent so I keep trying until what I have read has been completely uploaded
{
values[1]=String.format(Locale.ENGLISH, "%d",workLeft);
values[2]="bytes "+uploadPosition+"-"+(uploadPosition + workLeft - 1)+"/"+file.length();
System.out.println("Specified Range="+values[1]+"/"+values[2]);
HttpURLConnection request=URLMaker.openConnection(session,"PUT",fields,values, true, true);
try(OutputStream bytes=request.getOutputStream()){bytes.write(buffer,0,(int)workLeft);}  
request.connect();
code=request.getResponseCode();
if(code==308)   //Means successful but more data needs to be uploaded
{
String range = request.getHeaderField("range");
int bytesSent=Integer.parseInt(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1
System.out.println("Sent Range="+range);
workLeft-=bytesSent;  //Work Reduced By this many bytes
bytesDone+=bytesSent; //Progress increased by this many bytes
System.out.println("Bytes="+bytesDone+"/"+file.length());
}
else if(code!=200)
{
System.err.println("Upload Error:"+request.getResponseMessage());
System.err.println("Upload Error Msg:"+new String(request.getErrorStream().readAllBytes()));
code=-1;
break;
}
request.disconnect();
}
if(code==-1){return;}
else if(code==200 || code==201){System.out.println("Upload Completed");}
} 
}
}
private static long getLastUploadedByte(String session,long fileLength)throws IOException
{
HttpURLConnection request = URLMaker.openConnection(session
,"PUT"
,new String[]{"Content-Length","Content-Range"}
,new String[]{"0","bytes */" + fileLength},true,true);
try(OutputStream output=request.getOutputStream()){output.flush();}
request.connect();
long chunkPosition=-1;
if(request.getResponseCode()==308 || request.getResponseCode()==200)
{
String range = request.getHeaderField("range"); //previously sent byte position[ it's a 0-length-1 value]
if(range==null){return 0;} //suppose it's my first time ever using this uri then there would have been no bytes uploaded and hence no range header
chunkPosition = Long.parseLong(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1;//format[0-value]
}
else
{
System.err.println("Seek Error:"+request.getResponseCode()+"/"+request.getResponseMessage());
System.err.println("Seek Error Msg:"+new String(request.getErrorStream().readAllBytes()));
}
request.disconnect();
return chunkPosition;
}
}

和我的主要班级

class FileUpload 
{
private static final Credential getCredentials(NetHttpTransport HTTP_TRANSPORT,String authorize,Collection<String> SCOPES,boolean clearTokens)throws IOException,GeneralSecurityException
{
// Load client secrets.    
InputStream in = GDrive.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
if (in == null){throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);}
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT,JSON_FACTORY,clientSecrets,SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new File("tokens")))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow,receiver).authorize(authorize);
}
public static Credential makeCredentials(String authorize,Collection<String> SCOPES,boolean clearTokens)throws IOException,GeneralSecurityException
{
NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
return getCredentials(HTTP_TRANSPORT,authorize,SCOPES,clearTokens);
}
public static void main(String args[])throws Exception
{
Credential credentials=makeCredentials("user",List.of(DriveScopes.DRIVE_FILE));//authorize,scopes
File media=new File("E:\Music\sample.mp3");
String session=Session.makeSession(credentials.getAccessToken(),media);
System.out.println("Session="+session);
if(session==null){return;}
Uploader.uploadFile(session,media);
}
} 

现在问题是我有一个 2.01 MB(21,13,939 字节(,您甚至可以下载该文件并从这里自己测试或使用任何类似的文件

这是日志文件

Specified Range=262144/bytes 0-262143/2113939 Ok so I start of good uploading the first 256KB of data at every step
Sent Range=bytes=0-262143
Bytes=262144/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=524288/2113939
Specified Range=262144/bytes 0-262143/2113939   //No Problems here still an multiple of 256KB
Sent Range=bytes=0-262143
Bytes=786432/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=1048576/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=1310720/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=1572864/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=1835008/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=2097152/2113939
//       HERE IS WHERE LOGIC IS THROWN OUT THE WINDOW  THE LAST CHUNK   //
Specified Range=16787/bytes 0-16786/2113939  <---- I am clearly telling google that this is the last 16786 bytes of data of my file 
Sent Range=bytes=0-262143         <---- But google dosen't seem to care and uploads the whole 256KB byte array along with the garbage data from the previous read
Bytes=2359296/2113939     <--- And now I have uploaded more bytes than the actual file itself [format is bytesDone/fileSize]

在应用程序退出之前我得到的最后一个响应代码没有错误308我希望看到"Upload Completed"的消息,但它永远不会出现,并且该文件永远不会出现在我的 Google 云端硬盘上。

正如您已经猜到的那样,此代码适用于<256KB或其倍数的文件,这就是为什么直到现在我从未看到此代码出现任何问题的原因

我发布了所有类,以便任何人都可以自己复制和测试它,但我怀疑的主要问题在于 Uploader 类,这是我需要您帮助的地方。

我终于从你的输出中找到了你的问题:

Specified Range=262144/bytes 0-262143/2113939 Ok so I start of good uploading the first 256KB of data at every step
Sent Range=bytes=0-262143
Bytes=262144/2113939
Specified Range=262144/bytes 0-262143/2113939
Sent Range=bytes=0-262143
Bytes=524288/2113939
Specified Range=262144/bytes 0-262143/2113939   //No Problems here still an multiple of 256KB
Sent Range=bytes=0-262143
Bytes=786432/2113939
Specified Range=262144/bytes 0-262143/2113939

查看您的代码,实际进入Content-Range的部分是values数组的第三个位置:

values[2]="bytes "+uploadPosition+"-"+(uploadPosition + workLeft - 1)+"/"+file.length();

似乎您没有更新变量uploadPoistion因此您总是一遍又一遍地发送相同的范围。即使从驱动器获得的响应中,您也可以看到这一点:

String range = request.getHeaderField("range"); // This is always the same
int bytesSent=Integer.parseInt(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1
System.out.println("Sent Range="+range);
Sent Range=bytes=0-262143

此值在日志中一遍又一遍地重复,因此您只是通知前 262144 个字节的信息。

您应该尝试每次增加Content-Range以覆盖文件的所有字节:

bytes 0-262143/2113939
bytes 262144-524287/2113939
bytes 524288- ....

这是对我有用的@Raserhin答案的后续。我只是在这里发布完整的代码。

变化

1(重命名上传位置到驱动器索引

2( 发送范围不返回每个块上传的字节数,但返回每个块上传的文件的 EOF 指针[0- 上传文件的最后一个索引位置]

3(应分配driveIndex返回的范围

4( 应在 while 循环中引入localPosition,以便从上一个上传成功的地方读取字节

法典

public static void uploadFile(String session,File file)throws IOException
{
long driveIndex;
if((driveIndex=getLastUploadedByte(session,file.length()))==-1){return;}
String
fields[]={"Content-Type","Content-Length","Content-Range"},
values[]=new String[]{"audio/mp3","",""}; 
long fileIndex;
int localPos,workLeft;
int bytesLeft,bytesUploaded;
try(RandomAccessFile raFile = new RandomAccessFile(file,"r"))
{
raFile.seek(driveIndex); 
byte buffer[]=new byte[_256KB];
while((workLeft=raFile.read(buffer))!=-1)
{
int code=0;
localPos=0;
fileIndex=raFile.getFilePointer();
while(workLeft>0)
{
values[1]=String.format(Locale.ENGLISH, "%d",0);
values[2]="bytes "+driveIndex+"-"+(driveIndex + workLeft - 1)+"/"+file.length();
HttpURLConnection request=URLMaker.openConnection(session,"PUT",fields,values, true, true);
try(OutputStream bytes=request.getOutputStream()){bytes.write(buffer,localPos,workLeft);}
request.connect();
code=request.getResponseCode();
if(code==308)
{
String range = request.getHeaderField("range");
driveIndex=Long.parseLong(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1;
bytesLeft=(int)(fileIndex-driveIndex);
bytesUploaded=workLeft-bytesLeft;
workLeft-=bytesUploaded;           //bytes left to uploaded decreases
localPos+=(int)bytesUploaded;      //next read start's from the next chunk 
System.out.println(driveIndex+"/"+(file.length()-1));
}
else if(code==200)
{
System.out.println("Upload Completed");
break;
}
else
{
System.err.println("Upload Error:"+request.getResponseMessage());
System.err.println("Upload Error Msg:"+new String(request.getErrorStream().readAllBytes()));
code=-1;
break;
}
request.disconnect();
} 
if(code==-1 || code==200){break;}
} 
System.out.println("File Closed");
}
System.out.println("End");
}

对于任何想要解释逻辑的人来说,这是一个粗略的例子

Current=last uploaded byte index to drive [Assume finished at index 2]
FilePos=is the Filepointer after reading from Current[Here for example purpose we assume we read 5 bytes]

[2]       [7]
0,1,2,3,4,5,6,7,8,9 <--------- File=10
^         ^
|         |
Current    FilePos
Bytes read                                              = 2,3,4,5,6,7
Local pos                                               = 0
while(workLeft>0)
{
Cycle 1
Bytes read                                              = 2,3,4,5,6,7[6 Bytes,Last Index=7]
Work left                                               = 6
Bytes uploaded                                          = 2,3[Assume only 2 bytes were uploaded for explanation sake,Last Index=3]                                     

Num of bytes left[FilePos-UploadPos]                    = 7-3=>4
Number of bytes uploaded[Work Left-Num of bytes left]   = 6-4=>2
Work left-=Number of bytes uploaded                     = 6-2=>4
Local pos+=Number of bytes uploaded                     = 0+2=>2
Cycle 2
Bytes Read                                              = 2,3,4,5,6,7[Last Index=7]
Work left                                               = 4
Bytes uploaded                                          = -,-,4,5,6[Assume Only 3 bytes were uploaded,Last Index=6]
Num of bytes left[FilePos-UploadPos]                    = 7-6=>1
Number of bytes uploaded[Work Left-Num of bytes left]   = 4-1=>3
Work left-=Number of bytes uploaded                     = 4-3=>1
Local Pos+=Number of bytes uploaded                     = 2+3=>5
Cycle 3
Bytes Read                                              = 2,3,4,5,6,7[Last Index=7]
Work left                                               = 1
Bytes uploaded                                          = -,-,-,-,-,7[Last Byte Uploaded,Last Index=7]
Num of bytes left[FilePos-UploadPos]                    = 7-7=>0
Number of bytes uploaded[Work Left-Num of bytes left]   = 1-0=>1
Work left-=Number of bytes uploaded                     = 1-2=>0
Local Pos+=Number of bytes uploaded                     = 5+1=>6
}
//when workleft becomes 0 while loop break's

相关内容

最新更新