web-dev-qa-db-fra.com

Hébergement de site Web statique S3 route tous les chemins d'accès à Index.html

J'utilise S3 pour héberger une application javascript qui utilisera des pushStates HTML5. Le problème est que si l'utilisateur marque des URL, il ne résoudra rien. Ce dont j'ai besoin, c'est de pouvoir accepter toutes les demandes d'URL et de servir le fichier racine index.html dans mon compartiment S3, plutôt que de simplement effectuer une redirection complète. Ensuite, mon application javascript pourrait analyser l'URL et servir la page appropriée. 

Existe-t-il un moyen de dire à S3 de servir le fichier index.html pour toutes les demandes d’URL au lieu de faire des redirections? Cela ressemblerait à configurer Apache pour gérer toutes les demandes entrantes en servant un seul index.html comme dans cet exemple: https://stackoverflow.com/a/10647521/1762614 . J'aimerais vraiment éviter de faire fonctionner un serveur Web uniquement pour gérer ces itinéraires. Tout faire à partir de S3 est très attrayant. 

193
Mark Nutter

Voici comment j'ai pu obtenir que cela fonctionne:

Dans la section Edit Redirection Rules de la console S3 de votre domaine, ajoutez les règles suivantes:

<RoutingRules>
  <RoutingRule>
    <Condition>
      <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
    </Condition>
    <Redirect>
      <HostName>yourdomainname.com</HostName>
      <ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
    </Redirect>
  </RoutingRule>
</RoutingRules>

Cela redirigera tous les chemins qui aboutissent à un 404 non trouvé vers votre domaine racine avec une version hachée du chemin. So http://votrenomdedomaine.com/posts sera redirigé vers http://votredomaindomaine.com/#!/posts à condition qu'aucun fichier ne se trouve dans/posts.

Pour utiliser HTML5 pushStates, nous devons cependant prendre cette demande et établir manuellement le bon pushState en fonction du chemin de hachage. Ajoutez donc ceci en haut de votre fichier index.html:

<script>
  history.pushState({}, "entry page", location.hash.substring(1));
</script>

Cela saisit le hachage et le transforme en un pushState HTML5. À partir de ce moment, vous pouvez utiliser pushStates pour créer des chemins non hash-bang dans votre application.

190
Mark Nutter

Il est très facile de le résoudre sans piratage d'URL, avec l'aide de CloudFront.

  • Créez un compartiment S3, par exemple: react
  • Créez des distributions CloudFront avec ces paramètres:
    • Objet racine par défaut : index.html
    • Nom de domaine d'origine : domaine de compartiment S3, par exemple: react.s3.amazonaws.com
  • Allez à Pages d'erreur onglet, cliquez sur Créer une réponse d'erreur personnalisée :
    • Code d'erreur HTTP : 403: interdit (404: introuvable, dans le cas d'un site Web statique S3)
    • Personnaliser la réponse à l'erreur : Oui
    • Chemin de la page de réponse : /index.html
    • Code de réponse HTTP : 200: OK
    • Cliquez sur Créer
179
lenaten

Il y a peu de problèmes avec l'approche basée sur S3/Redirect mentionnée par d'autres.

  1. plusieurs redirections se produisent lorsque les chemins de votre application sont résolus. Par exemple: www.myapp.com/path/for/test est redirigé sous www.myapp.com/#/path/for/test
  2. Il y a un scintillement dans la barre d'URL lorsque le "#" va et vient en raison de l'action du cadre de votre SPA.
  3. Le référencement est impacté car - 'Hey! Son google forçant sa main sur les redirections '
  4. Safari prend en charge votre application.

La solution est:

  1. Assurez-vous que l'itinéraire d'index est configuré pour votre site Web. C'est principalement index.html
  2. Supprimer les règles de routage des configurations S3
  3. Placez une façade en nuage devant votre panier S3. 
  4. Configurez les règles de page d'erreur pour votre instance Cloudfront. Dans les règles d'erreur, spécifiez:
    • Code d'erreur HTTP: 404 (et 403 ou autres erreurs selon les besoins)
    • Erreur de mise en cache minimum TTL (secondes): 0
    • Personnaliser la réponse: oui
    • Chemin de la page de réponse: /index.html
    • Code de réponse HTTP: 200

5.Pour le référencement, vous devez vous assurer que votre index.html ne cache pas, procédez comme suit:

  • Configurez une instance EC2 et configurez un serveur nginx. 
  • Attribuez une adresse IP publique à votre instance EC2.
  • Créer un ELB avec l'instance EC2 créée en tant qu'instance
  • Vous devriez pouvoir affecter l'ELB à votre DNS.
  • Maintenant, configurez votre serveur nginx pour effectuer les opérations suivantes: Proxy_pass toutes les requêtes sur votre CDN (pour index.html uniquement, servir directement d’autres actifs depuis votre cloudfront) et pour les robots de recherche, rediriger le trafic comme le stipulent des services comme Prerender.io.

Je peux vous aider avec plus de détails en ce qui concerne la configuration de nginx, laissez simplement une note. Je l'ai appris à la dure.

Une fois la mise à jour de la distribution front cloud. Invalidez votre cache Cloudfront une fois pour passer en mode vierge . Appuyez sur l’URL dans le navigateur et tout devrait bien se passer.

120
moha297

C'est tangentiel, mais voici un conseil pour ceux qui utilisent Rackt _ { bibliothèque React Router avec l'historique du navigateur (HTML5) qui souhaitent héberger sur S3.

Supposons qu'un utilisateur visite /foo/bear sur votre site Web statique hébergé par S3. Étant donné David } _ suggestion antérieure , les règles de redirection les enverront à /#/foo/bear. Si votre application est construite en utilisant l'historique du navigateur, cela ne fera pas beaucoup de bien. Cependant, votre application est chargée à ce stade et elle peut maintenant manipuler l'historique.

Si vous incluez Rackt history dans notre projet (voir aussi Utilisation d'histoires personnalisées du projet React Router), vous pouvez ajouter un écouteur conscient des chemins de l'historique de hachage et le remplacer selon le cas, comme illustré dans cet exemple:

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'))
);

Récapituler:

  1. La règle de redirection S3 de David dirigera /foo/bear vers /#/foo/bear.
  2. Votre application va se charger.
  3. L'écouteur d'historique détectera la notation d'historique #/foo/bear.
  4. Et remplacez l'histoire par le chemin correct.

Link tags fonctionnera comme prévu, de même que toutes les autres fonctions d'historique du navigateur. Le seul inconvénient que j'ai remarqué est la redirection interstitielle qui se produit lors de la demande initiale.

Ceci a été inspiré par une solution pour AngularJS , et je suppose qu’il pourrait être facilement adapté à n’importe quelle application.

14
Michael Ahlers

Je vois 4 solutions à ce problème. Les 3 premiers étaient déjà couverts en réponses et le dernier est ma contribution.

  1. Définissez le document d'erreur sur index.html.
    Problème: le corps de la réponse sera correct, mais le code d'état sera 404, ce qui nuit au référencement.

  2. Définissez les règles de redirection.
    Problème: URL polluée par #! et la page clignote lors du chargement.

  3. Configurez CloudFront.
    Problème: toutes les pages renverront 404 à partir d’Origine. Vous devez donc choisir si vous ne cachez rien (TTL 0 comme suggéré) ou si vous avez des problèmes de cache lors de la mise à jour du site.

  4. Prérender toutes les pages.
    Problème: travail supplémentaire pour les pages de pré-édition, spécialement lorsque les pages changent fréquemment. Par exemple, un site de nouvelles.

Ma suggestion est d'utiliser l'option 4. Si vous prérender toutes les pages, il n'y aura pas d'erreur 404 pour les pages attendues. La page se chargera bien et le cadre prendra le contrôle et agira normalement en tant que SPA. Vous pouvez également configurer le document d'erreur pour qu'il affiche une page générique error.html et une règle de redirection permettant de rediriger les erreurs 404 vers une page 404.html (sans le hashbang).

En ce qui concerne les 403 erreurs interdites, je ne les laisse pas du tout se produire. Dans mon application, je considère que les fichiers tous du compartiment Host sont publics et j'ai défini cela avec l'option tout le monde avec l'autorisation read. Si votre site contient des pages privées, permettre à l'utilisateur de voir le code HTML mise en page ne devrait pas être un problème. Ce que vous devez protéger, c'est le data et cela se fait dans le backend.

De plus, si vous avez des ressources privées, telles que des photos d'utilisateurs, vous pouvez les enregistrer dans le compartiment autre. Parce que les actifs privés nécessitent les mêmes soins que données et ne peuvent pas être comparés aux fichiers d'actif utilisés pour héberger l'application.

10
Zanon

J'ai rencontré le même problème aujourd'hui mais la solution de @ Mark-Nutter était incomplète pour supprimer le hashbang de mon application angularjs.

En fait, vous devez aller à Editer les autorisations, cliquer sur Ajouter plus de permissions puis ajouter le droit List sur votre compartiment à tout le monde. Avec cette configuration, AWS S3 sera désormais en mesure de renvoyer une erreur 404, puis la règle de redirection interceptera correctement le cas.

Juste comme ça :  enter image description here

Et ensuite, vous pouvez aller à Editer les règles de redirection et ajouter cette règle:

<RoutingRules>
    <RoutingRule>
        <Condition>
            <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
        </Condition>
        <Redirect>
            <HostName>subdomain.domain.fr</HostName>
            <ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
        </Redirect>
    </RoutingRule>
</RoutingRules>

Ici, vous pouvez remplacer le HostName subdomain.domain.fr par votre domaine et le KeyPrefix #!/Si vous n'utilisez pas la méthode hashbang à des fins de référencement.

Bien sûr, tout cela ne fonctionnera que si vous avez déjà configuré html5mode dans votre application angulaire.

$locationProvider.html5Mode(true).hashPrefix('!');
10
davidbonachera

La solution la plus simple pour que l'application Angular 2+ servie à partir d'Amazon S3 et les URL directes fonctionne consiste à spécifier index.html en tant que documents d'index et d'erreur dans la configuration de compartiment S3.

 enter image description here

6

puisque le problème est toujours là, j’ai eu l’intention de proposer une autre solution ..__ Mon cas était que je souhaitais déployer automatiquement toutes les demandes d'extraction sur s3 à des fins de test avant de les fusionner pour les rendre accessibles sur [mydomain]/pull-request/[pr number ] /
(ex. www.example.com/pull-requests/822/)

Autant que je sache, des scénarios autres que ceux de règles s3 permettraient d'avoir plusieurs projets dans un compartiment en utilisant le routage html5. Ainsi, bien que la suggestion la plus votée ci-dessus fonctionne pour un projet dans le dossier racine, elle ne le fait pas pour plusieurs projets dans ses propres sous-dossiers.

Donc, j'ai pointé mon domaine sur mon serveur où la configuration suivante de nginx a fait le travail

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;
}

il essaie d'obtenir le fichier et s'il ne le trouve pas, il suppose qu'il s'agit d'un itinéraire html5 et le tente. Si vous avez une page angulaire 404 pour les itinéraires non trouvés, vous n'obtiendrez jamais à @not_found et vous obtiendrez une page angulaire 404 renvoyée à la place des fichiers non trouvés, ce qui pourrait être corrigé avec certaines règles if dans @get_routes.

Je dois dire que je ne me sens pas trop à l'aise dans nginx config et que j'utilise regex, j'ai eu ce travail avec quelques essais et erreurs, alors pendant que cela fonctionne, je suis sûr qu'il y a place à amélioration et je vous prie de partager vos pensées. .

Remarque : supprime les règles de redirection s3 si vous les aviez dans la configuration S3.

et d'ailleurs travaille dans Safari

4
Andrew Arnautov

Je cherchais le même genre de problème… .. Je me suis retrouvé à utiliser un mélange des solutions suggérées ci-dessus.

Tout d'abord, j'ai un compartiment s3 avec plusieurs dossiers, chaque dossier représentant un site Web réactif/redux… .. J'utilise également cloudfront pour l'invalidation du cache.

J'ai donc dû utiliser les règles de routage pour prendre en charge 404 et les rediriger vers une configuration de hachage:

<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>

Dans mon code js, je devais le manipuler avec une configbaseName pour reagir-routeur . Tout d'abord, assurez-vous que vos dépendances sont interopérables, j'avais installé history==4.0.0 qui était incompatible avec react-router==3.0.1.

Mes dépendances sont:

  • "historique": "3.2.0",
  • "réagir": "15.4.1",
  • "react-redux": "4.4.6",
  • "react-router": "3.0.1",
  • "react-router-redux": "4.0.7",

J'ai créé un fichier history.js pour charger l'historique:

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;

Ce morceau de code permet de manipuler la 404 envoyée par le serveur avec un hachage, et de les remplacer dans l'historique pour charger nos routes.

Vous pouvez maintenant utiliser ce fichier pour configurer votre magasin et votre fichier racine.

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;

J'espère que ça va aider . Vous remarquerez avec cette configuration que j'utilise un injecteur redux et un injecteur homebrew sagas pour charger javascript de manière asynchrone via un routage . Ne vous occupez pas de ces lignes.

3
Guillaume Cisco

Si vous avez atterri ici à la recherche d'une solution fonctionnant avec React Router et AWS Amplify Console, vous savez déjà que vous ne pouvez pas utiliser directement les règles de redirection CloudFront car Amplify Console n'expose pas la distribution CloudFront pour l'application.

La solution, cependant, est très simple: il vous suffit d'ajouter une règle de redirection/réécriture dans Amplify Console comme ceci:

Amplify Console Rewrite rule for React Router

Voir les liens suivants pour plus d'informations (et la règle de copie facile de la capture d'écran):

1
Yaro

Je cherchais une réponse à cette question moi-même. S3 semble ne prendre en charge que les redirections; vous ne pouvez pas simplement réécrire l'URL et renvoyer en silence une autre ressource. J'envisage d'utiliser mon script de génération pour simplement faire des copies de mon index.html dans tous les emplacements de chemin requis. Peut-être que cela fonctionnera pour vous aussi.

0
Ed Thomas

C'est la solution la plus élégante que j'ai trouvée - utilisez le module de routeur d'application avec une redirection générique.

{ path: '**', redirectTo: '' }

Ensuite, comme mentionné dans les innombrables publications ci-dessus, assurez-vous de rediriger les erreurs 404/403 vers index.html avec le statut 200. Le problème est que cela entraîne une actualisation du navigateur chargeant le href de base comme (href + route précédente). Si vous visualisiez la vue du routeur à

www.my-app.com/home alors l'actualisation montrera

www.my-app.com/home/home

Pour supprimer le chemin en double, utilisez le module APP_BASE_HREF pour réaffecter la base du navigateur. Href comme cela

Si vous devez conserver le premier paramètre url, ajoutez plusieurs résultats à partir de la division '/'.

Les hits du navigateur pour votre redirect SPA pour www.your-app.com/home/home vont maintenant remplacer l'URL par www.your-app.com/home et l'application se comportera comme prévu à partir de votre configuration de routage in-app

0
Chris Concannon

Juste pour mettre la réponse extrêmement simple. Utilisez simplement la stratégie d’emplacement de hachage pour le routeur si vous hébergez sur S3.

export const AppRoutingModule: ModuleWithProviders = RouterModule.forRoot (itinéraires, {useHash: true, scrollPositionRestoration: 'activé'});

0
Meester Over