AngularJS HTML5模式 - 如何在没有服务器特定更改的情况下使直接链接起作用?

46

注意:这个问题也可以被理解为:

如何在Java中支持不带哈希标记的客户端MVC框架的书签功能。

我正在将使用hashtags的angular应用程序转换为使用html5mode的应用程序。 我已成功设置了

$locationProvider.html5Mode(true);

所有从落地页(index.html)的链接都能正常工作。

问题是,如果直接引用部分URL,则会得到404错误,这很自然,因为服务器端点定义与客户端定义的路由没有耦合。

因此,在没有HTML5的情况下,我们得到了非SEO友好的哈希标记,但是有了它,我们无法收藏除落地页(引导angular的页面)以外的任何东西。

如果首先请求默认落地页(index.html),例如htpp://mydomain.example/,为什么它有效:

  1. 浏览器从服务器请求index.html
  2. 服务器返回index.html,浏览器加载angular框架
  3. URL更改被发送到客户端路由器,并加载适当的部分/ s。

如果直接从浏览器请求(例如)http://mydomain.example/foo,为什么它不起作用:

  1. 浏览器从服务器请求mydomain/foo。
  2. 资源不存在
  3. 服务器返回404

这个故事中缺少一些东西,我不知道是什么。这里是我所能看到的唯一两个答案......

  • 这是设计如此。 这就是它应该工作的方式吗?用户必须始终在客户端MVC框架的引导页面上(通常是index.html),然后从那里导航。这不是理想的,因为无法保存状态,也没有方法可以收藏......更不用说爬行了。
  • 服务器解决方案。 是否可以通过服务器端技巧解决这个问题?例如,在所有请求上,返回index.html并立即使用其他上下文调用路由器。如果是这样,这与AngularJS完全是客户端的目标相违背,并且似乎是一种hack。

你应该在主HTML文件的头部指定URL基础(<base href="/my-base">)。 - dimirc
2
最后一个要点是解决方案。在服务器端实现某种过滤器,为所有应用程序URL提供index.html服务即可。不需要更多的操作。路由器将自动显示与位置栏中URL相关联的页面。 - JB Nizet
9
似乎应该将这一点包含在 AngularJS 的文档中。可以加上一些类似于“噢,是的,当你启用 html5mode 时,你还需要进行服务器端更改,以确保所有包含基础 URI 的请求都会返回引导 HTML(即 index.html)资源。”的内容。 - Robert Christian
1
我同意你的观点。问题在于Angular是一个纯客户端框架,而与服务器端配置相关的技术取决于服务器端使用的技术,这并不是Angular所关心的。我猜想他们认为这一点很明显,不需要特别提及。考虑到Angular的复杂性,我希望它能被有经验的开发人员使用,并知道在服务器端必须做什么。 - JB Nizet
1
我找到了我认为是最好的解决方案,正如JB所说,它是我上面提到的第二个要点。原来Angular在他们的文档中确实提到了服务器端支持的要求:“使用(html5)模式需要服务器端的URL重写,基本上你必须将所有链接重写到你应用程序的入口点(例如index.html)”。 - Robert Christian
显示剩余2条评论
3个回答

40

事实上,AngularJS文档确实提到了这一点。

服务器端使用此模式需要在服务器端进行URL重写,基本上需要将所有链接重写为应用程序的入口点(例如index.html)。

在这种情况下,一种Java解决方案是告诉服务器“将所有URL映射到index.html”。这可以在任何HTTP服务器或容器中完成。我使用Java / Servlet实现了这一点,因为我希望我的应用程序能够独立于HTTP服务器(如Apache与NginX或仅限于Tomcat / JBoss)。

在web.xml中:

  <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <servlet>
      <servlet-name>StaticServlet</servlet-name>
      <jsp-file>/index.jsp</jsp-file>
  </servlet>

  <servlet-mapping>
      <servlet-name>StaticServlet</servlet-name>
      <url-pattern>/app</url-pattern>
  </servlet-mapping>

而 index.jsp 的页面看起来就像这样:

<%@ include file="index.html" %>

在index.html文件的标签中添加以下内容:

<base href="/app" />

对我来说它不起作用。能否再解释一下或者给我一个文件示例? - vimal prakash
1
对我也不起作用。实际上,所有的请求都被转发到 index.jsp,包括 .js.css 资源的请求,这是不正确的。 - Romain Linsolas
基本上,您需要为应用程序的每个入口点注册一个servlet-mapping到静态servlet。因此,在上面的示例中,应用程序具有入口点<application> /app。如果您有更多入口点,只需为每个入口点添加另一个servlet-mapping,或者使用/*来覆盖我的所有入口点。 - Tobias Michelchen
1
谢谢,对我有用。 我只需要添加我的上下文根,因为它只显示了#号。 现在它已经变成/contextroot#/...所以当用户刷新浏览器时,就不会再出现错误了。 - arn-arn

3

我为我的PHP网站考虑了一段时间。我将所有的服务器端代码都挂在/api路由下,以使其与Angular分离。下面是我通过更新Apache配置所得到的解决方案:

RewriteEngine on
#let the php framework do its thing
RewriteRule ^(api/.*)$ index.php?url=$1 [QSA,L,NC]
#let angular do its thing
RewriteCond %{REQUEST_FILENAME} !-f      
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.html [NC,L]

我使用Flight PHP框架的方式记录了如何使用AngularJs。请访问http://www.awnage.com/2013/10/10/taking-flight-with-angularjs/

我是Java世界的新手。我不知道如何添加.htaccess文件或Apache配置文件。您能否在教程中添加这些内容,例如哪个文件夹和哪个文件等等。 - vimal prakash
1
.htaccess和Apache配置与Apache HTTP服务器有关,而不是Java。它们已经存在了几十年,并且有着很好的文档记录。 - Robert Christian

-3

你所链接和放置在用户地址栏中的URL应该指向服务器上的有效内容,并且如果可能的话,它们应该指向正确的内容。当然,你可以为每个URL提供相同的页面,并让客户端代码去加载真正的内容,这样事情就基本上与哈希URL世界中的方式一样了。事实上,这是最省代码的方法。但它也算是“破坏互联网”的行为,这就是为什么HTML5和History API为你提供了一种链接到语义正确的URL的方式。

作为一个小例子,如果你访问https://github.com/kriskowal/q并点击“examples”,客户端代码将在不离开页面的情况下将“examples”目录加载到文件浏览器中,URL栏将显示https://github.com/kriskowal/q/tree/master/examples。如果直接访问https://github.com/kriskowal/q/tree/master/examples,你将直接收到“examples”目录的内容,无需客户端代码进行中介。是的,这样做更难,而且在“单页应用程序”中可能是不可能的,但只要有可能,这就是正确的做法。

2
我理解这个争论,但基本上是在说“不要做单页应用程序”,或者“如果你创建单页应用程序,请确保也有服务器端MVC实现,以免破坏互联网。” - Robert Christian
@rob 我不会撒谎,单页应用程序是一种脑损伤。在不久的将来,我们会像今天看待使用Flash的网站一样看待它们。 - hobbs
11
现在我们开始哲学探讨,我理解你的观点。但它们与Flash(和小程序)从一开始就有根本区别,因为它们不是专有的。如果Web应用程序开发是从客户端Mvc开始的,您可能会反对将视图与服务器耦合...毕竟,如果我们要“避免破坏互联网”,服务器不应该是一个简单的REST API吗?在我看来,在浏览器上所做的事情与互联网无关...互联网是指服务器而不是客户端。 - Robert Christian

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