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

Mar*_*ter 225 redirect routing amazon-s3 amazon-web-services 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做一切非常有吸引力.

len*_*ten 250

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

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

  • 谢谢.到目前为止最好的答案. (7认同)
  • 这对我来说就像一个魅力,只有我需要的自定义错误代码是404,而不是403 (4认同)
  • 这对我不起作用。该解决方案始终重定向到主页而不是正确的页面... (4认同)
  • 有点破解,但效果很好:)如果CloudFront让我们将一系列路径映射到S3文件(没有重定向),那就太好了. (3认同)
  • 此解决方案会导致页面速度或其他流行测试中出现“初始服务器响应时间缓慢”错误。每次,cloudfront 请求都会尝试找到它,出错并重定向到错误页面。每次页面加载都会发生这种情况,看起来相当浪费。是否有另一种方法不需要花费太多服务器时间?(性能更高的解决方案)此方法将初始服务器响应时间增加约 500 毫秒,有效地覆盖 CDN。 (3认同)
  • @NathanielMaman 因为您的 cf 发行版中可能有两个来源。`.com/app` (s3) 和 `.com/auth` (ec2/whatever)。错误响应是顶级的,因此无法从 /auth 或其他任何地方告诉 s3 403 和 403 之间的差异。私有的、仅供预览的 Lambda@Edge 是解决方案,是 mod_rewrite 的荒谬解决方案。 (2认同)

Mar*_*ter 195

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

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

<RoutingRules>
  <RoutingRule>
    <Condition>
      <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
    </Condition>
    <Redirect>
      <HostName>yourdomainname.com</HostName>
      <ReplaceKeyPrefixWith>#!/</ReplaceKeyPrefixWith>
    </Redirect>
  </RoutingRule>
</RoutingRules>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 使用HTML5 pushStates和`<ReplaceKeyPrefixWith>#/ </ ReplaceKeyPrefixWith>成功使用`react-router`解决方案 (10认同)
  • 不幸的是,这在Safari中不起作用. (8认同)
  • 它在safari中不起作用,并且是部署策略的一个大问题.写一个小的js重定向是一种破旧的方法.此外,重定向的数量也是一个问题.我想知道是否有一种方法让所有S3网址始终指向index.html. (5认同)
  • 这个解决方案很棒!事实上,如果配置了html模式,angularjs将自动执行历史记录pushState. (4认同)
  • 谢谢!通过小调整,这对我来说非常适合我.我在旧版浏览器的检查中添加了:`<script language ="javascript"> if(typeof(window.history.pushState)=='function'){window.history.pushState(null,"Site Name",window. location.hash.substring(2)); } else {window.location.hash = window.location.hash.substring(2); } </ script>` (3认同)

moh*_*297 125

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

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

解决方案是:

  1. 确保为您的网站配置了索引路由.主要是index.html
  2. 从S3配置中删除路由规则
  3. 将Cloudfront放在S3存储桶前面.
  4. 配置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设置,我可以提供更多详细信息,只需留言即可.已经很难学会了.

一旦云前端分发更新.使您的云端缓存无效一次,使其处于原始模式.点击浏览器中的网址,一切都应该是好的.

  • 由于S3在文件不存在时以403 Forbidden响应,我认为上面的步骤4也必须复制为Http错误代码403 (5认同)
  • 对我来说,这是导致预期(接受)行为的唯一答案 (4认同)
  • @ moha297请你解释一下这条评论:"你永远不应该从CDN服务index.html"?我没有看到使用CloudFront从S3提供index.html的问题. (2认同)

Mic*_*ers 17

它是切向的,但是对于那些使用具有(HTML5)浏览器历史记录而想要在S3上托管的Rackt的 React Router库的人来说,这是一个提示.

假设用户访问/foo/bearS3托管的静态网站.鉴于大卫 先前的建议,重定向规则将发送给他们/#/foo/bear.如果您的应用程序是使用浏览器历史记录构建的,那么这将无济于事.但是,此时您的应用程序已加载,现在可以操作历史记录.

在我们的项目中包括Rackt 历史记录(另请参阅使用 React Router项目中的Custom Histories),您可以添加一个知道哈希历史路径的侦听器并根据需要替换路径,如下例所示:

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'))
);
Run Code Online (Sandbox Code Playgroud)

回顾一下:

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

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

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

  • 这对迈克尔有帮助,谢谢!您可能希望从历史记录中更改引用,只需使用BrowserHistory即可.即`browserHistory.listen` (2认同)

Zan*_*non 13

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

  1. 将错误文档设置为index.html.
    问题:响应正文是正确的,但状态代码将是404,这会伤害搜索引擎优化.

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

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

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

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

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

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


dav*_*era 12

我今天遇到了同样的问题,但@ 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>
Run Code Online (Sandbox Code Playgroud)

在这里,您可以使用您的域和KeyPrefix#!/ 替换HostName subdomain.domain.fr,如果您不使用hashbang方法进行SEO目的.

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

$locationProvider.html5Mode(true).hashPrefix('!');
Run Code Online (Sandbox Code Playgroud)

  • 我认为您应该为 403 错误添加规则,而不是向所有人授予列表权限。 (2认同)

cla*_*mes 12

与使用 的另一个答案类似Lambda@Edge,您可以使用Cloudfront Functions (这是免费层的一部分)。

我的网址结构是这样的:

  • 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. default (*)将 CloudFront 功能设置为行为的“查看者请求”

我正在使用基于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;
}
Run Code Online (Sandbox Code Playgroud)

编辑 2022 年 6 月

我已将我的功能更新为:

function handler(event) {
    var request = event.request;
    var uri = request.uri;
    
    // Check whether the URI is missing a file name.
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    } 
    // Check whether the URI is missing a file extension.
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}
Run Code Online (Sandbox Code Playgroud)


Ser*_*rov 10

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

在此输入图像描述

  • 这是[这个严重低估的答案](http://stackoverflow.com/a/16979716/1476885)的答案.它工作正常,但仅适用于响应的"body".状态代码将是404,它将损害SEO. (11认同)

Lor*_*ren 10

您现在可以使用 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);
};
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我在下面提供了一个使用 Cloudfront Functions(AWS 免费套餐的一部分)的答案。 (2认同)

小智 7

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

  • 现在您确实不应该使用 S3 的内置静态网站托管。所有新存储桶都应配置为启用“阻止公共访问”。为此,请使用 Cloudfront。 (2认同)

小智 5

由于问题仍然存在,我虽然提出了另一个解决方案。我的情况是,我想在合并之前将所有拉取请求自动部署到 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;
}
Run Code Online (Sandbox Code Playgroud)

它尝试获取该文件,如果找不到,则假定它是 HTML5 路由并尝试该文件。如果您有一个未找到路线的 404 角度页面,您将永远不会到达 @not_found 并返回角度 404 页面而不是未找到文件,这可以通过一些 if 规则@get_routes或其他内容来修复。

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

注意:如果 S3 配置中有 s3 重定向规则,请删除它们。

顺便说一句,在 Safari 中工作


Gui*_*sco 5

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

首先,我有一个包含多个文件夹的 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>
Run Code Online (Sandbox Code Playgroud)

在我的 js 代码中,我需要使用baseNamereact-router的配置来处理它。首先,确保您的依赖项是可互操作的,我安装了history==4.0.0react-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;
Run Code Online (Sandbox Code Playgroud)

这段代码允许使用散列处理服务器发送的 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));
}
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)

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


Ed *_*mas 0

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

  • 我也曾想过为每个路径生成索引文件,但是很难拥有像 http://example.com/groups/5/show 这样的动态路径。如果您看到我对这个问题的回答,我相信这可以解决大部分问题。这有点像黑客,但至少它有效。 (2认同)