0

I've read lot of similar questions but did not find acceptable answer for me.

I'm writing simple drive uploading tasks for gradle to upload android .apk file after a successful build. I don't use Drive Java API, just REST requests.

This task retrieves token:

task getToken << {
  def keyStoreFile = file("SomeProject-PrivateKey.p12")
  def keyStorePass = "notasecret"
  def serviceAccountEmail = "someserviceemail@someproject.iam.gserviceaccount.com"

  def keyStore = java.security.KeyStore.getInstance("PKCS12")
  keyStore.load(keyStoreFile.newInputStream(), keyStorePass.toCharArray())
  def privateKey = keyStore.getKey(keyStore.aliases().nextElement(), keyStorePass.toCharArray())

  def JWTHeader = '{"alg":"RS256","typ":"JWT"}'
  def JWTClaimSet = '{\n' +
        '  "iss":' + serviceAccountEmail + ',\n' +
        '  "scope":"https://www.googleapis.com/auth/drive",\n' +
        '  "aud":"https://www.googleapis.com/oauth2/v4/token",\n' +
        '  "exp":' + (System.currentTimeSeconds() + 5 * 60) + ',\n' +   // + 5 minutes
        '  "iat":' + System.currentTimeSeconds() + '\n' +
        '}'
  def JWT = new String(Base64.urlEncoder.encode(JWTHeader.bytes)) +
        '.' + new String(Base64.urlEncoder.encode(JWTClaimSet.bytes))

  def signature = java.security.Signature.getInstance("SHA256withRSA")
  signature.initSign(privateKey)
  signature.update(JWT.bytes)
  JWT += '.' + new String(Base64.urlEncoder.encode(signature.sign()))

  System.out.print "assertion: " + JWT + "\n"

  def authUrl = new URL("https://www.googleapis.com/oauth2/v4/token")
  HttpURLConnection auth = authUrl.openConnection()
  auth.setRequestMethod("POST")
  auth.setRequestProperty("Host", "www.googleapis.com")
  auth.setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
  auth.setDoOutput(true)
  def body = 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer' +
        '&assertion=' + JWT
  auth.outputStream.write(body.bytes)
  def reader = new BufferedReader(new InputStreamReader(auth.inputStream))

  StringBuilder sb = new StringBuilder()
  for (int c; (c = reader.read()) >= 0;)
    sb.append((char) c)
  System.out.println(sb.toString())
}

And this one upload file to some public folder or folder shared with serviceemail@someproject.iam.gserviceaccount.com:

task uploadToDrive << {
  File apk = file("test1.txt")
  //    File apk = file("test2.apk")
  def url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart");

  HttpURLConnection drive = url.openConnection();
  drive.setRequestMethod("POST")
  drive.setRequestProperty("Host", "www.googleapis.com")
  drive.setRequestProperty("Authorization", "Bearer " + "ya29.ElnDA_idxn6dL4cji6Oh3dQbdCaMGtGfmIMwIDa4yrEgiL9G8I6qHeSoCNUwcfYESZiLBBaymYLWJQBuTgypqyOy_YVUNpwyd2Gf8rYLgAsSksNnruygoFBS9g")
  drive.setRequestProperty("Content-Type", "multipart/related; boundary=foo_bar_baz")

  String body = '\n' +
        '--foo_bar_baz\n' +
        'Content-Type: application/json; charset=UTF-8\n\n' +
        '{\n' +
        '  "name": "TEST",\n' +
        '  "parents": [ "0BzkpQECQo2d2SEo5OTd4RHpnOFE" ]\n' +
        '}\n\n' +
        '--foo_bar_baz\n' +
        'Content-Type: application/octet-stream\n\n'
  String end = '--foo_bar_baz--'

  drive.setFixedLengthStreamingMode(body.bytes.length + apk.length() + end.bytes.length)     // Content Length

  drive.setDoOutput(true)
  drive.setDoInput(true)
  drive.outputStream.write(body.bytes)
  apk.withInputStream { is ->
    def buffer = new byte[1024];
    int len;
    while ((len = is.read(buffer)) != -1) {
        drive.outputStream.write(buffer, 0, len);
    }
  }
  drive.outputStream.write(end.bytes)

  def reader = new BufferedReader(new InputStreamReader(drive.inputStream))
  StringBuilder sb = new StringBuilder()
  for (int c; (c = reader.read()) >= 0;)
    sb.append((char) c);
  System.out.println(sb.toString())
}

For small files it works well! But when I try to upload real .apk file or even ~1Mb image gradle fails:

Execution failed for task ':uploadToDrive'. Server returned HTTP response code: 400 for URL: https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart

Any ideas?

  • the official advice is to not use multipart uploads for large files. This is especially true for mobile. You should be using resumable upload. I don't know if this explains the 400, but is something you should be doing anyway. – pinoyyid Dec 30 '16 at 18:19
  • You might want to check [Handling API Errors](https://developers.google.com/drive/v3/web/handle-errors) which describes solutions when encountering error responses. Also try using [resumable upload](https://developers.google.com/drive/v3/web/manage-uploads#resumable) to check if you will still encounter the same error. Lastly you might want to check the related [SO post](http://stackoverflow.com/a/24459799/5995040), using an android specific API might help you as all of this is handled for you. – Mr.Rebot Jan 01 '17 at 15:13
  • Here is a GitHub from Google - [Google Drive Android API Demos](https://github.com/googledrive/android-demos) and a tutorial - [Integrate Google Drive in Android](https://www.numetriclabz.com/integrate-google-drive-in-android-tutorial/) this will help you understand code implementation for Google Drive in Android. Hope this information helps. – Mr.Rebot Jan 01 '17 at 15:13

1 Answers1

0

Not really an answer (why 400?) but it solves the problem. Thanks to Mr.Rebot & pinoyyid.

Resumable upload example that works for large files:

task uploadResumable << {
  File apk = file("dog.jpg")
  def url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");

  HttpURLConnection drive = url.openConnection();
  drive.setRequestMethod("POST")
  drive.setRequestProperty("Host", "www.googleapis.com")
  drive.setRequestProperty("Authorization", "Bearer " + "ya29.ElnDA_idxn6dL4cji6Oh3dQbdCaMGtGfmIMwIDa4yrEgiL9G8I6qHeSoCNUwcfYESZiLBBaymYLWJQBuTgypqyOy_YVUNpwyd2Gf8rYLgAsSksNnruygoFBS9g")
  drive.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
  drive.setRequestProperty("X-Upload-Content-Type", "image/jpeg")

  String body = '\n' +
        '{\n' +
        '  "name": "DOGresum",\n' +
        '  "parents": [ "0BzkpQECQo2d2SEo5OTd4RHpnOFE" ]\n' +
        '}\n\n';

  drive.setFixedLengthStreamingMode(body.bytes.length)     // Content Length
  drive.setDoOutput(true)
  drive.setDoInput(true)
  drive.outputStream.write(body.bytes)

  // response header example:
  //      -> Location: [https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&upload_id=AEnB...]
  // get "Location" header
  String loc = drive.getHeaderFields().get("Location")

  // trim '[' and ']' brackets and get URL query
  def query = new URL(loc[1..-2]).query

  // find upload_id value
  String uploadId;
  query.split('&').each {
    if (it.split('=')[0] == 'upload_id')
        uploadId = it.split('=')[1]
  }

  // start new upload
  def url2 = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&upload_id=" + uploadId);
  HttpURLConnection drive2 = url2.openConnection()
  drive2.setRequestMethod('POST')
  drive2.setRequestProperty("Content-Type", "image/jpeg")
  drive2.setFixedLengthStreamingMode(apk.bytes.length)     // Content Length
  drive2.setDoOutput true
  drive2.setDoInput true
  drive2.outputStream.write apk.bytes
}
Community
  • 1
  • 1