Хостинг статического веб-сайта S3 Все пути к Index.html
Я использую S3 для размещения приложения JavaScript, которое будет использовать push5tates HTML5. Проблема в том, что если пользователь добавит в закладки какой-либо из URL-адресов, он ни к чему не приведет. Что мне нужно, так это возможность принимать все URL-запросы и обслуживать корневой файл index.html в моем контейнере S3, а не просто выполнять полное перенаправление. Тогда мое javascript-приложение могло бы проанализировать URL-адрес и предоставить нужную страницу.
Есть ли способ сказать S3 обслуживать index.html для всех URL-запросов вместо того, чтобы делать перенаправления? Это было бы похоже на настройку apache для обработки всех входящих запросов путем предоставления одного index.html, как в этом примере: /questions/31802794/horoshij-uchebnik-po-ispolzovaniyu-html5-history-api-pushstate/31802805#31802805. Я действительно хотел бы избежать запуска веб-сервера только для обработки этих маршрутов. Делать все из S3 очень привлекательно.
20 ответов
Решение Mark не плохо, но есть еще более простое решение. В ваших свойствах корзины в документе об ошибках просто используйте тот же файл, что и индексный документ. Фреймворки JavaScript, такие как Backbone, AngularJS и т. Д., Будут работать "из коробки", и обновление страницы будет полностью поддерживаться.
С помощью CloudFront очень легко решить эту проблему без URL-хаков.
- Создайте S3 Bucket, например: реагировать
- Создайте дистрибутивы CloudFront со следующими настройками:
- Корневой объект по умолчанию: index.html
- Исходное доменное имя: S3 bucket domain, например: Reaction.s3.amazonaws.com
- Перейдите на вкладку " Страницы ошибок ", нажмите " Создать пользовательский ответ об ошибке":
- Код ошибки HTTP: 403: запрещено (404: не найдено, в случае статического веб-сайта S3)
- Настройка ответа об ошибке: Да
- Путь к странице ответа: /index.html
- Код ответа HTTP: 200: ОК
- Нажмите на Создать
Я смог заставить это работать следующим образом:
В разделе " Изменить правила перенаправления" консоли S3 для своего домена добавьте следующие правила:
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<HostName>yourdomainname.com</HostName>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
Это перенаправит все пути, в результате которых 404 не найден в вашем корневом домене с версией хеш-взрыва пути. Поэтому http://yourdomainname.com/posts будет перенаправлять на http://yourdomainname.com/ при условии, что в / posts отсутствует файл.
Однако, чтобы использовать pushStates в HTML5, нам нужно принять этот запрос и вручную установить правильное pushState на основе пути хеш-взрыва. Так что добавьте это в начало вашего файла index.html:
<script>
history.pushState({}, "entry page", location.hash.substring(1));
</script>
Это захватывает хэш и превращает его в push5-состояние HTML5. С этого момента вы можете использовать pushStates, чтобы в вашем приложении были пути без хэша.
Есть несколько проблем с подходом на основе S3/Redirect, упомянутых другими.
- Mutliple перенаправления происходят, когда пути вашего приложения разрешены. Например: www.myapp.com/path/for/test перенаправляется как www.myapp.com/#/path/for/test
- В строке URL появляется мерцание, когда "#" появляется и уходит из-за действия вашей структуры SPA.
- На SEO влияет, потому что - "Эй! Это Google заставляет его руку на перенаправления
- Поддержка Safari для вашего приложения требует больших усилий.
Решение:
- Убедитесь, что у вас настроен индексный маршрут для вашего сайта. В основном это index.html
- Удалить правила маршрутизации из конфигураций S3
- Поместите Cloudfront перед вашей корзиной S3.
- Настройте правила страницы ошибок для своего экземпляра Cloudfront. В правилах ошибок укажите:
- Http код ошибки: 404 (и 403 или другие ошибки по необходимости)
- Минимальный TTL кэширования ошибок (секунд): 0
- Настроить ответ: Да
- Путь к странице ответа: /index.html
- Код ответа HTTP: 200
5.Для SEO необходимо убедиться, что ваш index.html не кэшируется, выполните следующие действия:
- Сконфигурируйте экземпляр EC2 и настройте сервер nginx.
- Назначьте публичный IP-адрес вашему экземпляру EC2.
- Создайте ELB с экземпляром EC2, который вы создали в качестве экземпляра.
- Вы должны быть в состоянии назначить ELB вашему DNS.
- Теперь настройте сервер nginx на следующие действия: Proxy_pass все запросы к вашему CDN (только для index.html, обслуживайте другие ресурсы прямо из вашего облачного фронта) и для поисковых роботов, перенаправляйте трафик в соответствии с сервисами, такими как Prerender.io
Я могу помочь более подробно в отношении настройки nginx, просто оставьте записку. Выучил это трудным путем.
После обновления распределения фронта облака. Сделайте недействительным свой кэш облачного фронта один раз, чтобы быть в первозданном режиме. Хит URL в браузере и все должно быть хорошо.
Это касательно, но вот совет для тех, кто использует библиотеку Rackt React Router с (HTML5) историей браузера и хочет разместить на S3.
Предположим, пользователь посещает /foo/bear
на вашем статическом веб-сайте S3. Учитывая более раннее предложение davidbonachera, правила перенаправления отправят их /#/foo/bear
, Если ваше приложение построено с использованием истории браузера, это не принесет много пользы. Однако в этот момент ваше приложение загружено, и теперь оно может манипулировать историей.
Включив историю Rackt в наш проект (см. Также раздел " Использование пользовательских историй из проекта React Router"), вы можете добавить прослушиватель, который знает пути к истории хеша, и при необходимости заменить путь, как показано в этом примере:
import ReactDOM from 'react-dom';
/* Application-specific details. */
const route = {};
import { Router, useRouterHistory } from 'react-router';
import { createHistory } from 'history';
const history = useRouterHistory(createHistory)();
history.listen(function (location) {
const path = (/#(\/.*)$/.exec(location.hash) || [])[1];
if (path) history.replace(path);
});
ReactDOM.render(
<Router history={history} routes={route}/>,
document.body.appendChild(document.createElement('div'))
);
Подведем итог:
- Правило перенаправления S3 Дэвида будет направлять
/foo/bear
в/#/foo/bear
, - Ваше приложение загрузится.
- Слушатель истории обнаружит
#/foo/bear
обозначение истории. - И замени историю на правильный путь.
Link
теги будут работать как положено, как и все другие функции истории браузера. Единственный недостаток, который я заметил, это промежуточное перенаправление, которое происходит при первоначальном запросе.
Это было вдохновлено решением для AngularJS, и я подозреваю, что его можно легко адаптировать к любому приложению.
Я вижу 4 решения этой проблемы. Первые 3 уже были охвачены в ответах, и последний - мой вклад.
Установите документ об ошибке в index.html.
Проблема: тело ответа будет правильным, но код состояния будет 404, что вредит SEO.Установите правила перенаправления.
Проблема: URL загрязнен#!
и страница мигает при загрузке.Настройте CloudFront.
Проблема: все страницы вернут 404 от источника, поэтому вам нужно выбрать, если вы не будете кэшировать что-либо (TTL 0, как предлагается) или вы будете кэшировать и иметь проблемы при обновлении сайта.Предварительно отобразить все страницы.
Проблема: дополнительная работа по предварительному отображению страниц, особенно когда страницы часто меняются. Например, новостной сайт.
Я предлагаю использовать вариант 4. Если вы предварительно отобразите все страницы, на ожидаемых страницах не будет 404 ошибок. Страница будет нормально загружаться, а фреймворк получит контроль и будет действовать как SPA. Можно также настроить документ ошибок для отображения общей страницы error.html и правила перенаправления для перенаправления ошибок 404 на страницу 404.html (без хеш-бенга).
Что касается 403 Запрещенных ошибок, я не позволяю им случиться вообще. В моем приложении я считаю, что все файлы внутри хоста являются общедоступными, и я установил это с опцией Everyone с разрешением на чтение. Если на вашем сайте есть страницы, которые являются частными, разрешение пользователю видеть HTML- макет не должно быть проблемой. Вам нужно защитить данные, и это делается в бэкэнде.
Кроме того, если у вас есть личные ресурсы, такие как пользовательские фотографии, вы можете сохранить их в другом ведре. Потому что частные активы требуют такой же заботы, как и данные, и их нельзя сравнивать с файлами активов, которые используются для размещения приложения.
Самым простым решением сделать приложение Angular 2+ из Amazon S3 и работающих прямых URL-адресов является указание index.html как документов Index и Error в конфигурации S3 bucket.
Сегодня я столкнулся с той же проблемой, но решение @Mark-Nutter было неполным, чтобы удалить хэш-бэнг из моего приложения angularjs.
Фактически, вам нужно перейти в " Редактировать разрешения", нажать " Добавить дополнительные разрешения", а затем добавить правильный список в вашем списке для всех. С этой конфигурацией AWS S3 теперь сможет возвращать ошибку 404, а затем правило перенаправления правильно поймает случай.
А затем вы можете перейти к " Изменить правила перенаправления" и добавить это правило:
<RoutingRules>
<RoutingRule>
<Condition>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<HostName>subdomain.domain.fr</HostName>
<ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
Здесь вы можете заменить имя хоста subdomain.domain.fr своим доменом и KeyPrefix #! /, Если вы не используете метод hashbang для целей SEO.
Конечно, все это будет работать, только если у вас уже есть настройка html5mode в вашем угловом приложении.
$locationProvider.html5Mode(true).hashPrefix('!');
Теперь вы можете сделать это с помощью Lambda@Edge, чтобы переписать пути
Вот рабочая лямбда-функция @Edge:
- Создайте новую лямбда-функцию, но используйте один из уже существующих Blueprints вместо пустой функции.
- Выполните поиск по запросу "cloudfront" и выберите создание ответа cloudfront из результатов поиска.
- После создания функции замените содержимое приведенным ниже. Мне также пришлось изменить время выполнения узла на 10.x, потому что облачный фронт не поддерживал узел 12 на момент написания.
'use strict';
exports.handler = (event, context, callback) => {
// Extract the request from the CloudFront event that is sent to Lambda@Edge
var request = event.Records[0].cf.request;
// Extract the URI from the request
var olduri = request.uri;
// Match any '/' that occurs at the end of a URI. Replace it with a default index
var newuri = olduri.replace(/\/$/, '\/index.html');
// Log the URI as received by CloudFront and the new URI to be used to fetch from origin
console.log("Old URI: " + olduri);
console.log("New URI: " + newuri);
// Replace the received URI with the URI that includes the index page
request.uri = newuri;
return callback(null, request);
};
В поведении облачного интерфейса вы отредактируете их, чтобы добавить вызов этой лямбда-функции в "Запрос средства просмотра".
Полное руководство: https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/
Подобно другому ответу с использованием Lambda@Edge, вы можете использовать функции Cloudfront (которые с августа 2021 года являются частью бесплатного уровня ).
Моя структура URL-адресов такая:
- example.com - React SPA, размещенный на S3
- example.com/blog - блог, размещенный на EC2
Поскольку я размещаю блог в том же домене, что и SPA, я не мог использовать предложенный ответ о наличии страниц ошибок Cloudfront 403/404 с использованием страницы ошибок по умолчанию.
Моя установка для Cloudfront :
- Установите два источника (S3 и мой экземпляр EC2 через эластичный ALB)
- Настройте два поведения:
-
/blog
-
- Создайте функцию Cloudfront
- Настройте функцию Cloudfront как «Запрос средства просмотра»
default (*)
поведение
Я использую эту функцию Cloudfront на примере AWS . Это может работать не во всех случаях, но моя структура URL-адресов для приложения React не содержит никаких
.
.
function handler(event) {
var request = event.request;
var uri = request.uri;
// If the request is not for an asset (js, png, etc), render the index.html
if (!uri.includes('.')) {
request.uri = '/index.html';
}
return request;
}
Искал такую же проблему. Я закончил тем, что использовал смесь предложенных решений, описанных выше.
Во-первых, у меня есть корзина s3 с несколькими папками, каждая папка представляет собой сайт реакции / редукции. Я также использую cloudfront для аннулирования кэша.
Поэтому мне пришлось использовать правила маршрутизации для поддержки 404 и перенаправить их в конфигурацию хэша:
<RoutingRules>
<RoutingRule>
<Condition>
<KeyPrefixEquals>website1/</KeyPrefixEquals>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>my.host.com</HostName>
<ReplaceKeyPrefixWith>website1#</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
<RoutingRule>
<Condition>
<KeyPrefixEquals>website2/</KeyPrefixEquals>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>my.host.com</HostName>
<ReplaceKeyPrefixWith>website2#</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
<RoutingRule>
<Condition>
<KeyPrefixEquals>website3/</KeyPrefixEquals>
<HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
</Condition>
<Redirect>
<Protocol>https</Protocol>
<HostName>my.host.com</HostName>
<ReplaceKeyPrefixWith>website3#</ReplaceKeyPrefixWith>
</Redirect>
</RoutingRule>
</RoutingRules>
В моем коде JS мне нужно было справиться с baseName
конфиг для реакции-роутера. Прежде всего, убедитесь, что ваши зависимости совместимы, я установил history==4.0.0
который был несовместим с react-router==3.0.1
,
Мои зависимости:
- "история": "3.2.0",
- "реагировать": "15.4.1",
- "реаги-редукс": "4.4.6",
- "реагирующий маршрутизатор": "3.0.1",
- "response-router-redux": "4.0.7",
Я создал history.js
файл для загрузки истории:
import {useRouterHistory} from 'react-router';
import createBrowserHistory from 'history/lib/createBrowserHistory';
export const browserHistory = useRouterHistory(createBrowserHistory)({
basename: '/website1/',
});
browserHistory.listen((location) => {
const path = (/#(.*)$/.exec(location.hash) || [])[1];
if (path) {
browserHistory.replace(path);
}
});
export default browserHistory;
Этот фрагмент кода позволяет обрабатывать 404, отправленные сервером, с помощью хэша и заменять их в истории для загрузки наших маршрутов.
Теперь вы можете использовать этот файл для настройки вашего хранилища и вашего корневого файла.
import {routerMiddleware} from 'react-router-redux';
import {applyMiddleware, compose} from 'redux';
import rootSaga from '../sagas';
import rootReducer from '../reducers';
import {createInjectSagasStore, sagaMiddleware} from './redux-sagas-injector';
import {browserHistory} from '../history';
export default function configureStore(initialState) {
const enhancers = [
applyMiddleware(
sagaMiddleware,
routerMiddleware(browserHistory),
)];
return createInjectSagasStore(rootReducer, rootSaga, initialState, compose(...enhancers));
}
import React, {PropTypes} from 'react';
import {Provider} from 'react-redux';
import {Router} from 'react-router';
import {syncHistoryWithStore} from 'react-router-redux';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import variables from '!!sass-variable-loader!../../../css/variables/variables.prod.scss';
import routesFactory from '../routes';
import {browserHistory} from '../history';
const muiTheme = getMuiTheme({
palette: {
primary1Color: variables.baseColor,
},
});
const Root = ({store}) => {
const history = syncHistoryWithStore(browserHistory, store);
const routes = routesFactory(store);
return (
<Provider {...{store}}>
<MuiThemeProvider muiTheme={muiTheme}>
<Router {...{history, routes}} />
</MuiThemeProvider>
</Provider>
);
};
Root.propTypes = {
store: PropTypes.shape({}).isRequired,
};
export default Root;
Надеюсь, поможет. Вы заметите, что в этой конфигурации я использую редукторный инжектор и доморощенный саговый инжектор для асинхронной загрузки javascript через маршрутизацию. Не возражайте против этих строк.
Перейдите к настройкам хостинга статических веб-сайтов вашего сегмента и установите для параметра Error document значение index.html.
Так как проблема все еще существует, я, однако, добавляю другое решение. Мой случай состоял в том, что я хотел автоматически развернуть все pull-запросы на s3 для тестирования перед слиянием, сделав их доступными в [mydomain]/pull-запросы /[pr номер] /
(напр. www.example.com/pull-requests/822/)
Насколько мне известно, ни один из сценариев правил s3 не позволял бы иметь несколько проектов в одном сегменте с использованием маршрутизации html5, поэтому, хотя предложение с наибольшим количеством голосов работает для проекта в корневой папке, оно не подходит для нескольких проектов в собственных подпапках.
Поэтому я указал свой домен на мой сервер, где следующий конфиг nginx сделал свою работу
location /pull-requests/ {
try_files $uri @get_files;
}
location @get_files {
rewrite ^\/pull-requests\/(.*) /$1 break;
proxy_pass http://<your-amazon-bucket-url>;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 404 = @get_routes;
}
location @get_routes {
rewrite ^\/(\w+)\/(.+) /$1/ break;
proxy_pass http://<your-amazon-bucket-url>;
proxy_intercept_errors on;
recursive_error_pages on;
error_page 404 = @not_found;
}
location @not_found {
return 404;
}
он пытается получить файл и, если он не найден, предполагает, что это маршрут html5, и пытается это сделать. Если у вас есть угловая страница 404 для не найденных маршрутов, вы никогда не доберетесь до @not_found и получите угловую страницу 404, а не найденные файлы, которые можно исправить с помощью некоторого правила if в @get_routes или чего-то подобного.
Я должен сказать, что я не чувствую себя слишком комфортно в области конфигурации nginx и использования регулярных выражений в этом отношении, я получил это, работая с пробой и ошибкой, так что пока это работает, я уверен, что есть место для улучшения, и, пожалуйста, поделитесь своими мыслями,
Примечание: удалите правила перенаправления s3, если они были в конфигурации S3.
и кстати работает в сафари
Проблема с большинством предлагаемых решений, особенно с самым популярным ответом, заключается в том, что вы никогда не получите ошибку 404. Если вы хотите продолжать получать 404, обратитесь к этому решению.
- Я добавил корневой путь ко всем своим маршрутам (что является новым для этого решения), например, все мои маршруты маршрутов начинаются с одного и того же корня ...
вместо paths /foo / baz, /foo2 / baz Теперь у меня / root / foo / baz, / root / foo2 / baz пути. - Выбор одного и того же корня для всех маршрутов таков, что он не конфликтует с какой-либо реальной папкой.
- Теперь я могу использовать этот корень для создания простых правил перенаправления где угодно. Все мои изменения перенаправления будут влиять только на этот путь, а все остальное будет как раньше.
Это правило перенаправления, которое я добавил в свою корзину s3
[
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404",
"KeyPrefixEquals": "root/"
},
"Redirect": {
"HostName": "mydomain.com",
"ReplaceKeyPrefixWith": "#/"
}
}
]
- После этого / root / foo / baz перенаправляется в / # / foo / baz, и страница загружается дома без ошибок.
Я добавил следующий код при загрузке после того, как мое приложение Vue смонтировано. Это приведет мое приложение к желаемому маршруту. Вы можете изменить часть router.push в соответствии с Angular или тем, что вы используете.
if(location.href.includes("#"))
{
let currentRoute = location.href.split("#")[1];
router.push({ path: `/root${currentRoute}` })
}
Даже если вы не используете перенаправление на уровне s3, наличие единой базы для всех маршрутов может быть полезно для любых других перенаправлений, которые вы можете предпочесть. Это помогает серверной части определить, что это не запрос реального внутреннего ресурса, и внешнее приложение сможет ее обработать.
Если вы попали сюда в поисках решения, которое работает с React Router и AWS Amplify Console - вы уже знаете, что не можете напрямую использовать правила перенаправления CloudFront, поскольку Amplify Console не предоставляет CloudFront Distribution для приложения.
Решение, однако, очень простое - вам просто нужно добавить правило перенаправления / перезаписи в Amplify Console следующим образом:
Смотрите следующие ссылки для получения дополнительной информации (и правила для копирования на скриншоте):
Решение, не упомянутое здесь, заключается в использовании CloudFront Functions для перезаписи URI запроса на
index.html
по запросу зрителя:
function handler(event) {
var request = event.request;
request.uri = '/index.html';
return request;
}
Это самое элегантное решение, которое я нашел - использовать модуль маршрутизатора приложения с подстановочным символом перенаправления.
{ path: '**', redirectTo: '' }
Затем, как упоминалось в бесчисленных публикациях выше, убедитесь, что вы перенаправляете ошибки 404/403 в файл index.html со статусом 200. Проблема заключается в том, что в результате обновления браузера загружается базовый href как (href + предыдущий маршрут). Если вы просматривали вид маршрутизатора на
www.my-app.com/home
тогда обновление покажет
www.my-app.com/home/home
Чтобы удалить дублированный путь маршрута, используйте модуль APP_BASE_HREF, чтобы переназначить базовую ссылку браузера так, как это
Если вам нужно сохранить первый параметр url, добавьте несколько результатов из '/'
Трещина.
Браузер переходит к вашему перенаправлению SPA для www.your-app.com/home/home
теперь заменит URL на www.your-app.com/home
и приложение будет вести себя так, как и ожидалось из вашей конфигурации маршрутизации в приложении
Я искал ответ на это сам. Похоже, что S3 поддерживает только перенаправления, вы не можете просто переписать URL-адрес и молча вернуть другой ресурс. Я рассматриваю возможность использования моего скрипта сборки, чтобы просто сделать копии моего index.html во всех необходимых местах пути. Может быть, это будет работать для вас тоже.
Просто чтобы поставить предельно простой ответ. Просто используйте стратегию определения местоположения хэша для маршрутизатора, если вы размещаете на S3.
экспорт const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot(маршруты, { useHash: true, scrollPositionRestoration: 'enabled' });