Mobile Services and Blob Storage

This is a quick write-up of how I got this working.

Here’s the situation:

  • I want to store images and other binaries in Azure blob storage (which is like Amazon S3) rather than in a SQL database or NoSQL table.

  • I want the client to upload directly to blob storage — I don’t want the image data to pass through the API server.

I struggled with this. For a while I was looking at the REST API and building a SharedKey. It wasn’t until I found Chris Risner’s page on this from last year that I found what I needed. (Thanks tons to Chris for writing this up.)

So this tutorial exists in case you’re in the same boat.

I’ll assume you already have a Mobile Service and know how to set up custom APIs. I’ll talk about the server side mostly, since you already know how to use NSURLSession and friends.

Get a blobService

Set up a storage service if you haven’t already. (New > Data Services > Storage.) A storage service is NoSQL table storage, queues, and blobs.

Add the storage service’s name and primary access key to your Mobile Service’s key storage (see the Configure pane, under app settings). Key names such as BLOB_ACCOUNT and BLOB_ACCESS_KEY are fine. Whatever you want.

In your code:

var azure = require('azure');

The azure module is available without you having to do anything. Just require it to use it.

The azure module gets you a blobService:

var blobService = azure.​createBlobService​(accountName, key, host);

accountName and key should come from your Mobile Service’s key storage — request.​service.​config.​appSettings.​BLOB_ACCOUNT, for instance.

The host is accountName + '.blob.core.windows.net'.

Whenever you’re going to do something with blob storage, get a blobService.

Creating a container

blobService.​createContainerIfNotExists​(containerName, function(err)…

This creates a private container. (You can create public containers too, but that’s outside of the scope here. It’s easy, though.)

Listing files

blobService.​listBlobs​(containerName, function(err, results)…

The results are in JSON. You could return those to the client, or filter them to just filenames or whatever the client needs.

Deleting a file

blobService.​deleteBlob​(containerName, filename, function(err)…

Uploading

Here’s the fun part. You don’t want the image binary to go through the API server. Instead, the API server generates a temporary shared access URL that will allow the client to upload directly.

First use createContainerIfNotExists. (I assume you have some way of deciding what the container names should be. They could be based on a person’s user ID, for instance. Depending on the security needs of your app, it may be a good idea to hash the container names, so they’re not personally identifiable.)

You need a sharedAccessPolicy.

var sharedAccessPolicy = {
  AccessPolicy: {
    Permissions: 'w',
    Expiry: azure.date.​minutesFromNow(5)
  }
}

Then you generate the URL to return to the client:

var sasURL = blobService.​generateSharedAccessSignature​(containerName, filename, sharedAccessPolicy);
var accountName = request.​service.​config.​appSettings.​BLOB_ACCOUNT;
var urlForUploading = 'https://' + accountName + '.blob.core.windows.net' + sasURL.path + '?' + qs.stringify(sasURL.queryString);

(Somewhere above this you need var qs = require('querystring').)

Return this URL to the client.

The client can use an NSURLSession​UploadTask. Use the PUT verb. Add Content-Type and Content-Length headers. (It’s a good idea to add Content-MD5 too, but not required.)

Downloading

It’s almost exactly the same as uploading, except that sharedAccessPolicy​.AccessPolicy.​Permissions is 'r' instead of 'w'.

I have the server respond with a redirect to the URL.

That’s it. Pretty easy.

22 Apr 2014

Archive