AngularJS和处理404错误

45

如何在AngularJS应用中提供适当的404页面?

背景简介:我正在构建一个Angular应用,并选择使用

$locationProvider.html5Mode(true);

因为我希望URL看起来很自然(并且无法与多页面的“传统”Web应用程序区分开来)。

在服务器端(一个简单的Python Flask应用程序)上,我有一个捕获所有的处理程序将所有内容重定向到Angular应用程序:

@app.route('/', defaults={'path': ''})
@app.route('/<path>')
def index(path):
    return make_response(open('Ang/templates/index.html').read())

现在,我正试图弄清楚如何处理404错误。我见过的大多数Angular应用程序都采取以下做法:

.otherwise({ redirectTo: '/' })
这意味着他们无法提供正确的404页面。然而,出于SEO目的,我更希望返回一个包含404状态码的正确的404页面。在Angular中处理404页面的最佳方法是什么?我应该不必担心它并坚持使用通配符吗?还是应该删除通配符并在服务器端提供正确的404页面?

你在404页面想要做什么?能否提及你的使用情况? - Ajay Beniwal
1
当访问/asdf时,应该跳转到404页面:"抱歉,您尝试访问的页面不存在"。应返回404状态码。 - Ryan Shea
你可以注册一个响应拦截器并在拦截器中检查状态码,如果发现HTTP状态码为404,则可以使用$location.path('path')重定向到任何路径。 - Ajay Beniwal
5个回答

34
我认为你将Flask路由与Angular路由混淆了。
404错误代码是HTTP协议的一部分。当Web服务器不知道所请求的URL时,它会将其作为响应发送给客户端。因为您在Flask服务器上设置了万能路由,所以您永远不会收到404错误,Flask将为您在地址栏中输入的任何URL调用视图功能。在我看来,您不应该使用万能路由,而应该让Flask在用户在地址栏中键入无效URL时响应404,这没有什么问题。Flask甚至允许您在返回404代码时发送自定义页面,因此您可以使错误页面看起来像站点的其他部分。
在Angular方面,实际上不存在HTTP事务,因为应用程序内部的所有路由都在客户端中进行,而服务器甚至不知道。这可能是你疑惑的一部分,即使在html5mode下,Angular链接也完全在客户端处理,而无需向服务器发出任何请求,因此在这种情况下并没有404错误的概念,只是因为没有服务器参与。将您发送到未知路由的Angular链接将落在otherwise子句中。在这里正确的做法是要么显示错误消息(如果用户需要知道此条件并且可以采取某些措施),要么忽略未知路由,就像redirectTo: '/'一样。
这似乎不是您的情况,但是如果除了提供Angular应用程序之外,您的服务器还实施了API供Angular在运行时使用,那么如果Angular使用无效URL进行异步请求,它可能会从Flask获取404。但是,如果发生这种情况,您将不希望向用户显示404错误页面,因为此请求是由应用程序内部而不是由用户直接触发的。
我希望这有助于澄清情况!

12
@Miguel,我没有混淆它们。考虑这个问题:我的Angular应用程序在路由中有“/”和“/about”。我还启用了html5Mode。这意味着,如果用户访问“/”,将向服务器发送一个请求,然后Angular应用程序将启动。当用户导航到“/about”时,请求将不会发送到服务器,因为Angular应用程序当前正在处理它。但是,如果用户刷新页面,“/about”将被发送到服务器。这意味着,服务器需要有一个“/about”端点将请求传递给Angular。 - Ryan Shea
抱歉,如果您希望用户可以从地址栏中显示的任何链接访问应用程序,则必须配置服务器将所有这些URL重写为“/”,以便始终提供主应用程序。在Angular文档中找到了关于html5模式的内容:“使用此模式需要在服务器端进行URL重写,基本上您必须将所有链接重写为应用程序的入口点(例如index.html)”。 - Miguel Grinberg
没错。这可能非常令人恼火,因为您可能会发现自己在 Angular 代码中更改了 URL,但忘记在服务器代码中进行更改。这也不是很优雅。以下是我最终采取的做法:(1)将路由信息放入 JSON 文件中;(2)设置我的服务器以提取路由中的端点,并使这些端点返回“index.html”(供 Angular 处理);(3)编写一个脚本,在每次服务器启动时从 JSON 文件重新生成一个新的 app.js。-- 然而,依我之见,这也相当不优雅,我感到相当不满意。希望有更好的方法。 - Ryan Shea
或者你可以直接按照Angular团队的建议实现URL重写,这似乎更简单和更清晰。 - Miguel Grinberg
2
我想友善地指出,上面的答案是不正确的,并且错误地表明了我的困惑,而实际上并没有。在回答的开头反映这一点会很好。 - Ryan Shea
显示剩余8条评论

18

经过一段时间的尝试以及与Miguel的讨论,我总结出了几种不同的解决方案:

  1. 只需使用Catch-all并不用担心正确的404。这可以通过服务器端代码(如我的原始解决方案)或更好地通过Web服务器上的URL重写来设置。
  2. 为你的angular应用程序保留特定的站点部分(例如/app)。对于这个部分,设置catch-all并不用担心正确的404。你的其他页面将作为常规页面提供,并访问任何无效的URL, 如果它不以 /app 开头,则会返回正确的404。
  3. 不断确保在app.js中的所有路由都在你的服务器端代码中进行了映射(是的,相当麻烦),在那里你将有这些路由提供你的angular应用程序。所有其他路由都会返回404。

附言:第二个选项是我个人最喜欢的。我已经尝试过这种方法,效果非常好。


我在使用 Angular / Node.js 堆栈时遇到了类似的问题。我有一个路由,它使用特定的 ID 进行数据查找。对于良好的 URL,这很好地工作。对于错误的 URL,我只会得到一个破碎的网页。URL 看起来像:/details/goodID vs. details/badID(我还注意到某些机器人喜欢尝试/details/details/goodID,这也是一个错误的路由 :/)。解决方案 #2 并没有真正处理这种情况,如果可能的话,我想避免 #3。你有其他想法吗?是否有一种 404-ish 的方式在客户端报告错误? - user3233032
@Ryan - 关于第三点,通过browserify要求一个包含所有定义的路由模块并不是那么糟糕,对吧?反正我一直都是这样做的。 - aaaaaa
@Ryan 不好意思,但这个服务器调用是否不是抵消了客户端SPA的全部意义?我们只需调用一次SPA,然后通过Ajax使用Api。如果您必须为每个端点转到服务器,那么为什么不直接使用客户端/服务器模型呢? - tinmac

2

这是一个老帖子,但我在寻找答案时遇到了它。

将此添加到您的 appRoutes.js 结尾,并创建一个名为 404.html 的视图。

.when('/404', {
    templateUrl: 'views/404.html',
    controller: 'MainController'
})

.otherwise({ redirectTo: '/404' })

0

我认为,如果您没有为站点的实际页面提供可用的非JavaScript内容,则真正的http 404对于“SEO目的”将会非常无用。搜索索引器不太可能能够呈现您的角度网站以进行索引。

如果您担心SEO,您需要某种服务器端方式来呈现您的角度页面呈现的内容。如果您有这个功能,那么添加无效URL的404是问题中最简单的部分。


-1

这是处理错误并且运行良好的最佳方法

function ($routeProvider, $locationProvider, $httpProvider) {
    var interceptor = [
        '$rootScope', '$q', function (scope, $q) {

            function success(response) {
                return response;
            }

            function error(response) {
                var status = response.status;

                if (status == 401) {
                    var deferred = $q.defer();
                    var req = {
                        config: response.config,
                        deferred: deferred
                    };
                    window.location = "/";
                }

                if (status == 404) {
                    var deferred = $q.defer();
                    var req = {
                        config: response.config,
                        deferred: deferred
                    };
                    window.location = "#/404";
                }
                // otherwise
                //return $q.reject(response);
                window.location = "#/500";
            }

            return function (promise) {
                return promise.then(success, error);
            };

        }
    ];
    $httpProvider.responseInterceptors.push(interceptor);
});

// routes
app.config(function($routeProvider, $locationProvider) {
    $routeProvider
        .when('/404', {
            templateUrl: '/app/html/inserts/error404.html',
            controller: 'RouteCtrl'
        })
        .when('/500', {
            templateUrl: '/app/html/inserts/error404.html',
            controller: 'RouteCtrl'
        })

        ......

   };

2
我没有点踩,但我知道SO更喜欢那些不仅仅是代码的答案。这只是一个建议,因为我很感激那些花时间回答别人问题的人。 - aaaaaa
这是关于“如何在AngularJS中拦截和处理所有404错误”的问题的答案。我正在寻找这个答案,但是找不到。我知道我要找的是如何为所有$ http调用添加拦截器,因为如果没有这个,AngularJS将不会将404视为错误,而只是另一个代码,并且它将在承诺中解决。我需要一种自动错误所有404的方法,以便我可以捕获它们,例如在登录时返回404时表示登录不成功。希望对某人有所帮助 #angularjs #httpinterceptor #interceptor - Toddy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接