S3静态网站托管路由到Index.html的所有路径

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

有没有办法告诉S3为所有URL请求服务index.html,而不是做重定向?这类似于通过提供一个index.html来设置apache来处理所有传入的请求,就像这个例子:https://stackoverflow.com/a/10647521/1762614。我真的希望避免仅仅为了处理这些路由而运行web服务器。从S3执行所有操作非常有吸引力。

146793 次浏览

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

我做到这一点的方法如下:

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

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

这将导致404错误的所有路径重定向到根域,并显示路径的散列版本。因此,如果/posts上没有文件,http://yourdomainname.com/posts将重定向到http://yourdomainname.com/ !/职位

然而,要使用HTML5的pushState,我们需要接受这个请求,并根据散列路径手动建立正确的pushState。所以把这个添加到index.html文件的顶部:

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

这将获取散列并将其转换为HTML5 pushState。从这一点开始,你可以使用pushStates在你的应用程序中拥有非哈希键路径。

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

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

就像这样: enter image description here < / p >

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

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

在这里,你可以用你的域和KeyPrefix #!/如果你不使用hashbang方法用于SEO目的。

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

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

这是切题的,但这里有一个提示,对于那些使用Rackt的 React路由器库和(HTML5)浏览器历史的人来说,他们想要在S3上托管。

假设用户在s3托管的静态网站上访问/foo/bear。给定大卫的 之前的建议,重定向规则将把它们发送到/#/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的解决方案的启发,我怀疑可以很容易地适应任何应用程序。

其他人提到的基于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. 在S3存储桶前面放置Cloudfront。
  4. 配置Cloudfront实例的错误页面规则。在错误规则中指定:

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

      1. 对于SEO需求+确保你的index.html不缓存,做以下事情:
      2. 李< / ol > < / >
      3. 配置一个EC2实例并设置一个nginx服务器。

      4. 为EC2实例分配一个公共ip。
      5. 创建一个ELB,其中包含您创建的EC2实例作为实例
      6. 您应该能够将ELB分配给您的DNS。
      7. 现在,配置你的nginx服务器来做以下事情:Proxy_pass所有请求到你的CDN(仅针对index.html,直接从你的cloudfront服务其他资产)和搜索机器人,重定向流量由服务规定,如prerrender .io
      8. 李< / ul > < / >

      我可以提供更多关于nginx设置的细节,请留下一张便条。经历了惨痛的教训。

      一旦云前端分布更新。使您的云前缓存失效一次,使其处于原始模式。 在浏览器中点击url,一切都应该是好的

在CloudFront的帮助下,不需要url黑客就可以很容易地解决这个问题。

  • 创建S3桶,例如:react
  • 使用以下设置创建CloudFront发行版:
    • 默认根对象: index . html
    • 来源域名: S3桶域,例如:react.s3.amazonaws.com
    • 李< / ul > < / >
    • 进入错误页面选项卡,单击创建自定义错误响应:
      • HTTP错误码: 403:禁止(404:未找到,在S3静态网站的情况下)
      • 自定义错误响应:是的
      • 响应页面路径: / index . html
      • __abc0: 200: ok
      • 单击创建
      • 李< / ul > < / >

因为问题仍然存在,我想我抛出了另一个解决方案。 我的情况是,我想在合并之前自动将所有拉请求部署到s3进行测试,使它们可以在[mydomain]/pull-requests/[pr number]/
上访问 (例如www.example.com/pull-requests/822/) < / p >

据我所知,没有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路由并尝试。如果你在angular的404页面中没有找到路由,你将永远不会到达@not_found,而会返回angular 404页面而不是not found files,这可以通过在@get_routes中添加If规则来修复。

我不得不说我在nginx配置和使用regex方面感到不太舒服,我得到了这个工作,经过了一些试验和错误,所以虽然这是有效的,但我相信有改进的空间,请分享你的想法。

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

顺便说一下,在Safari中工作

正在寻找同样的问题。 我最终使用了上述建议解决方案的混合 首先,我有一个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代码中,我需要用react-router的baseName配置来处理它。 首先,确保你的依赖是可互操作的,我已经安装了history==4.0.0,它与react-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。

让Angular 2+应用程序通过Amazon S3和直接url工作的最简单的解决方案是在S3桶配置中指定Index .html作为Index和Error文档。

enter image description here

对于这个问题,我有4种解决方案。前三个问题已经在答案中提到了,最后一个是我的贡献。

    <李> < p > 将错误文档设置为index.html。 < br > 问题:响应体将是正确的,但状态代码将是404,这将损害SEO <李> < p > 设置重定向规则。 < br > 问题: URL被#!污染,加载时页面闪烁 <李> < p > 配置CloudFront。 < br > 问题:所有页面将从原点返回404,因此您需要选择是否不缓存任何内容(建议的TTL 0),或者是否缓存并在更新站点时出现问题
  1. 预播放所有页面 问题:预渲染页面的额外工作,特别是当页面频繁更改时。例如,一个新闻网站。

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

关于403禁忌错误,我不让它们发生。在我的应用程序中,我认为主机桶中的所有文件是公共的,并且我使用具有权限的每一个人选项设置了这一点。如果你的网站有私有页面,让用户看到HTML 布局应该不是一个问题。你需要保护的是数据,这是在后端完成的。

另外,如果你有私有资产,比如用户照片,你可以将它们保存在另一个 bucket中。因为私有资产需要像数据一样小心,并且不能与用于托管应用程序的资产文件进行比较。

这是一个非常简单的答案。如果您在S3上托管,则只需为路由器使用散列位置策略。

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

如果你在这里寻找与React路由器和AWS Amplify控制台一起工作的解决方案,你已经知道你不能直接使用CloudFront重定向规则,因为Amplify控制台不会为应用程序公开CloudFront Distribution。

然而,解决方案非常简单-你只需要在Amplify控制台中添加一个重定向/重写规则,就像这样:

Amplify Console Rewrite rule for React Router

查看以下链接获取更多信息(以及截图中的复制友好规则):

现在可以使用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);
};

在您的cloudfront行为中,您将编辑它们以在“查看器请求”上添加对lambda函数的调用; enter image description here < / p >

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

大多数建议的解决方案(尤其是最流行的答案)的问题是,对于不存在的后端资源,您永远不会看到404错误。如果希望继续得到404,请参考此解决方案

    我添加了一个根路径到我所有的路由(这是新的解决方案) 例如,我所有的路由路径都以相同的前端根开始…< /强>
    我现在有了/root/foo/baz, /root/foo2/baz路径
  1. 前端根目录的选择是这样的:它不会与后端任何实际文件夹或路径冲突. xml
  2. 现在我可以使用这个根在任何地方创建简单的重定向规则。我的所有重定向更改将只影响这条路径,其他一切都将像以前一样。

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

[
{
"Condition": {
"HttpErrorCodeReturnedEquals": "404",
"KeyPrefixEquals": "root/"
},
"Redirect": {
"HostName": "mydomain.com",
"ReplaceKeyPrefixWith": "#/"
}
}
]

甚至是跟随

[
{
"Condition": {
               

"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级别使用重定向,在您可能喜欢的任何其他重定向中,为所有路由提供一个单一的基础也会很方便。它帮助后端识别这不是一个真正的后端资源的请求,前端应用程序将能够处理它。

类似于使用Lambda@Edge的另一个回答,你可以使用Cloudfront功能(截至2021年8月,它是自由层的一部分)。

我的URL结构是这样的:

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

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

我对Cloudfront的设置是:

  1. 设置两个源(S3和我的EC2实例通过Elastic ALB)
  2. 设置两个行为:
    • /blog
    • default (*)
  3. 创建Cloudfront函数
  4. 将Cloudfront函数设置为“Viewer request"default (*)行为

我使用这个Cloudfront函数基于AWS的例子。这可能并不适用于所有情况,但我的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;
}

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;
}
进入你的桶的静态网站托管设置 并且将错误的文档设置为index . html

这里没有提到的解决方案是使用CloudFront函数在观众要求上重写请求URI为index.html:

function handler(event) {
var request = event.request;
request.uri = '/index.html';
return request;
}

在2022年,Lambda函数是

function handler(event) {
var request = event.request;
var uri = request.uri;
    

if (uri.endsWith("/")) {
request.uri += "index.html"
} else if (!uri.includes(".")){
request.uri += "/index.html"
}
    

return request;
}