Resumable upload in Drive Rest API V3

前端 未结 4 1876
你的背包
你的背包 2020-12-05 16:00

I am trying to create a resumable upload session using drive rest API in Android.

As per the documentation the 3 steps needed to be followed are

  1. Start
4条回答
  •  情书的邮戳
    2020-12-05 16:34

    I was trying for the better part of a week now and I finally got the resumable uploads to run. It does not work how I expected it would, but it does work.

    Don't Use the Drive REST API for Everything

    What I learned is that the Google Drive REST API is, as far as I know, not really capable of making chunked uploads. This may be a bug or it may be by design. I may also be too stupid.

    But what got me thinking was that I could not see code examples anywhere. Everybody just talked about Http headers all the time. So this is what we're gonna do below. We'll use just the headers.

    So here is how you do resumable, chunked uploads with the Google Drive REST API and Android:

    0) Initialization

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

    1) Start a Resumable Session

    Follow the rules outlined by Google in 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"
    }
    

    Set all the header fields just like in Google's example. Send it as a POST request. Use your credential variable to get the authorization token. The mime type for X-Upload-Content-Type is not so important, it works without it too (this SO answer provides a nice function to retrieve it from a path). Set the X-Upload-Content-Length to the total length of your file. Set Content-Type to JSON format, since our body will provide the metadata for Google in the JSON format.

    Now create your metadata body. I put in a file name and a parent. Set the Content-Length to the length of your body in bytes. Then write your body to the request.getOutputStream() output stream.

    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) Save the Resumable Session URI

    Finally, connect() and wait for a response. If the response code is 200, you have successfully initiated a chunked, resumable upload. Now save the location header URI somewhere (database, text file, whatever). You're gonna need it later.

    if (request.getResponseCode() == HttpURLConnection.HTTP_OK) {
        String sessionUri = request.getHeaderField("location");
    }
    

    3) Upload the File

    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
    

    Put the following code in a loop, until the entire file is uploaded. After every chunk, you will get a response with code 308 and a range header. From this range header, you can read the next chunk start (see (4)).

    Content-Type is going to be the mime type again. Content-Length is the number of bytes you upload in this chunk. Content-Range needs to be of the form bytes startByte-EndByte/BytesTotal. You put this in a PUT request.

    Then you create a FileInputStream and set the position to your start byte (which you got from your last response range header) and read another chunk into your buffer. This buffer is then written to the connection output stream. Finally, 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) Handle Response

    After this you will get a response with code 308 (if successful). This response contains a range header (mentioned).

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

    You split this up and obtain your new chunk start byte.

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

    5) The Response Code Is Not 308?!

    It can happen that you get a 5xx response. Your internet connection could fail, the file could be deleted/renamed during upload, etc. etc. Don't worry. As long as you save your session URI and your chunk start byte, you can resume the upload anytime.

    In order to do that, send a header of the following form:

    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();
    

    You will then receive a 308 with a range header, from which you can read the last uploaded byte (just as we did above). Take this number and start to loop again.

    I hope I could help some of you. If you have any more questions, just ask in the comments and I will edit the answer.

提交回复
热议问题