S3 Static Website Hosting路由指向Index.html的所有路径

时间:2013-04-28 20:13:49

标签: amazon-web-services redirect amazon-s3 routing pushstate

我使用S3来托管将使用HTML5 pushStates的javascript应用。问题是如果用户为任何URL添加书签,它将无法解析为任何内容。我需要的是能够获取所有URL请求并在我的S3存储桶中提供root index.html,而不仅仅是执行完全重定向。然后我的javascript应用程序可以解析URL并提供正确的页面。

有没有办法告诉S3为所有URL请求提供index.html而不是重定向?这与设置apache以通过提供单个index.html来处理所有传入请求类似,如下例所示:https://stackoverflow.com/a/10647521/1762614。我真的想避免运行Web服务器来处理这些路由。从S3做一切非常有吸引力。

16 个答案:

答案 0 :(得分:202)

使用CloudFront帮助,在没有网址攻击的情况下解决它很容易。

  • 创建S3存储桶,例如:react
  • 使用以下设置创建CloudFront分配:
    • 默认根对象:index.html
    • 原始域名:S3存储区域,例如:react.s3.amazonaws.com
  • 转到错误页面标签,点击创建自定义错误响应
    • HTTP错误代码:403:禁止访问(404:在S3静态网站的情况下找不到)
    • 自定义错误响应:是
    • 响应页面路径:/ index.html
    • HTTP响应代码:200:确定
    • 点击创建

答案 1 :(得分:191)

我能够让它发挥作用的方式如下:

在您的域的S3控制台的编辑重定向规则部分中,添加以下规则:

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

这将使用路径的hash-bang版本将导致未找到404的所有路径重定向到根域。因此,如果/ post没有文件,http://yourdomainname.com/posts会重定向到http://yourdomainname.com/#!/posts

然而,要使用HTML5 pushStates,我们需要接受此请求并根据hash-bang路径手动建立正确的pushState。所以将它添加到index.html文件的顶部:

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

这会抓取哈希并将其转换为HTML5 pushState。从这一点开始,您可以使用pushStates在您的应用程序中使用非哈希爆炸路径。

答案 2 :(得分:123)

其他人提到的基于S3 / Redirect的方法几乎没有问题。

  1. 当应用程序的路径得到解决时,会发生多重重定向。例如:www.myapp.com/path/for/test被重定向为www.myapp.com /#/ path / for / test
  2. 网址栏中有一个闪烁,因为“#”来自于SPA框架的操作。
  3. seo受到影响,因为 - '嘿!它谷歌强迫他的手重定向'
  4. Safari对您的应用程序的支持可供选择。
  5. 解决方案是:

    1. 确保为您的网站配置了索引路由。主要是index.html
    2. 从S3配置中删除路由规则
    3. 将Cloudfront放在S3存储桶前面。
    4. 配置Cloudfront实例的错误页面规则。在错误规则中指定:
      • Http错误代码:404(以及403或其他需要的错误)
      • 错误缓存最小TTL(秒):0
      • 自定义回复:是
      • 响应页面路径:/index.html
      • HTTP响应代码:200
    5. 5.对于SEO需求+确保您的index.html没有缓存,请执行以下操作:

      • 配置EC2实例并设置nginx服务器。
      • 将公共IP分配给您的EC2实例。
      • 创建一个ELB,其中包含您作为实例创建的EC2实例
      • 您应该能够将ELB分配给您的DNS。
      • 现在,配置您的nginx服务器以执行以下操作:Proxy_pass对您的CDN的所有请求(仅限index.html,直接从您的云端服务其他资产)和搜索机器人,按照Prerender等服务的规定重定向流量。 IO

      我可以提供有关nginx设置的更多详细信息,只需留言即可。已经很难学会了。

      云端分发更新后。使您的云端缓存无效一次,使其处于原始模式。 点击浏览器中的网址,一切都很好。

答案 3 :(得分:14)

这是切向的,但是对于那些使用Rackt's React Router library并且希望在S3上托管的(HTML5)浏览器历史记录的人来说,这是一个提示。

假设用户在S3托管的静态网站上访问/foo/bear。根据{{​​3}} David's,重定向规则会将其发送到/#/foo/bear。如果您的应用程序是使用浏览器历史记录构建的,那么这将无济于事。但是,此时您的应用程序已加载,现在可以操作历史记录。

在项目中包含Rackt earlier suggestion(另请参阅React Router项目中的history),您可以添加一个知道哈希历史路径的侦听器并根据需要替换路径,如下所示例如:

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

回顾一下:

  1. David的S3重定向规则会将/foo/bear定向到/#/foo/bear
  2. 您的应用程序将加载。
  3. 历史记录侦听器将检测#/foo/bear历史记录符号。
  4. 用正确的路径替换历史记录。
  5. Using Custom Histories将按预期工作,所有其他浏览器历史记录功能也将如此。我注意到的唯一缺点是初始请求时发生的插页式重定向。

    这受到Link tags的启发,我怀疑可以很容易地适应任何应用程序。

答案 4 :(得分:11)

我看到了这个问题的4个解决方案。前3个已经包含在答案中,最后一个是我的贡献。

  1. Set the error document to index.html.
    问题:响应正文将是正确的,但状态代码将为404,这会伤害搜索引擎优化。

  2. Set the redirection rules.
    问题:受#!污染的网址和加载时页面闪烁。

  3. Configure CloudFront.
    问题:所有页面都将从原点返回404,因此您需要选择是否不缓存任何内容(建议使用TTL 0),或者在更新站点时是否缓存并出现问题。

  4. 预呈现所有页面 问题:预呈现页面的额外工作,特别是在页面频繁更改时。例如,新闻网站。

  5. 我的建议是使用选项4.如果您预渲染所有页面,预期页面将不会有404错误。页面将加载正常,框架将采取控制并正常作为SPA。您还可以设置错误文档以显示通用error.html页面和重定向规则,以将404错误重定向到404.html页面(不使用hashbang)。

    关于403 Forbidden错误,我根本不让它们发生。在我的应用程序中,我认为主机存储桶中的所有文件都是公共的,我使用所有选项和读取权限进行设置。如果您的网站包含私有网页,则让用户看到HTML 布局应该不是问题。您需要保护的是数据,这在后端完成。

    此外,如果您有私有资产,例如用户照片,则可以将其保存在另一个存储桶中。由于私有资产需要与数据相同,并且无法与用于托管应用程序的资产文件进行比较。

答案 5 :(得分:10)

我今天遇到了同样的问题,但@ Mark-Nutter的解决方案不完整,无法从我的angularjs应用程序中删除hashbang。

事实上,您必须转到修改权限,点击添加更多权限,然后在您的存储桶中添加正确的列表给大家。使用此配置,AWS S3现在将能够返回404错误,然后重定向规则将正确捕获案例。

就像这样: enter image description here

然后您可以转到编辑重定向规则并添加此规则:

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

在这里,您可以将 HostName subdomain.domain.fr替换为您的域名和 KeyPrefix #!/,如果您不使用hashbang方法进行搜索引擎优化目的

当然,只有在角度应用程序中已经安装了html5mode时,所有这一切才有效。

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

答案 6 :(得分:7)

使用Amazon S3提供的Angular 2+应用程序和直接URL工作的最简单的解决方案是将index.html指定为S3存储桶配置中的索引和错误文档。

enter image description here

答案 7 :(得分:5)

您现在可以使用Lambda @ Edge来重写路径

这是一个有效的lambda @ Edge函数:

  1. 创建一个新的Lambda函数,但使用其中一个预先存在的蓝图代替空白函数。
  2. 搜索“ cloudfront”,然后从搜索结果中选择生成cloudfront-response。
  3. 创建函数后,将内容替换为以下内容。我还必须将节点运行时更改为10.x,因为在撰写本文时cloudfront不支持节点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);
};

在您的Cloudfront行为中,您将对其进行编辑以在“查看器请求”上添加对该Lambda函数的调用 enter image description here

完整教程:https://aws.amazon.com/blogs/compute/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/

答案 8 :(得分:4)

因为问题仍然存在,我虽然投入另一种解决方案。 我的情况是我想在合并之前将所有拉取请求自动部署到s3进行测试,以便在[mydomain] / pull-requests / [pr number] /
上访问它们 (例如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页面而不是找不到的文件,这可以通过@get_routes中的某些if规则修复。

我不得不说我在nginx配置和使用正则表达式方面感觉不太舒服,我得到了一些试验和错误,所以虽然这有效,但我确信还有改进的空间,请做分享你的想法。

注意:如果您在S3配置中使用了s3重定向规则,请将其删除。

和btw在Safari中运行

答案 9 :(得分:3)

正在寻找同样的问题。 我最终使用了上述建议解决方案的混合。

首先,我有一个包含多个文件夹的s3存储桶,每个文件夹代表一个react / redux网站。 我还使用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配置react-router来处理它。 首先,确保您的依赖项是可互操作的,我安装了history==4.0.0react-router==3.0.1不兼容。

我的依赖关系是:

  • “history”:“3.2.0”,
  • “react”:“15.4.1”,
  • “react-redux”:“4.4.6”,
  • “react-router”:“3.0.1”,
  • “react-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;

希望它有所帮助。 你会注意到这个配置我使用redux注入器和自制的sagas注入器来通过路由异步加载javascript。 不介意用这些线。

答案 10 :(得分:2)

如果您登陆此处寻找适用于React Router和AWS Amplify Console的解决方案-您已经知道您不能直接使用CloudFront重定向规则,因为Amplify Console不会公开该应用程序的CloudFront分布。

但是,解决方案非常简单-您只需要在Amplify Console中添加重定向/重写规则,如下所示:

Amplify Console Rewrite rule for React Router

有关更多信息(以及屏幕截图中的易于复制的规则),请参见以下链接:

答案 11 :(得分:0)

我自己正在寻找答案。 S3似乎只支持重定向,您不能只重写URL并静默返回不同的资源。我正在考虑使用我的构建脚本来简单地在所有必需的路径位置复制我的index.html。也许这对你也有用。

答案 12 :(得分:0)

我发现这是the most elegant solution - 使用带有通配符重定向的app路由器模块。

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

然后,正如上面无数帖子中所述,请确保您将404/403错误重定向到index.html,状态为200。问题是这会导致浏览器刷新将基本href加载为(href + previous route)。如果您正在

查看路由器视图

www.my-app.com/home然后刷新将显示

www.my-app.com/home/home

要剥离重复的路径路径,请使用APP_BASE_HREF模块重新分配浏览器基础href just like this

如果您需要保留第一个url参数,请附加'/'拆分中的多个结果。

浏览器点击您的SPA重定向www.your-app.com/home/home现在将用www.your-app.com/home替换该网址,该应用将按照您的应用内路由配置中的预期行为

答案 13 :(得分:0)

大多数建议的解决方案的问题,尤其是最受欢迎的答案,是您永远不会收到 404 错误。如果你想继续得到404,参考这个解决方案

  1. 我为我的所有路由添加了一个根路径(这是这个解决方案的新内容) 例如我所有的路由路径都从同一个根开始...
    代替路径 /foo/baz, /foo2/baz 我现在有 /root/foo/baz , /root/foo2/baz 路径。
  2. 选择所有路由路径的同一个根目录不会与任何真实文件夹发生冲突。
  3. 现在我可以使用这个根目录在任何我喜欢的地方创建简单的重定向规则。我所有的重定向更改将仅影响此路径,其他一切都将与之前一样。

这是我在s3存储桶中添加的重定向规则

code <file name>
  1. 此后,/root/foo/baz 被重定向到 /#/foo/baz,页面在首页加载时没有错误。

在我的 Vue 应用程序安装后,我在加载时添加了以下代码。它会将我的应用程序带到所需的路线。您可以根据 Angular 或您正在使用的任何内容更改 router.push 部分。

code inde.html style.css

即使您不在 s3 级别使用重定向,在您可能喜欢的任何其他重定向中,所有路由的单一基础也很方便。它有助于后端识别不是对真正后端资源的请求,前端应用将能够处理它。

答案 14 :(得分:0)

与使用 Lambda@Edge 的另一个 answer 类似,您可以使用 Cloudfront Functions(截至 2021 年 8 月,它是 free tier 的一部分)。

我的网址结构是这样的:

  • example.com - 在 S3 上托管的 React SPA
  • example.com/blog - 在 EC2 上托管的博客

由于我将博客托管在与 SPA 相同的域上,因此我无法使用使用默认错误页面的 Cloudfront 403/404 错误页面的建议答案。

我对 Cloudfront 的设置是

  1. 设置两个源(S3 和我通过 Elastic ALB 的 EC2 实例)
  2. 设置两种行为:
    • /blog
    • default (*)
  3. 创建 Cloudfront 函数
  4. 将 Cloudfront 函数设置为 default (*) 行为的“查看者请求”

我正在使用这个基于 AWS example 的 Cloudfront 函数。这可能不适用于所有情况,但我的 React 应用程序 URL 结构不包含任何 .

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

答案 15 :(得分:-1)

只需提出极其简单的答案。如果您托管在S3上,只需对路由器使用哈希定位策略即可。

export const AppRoutingModule:ModuleWithProviders = RouterModule.forRoot(routes,{useHash:true,scrollPositionRestoration:'enabled'});