Azure / azure-storage-android

Microsoft Azure Storage Library for Android
Apache License 2.0
81 stars 47 forks source link

Geting a Storage Exception when trying to upload a file #23

Closed thib-rdr closed 8 years ago

thib-rdr commented 8 years ago

I have a problem while trying to upload a file from my app. I followed this tutorial, but nothing I do seems to work.

Here is my code :

private boolean doFileUpload(String sas) {
        boolean hasFailed = false;
        int i = 0;
        //Attempt 3 times to upload file
        while (!hasFailed) {
            StorageCredentials cred = new StorageCredentialsSharedAccessSignature(sas);
            try {
                String[] split = sas.split("/");
                URI imageUri = new URI((BuildConfig.API_STORAGE_URL + "/" + split[3] + "/" + split[4] + "/" + file.getGuid().toLowerCase() + "." + file.getExtension()).toLowerCase());
                LogUtil.logw(TAG, imageUri);
                CloudBlockBlob blobFromSASCredential = new CloudBlockBlob(imageUri, cred);
                blobFromSASCredential.uploadFromFile(file.getFullFilePath());
                return true;
            } catch (Exception ex) {
                ErrorManager.crash(TAG, ex);
                LogUtil.logw(TAG, file);
                System.gc();
            }

            if (i == 3) {
                hasFailed = true;
            }
            i++;
        }
        return false;
    }

And here is the exception message :

com.microsoft.azure.storage.StorageException: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
            at com.microsoft.azure.storage.core.StorageRequest.materializeException(StorageRequest.java:305)
            at com.microsoft.azure.storage.core.ExecutionEngine.executeWithRetry(ExecutionEngine.java:175)
            at com.microsoft.azure.storage.blob.CloudBlockBlob.uploadFullBlob(CloudBlockBlob.java:826)
            at com.microsoft.azure.storage.blob.CloudBlockBlob.upload(CloudBlockBlob.java:779)
            at com.microsoft.azure.storage.blob.CloudBlob.uploadFromFile(CloudBlob.java:1964)
            at com.microsoft.azure.storage.blob.CloudBlob.uploadFromFile(CloudBlob.java:1936)
            at com.x.y.api.FileUploaderTask.doFileUpload(FileUploaderTask.java:134)

This is driving me pretty nuts, any help would be appreciated...

Thanks !

emgerner-msft commented 8 years ago

First of all, I think I found in the sample the code you're trying to use but it doesn't look too similar to what you wrote above. If you double check that sample, the SAS token is a separate param from the blob URL. The a SAS token shouldn't even have "/" in it -- it's just a query value. So, why are you splitting the SAS and putting in it in the credentials? Is this a URL or just a plain SAS token? The URL passed to CloudBlockBlob should just be the plain old URL to the blob, and the credentials should just take the SAS token (not the full URL).

This first part is likely where things are messed up, but also check the SAS token itself: 1) Was this upload was done after the expiry time? 2) If start time was given (usually unnecessary), was this request done before or close to the start time? If so, check for clock skew on the dev box and/or don't specify start time if you can control that. 3) Does the SAS give you write permissions? 4) Does the SAS give you access to the blob you're trying to access, or the container the blob is in.

thib-rdr commented 8 years ago

Hi, thanks for the answer. 1) Was this upload was done after the expiry time?

Yes, it is done a few ms after getting the SAS. 2) If start time was given (usually unnecessary), was this request done before or close to the start time? If so, check for clock skew on the dev box and/or don't specify start time if you can control that.

I dont specify a start time.

3) Does the SAS give you write permissions?

Yes, because when I upload "by hand", it works.

4) Does the SAS give you access to the blob you're trying to access, or the container the blob is in.

Same answer as 3), when I upload with code right underneath it works.

try {
                //Get the rocket data
                FileInputStream fis = new FileInputStream(file.getFullFilePath());
                int bytesRead = 0;
                MyByteArrayOutputStream bos = new MyByteArrayOutputStream();
                byte[] b = new byte[1024];
                while ((bytesRead = fis.read(b)) != -1) {
                    final int copy = bytesRead;
                    bos.write(b, 0, bytesRead);
                    ((Activity) context).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            ViewHelper.updateSyncDialogUpload(status, copy);
                        }
                    });
                }
                byte[] bytes = bos.toByteArray();
                fis.close();
                length = bytes.length;
                URL url = new URL(sas);
                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
                urlConnection.setDoOutput(true);
                urlConnection.setConnectTimeout(Consts.AZURE_CONNECTION_TIMEOUT);
                urlConnection.setReadTimeout(Consts.AZURE_CONNECTION_TIMEOUT);
                urlConnection.setRequestMethod("PUT");
                urlConnection.addRequestProperty("Content-Type", file.getMimeType());
                urlConnection.setRequestProperty("Content-Length", "" + length);
                urlConnection.setRequestProperty("x-ms-blob-type", "BlockBlob");
                Log.d(TAG, "Upload started " + file.getFilename() + " " + file.getMimeType());
                // Write image data to server
                DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
                wr.write(bytes);
                wr.flush();
                wr.close();
                int response = urlConnection.getResponseCode();
                Log.w(TAG, "Response code " + response);
                //If we successfully uploaded, return true
                if (response == 201
                        && urlConnection.getResponseMessage().equals("Created")) {
                    return true;
                }
            } catch (Exception ex) {
                ErrorManager.crash(TAG, ex);
                System.gc();
            }

My sas looks like that :

https://myapp.blob.core.windows.net:443/projectea8fafab-36b8-41c7-9bb9-33d5d9032f7f/a7faba58-7df0-446b-8b13-de78e56ef726/filename.mp4?se=2016-03-25T19%3A47%3A34Z&sp=w&sv=2014-02-14&sr=b&sig=kP%2BDtcV6Zlr8PhxD9aREb7CGzjkRRgTavwXp5WpY3gU%3D

Thanks

emgerner-msft commented 8 years ago

Good to know the SAS is working! To make progress then please address the first paragraph of my response which goes into the problems with your code using the library.

thib-rdr commented 8 years ago

Hello, and thanks for your answer.

As I was saying, I used the code found here, but the explanation around it are kinda light in my opinion. I thought that the field imageUri should be the final URL of the image after the upload, but it seems that I was wrong. That is what this snippet is supposed to do :

String[] split = sas.split("/");
URI imageUri = new URI((BuildConfig.API_STORAGE_URL + "/" + split[3] + "/" + split[4] + "/" + file.getGuid().toLowerCase() + "." + file.getExtension()).toLowerCase());

Please remember that my API gives me a SAS looking like that : https://myapp.blob.core.windows.net:443/projectea8fafab-36b8-41c7-9bb9-33d5d9032f7f/a7faba58-7df0-446b-8b13-de78e56ef726/filename.mp4?se=2016-03-25T19%3A47%3A34Z&sp=w&sv=2014-02-14&sr=b&sig=kP%2BDtcV6Zlr8PhxD9aREb7CGzjkRRgTavwXp5WpY3gU%3D

So my question is, what should I input in the CloudBlockBlob, besides the StorageCredentialsSharedAccessSignature, to make it work ?

CloudBlockBlob blobFromSASCredential = new CloudBlockBlob(imageUri, cred);

Thanks very much for your help :)

emgerner-msft commented 8 years ago

The reason the constructors in the library take just the SAS token is that our methods to generate SAS tokens return only the token. My first thought is that it would be optimal to return something different in your app. However, I realize that one cannot always change these things.

In general the CloudBlockBlob(URI) constructor is intended for unauthenticated access -- AKA public containers and blobs. However, does work for SAS as well with a couple caveats. First, make sure you use a URI constructor that won't re-encode your URL since it already looks encoded. Second, it removes some extraneous query params, though looking at your URL I don't think this will affect you. So, try just directly constructing a block blob with your full URI including SAS.

CloudBlockBlob blobFromSASCredential = new CloudBlockBlob(new URI(sas));
thib-rdr commented 8 years ago

YES ! It works !

Thank you very much. If I may, I think this could be specified in the doc :)

emgerner-msft commented 8 years ago

Glad you got it. What would've made it clearer for you?

emgerner-msft commented 8 years ago

Closing as this looks resolved. Feel free to reopen this for related questions, or open up a new issue if needed.