294

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

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

4

16 回答 16

423

在 CloudFront 的帮助下,无需 url hack 即可轻松解决。

  • 创建 S3 存储桶,例如:react
  • 使用以下设置创建 CloudFront 分配:
    • 默认根对象:index.html
    • Origin Domain Name:S3存储桶域,例如:react.s3.amazonaws.com
  • 转到错误页面选项卡,单击创建自定义错误响应
    • HTTP 错误代码:403:禁止(404:未找到,如果是 S3 静态网站)
    • 自定义错误响应:是
    • 响应页面路径:/index.html
    • HTTP 响应代码:200:好的
    • 点击创建
于 2016-02-27T17:51:31.690 回答
209

我能够让它工作的方式如下:

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

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

这会将导致找不到 404 的所有路径重定向到您的根域,并使用该路径的 hash-bang 版本。因此http://yourdomainname.com/posts将重定向到http://yourdomainname.com/#!/posts,前提是 /posts 中没有文件。

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

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

这会抓取哈希并将其转换为 HTML5 pushState。从此时起,您可以使用 pushStates 在您的应用程序中设置非散列路径。

于 2013-06-01T21:02:57.233 回答
137

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

  1. 当您的应用程序的路径被解析时,会发生多次重定向。例如: www.myapp.com/path/for/test 被重定向为 www.myapp.com/#/path/for/test
  2. 由于您的 SPA 框架的操作,当 '#' 出现和消失时,url 栏中会闪烁。
  3. seo 受到影响是因为 - '嘿!它的谷歌强迫他的手重定向'
  4. Safari 对您的应用程序的支持值得一试。

解决方案是:

  1. 确保您已为您的网站配置了索引路由。主要是 index.html
  2. 从 S3 配置中删除路由规则
  3. 将 Cloudfront 放在您的 S3 存储桶前面。
  4. 为您的 Cloudfront 实例配置错误页面规则。在错误规则中指定:

    • Http错误代码:404(和403或其他错误根据需要)
    • 错误缓存最小 TTL(秒):0
    • 自定义响应:是
    • 响应页面路径:/index.html
    • HTTP 响应代码:200

      1. 对于 SEO 需求 + 确保您的 index.html 不缓存,请执行以下操作:
    • 配置 EC2 实例并设置 nginx 服务器。

    • 为您的 EC2 实例分配一个公共 IP。
    • 创建一个将您创建的 EC2 实例作为实例的 ELB
    • 您应该能够将 ELB 分配给您的 DNS。
    • 现在,配置您的 nginx 服务器以执行以下操作:代理_将所有请求传递给您的 CDN(仅用于 index.html,直接从您的云端提供其他资产)和搜索机器人,按照 Prerender.io 等服务的规定重定向流量

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

一旦云端分发更新。使您的云端缓存失效一次以进入原始模式。在浏览器中点击 url,一切都应该很好。

于 2016-02-12T04:09:32.653 回答
18

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

假设用户访问/foo/bear您的 S3 托管的静态网站。鉴于David 之前的建议,重定向规则会将它们发送到/#/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'))
);

回顾一下:

  1. David 的 S3 重定向规则将/foo/bear指向/#/foo/bear.
  2. 您的应用程序将加载。
  3. 历史监听器将检测#/foo/bear历史符号。
  4. 并用正确的路径替换历史。

Link标签将按预期工作,所有其他浏览器历史记录功能也是如此。我注意到的唯一缺点是初始请求时发生的插页式重定向。

这是受到AngularJS 解决方案的启发,我怀疑它可以很容易地适应任何应用程序。

于 2016-01-23T00:08:19.687 回答
16

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

  1. 将错误文档设置为 index.html。
    问题:响应正文将是正确的,但状态码将是 404,这会伤害 SEO。

  2. 设置重定向规则。
    问题:加载时 URL 被污染#!并且页面闪烁。

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

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

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

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

此外,如果您有私人资产,例如用户照片,您可以将它们保存在另一个存储桶中。因为私有资产需要与数据一样的关注,并且无法与用于托管应用程序的资产文件进行比较。

于 2017-05-14T11:18:02.947 回答
13

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

在此处输入图像描述

于 2017-05-04T18:26:10.733 回答
13

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

事实上,你必须去编辑权限,点击添加更多权限,然后将你的存储桶上的正确列表添加到每个人。使用此配置,AWS S3 现在将能够返回 404 错误,然后重定向规则将正确捕获该情况。

像这样 : 在此处输入图像描述

然后你可以去编辑重定向规则并添加这个规则:

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

如果您不将 hashbang 方法用于 SEO ,您可以在此处将HostName subdomain.domain.fr 替换为您的域和KeyPrefix #!/。

当然,所有这些只有在你的 Angular 应用程序中已经设置了 html5mode 时才会起作用。

$locationProvider.html5Mode(true).hashPrefix('!');
于 2015-09-04T13:04:49.430 回答
11

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

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

  1. 创建一个新的 Lambda 函数,但使用预先存在的蓝图之一而不是空白函数。
  2. 搜索“cloudfront”并从搜索结果中选择 cloudfront-response-generation。
  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);
};

在您的云端行为中,您将编辑它们以在“查看器请求”上添加对该 lambda 函数的调用 在此处输入图像描述

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

于 2020-02-19T16:30:14.493 回答
5

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

首先,我有一个包含多个文件夹的 s3 存储桶,每个文件夹代表一个 react/redux 网站。我还使用云端缓存失效。

所以我不得不使用路由规则来支持 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>

baseName在我的 js 代码中,我需要使用react-router的配置来处理它。首先,确保您的依赖项是可互操作的,我已经安装history==4.0.0了与react-router==3.0.1.

我的依赖是:

  • “历史”:“3.2.0”,
  • “反应”:“15.4.1”,
  • "react-redux": "4.4.6",
  • “反应路由器”:“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。不要介意这些行。

于 2017-01-17T17:01:53.770 回答
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 重定向规则,请删除它们。

顺便说一句,在 Safari 中工作

于 2016-05-22T07:59:28.913 回答
3

转到您的存储桶的静态网站托管设置并将错误文档设置为index.html

于 2021-10-31T09:11:44.963 回答
2

如果您在这里寻找可与 React Router 和 AWS Amplify 控制台一起使用的解决方案 - 您已经知道您不能直接使用 CloudFront 重定向规则,因为 Amplify 控制台不会为应用程序公开 CloudFront 分发。

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

放大 React 路由器的控制台重写规则

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

于 2019-08-28T08:10:35.870 回答
1

与使用 Lambda@Edge的另一个答案类似,您可以使用Cloudfront Functions(截至 2021 年 8 月,它是免费层的一部分)。

我的网址结构是这样的:

  • 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 示例的 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;
}
于 2021-08-02T15:05:29.163 回答
1

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

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

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

[
    {
        "Condition": {
            "HttpErrorCodeReturnedEquals": "404",
            "KeyPrefixEquals": "root/"
        },
        "Redirect": {
            "HostName": "mydomain.com",
            "ReplaceKeyPrefixWith": "#/"
        }
    }
]
  1. 在此之后,/root/foo/baz 被重定向到 /#/foo/baz,并且页面在首页加载而没有错误。

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

if(location.href.includes("#"))
{
  let currentRoute = location.href.split("#")[1];

  router.push({ path: `/root${currentRoute}` })
}

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

于 2021-06-23T07:06:14.727 回答
0

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

于 2013-06-01T19:42:15.600 回答
-3

只是提出一个非常简单的答案。如果您在 S3 上托管,只需对路由器使用哈希定位策略。

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

于 2019-06-02T15:23:46.303 回答