Как я уже упоминал в Поддержка перенаправления URL-адресов HTTPS с помощью одного дистрибутива CloudFront , простое и понятное решение включает в себя два сегмента и два дистрибутива CloudFront - один для www и один для чистого домена.Я очень скептически отношусь к тому, что это окажет негативное влияние на SEO.
Однако этот ответ предшествует введению расширения CloudFront Lambda @ Edge , которое предлагает другое решение, поскольку оно позволяет вамзапускать функцию Javascript Lambda в определенных точках во время обработки запроса CloudFront, проверять запрос и, возможно, изменять его или иным образом реагировать на него.
В документации есть несколько примеров , новсе они очень минималистичны, так что вот полный рабочий пример с большим количеством комментариев, чем с реальным кодом, объясняющий, что именно он делает и как он это делает.
Эта функция - сконфигурированная как триггер исходного запроса -будет срабатывать каждый раз, когда будет отсутствовать кэш, и проверять заголовок Host
, отправленный браузером, чтобы выяснить, следует ли разрешить запрос или он должен быть перенаправлен без фактической отправки запроса на S3.При попадании в кэш эта функция не срабатывает, поскольку CloudFront уже имеет кэшированное содержимое.
Любое другое доменное имя, связанное с дистрибутивом CloudFront, будет перенаправлено на «реальное» доменное имя вашего сайта, как настроено втело функции.При желании он также вернет сгенерированный ответ 404, если кто-то напрямую получит доступ к имени хоста по умолчанию *.cloudfront.net
вашего дистрибутива.
Может быть, вам интересно, как кэш одного дистрибутива CloudFront может различать контент для example.com/some-path
иwww.example.com/some-path
и кэшируйте их по отдельности, но ответ таков: он может, и он делает , если вы настроите его соответствующим образом для этой настройки - это означает, что он будет кэшироваться на основе выбранных заголовков запросов - в частности, заголовок Host
.
Обычно включение этой конфигурации не совсем совместимо с S3, но здесь это работает, потому что функция Lambda также устанавливает заголовок Host обратно на то, что ожидает S3.Обратите внимание, что вам нужно настроить исходное доменное имя - конечную точку размещения веб-сайта вашего сегмента - встроенное в коде.
При такой конфигурации вам нужен только один блок, а имя блока не соответствуетНеобходимо сопоставить любое из доменных имен.Вы можете использовать любой сегмент, который вам нужен ... но вам нужно использовать конечную точку хостинга веб-сайта для сегмента, чтобы CloudFront рассматривал его как пользовательский источник.Создание «источника S3» с использованием конечной точки REST для сегмента не будет работать.
'use strict';
// if an incoming request is for a domain name other than the canonical
// (official) hostname for the site, this Lambda@Edge trigger
// will redirect the request back to the official site, subject to the
// configuration parameters below.
// this trigger must be deployed as an Origin Request trigger.
// in the CloudFront Cache Behavior settings, the Host header must be
// whitelisted for forwarding, in order for this function to work as intended;
// this is an artifact of the way the Lambda@Edge interface interacts with the
// CloudFront cache key mechanism -- we can't react to what we can't see,
// and if it isn't part of the cache key, CloudFront won't expose it.
// specify the official hostname of the site; requests to this domain will
// be passed through; others will redirect to it...
const canonical_domain_name = 'example.com';
// ...but note that every CloudFront distribution has a default *.cloudfront.net
// hostname that can't be disabled; you may not want this hostname to do
// anything at all, including redirect; set this parameter to true if you
// want to to return 404 for the default hostname; see the render_reject()
// function to customize the behavior further.
const reject_default_hostname = false;
// the "origin" is the server that provides your content; this is configured
// in the distribution and selected in the Cache Behavior settings, but
// that information needs to be provided here, so that we can modify
// successful requests to match what the destination expects.
const origin_domain_name = 'example-bucket.s3-website.us-east-2.amazonaws.com';
// http status code for redirects; you may want 302 or 307 for testing,
// and 301 or 308 for production; note that this is a string, not a number.
const redirect_http_status_code = '302';
// for generated redirects, we can also set a cache control header; you'll need
// to ensure you format this correctly, since the code below does not validate
// the syntax; here, max-age is how long the browser should cache redirects,
// while s-maxage tells CloudFront how long to potentially cache them;
// higher values should result in less traffic and potentially lower costs;
// set to empty string or null if you don't want to set a value.
const redirect_cache_control = 'max-age=300, s-maxage=86400';
// set false to drop the query string on redirects; true to preserve
const redirect_preserve_querystring = true;
// set false to change the path to '/' on redirects; true to preserve
const redirect_preserve_path = true;
// end of configuration
// the URL in the generated redirect will always use https unless you
// configure whitelisting of CloudFront-Forwarded-Proto, in which case we
// will use that value; if you want to send http to https, use the
// Viewer Protocol Policy settings in the CloudFront cache behavior.
exports.handler = (event, context, callback) => {
// extract the CloudFront object from the trigger event
const cf = event.Records[0].cf;
// extract the request object
const request = cf.request;
// extract the HTTP Host header
const host = request.headers.host[0].value;
// check whether the host header matches the canonical value; if so,
// set the host header to what the origin expects, and return control
// to CloudFront
if(host === canonical_domain_name)
{
request.headers.host[0].value = origin_domain_name;
return callback(null, request);
}
// check for rejection
if (reject_default_hostname && host.endsWith('.cloudfront.net'))
{
return render_reject(cf, callback);
}
// if neither 'return' above has been invoked, then we need to generate a redirect.
const proto = (request.headers['cloudfront-forwarded-proto'] || [{ value: 'https' }])[0].value;
const path = redirect_preserve_path ? request.uri : '/';
const query = redirect_preserve_querystring && (request.querystring != '') ? ('?' + request.querystring) : '';
const location = proto + '://' + canonical_domain_name + path + query;
// build a response object to redirect the browser.
const response = {
status: redirect_http_status_code,
headers: {
'location': [ { key: 'Location', value: location } ],
},
body: '',
};
// add the cache control header, if configured
if(redirect_cache_control)
{
response.headers['cache-control'] = [{ key: 'Cache-Control', value: redirect_cache_control }];
}
// return the response object, preventing the request from being sent to
// the origin server
return callback(null, response);
};
function render_reject(cf, callback) {
// only invoked if the request is for *.cloudfront.net and you set
// reject_default_hostname to true; here, we generate a very simple
// response, text/plain, with a 404 error. This can be customized to HTML
// or XML, etc., according to your local practices, but be sure you properly
// escape the request URI, since it is untrusted data and could lead to an
// XSS injection otherwise; no similar vulnerability exists with plain text.
const body_text = `The requested URL '${cf.request.uri}' does not exist ` +
'on this server, or access is not enabled via the ' +
`${ cf.request.headers.host[0].value } endpoint.\r\n`;
// generate a response; you may want to customize this; note that
// Lambda@Edge is strict with regard to the way headers are specified;
// the outer keys are lowercase, the inner keys can be mixed.
const response = {
status: '404',
headers: {
'cache-control': [{ key: 'Cache-Control', value: 'no-cache, s-maxage=86400' }],
'content-type': [{ key: 'Content-Type', value: 'text/plain' }],
},
body: body_text,
};
return callback(null, response);
}
// eof