3

我正在构建一个应用程序,该应用程序需要允许用户将大图像(最大约 100 MB)上传到 Windows Azure Blob 存储服务。在阅读了Rob Gillen 关于 Windows Azure 文件上传优化的优秀文章后,我借用了他的方法来并行上传文件块,在Parallel.For循环中使用CloudBlockBlob.PutBlock()方法(代码可在此处获得)。

我遇到的问题是,每当我尝试上传文件时,我都会从存储客户端收到“ InvalidMd5 ”异常。怀疑问题可能出在开发存储中,我还尝试对我的实时 Azure 存储帐户运行代码,但我得到了同样的错误。查看Fiddler的流量,我发现“ Content-MD5 ”标头设置为有效的 MD5 哈希。错误描述说“请求中指定的 MD5 值无效。MD5 值必须是 128 位和 Base64 编码。 ”,但据我所知,我在 Fiddler 中看到的值是有效的(例如a91c588092cedbdb1b82c2d3786fd509)。

这是我用于计算哈希的代码(由 Rob Gillen 提供):

public static string GetMD5HashFromStream(byte[] data)
{
    MD5 md5 = new MD5CryptoServiceProvider();
    byte[] retVal = md5.ComputeHash(data);

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < retVal.Length; i++)
    {
        sb.Append(retVal[i].ToString("x2"));
    }
    return sb.ToString();
}

这是对 PutBlock() 的实际调用:

blob.PutBlock(transferDetails[j].BlockId, new MemoryStream(buff), blockHash, options);

我也尝试像这样传递哈希:

Convert.ToBase64String(Encoding.UTF8.GetBytes(blockHash))

但结果是一样的 - “ InvalidMd5 ”错误:(

使用 base64 编码(例如YTkxYzU4ODA5MmNlZGJkYjFiODJjMmQzNzg2ZmQ1MDk=)和没有它(例如a91c588092cedbdb1b82c2d3786fd509 )传递给 PutBlock() 的 MD5 哈希似乎没有什么不同。

Rob 的代码显然对他有用,我真的不知道是什么导致了我的问题。我对 Rob 的代码所做的唯一更改是更改 ParallelUpload() 扩展方法以采用 Stream 而不是文件名,并根据上传文件的大小动态确定块大小。

请,如果有人知道如何解决这个问题,请告诉我!我将不胜感激!我已经为此苦苦挣扎了两天。

4

2 回答 2

3

提雄,

看到这篇文章后,我回去重新测试了我的代码,我认为传递的数据有问题(可能是你传递给函数的内容?)。

我立即跳出来的一件事是您提供的 md5 哈希...在我测试的每种情况下,我的 md5 哈希以两个等号结尾,如下所示(从提琴手捕获):

内容-MD5:D1Mxthoqhlwm9cC0729mWA==

我不是加密专家,但我通过使用块 blob 的块 ID 知道,如果在将 blob ID 转换为 base64 编码值之前您的 blob ID 中有无效/不安全字符,您将获得无效数据和块 ID Azure 无法解释。

于 2010-11-03T21:33:08.980 回答
3

Rob,感谢您提供帮助并指出 MD5 哈希值的差异。你的回答让我想到了正确的方向。我又花了一整天的时间研究这个,但幸运的是(感谢你的评论:))我终于设法解决了这个问题。事实证明,在我的案例中实际上存在两个问题:

1) MD5 散列:我注意到你粘贴在答案中的散列比我得到的要短,但我花了一段时间才看到你的散列正好短了两倍。经过一些实验,我发现您的测试应用程序中的GetMD5HashFromStream()方法正在将MD5CryptoServiceProvider生成的16 字节哈希转换为32 个字符的字符串。正是这个 32 个字符的字符串导致了问题,因为它被转换为Base64并传递给PutBlock()方法,因此 Blob 存储服务抱怨的哈希值要长两倍,因此哈希值无效。这是我最终得到的代码:

原来的:

public static string GetMD5HashFromStream(byte[] data)
{
    MD5 md5 = new MD5CryptoServiceProvider();
    byte[] retVal = md5.ComputeHash(data);

    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < retVal.Length; i++)
    {
        sb.Append(retVal[i].ToString("x2"));
    }
    return sb.ToString();
}

以及对 PutBlock() 的调用:

// calculate the block-level hash
string blockHash = Helpers.GetMD5HashFromStream(buff);
blob.PutBlock(transferDetails[j].BlockId, new MemoryStream(buff), blockHash, options);

最终的:

MD5 md5 = new MD5CryptoServiceProvider();
byte[] blockHash = md5.ComputeHash(buff);
string convertedHash = Convert.ToBase64String(blockHash, 0, 16);
blob.PutBlock(transferDetails[j].BlockId, new MemoryStream(buff), convertedHash, options);

Rob,我真的很好奇您的代码在您的案例中是如何工作的,以及为什么它在我的案例中没有 - 它是特定于我机器上的设置的,还是 Azure 工具的不同版本(我使用的是 v1. 2)...如果您有任何想法,请告诉我。

2)开发存储中的一个错误:通过网络进行的大量梳理将我带到这个页面,其中提到了开发存储中一个晦涩但显然已知的错误:

如果两个请求尝试将块上传到开发存储中尚不存在的 blob,则一个请求将创建 blob,另一个请求可能返回状态代码 409(冲突),存储服务错误代码为 BlobAlreadyExists。

这是我想出的解决方法:

public static bool IsDevelopmentStorageRunning()
{
    return new Microsoft.ServiceHosting.Tools.DevelopmentStorage.DevStore().IsRunning();
}

您需要添加对Microsoft.ServiceHosting.Tools.dll的引用,该引用位于我的机器上的“ C:\Program Files\Windows Azure SDK\v1.2\bin ”中。然后,我在处理文件块的Parallel.For循环之前使用此方法,如下所示:

bool isDevStorageRunning = StorageProxy.IsDevelopmentStorageRunning();
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = isDevStorageRunning ? 1 : 4;
Parallel.For(0, transferDetails.Length, parallelOptions, j => { ... });

我希望这可以为某人省去我所经历的所有麻烦。Rob,再次感谢您的帮助:)

于 2010-11-04T18:51:47.420 回答