I have two S3 buckets that are serving as my Cloudfront origin servers:
example-bucket-1
example-bucket-2
The contents of both buckets live in the root of those buckets. I am trying to configure my Cloudfront distribution to route or rewrite based on a URL pattern. For example, with these files
example-bucket-1/something.jpg
example-bucket-2/something-else.jpg
I would like to make these URLs point to the respective files
http://example.cloudfront.net/path1/something.jpg
http://example.cloudfront.net/path2/something-else.jpg
I tried setting up cache behaviors that match the path1 and path2 patterns, but it doesn't work. Do the patterns have to actually exist in the S3 bucket?
Update: the original answer, shown below, is was accurate when written in 2015, and is correct based on the built-in behavior of CloudFront itself. Originally, the entire request path needed to exist at the origin.
If the URI is /download/images/cat.png but the origin expects only /images/cat.png then the CloudFront Cache Behavior /download/* will not do what you might assume -- the cache behavior's path pattern is only for matching -- the matched prefix isn't removed.
By itself, CloudFront doesn't provide a way to remove elements from the path requested by the browser when sending the request to the origin. The request is always forwarded as it was received, or with extra characters at the beginning, if the origin path is specified.
However, the introduction of Lambda#Edge in 2017 changes the dynamic.
Lambda#Edge allows you to declare trigger hooks in the CloudFront flow and write small Javascript functions that inspect and can modify the incoming request, either before the CloudFront cache is checked (viewer request), or after the cache is checked (origin request). This allows you to rewrite the path in the request URI. You could, for example, transform a request path from the browser of /download/images/cat.png to remove /download, resulting in a request being sent to S3 (or a custom orgin) for /images/cat.png.
This option does not modify which Cache Behavior will actually service the request, because this is always based on the path as requested by the browser -- but you can then modify the path in-flight so that the actual requested object is at a path other than the one requested by the browser. When used in an Origin Request trigger, the response is cached under the path requested by the browser, so subsequent responses don't need to be rewritten -- they can be served from the cache -- and the trigger won't need to fire for every request.
Lambda#Edge functions can be quite simple to implement. Here's an example function that would remove the first path element, whatever it may be.
'use strict';
// lambda#edge Origin Request trigger to remove the first path element
// compatible with either Node.js 6.10 or 8.10 Lambda runtime environment
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request; // extract the request object
request.uri = request.uri.replace(/^\/[^\/]+\//,'/'); // modify the URI
return callback(null, request); // return control to CloudFront
};
That's it. In .replace(/^\/[^\/]+\//,'/'), we're matching the URI against a regular expression that matches the leading / followed by 1 or more characters that must not be /, and then one more /, and replacing the entire match with a single / -- so the path is rewritten from /abc/def/ghi/... to /def/ghi/... regardless of the exact value of abc. This could be made more complex to suit specific requirements without any notable increase in execution time... but remember that a Lambda#Edge function is tied to one or more Cache Behaviors, so you don't need a single function to handle all requests going through the distribution -- just the request matched by the associated cache behavior's path pattern.
To simply prepend a prefix onto the request from the browser, the Origin Path setting can still be used, as noted below, but to remove or modify path components requires Lambda#Edge, as above.
Original answer.
Yes, the patterns have to exist at the origin.
CloudFront, natively, can prepend to the path for a given origin, but it does not currently have the capability of removing elements of the path (without Lambda#Edge, as noted above).
If your files were in /secret/files/ at the origin, you could have the path pattern /files/* transformed before sending the request to the origin by setting the "origin path."
The opposite isn't true. If the files were in /files at the origin, there is not a built-in way to serve those files from path pattern /download/files/*.
You can add (prefix) but not take away.
A relatively simple workaround would be a reverse proxy server on an EC2 instance in the same region as the S3 bucket, pointing CloudFront to the proxy and the proxy to S3. The proxy would rewrite the HTTP request on its way to S3 and stream the resulting response back to CloudFront. I use a setup like this and it has never disappointed me with its performance. (The reverse proxy software I developed can actually check multiple buckets in parallel or series and return the first non-error response it receives, to CloudFront and the requester).
Or, if using the S3 Website Endpoints as the custom origins, you could use S3 redirect routing rules to return a redirect to CloudFront, sending the browser back with the unhandled prefix removed. This would mean an extra request for each object, increasing latency and cost somewhat, but S3 redirect rules can be set to fire only when the request doesn't actually match a file in the bucket. This is useful for transitioning from one hierarchical structure to another.
http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html
http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html
Related
Case:
I have a few S3 buckets that store my media files. I wanted to map all these s3 buckets to a single CF distribution. (their files should be accessed from different paths).
I have made a CF distribution and added 2 buckets. For the behaviour, the first bucket is on Default(*) and the second bucket is on path nature/*.
Issues:
I am able to access the primary bucket (one with default behaviour) but not able to access the secondary bucket (with path nature/*). The error on accessing the secondary bucket is "Access Denied".
Additional details:
Both my buckets are not available to global access and CF is accessing them from OAI.
References:
https://aswinkumar4018.medium.com/amazon-cloudfront-with-multiple-origin-s3-buckets-71b9e6f8936
https://vimalpaliwal.com/blog/2018/10/10f435c29f/serving-multiple-s3-buckets-via-single-aws-cloudfront-distribution.html
https://youtu.be/5r9Q-tI7mMw
Your files in the second bucket must start with a top level prefix nature/ or else the won't resolve. CF doesn't remove the path matched when routing, it is still there. The CloudFront behavior will correctly match and send the request to the second bucket, but the path will still be nature/....
If you can't move the objects in the second bucket into a nature/ prefix, then you need a CF Function to remove this part of the path from the object key before forwarding the request to the S3 origin.
Moving all the objects include the nature prefix is easy but annoying. It is the best strategy because it is also cheapest (from a monetary standpoint), but may require extra overhead on your side. A CF function is easier but costly both from a money standpoint and performance, since the CF function has to be run every time.
An example CF function might be:
function handler(event) {
var request = event.request;
request.uri = request.uri.replace(/^\/nature/, '');
return request;
}
CloudFront doesn't natively support this.
The original request path is forwarded intact to the origin server, with only one exception: if the origin has an Origin Path configured, that value is added to the beginning of the path before the request is sent to the origin (and, of course, this doesn't help, here).
However, we can modify the path during CloudFront processing using a CF-Function
The function code to remove the first level from the path might look something like this:
function handler(event) {
var request = event.request;
request.uri = request.uri.replace(/^\/[^/]*\//, "/");
return request;
}
I have setup a lambda#edge function that dynamically decides what bucket (origin) to use to get the s3 object from (uri..).
I want to make sure that what I am doing does not defeat the whole purpose of using CloudFront in the first place, meaning, allowing origins contents (s3 buckets) to be cached at edges so that users can get what they request fast from the closest edge location to them.
I am following this flow:
'use strict';
const querystring = require('querystring');
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request;
/**
* Reads query string to check if S3 origin should be used, and
* if true, sets S3 origin properties.
*/
const params = querystring.parse(request.querystring);
if (params['useS3Origin']) {
if (params['useS3Origin'] === 'true') {
const s3DomainName = 'my-bucket.s3.amazonaws.com';
/* Set S3 origin fields */
request.origin = {
s3: {
domainName: s3DomainName,
region: '',
authMethod: 'none',
path: '',
customHeaders: {}
}
};
request.headers['host'] = [{ key: 'host', value: s3DomainName}];
}
}
callback(null, request);
};
That is just an example of what I am doing. ( found from https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html#lambda-examples-content-based-S3-origin-request-trigger )
I have setup an Origin-Request trigger that runs my lambda#adge function which will decide what bucket to use as origin.
When the lambda function sets the bucket (s3DomainName) as it gets run in the Origin-Request trigger, as long as that bucket is setup in CloudFront as one of the origin already, will CloudFront be able to serve the file down from cache still? Or am I bypassing the whole point of using CloudFront by setting the origin bucket like shown in the cod example?
I want to make sure that what I am doing does not defeat the whole purpose of using CloudFront
It doesn't defeat the purpose. This works as intended and the response would still be cached using the same rules that would be used if this bucket were a statically configured origin and no trigger was in place.
Origin Request triggers only fire when there's a cache miss -- after the cache is checked and the requested resource is not there, and CloudFront concludes that the request needs to be forwarded to an origin server. The Origin Request trigger causes CloudFront to briefly suspend its processing while your trigger code evaluates and possibly modifies the request before returning control to CloudFront. Changing/setting the origin server in the trigger modifies the destination where CloudFront will then send the request, but it doesn't have any impact on whether the response can/will be cached or otherwise change what CloudFront would do when the response arrives from the origin server. And, if a suitable response is already available in the cache when a new request arrives, the trigger won't fire at all, since there's nothing for it to do.
as long as that bucket is setup in CloudFront as one of the origin already
This isn't actually required. Any S3 bucket with publicly-accessible content can be specified as the origin server in an Origin Request trigger, even if it isn't one of the origins configured on the distribution.
There are other rules and complexities that come into play if a trigger like this is desired and your buckets require authentication and are using KMS-based object encryption, but in those cases, there would still be no impact of this design on caching -- just some extra things you need to do to actually make authentication work between CloudFront and the bucket, since an Origin Access Identity (normally used to allow CloudFront to authenticate itself to S3) isn't sufficient for this use case.
It's not directly related to the Origin Request trigger and the gist of this question, but noteworthy since you are using request.querystring: be sure you understand the concept of the cache key, which is the combination of request attributes that CloudFront will use when determining whether a future request can be served using a given cached object. If two similar requests result in different cache keys, then CloudFront won't treat them as identical requests and the response for one cannot be used as a cached response for the other, even though your expectation might have been otherwise, since the two request are for the same object. An Origin Request trigger can't change the cache key for a request, either accidentally or deliberately, since it's already been calculated by CloudFront by the time the trigger fires.
meaning, allowing origins contents (s3 buckets) to be cached at edges so that users can get what they request fast from the closest edge location to them
Ah. Having written all the above, it occurs to me now that perhaps your question might actually stem from an assumption I've seen people occasionally make about how CloudFront and S3 interact. It's an assumption that is understandable, but turns out to be inaccurate. When a bucket is configured as a CloudFront origin, there is no mechanism that pushes the bucket's content out to the CloudFront edges proactively. If you were working from a belief that this is something that happens when a bucket is configured as a CloudFront S3 origin, then this question takes on a different significance, since you're essentially asking whether the content from those various buckets would still arrive at CloudFront as expected... but CloudFront is what I call a "pull-through CDN." Objects are stored in the cache at a given edge only when someone requests them through that edge -- they're cached as they are fetched as a result of that initial request, not before. So your setup simply changes where CloudFront goes to fetch a given object. The rest of the system works the same as always, with CloudFront pulling and caching the content on demand.
TLDR: We have to trick CloudFront 307 redirect caching by creating new cache behavior for responses coming from our Lambda function.
You will not believe how close we are to achieve this. We have stucked so badly in the last step.
Business case:
Our application stores images in S3 and serves them with CloudFront in order to avoid any geographic slow downs around the globe.
Now, we want to be really flexible with the design and to be able to request new image dimentions directly in the CouldFront URL!
Each new image size will be created on demand and then stored in S3, so the second time it is requested it will be
served really quickly as it will exist in S3 and also will be cached in CloudFront.
Lets say the user had uploaded the image chucknorris.jpg.
Only the original image will be stored in S3 and wil be served on our page like this:
//xxxxx.cloudfront.net/chucknorris.jpg
We have calculated that we now need to display a thumbnail of 200x200 pixels.
Therefore we put the image src to be in our template:
//xxxxx.cloudfront.net/chucknorris-200x200.jpg
When this new size is requested, the amazon web services have to provide it on the fly in the same bucket and with the requested key.
This way the image will be directly loaded in the same URL of CloudFront.
I made an ugly drawing with the architecture overview and the workflow on how we are doing this in AWS:
Here is how Python Lambda ends:
return {
'statusCode': '301',
'headers': {'location': redirect_url},
'body': ''
}
The problem:
If we make the Lambda function redirect to S3, it works like a charm.
If we redirect to CloudFront, it goes into redirect loop because CloudFront caches 307 (as well as 301, 302 and 303).
As soon as our Lambda function redirects to CloudFront, CloudFront calls the API Getaway URL instead of fetching the image from S3:
I would like to create new cache behavior in CloudFront's Behaviors settings tab.
This behavior should not cache responses from Lambda or S3 (don't know what exactly is happening internally there), but should still cache any followed requests to this very same resized image.
I am trying to set path pattern -\d+x\d+\..+$, add the ARN of the Lambda function in add "Lambda Function Association"
and set Event Type Origin Response.
Next to that, I am setting the "Default TTL" to 0.
But I cannot save the behavior due to some error:
Are we on the right way, or is the idea of this "Lambda Function Association" totally different?
Finally I was able to solve it. Although this is not really a structural solution, it does what we need.
First, thanks to the answer of Michael, I have used path patterns to match all media types. Second, the Cache Behavior page was a bit misleading to me: indeed the Lambda association is for Lambda#Edge, although I did not see this anywhere in all the tooltips of the cache behavior: all you see is just Lambda. This feature cannot help us as we do not want to extend our AWS service scope with Lambda#Edge just because of that particular problem.
Here is the solution approach:
I have defined multiple cache behaviors, one per media type that we support:
For each cache behavior I set the Default TTL to be 0.
And the most important part: In the Lambda function, I have added a Cache-Control header to the resized images when putting them in S3:
s3_resource.Bucket(BUCKET).put_object(Key=new_key,
Body=edited_image_obj,
CacheControl='max-age=12312312',
ContentType=content_type)
To validate that everything works, I see now that the new image dimention is served with the cache header in CloudFront:
You're on the right track... maybe... but there are at least two problems.
The "Lambda Function Association" that you're configuring here is called Lambda#Edge, and it's not yet available. The only users who can access it is users who have applied to be included in the limited preview. The "maximum allowed is 0" error means you are not a preview participant. I have not seen any announcements related to when this will be live for all accounts.
But even once it is available, it's not going to help you, here, in the way you seem to expect, because I don't believe an Origin Response trigger allows you to do anything to trigger CloudFront to try a different destination and follow the redirect. If you see documentation that contradicts this assertion, please bring it to my attention.
However... Lambda#Edge will be useful for setting Cache-Control: no-cache on the 307 so CloudFront won't cache it, but the redirect itself will still need to go all the way back to the browser.
Note also, Lambda#Edge only supports Node, not Python... so maybe this isn't even part of your plan, yet. I can't really tell, from the question.
Read about the Lambda#Edge limited preview.
The second problem:
I am trying to set path pattern -\d+x\d+\..+$
You can't do that. Path patterns are string matches supporting * wildcards. They are not regular expressions. You might get away with /*-*x*.jpg, though, since multiple wildcards appear to be supported.
I am trying to figure out the correct path for an object to invalidate on CloudFront distribution.
CloudFront is configured with an alternate domain name of *.example.com
The tricky part is that I've setup a custom origin on EC2 that uses HAProxy to do some path rewriting.
So that the request to
mysubdomain.example.com/icon.png
is rewritten to
s3.amazonaws.com/examplebucket/somedirectory/mysubdomain/icon.png
and the result is then returned to CloudFront. (So, both the Path and the Host are being rewritten)
Now, I have trouble figuring out the correct path for this object when sending an invalidation request. (I don't want to use versioning, because I need the filename to remain the same)
I've tried with the following configuration, but it doesn't seem to be working. The invalidation is created and processed, but with no effect.
const invalidationParams = {
DistributionId: 'MY_DISTRIBUTION_ID',
InvalidationBatch: {
CallerReference: 'SOME_RANDOM_STRING',
Paths: {
Quantity: 1,
Items: [
'/somedirectory/mysubdomain/icon.png'
]
}
}
}
Since only PATH is specified, which is relative to the distribution, and no way to specify the full URL in the invalidation configuration, does it make it impossible to invalidate the object in this configuration?
CloudFront invalidations consider every object matching the path specification, as requested by the browser. To invalidate http://example.com/cat.jpg you specify one of the following:
cat.jpg
/cat.jpg
The leading slash is optional, but implied if absent.
Paths are the only values accepted for invalidation requests.
For each edge location, every copy of an object matching that path -- regardless of the alternate domain name or other attributes associated with in it -- will be evicted.
Note that "every copy of an object matching that path" may be confusing to some, since the assumption might be that only one copy would match a given path, but this is not correct. CloudFront caches different copies of the "same" object, depending on which request parameters are forwarded to the origin. If the query string, a cookie, whitelisted headers, etc., are forwarded, then many copies of the "same" object will be cached, because caching requires that the cache assume the response will vary if any if the forwarded request parameters vary. This is why so little forwarded by default -- it helps your hit rate because it reduces the likelihood of any given request seeming "unique" to the cache logic.
I have two S3 buckets that are serving as my Cloudfront origin servers:
example-bucket-1
example-bucket-2
The contents of both buckets live in the root of those buckets. I am trying to configure my Cloudfront distribution to route or rewrite based on a URL pattern. For example, with these files
example-bucket-1/something.jpg
example-bucket-2/something-else.jpg
I would like to make these URLs point to the respective files
http://example.cloudfront.net/path1/something.jpg
http://example.cloudfront.net/path2/something-else.jpg
I tried setting up cache behaviors that match the path1 and path2 patterns, but it doesn't work. Do the patterns have to actually exist in the S3 bucket?
Update: the original answer, shown below, is was accurate when written in 2015, and is correct based on the built-in behavior of CloudFront itself. Originally, the entire request path needed to exist at the origin.
If the URI is /download/images/cat.png but the origin expects only /images/cat.png then the CloudFront Cache Behavior /download/* will not do what you might assume -- the cache behavior's path pattern is only for matching -- the matched prefix isn't removed.
By itself, CloudFront doesn't provide a way to remove elements from the path requested by the browser when sending the request to the origin. The request is always forwarded as it was received, or with extra characters at the beginning, if the origin path is specified.
However, the introduction of Lambda#Edge in 2017 changes the dynamic.
Lambda#Edge allows you to declare trigger hooks in the CloudFront flow and write small Javascript functions that inspect and can modify the incoming request, either before the CloudFront cache is checked (viewer request), or after the cache is checked (origin request). This allows you to rewrite the path in the request URI. You could, for example, transform a request path from the browser of /download/images/cat.png to remove /download, resulting in a request being sent to S3 (or a custom orgin) for /images/cat.png.
This option does not modify which Cache Behavior will actually service the request, because this is always based on the path as requested by the browser -- but you can then modify the path in-flight so that the actual requested object is at a path other than the one requested by the browser. When used in an Origin Request trigger, the response is cached under the path requested by the browser, so subsequent responses don't need to be rewritten -- they can be served from the cache -- and the trigger won't need to fire for every request.
Lambda#Edge functions can be quite simple to implement. Here's an example function that would remove the first path element, whatever it may be.
'use strict';
// lambda#edge Origin Request trigger to remove the first path element
// compatible with either Node.js 6.10 or 8.10 Lambda runtime environment
exports.handler = (event, context, callback) => {
const request = event.Records[0].cf.request; // extract the request object
request.uri = request.uri.replace(/^\/[^\/]+\//,'/'); // modify the URI
return callback(null, request); // return control to CloudFront
};
That's it. In .replace(/^\/[^\/]+\//,'/'), we're matching the URI against a regular expression that matches the leading / followed by 1 or more characters that must not be /, and then one more /, and replacing the entire match with a single / -- so the path is rewritten from /abc/def/ghi/... to /def/ghi/... regardless of the exact value of abc. This could be made more complex to suit specific requirements without any notable increase in execution time... but remember that a Lambda#Edge function is tied to one or more Cache Behaviors, so you don't need a single function to handle all requests going through the distribution -- just the request matched by the associated cache behavior's path pattern.
To simply prepend a prefix onto the request from the browser, the Origin Path setting can still be used, as noted below, but to remove or modify path components requires Lambda#Edge, as above.
Original answer.
Yes, the patterns have to exist at the origin.
CloudFront, natively, can prepend to the path for a given origin, but it does not currently have the capability of removing elements of the path (without Lambda#Edge, as noted above).
If your files were in /secret/files/ at the origin, you could have the path pattern /files/* transformed before sending the request to the origin by setting the "origin path."
The opposite isn't true. If the files were in /files at the origin, there is not a built-in way to serve those files from path pattern /download/files/*.
You can add (prefix) but not take away.
A relatively simple workaround would be a reverse proxy server on an EC2 instance in the same region as the S3 bucket, pointing CloudFront to the proxy and the proxy to S3. The proxy would rewrite the HTTP request on its way to S3 and stream the resulting response back to CloudFront. I use a setup like this and it has never disappointed me with its performance. (The reverse proxy software I developed can actually check multiple buckets in parallel or series and return the first non-error response it receives, to CloudFront and the requester).
Or, if using the S3 Website Endpoints as the custom origins, you could use S3 redirect routing rules to return a redirect to CloudFront, sending the browser back with the unhandled prefix removed. This would mean an extra request for each object, increasing latency and cost somewhat, but S3 redirect rules can be set to fire only when the request doesn't actually match a file in the bucket. This is useful for transitioning from one hierarchical structure to another.
http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html
http://docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html