Skip to content

Extract example for static sites from single page applications example #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This repository contains example CloudFront functions and instructions to deploy

[CloudFront Functions](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html) is a serverless edge compute feature allowing you to run JavaScript code at the 225+ [Amazon CloudFront](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html) edge locations for lightweight HTTP(S) transformations and manipulations. Functions is purpose-built to give you the flexibility of a full programming environment with the performance and security that modern web applications require. At a fraction of the price of [AWS Lambda@Edge](https://aws.amazon.com/lambda/edge/), CloudFront Functions can scale instantly and affordably to support millions of requests per second.

CloudFront Functions is natively built into CloudFront, allowing you to easily build, test, and deploy viewer request and viewer response functions entirely within CloudFront. This GitHub repo offers a collection of example code that can be used as a starting point for building functions. You can build functions using the IDE in the CloudFront console, or with the CloudFront APIs/CLI. Once your code is authored, you can test your function against a production CloudFront distribution, ensuring your function will execute properly once deployed. The test functionality in the console offers a visual editor to quickly create test events and validate functions. You can use CloudFront Functions in addition to the existing AWS Lambda@Edge capability that also allows you to run custom code in response to CloudFront events.
CloudFront Functions is natively built into CloudFront, allowing you to easily build, test, and deploy viewer request and viewer response functions entirely within CloudFront. This GitHub repo offers a collection of example code that can be used as a starting point for building functions. You can build functions using the IDE in the CloudFront console, or with the CloudFront APIs/CLI. Once your code is authored, you can test your function against a production CloudFront distribution, ensuring your function will execute properly once deployed. The test functionality in the console offers a visual editor to quickly create test events and validate functions. You can use CloudFront Functions in addition to the existing AWS Lambda@Edge capability that also allows you to run custom code in response to CloudFront events.

CloudFront functions are ideal for lightweight computation tasks on web requests. Some popular use cases are:

Expand All @@ -17,35 +17,39 @@ CloudFront functions are ideal for lightweight computation tasks on web requests

## Example CloudFront functions

|Example|Description|
|------|-------|
|[Add a `True-Client-IP` request header](add-true-client-ip-header/)| `True-Client-IP` is an HTTP request header that you can add to incoming CloudFront requests so that the IP address of the viewer (client) is passed along to the origin.|
|[Add HTTP security response headers](add-security-headers/)| This function adds several of the more common HTTP security headers to the response from CloudFront, including HTTP Strict Transport Security (HSTS), Content Security Policy (CSP), `X-Content-Type-Options`, `X-Frame-Options`, and `X-XSS-Protection`.|
|[Perform URL rewrite for single page applications](url-rewrite-single-page-apps/)| You can use this function to perform a URL rewrite to append "index.html" to the end of URLs that don’t include a filename or extension. This is particularly useful for single page applications or statically generated websites using frameworks like React, Angular, Vue, Gatsby, or Hugo.|
|[URL redirect based on a user’s country](redirect-based-on-country)| This function redirects a user to a country-specific version of a site based on the country of the user. In this example, if the user is in Germany, the function redirects the user to the `/de/index.html` page which is the German version of the site. If the user is not in Germany, the request passes through with no modification to the URL.|
|[Add origin request header if missing](add-origin-header/)| This function adds an origin header if it is not present on the incoming request. The origin header, part of cross-origin resource sharing (CORS), is a mechanism using HTTP headers to tell the web server which origin initiated this particular request.|
|[Verify JSON Web Tokens](verify-jwt/)| This function performs a lightweight security token validation using JSON Web Tokens. You can use this type of tokenization to give a user of your site a URL that is time-bound. Once the predetermined expiration time has occurred, the user can no longer access the content at that URL.|
|[Add CORS headers if missing](add-cors-header/)| This function adds an `Access-Control-Allow-Origin` response header if it is not present on the outgoing response from CloudFront.|
|[Add a `Cache-Control` header](add-cache-control-header/)| This function adds a `Cache-Control` response header to the outgoing response from CloudFront for browser caching.|
| Example | Description |
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Add a `True-Client-IP` request header](add-true-client-ip-header/) | `True-Client-IP` is an HTTP request header that you can add to incoming CloudFront requests so that the IP address of the viewer (client) is passed along to the origin. |
| [Add HTTP security response headers](add-security-headers/) | This function adds several of the more common HTTP security headers to the response from CloudFront, including HTTP Strict Transport Security (HSTS), Content Security Policy (CSP), `X-Content-Type-Options`, `X-Frame-Options`, and `X-XSS-Protection`. |
| [Perform URL rewrite for static websites](url-rewrite-static-websites/) | You can use this function to perform a URL rewrite to append "index.html" to the end of URLs that don’t include a filename or extension. This is particularly useful for static websites generated using frameworks like Next.js, Gatsby, or Hugo. |
| [Perform URL rewrite for single page application](url-rewrite-single-page-apps/) | You can use this function to perform a URL rewrite to "index.html" for URLs that don’t include an extension. This is particularly useful for single page applications using frameworks like Vue.js, React, or Angular. |
| [URL redirect based on a user’s country](redirect-based-on-country) | This function redirects a user to a country-specific version of a site based on the country of the user. In this example, if the user is in Germany, the function redirects the user to the `/de/index.html` page which is the German version of the site. If the user is not in Germany, the request passes through with no modification to the URL. |
| [Add origin request header if missing](add-origin-header/) | This function adds an origin header if it is not present on the incoming request. The origin header, part of cross-origin resource sharing (CORS), is a mechanism using HTTP headers to tell the web server which origin initiated this particular request. |
| [Verify JSON Web Tokens](verify-jwt/) | This function performs a lightweight security token validation using JSON Web Tokens. You can use this type of tokenization to give a user of your site a URL that is time-bound. Once the predetermined expiration time has occurred, the user can no longer access the content at that URL. |
| [Add CORS headers if missing](add-cors-header/) | This function adds an `Access-Control-Allow-Origin` response header if it is not present on the outgoing response from CloudFront. |
| [Add a `Cache-Control` header](add-cache-control-header/) | This function adds a `Cache-Control` response header to the outgoing response from CloudFront for browser caching. |

## Deploying a CloudFront function using the AWS CLI

We will use the example that adds cache control headers to responses as our function, but the same process can be used for all the functions with only minor changes.

**Step 1**: Install the [AWS CLI](https://aws.amazon.com/cli/). If you already have the AWS CLI, upgrade to the most recent version.

> Note: The examples below assume you are using version 2 of the AWS CLI. There are breaking changes between v1 and v2 of the AWS CLI, which can be found [here](https://docs.aws.amazon.com/cli/latest/userguide/cliv2-migration.html). In v2 of the AWS CLI, binary parameters are passed as base64-encoded strings by default. If you are using v2 of the AWS CLI, you need to do one of the following:
>
>- You can tell the AWS CLI version 2 to revert to the AWS CLI version 1 behavior by specifying the following line in the `~/.aws/config` file for a given profile: `cli_binary_format=raw-in-base64-out`
> - You can tell the AWS CLI version 2 to revert to the AWS CLI version 1 behavior by specifying the following line in the `~/.aws/config` file for a given profile: `cli_binary_format=raw-in-base64-out`
> - Pass all files using `fileb://`, which treats the file content as unencoded binary.

**Step 2**: Clone this repository.

**Step 3**: Change directories into the repo directory.

```
cd amazon-cloudfront-functions/
```

**Step 4**: Create a CloudFront function.

```
aws cloudfront create-function \
--name add-cache-control-headers \
Expand All @@ -71,17 +75,18 @@ aws cloudfront create-function \
}
}
```

Make sure you capture the `ETag` value, since you will need it to publish your function.

**Step 5**: Test the CloudFront function.

To test a function, you need to pass in a JSON request or response object used to simulate a viewer request or response. A sample of the event object format can be found [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html). This repo contains test events that you can use to test the example functions.

Test the function using the provided test event by running the following command, using the function's `ETag` (from the previous command's output) as the value for `--if-match`:
Test the function using the provided test event by running the following command, using the function's `ETag` (from the previous command's output) as the value for `--if-match`:

```
aws cloudfront test-function \
--name add-cache-control-headers \
--name add-cache-control-headers \
--if-match EXXXXXXXXX \
--event-object fileb://add-cache-control-header/test-event.json

Expand All @@ -108,11 +113,12 @@ aws cloudfront test-function \
}
}
```

The function is running successfully since we did not receive an error message and there is a `FunctionOutput` object returned. Inside the `FunctionOutput` object, we can see the `cache-control` header was added as expected.

**Step 6**: Publish your CloudFront function.
**Step 6**: Publish your CloudFront function.

If you received a similar response (to the one above) from the `test-function` command, you can publish the function.
If you received a similar response (to the one above) from the `test-function` command, you can publish the function.

```
aws cloudfront publish-function \
Expand Down
15 changes: 6 additions & 9 deletions url-rewrite-single-page-apps/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
## URL rewrite to append index.html to the URI for single page applications
## URL redirect to index.html for single page applications

**CloudFront Functions event type: viewer request**

You can use this function to perform a URL rewrite to append `index.html` to the end of URLs that don't include a filename or extension. This is particularly useful for single page applications or statically-generated websites using frameworks like React, Angular, Vue, Gatsby, or Hugo. These sites are usually stored in an S3 bucket and served through CloudFront for caching. Typically, these applications remove the filename and extension from the URL path. For example, if a user went to `www.example.com/blog`, the actual file in S3 is stored at `<bucket-name>/blog/index.html`. In order for CloudFront to direct the request to the correct file in S3, you need to rewrite the URL to become `www.example.com/blog/index.html` before fetching the file from S3. This function intercepts incoming requests to CloudFront and checks that there is a filename and extension. If there isn't a filename and extension, or if the URI ends with a "/", the function appends index.html to the URI.

There is a feature in CloudFront called the [default root object](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html) that allows you to specify an index document that applies to the root object only, but not on any subfolders. For example, if you set up index.html as the default root object and a user goes to `www.example.com`, CloudFront automatically rewrites the request to `www.example.com/index.html`. But if a user goes to `www.example.com/blog`, this request is no longer on the root directory, and therefore CloudFront does not rewrite this URL and instead sends it to the origin as is. This function handles rewriting URLs for the root directory and all subfolders. Therefore, you don't need to set up a default root object in CloudFront when you use this function (although there is no harm in setting it up).

**Note:** If you are using [S3 static website hosting](https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html), you don't need to use this function. S3 static website hosting allows you to set up an [index document](https://docs.aws.amazon.com/AmazonS3/latest/dev/IndexDocumentSupport.html). An index document is a webpage that Amazon S3 returns when any request lacks a filename, regardless of whether it's for the root of a website or a subfolder. This Amazon S3 feature performs the same action as this function.
You can use this function to perform a URL redirect to `index.html` for URLs that don't include an extension. This is particularly useful for single page applications using frameworks like Vue.js, React, or Angular. These applications are usually stored in an S3 bucket and served through CloudFront for caching. Tipically, these applications redirect all traffic to a single index.html page for handling by a client-side router function. For example, if a user requests `www.example.com/blog`, the actual file in S3 is stored at `<bucket-name>/index.html`. In order for CloudFront to direct the request to the correct file in S3, you need to rewrite the URL to become `www.example.com/index.html` before fetching the file from S3. This function intercepts incoming requests to CloudFront and checks that there is an extension. If there isn't an extension, the function redirects to the index.html stored in the root of the bucket.

**Testing the function**

Expand All @@ -16,7 +12,8 @@ To validate that the function is working as expected, you can use the JSON test
$ aws cloudfront test-function --if-match EXXXXXXXXXXXX --name url-rewrite-single-page-apps --event-object fileb://url-rewrite-single-page-apps/test-objects/file-name-no-extension.json
```

If the function has been set up correctly, you should see the `uri` being updated to `index.html` in the `FunctionOutput` JSON object:
If the function has been set up correctly, you should see the `uri` being changed to `index.html` in the `FunctionOutput` JSON object:

```
{
"TestResult": {
Expand All @@ -37,7 +34,7 @@ If the function has been set up correctly, you should see the `uri` being update
"ComputeUtilization": "14",
"FunctionExecutionLogs": [],
"FunctionErrorMessage": "",
"FunctionOutput": "{\"request\":{\"headers\":{\"host\":{\"value\":\"www.example.com\"},\"accept\":{\"value\":\"text/html\"}},\"method\":\"GET\",\"querystring\":{\"test\":{\"value\":\"true\"},\"arg\":{\"value\":\"val1\"}},\"uri\":\"/blog/index.html\",\"cookies\":{\"loggedIn\":{\"value\":\"false\"},\"id\":{\"value\":\"CookeIdValue\"}}}}"
"FunctionOutput": "{\"request\":{\"headers\":{\"host\":{\"value\":\"www.example.com\"},\"accept\":{\"value\":\"text/html\"}},\"method\":\"GET\",\"querystring\":{\"test\":{\"value\":\"true\"},\"arg\":{\"value\":\"val1\"}},\"uri\":\"/index.html\",\"cookies\":{\"loggedIn\":{\"value\":\"false\"},\"id\":{\"value\":\"CookeIdValue\"}}}}"
}
}
```
```
10 changes: 3 additions & 7 deletions url-rewrite-single-page-apps/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
function handler(event) {
var request = event.request;
var uri = request.uri;

// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}

// Check whether the URI is missing a file extension.
else if (!uri.includes('.')) {
request.uri += '/index.html';
if (!uri.includes('.')) {
request.uri = '/index.html';
}

return request;
Expand Down
Loading