Hosting Static Website Using CloudFlare and BackBlaze B2 (Part 3)
Thursday, June 23, 2022
Reading time 9 minutes
This is the third and final part of a series of posts I started detailing the steps I’ve followed to publish a static website using Cloudflare and B2, along with GitLab runners, which are responsible for building and uploading the site files. You can find the other parts of this series at the following links:
In the previous two parts of this series of articles, we focused on creating a static website and making that website be stored in a B2 Bucket. As I had specified earlier, in this final part we’ll dedicate ourselves to the final point of the configuration, which is routing all traffic coming from our domain to the friendly URL we were able to obtain in B2 in the second part of this series. Broadly speaking, what we’ll do is create a Cloudflare Worker and use it to serve our files from the B2 bucket from our domain root. It’s important that at this point, our domain is being managed by Cloudflare’s nameservers, otherwise things here probably won’t work.
Step 1. Configure the domain
The first thing to do before anything else is to make sure the domain is properly configured and that there is at least one A-type record at its root pointing somewhere. Actually where it points isn’t important, as our future Worker will be responsible for intercepting the domain requests, but if we don’t configure any main record, the domain won’t load. You can configure any IP address as the destination for your main A-type record and continue with the following.
Step 2. Create the Worker
A Worker, using Cloudflare terminology, is a way to create applications without needing a server. These “applications” would run once the user has entered a domain associated with a Worker, and their main function is to redirect, modify the HTTP request or response so that the client, which is the user loading the page, sees what the developer wants to show them without the user being manually redirected anywhere. The difference between our Worker and a DNS record like CName, for example, is that when entering our site, the worker will retrieve the file requested by the user and won’t allow them to see that we’re actually querying the URLs of our B2 bucket. This is also advantageous for us in a couple of other ways:
- B2 traffic through Cloudflare is free: Normally, B2 charges for storing objects, for uploading them (if they exceed their monthly free limit), and for downloading them. Because Backblaze and Cloudflare are members of the Bandwidth Alliance, this means that data traffic between both providers is completely free for the user, as announced by B2.
- Privacy: exposing the friendly URL of a B2 bucket is not the safest and most recommended way to give visitors access to a website, no matter how static it is. Through this friendly URL it’s possible to view files from other public buckets, even if they don’t belong to you. However, by using the Cloudflare Worker, it will only be in your bucket configured for your website where files to serve will be searched for and queried.
To create your first Worker, you need to access the Cloudflare dashboard. Select the “Workers” option from the main menu, which will show you the interface where we’ll start working with our first Worker.
To begin, simply locate and activate the link called “Create a service”. This will display a section within the Cloudflare website where we can configure the service name. By default, each service receives a name with random characters and a subdomain is assigned so you can test your service before proceeding with deployment. You can change the name, if you wish, so that it’s easier to remember what it’s about when you see it in the Workers list, for example. In my case, I’ve named it “manuelcortez-net”.
Next, it’s time to select a template for our service. Actually at this point you can select any, as we’ll soon overwrite the code that will be generated here. In my case I’ve selected the option called “HTTP handler”. Finally, it’s time to locate and activate the button called “create service”.
Once the service has been created, we’ll be on its page. Here you can check information about the number of requests it has received, as well as other important data once it’s up and running. Of all the information, what interests us at this moment is a link called “Quick edit”, which we should activate in order to display the code editor we need to change the operation of our Worker.
When loading the page with the code editor, it’s important to pay attention to what we’re going to do here. If you use a screen reader, locate the first of the edit fields being at the beginning of the page. This is where we can activate the screen reader’s focus mode, select all the text there, and proceed to delete it. Then, copy the following code snippet into a document of your choice (for example using Notepad), as we’ll need to modify it soon:
const baseURL = "https://f001.backblazeb2.com/file/manuelcortez-net"
addEventListener('fetch', event => {
event.respondWith(handleRequest(event))
})
async function handleRequest(event) {
// only allow get requests
if (event.request.method !== 'GET') {
return new Response('Method not allowed', { status: 405 })
}
const cache = caches.default
let cachedResponse = await cache.match(event.request)
const parsedUrl = new URL(event.request.url)
let path = parsedUrl.pathname
// check if a file or folder and 301 when tailing slash is missing
let lastSegment = path.substring(path.lastIndexOf('/'))
if (lastSegment.indexOf('.') === -1) {
if (!lastSegment.endsWith('/')){
return Response.redirect('https://' + parsedUrl.hostname + path + '/', 301)
} else {
path += 'index.html'
}
}
// fetch content from B2
const b2Response = await fetch(`${baseURL}${path}`)
// add some headers
const headers = {
'cache-control': 'public, max-age=14400',
'content-type': b2Response.headers.get('Content-Type')
}
const response = new Response(b2Response.body, { ...b2Response, headers })
// all is well, return the response
if (response.status < 400){
event.waitUntil(cache.put(event.request, response.clone()))
return response
} else if (response.status == 404){
// return error page
return fetch(baseURL + "/404.html")
}
// return minimal error page
if (response.status > 399) {
return new Response(response.statusText, { status: response.status })
}
}
Note: Modify the first line of the code snippet and locate your bucket’s friendly URL, but without including the filename. If my friendly URL was https://f001.backblazeb2.com/file/manuelcortez-net/index.html, you only need to remove the index.html from the URL. The rest of the code can remain as is. Once the code is modified, return to the worker page and paste the new code into the code editor that should still have focus.
What this code does is very simple: for each request this worker receives, as long as it’s sent via a GET request, it will try to find in our B2 bucket the file requested in the part of the URL that doesn’t belong to the domain (for example, if you search for manuelcortez.net/file.mp3, it will search for the file.mp3 file). If no file is being searched for, then it will check if there’s a directory with that name and if inside it there’s a file called index.html. If it finds any file, it will send it to the user’s browser. If not, it will just show an error.
Finally, once this code is pasted with the corresponding modifications, you need to exit the screen reader’s focus mode and activate the button called “Save and deploy”. If everything went well, you’ll see a note telling you that the Worker will be available at an address, generally belonging to the workers.dev domain. Press the save and deploy button again, and now yes, your worker should be deployed. Before continuing, make sure you can see your website’s homepage when loading this test domain in your browser. Otherwise, once the worker is deployed on the main domain, it will also fail.
Step 3. Configure the route
Once the Worker is created and deployed, and it’s been verified that it works, it’s necessary to assign it to a route. Right now, for the Worker to work, the user would have to access the subdomain that Cloudflare assigns for testing. And that doesn’t look at all like the domain someone would use for a website. So a route will do just that, it will assign a part of your domain (in our case the part is the main domain, but this can also be done with subdomains if you wish) to a worker. That way, when loading the main domain Cloudflare will redirect the user to our Worker, and the Worker will do the rest of the work.
Again in the Cloudflare Dashboard, access to manage the site where you want to create the route for the Worker. Once inside the site administration, in the main menu, select the “Workers” option again. You need to be inside the site administration to see the route options in Workers.
Once the page is shown, locate a heading called “HTTP Routes” and then a button called “add route”. This will show a dialog from where you can configure the important aspects about the route we have to create. From this dialog you only have to select two important things:
- Route: Specify the route the user must enter to be redirected to our Worker. You can use asterisks to indicate wildcards in URLs. For example, if you put domain.com/* you indicate that the worker will be used both if the user puts domain.com/hello and if they put domain.com/greetings.html; but not if they put hello.domain.com.
- In service, use the cursor arrows to select the name of the Worker you’ve created, and then simply press enter to confirm the selection.
- In environment, select the only one that will have been created by default. In my case this value was created automatically with my first Worker and is called Production.
- Finally, press save to deploy the worker on your specified domain route.
Conclusion
And this is as far as we’ve come in this series of articles about Cloudflare, B2, and how to serve your website without using a server. If everything went well, the website should load as soon as you enter your main domain in the browser. As I was commenting throughout the articles, this is a very basic configuration and I’ve practically written it to avoid forgetting what I’ve done in case I needed to do it again. This configuration is very oriented towards my own needs, but I think that whoever is interested in making a general configuration should be able to change the components exposed here for others that better suit each person’s particular need. In general, you can replace the part of creating the website (using Nikola, or some other static generator), uploading it (using github actions, some other CI, or simply a local machine with aws-cli installed) and even where to host it (there are many S3-type object storage offers today). I think that, broadly speaking, the experiment of hosting my website using services mentioned here has been satisfactory and the same level of availability can be achieved, as long as the content is static, as with a traditional web server.
S3 B2 Gitlab Static website CI/CD