Azure Functions are Microsoft’s answer to “serverless” computing. Functions enable applications developers to write event-driven code (ie. “functions”) than can be triggered by timers, manual integration, HTTP requests, service buses, message queues and many other integration points. These functions can be written in a number of different languages including C#, NodeJS (which this example uses), Python, PHP and a myriad of of other languages.
For this example we’re going to setup a simple Azure function that can serve up static files, and thus a website. Combined with other functions, you can create Single Page Apps all served up from Azure Functions without the need for VM’s or other more complex hosting options.
Create an Azure Function App
- In the Azure Portal, select Create Resource, Compute, then select Function App.
- Assign a new App name to the app, then click Create. The other settings are optional. And depending on nature of the site, you may want to change the hosting plan from Consumption to App Service Plan. Consumption plans are on demand rather rather than continuously available or even always on.
- Give the App a few minutes to deploy. Once it deploys, click Go to resource.
Create a New Function
- After the resource loads, you can now add a new function to the Function App. Simply select the + icon next to Functions.
- Then, select create your own custom function.
- On the next page, you will see all of the different kinds of triggers you can use with Azure Functions. Triggers are called “input bindings”. Functions can also have output bindings where results are published to more than an HTTP response. For our purposes, Under HTTP Trigger, select JavaScript.
- On the HTTP Trigger configuration blade, name the function “GetPage” without quotes and set Authorization level to Anonymous. This will let anyone on the internet access your website. You can add on Authentication and Authorization for functions either with keys or you can use one of many Identity Providers like Azure Active Directory, Facebook, or Twitter to Azure Functions, but for now we’re going to leave this like it is. Finally, click Create.
- After the function creates, copy and paste in the following code over the default code in the editor. This little function reads content from the content folder relative to the script.
var fs = require("fs") var mime = require('mime-types') module.exports = function (context, req) { var file="index.html" if (req.query.file) { file=req.query.file } file = file.replace(///g, ""); fs.readFile(__dirname + "content" + file, function(err, data) { context.log('GET ' + __dirname + "content" + file); if (!err){ var contentType = mime.lookup(file) context.res = { status: 200, body: data, isRaw: true, headers: { 'Content-Type': contentType } }; }else{ context.log("Error: " + err) context.res = { status: 404, body: "Not Found.", headers: { } }; } context.done() }); };
- Save the script with the Save button.
- Now, you need to install some dependencies for the application. The script uses a NodeJS module called “mime-types” to identify the content type being sent back to the calling client. This is installed via npm, which is a command line utility. You can launch a console in your browser by selecting the Overview, then Platform features, then clicking on Advanced tools (Kudu). Kudu is a portal for interacting with Azure Functions and other apps with the command line along with some other tools like file uploads, process explorer and so on.
- In Kudu, select CMD from the Debug console menu.
- Now, you can navigate to the folder by click on site -> wwwroot -> GetPage. In the GetPage folder, you should see the function.js and index.js files.
- In the console, issue each of the following command: npm install mime-types
The output will look like the following. If you see some npm WARN lines, this is OK.> npm install mime-types D:homesitewwwrootGetPage npm WARN enoent ENOENT: no such file or directory, open 'D:homesitewwwrootGetPagepackage.json' npm WARN GetPage No description npm WARN GetPage No repository field. npm WARN GetPage No README data npm WARN GetPage No license field.
Upload Content
- Now, you can upload content. In Kudu, start by creating a folder with the + icon and selecting New folder.
- Name the new folder “content” and then click on the folder.
- Now, you can drag and drop files from your local machine into the content folder. To upload multiple files at the same time, simply select all the files and drag them into the content folder. You will have to create folders for files in sub-folders.Alternatively, you can zip all your content and upload the zip file into Kudu. Kudu will automatically unzip the file whenever you upload it.
Create a Proxy
- Now, one last things remains. For the function to act like a server, we need to add a proxy. Typically, a Function App uses a URL that looks something like this:
https://mystaticsite.azurewebsites.net/api/GetPage?file=index.html
This, however, will not work for most static sites, which use a folder structure to point to content. In this case, you can setup a proxy that can transform a URL from…
https://mystaticsite.azurewebsites.net/index.html
… into the URL used by the function. To do this, the + icon next to Proxies in your Function App. Then, Name the “Page Proxy”. For Route template, enter “/{*page}” without the quotes, then “https://localhost/api/GetPage?file={page}” without the quotes for the Backend URL. Finally, click Create.
- Now, you can test the site. Simply point it to the URL for your function. The URL will look something like this: https://yourfunctionapp.azurewebsites.net/, where yourfunctionapp is the name of app you supplied in Step 2 under Creating an Azure Function App.
Conclusion
Functions for sites can be an ideal solution depending on the context. There are certainly other ways to host static sites on Azure. For serverless apps on Azure though, this might be ideal, because you can have other functions that act as API’s that can be called from static context.
You can see a working example using this script at:
https://mystaticsite.azurewebsites.net/
Stay tuned for Part 2!