可恢复&多部分文件上传到google drive使用Http.最后一个chunk溢出错误。

问题描述 投票:1回答:1

因此,我使用的准则为resumablemultipart的文件上传到谷歌驱动器的规格的谷歌文档。此处

一切都很顺利,我很早以前就写了这段代码,它曾为这么多文件工作了很长时间,直到昨天我试着用同样的代码上传一个样本文件,这不是256KB的倍数[又是google docs指定的]。

以下是相关课程

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) Session :- 用于重新驱动一个可恢复的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)Uploader:-将文件中的数据上传到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" 但它从来没有这样做,而且 文件永远不会显示在我的谷歌驱动器。

正如你已经可以猜到这个代码工作的文件lt;256KB或它的倍数,这就是为什么我从来没有看到任何问题,这个代码,直到现在。

我发布了所有的类,所以任何人都可以复制和测试它为自己,但主要的问题是。我怀疑是在Uploader类一个nd这就是我需要你的帮助。

java google-drive-api multipartform-data httpurlconnection
1个回答
1
投票

我终于找到了你的问题,从一块你的输出。

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- ....

1
投票

这是对 @Raserhin 答案,这对我的工作。我只是把完整的代码贴在这里。

变化

1)将uploadPosition改名为driveIndex。

2)Sent Range不返回每个chunk上传的字节数,而是返回每个chunk上传的文件的EOF指针[0-上传文件的最后一个索引位置]。

3) driveIndex 应与返回的Range

4) 一个 localPosition 应该在while循环中引入,以便从前一个上传的继承处读取字节。

代码

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
© www.soinside.com 2019 - 2024. All rights reserved.