In my previous article Blue Green websites on S3 with Cloudfront we discussed an approach to blue green deployment technique for websites hosted on AWS Cloudfront with an S3 origin. The example website content provided included index.html pages and JPEG files, where every hyperlink in the website exists as an object in the S3 bucket. If a user were to request a URL to the site for an object that does not exist in the S3 bucket, they would correctly get a response back from Cloudfront stating the requested object does not exist.
However, Cloudfront and S3 don’t just support use of static website content like HTML and JPEG files. You could also deploy website content that is more dynamic in nature, such as a JavaScript React based single page application, or SPA. Single page applications work by handling all website HTTP requests through a single HTML page, usually index.html, which in turns loads the JavaScript application which defines all of the content for the site.
Single page applications may also supply routing capabilities where a site can have URLs with many different website paths. As long as the JavaScript application is loaded into the browser, the JavaScript will return the HTTP request successfully. And it is here where the behavior of Cloudfront must be adjusted to support use of the single page application within Cloudfront with an S3 origin.
For the case of a site that does not support blue green deployments, this is simple enough to do within Cloudfront configuration. You just need to add a Custom Error Response so that when a site’s user makes an HTTP request for a path that does not exist in the S3 origin, Cloudfront responds by sending back the index page for the site and an HTTP 200 status code.
For example:
Typically for single page applications, a Cloudfront custom error response would be set up for any 403 or 404 HTTP responses coming back from the S3 origin. Using the above custom error response, when a user makes a request such as https://app.domain.com/foo/bar and the S3 origin returns a 404 response code, Cloudfront will instead serve https://app.domain.com/index.html along with a 200 response code. It is up to the JavaScript application loaded by index.html to determine how to serve the correct content for https://app.domain.com/foo/bar.
You will notice though that the Custom Error Response Settings only allow you to specify a fixed response page path, which isn’t very friendly to a site that has both a blue and green instance deployed. This is where we need to revisit the Lambda@Edge functions once again. We can make the HTTP error response handling dynamic where when a user makes a request of the blue site, the /blue/index.html response page path will be used, and when the user makes a request of the green site, the /green/index.html response page path will be used. For this case, we will be using the Origin Response Lambda, which you can see an example of below:
import requests
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
response = event["Records"][0]["cf"]["response"]
request_headers = request['headers']
headers = response["headers"]
# Handle specific origin errors by responding with index page body so that single page react JS app functions correctly
if int(response['status']) in [403,404]:
host_name = request_headers.get('x-blue-green-host')[0]['value']
site_request = requests.get(f"https://{host_name}/index.html")
response['status'] = 200
response['statusDescription'] = 'OK'
headers["content-type"] = [
{
"key": "Content-Type",
"value": "text/html",
}
]
response['body'] = site_request.content
return response
This lambda changes the HTTP response any time the S3 origin returns an HTTP 403 or 404 response by fetching the content of the index.html page from the correct site host and setting that as the response body. It also sets the response code (status) to an HTTP 200 and the status description to OK.
You will notice that this Lambda function looks up which site host to fetch the index.html content from using the HTTP header x-blue-green-host , which it expects to be set in the HTTP headers in order to function properly. You will recall from Blue Green websites on S3 with Cloudfront we made use of a Viewer Request Lambda@Edge function to make the blue green decision, and that it was a pre-cache function, so this is the ideal place to add the new x-blue-green-host HTTP header for the Origin Response Lambda to use. Here is how that Viewer Request function now looks to use it for this SPA application:
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
headers = request['headers']
hostName = headers.get('host')[0]['value']
request['headers']['x-blue-green-host'] = [{'key': 'x-blue-green-host', 'value': hostName}]
subDomain = hostName.split('.')[0]
print("Adding blue green header for host %s" % hostName)
if subDomain.endswith('-test'):
request['headers']['x-blue-green-context'] = [{'key': 'x-blue-green-context', 'value': "green"}]
else:
request['headers']['x-blue-green-context'] = [{'key': 'x-blue-green-context', 'value': "blue"}]
return request
You will notice it is mostly the same as the Viewer Request Lambda used in Blue Green websites on S3 with Cloudfront with the exception of adding a single line to set the x-blue-green-host HTTP header. The Origin Request Lambda used in Blue Green websites on S3 with Cloudfront is also used for our SPA implementation, but it has no additional changes and is used as is.
To review, for the SPA implementation of blue green deployments, we are now using 3 of the 4 Lambda@Edge functions for Cloudfront:
- A Viewer Request to set the x-blue-green-context and x-blue-green-host HTTP headers
- An Origin Request to dynamically set the S3 origin path based on the x-blue-green-context HTTP header
- An Origin Response to dynamically handle any HTTP 403 or 404 errors from the S3 origin by responding with the index.html page at the x-blue-green-host HTTP header
If you would like to see this fully functional for a simple single page application site, we have a sandbox implementation you can use at https://github.com/sbwise01/myspabluegreen . This sandbox contains Hashicorp Terraform configuration that you can use to provision all of the necessary resources in an AWS account to demo this functionality, including the S3 website content.
Stay tuned for future blog posts on blue green deployments where we will discuss use of a service mesh. And feel free to contact Foghorn Consulting to assist you with building continuous delivery automation using the blue green deployment technique for your web applications.