a href
HTML <a>
元素(或称锚元素)可以通过它的 href
属性创建通向其他网页、文件、同一页面内的位置、电子邮件地址或任何其他 URL 的超链接。<a>
中的内容应该应该指明链接的意图。如果存在 href
属性,当 <a>
元素聚焦时按下回车键就会激活它。 - mozilla.org
当使用 a href 时,偶尔会遇到404,或者展示的页面不是我们想要它展示的那个, 这个问题是怎么一回事? 我们从简单的开始
href - 可真不简单
- a href 是绝对路径
如果你用 / 做开头, 它将以站点的URI路径作为前缀,而不是当前html的URI路径,例如域名是 localhost, 前缀则是 localhost:
<a href="/mywebsite/pictures/picture.png">
不论当前页面的URI是什么,它将指向:
"http://localhost/mywebsite/pictures/picture.png"
要访问一个资源,需要路径指明它在网站内的完整子目录
- a href 是相对路径
如果用一个文件{夹}的名称作开头, 将被加上你的html 文件的路径做前缀:
<a href="pictures/picture.png">
当前路径是 "http://localhost/index.html", 将访问
"http://localhost/pictures/picture.png"
当前路径是 "http://localhost/mywebsite/index.html", 将访问
"http://localhost/mywebsite/pictures/picture.png"
3. a href 是点-斜杠 (./)
点 (.) 是指当前目录,加上斜杠是去访问当前目录, 所以
"pictures/picture.png"
和
"./pictures/picture.png"
是相同含义
- a href 是点点-斜杠 (../)
点点是上级目录,而加上斜杠是去访问当前目录的上级, 这产生了一些细微的反应
"../picture.png"
当前路径是 "http://localhost/index.html", 将访问的路径在不同网页服务端会有所不同, 但为安全考虑,现在一般的服务端处理是去访问
"http://localhost/picture.png"
当前路径是 "http://localhost/mywebsite/index.html", 将访问
"http://localhost/picture.png"
(../) 可以多级使用,也可以和其他目录组合使用.
"../mywebsite/picture.png"
5. <base href="…">
base href 用于文档中 相对地址的基础 URL。允许绝对和相对URL。
base 给 href 带来剧烈的反应, 首先HTML规范定义了一个页面内出现多个 base时的情况
多个<base>元素:
如果指定了多个 <base>元素,只会使用第一个 href 和 target 值, 其余都会被忽略。
6. 其次HTML规范定义了a href是页内锚的情况 - <a href="#some-id">
当href 定义是指向文档中某个片段的链接,加上 <base>
后解析, 将触发前缀是 base 的 href 的 HTTP 请求。
例如:给定 `<base href="https://localhost">`
以及此链接 `<a href="#anchor">Anker</a>`
链接指向 https://localhost/#anchor
7. <a href="/mywebsite/pictures/picture.png">
- a href是绝对路径
不受base影响,仍将访问
"https://localhost/mywebsite/pictures/picture.png"
8. 相对路径的href受到的base影响, 分为4种情况
值 | 位置 |
---|---|
<a href="pictures/picture.png"> | 相对路径 |
http://localhost/mywebsite/index.html | 当前路径 |
* <base href="https://localhost"> | 1. base 指示一个明确的带域名的URI |
将访问
"https://localhost/pictures/picture.png"
非常好!建议这样使用base: 相对路径 a href 和 明确域名的base
注意,不带协议语句块的 (//) 也将被浏览器解析为一个域名, 例如
<base href="//localhost">
浏览器将自动测试端口去判断要使用的协议, https, http,等
值 | 位置 |
---|---|
<a href="pictures/picture.png"> | 相对路径 |
http://localhost/mywebsite/index.html | 当前路径 |
<base href="/"> | 2.base是绝对路径 |
base是一个绝对路径, 结果开始令人意外, 所有的相对路径都变成了绝对路径,将访问
"https://localhost/pictures/picture.png"
<a href="picture.png">
则访问
"https://localhost/picture.png"
9.
值 | 位置 |
---|---|
<link rel="stylesheet" href="static/plugins/toastr/toastr.min.css"> | 相对路径 href |
http://localhost:8080/mywebsite/pictures | 当前访问的页面URI |
* <base id="base" href="../"> | 3. base也是相对路径 |
好消息是,结果很符合直觉,它们联合在一起,形成新的组合相对路径
"http://localhost:8080/mywebsite/static/plugins/toastr/toastr.min.css"
10.
值 | 位置 |
---|---|
<link rel="stylesheet" href="static/plugins/toastr/toastr.min.css"> | 相对路径 |
http://localhost:8080/mywebsite/pictures | 当前访问的页面URI |
* <base id="base" href="/mywebsite"> | 4. base是一个绝对路径,但不是带后缀(/)的完整目录 |
结果令人意外, 不是完整目录的 base href {tag}让所有的a href 变成了以域名为根目录的绝对路径, 最终将访问
http://localhost:8080/static/plugins/toastr/toastr.min.css
这样的base href 格式是错误的,不同浏览器间可能有不同的结果
- 至于
<base id="base" href="/mywebsite/">
的情况,和 8.绝对路径 相同
渐入佳境 - 乱成了一锅粥
Spring + JSTL + request.contextPath
纯粹的HTML要实现根据变量值动态展示数据十分繁琐,要按照HTML tag 解析和生成字符串. 所以前端开发语言和库基本都设置了HTML模板语法,用简单的模板语法生成复杂的HTML结果.
Spring 是最常用的 java 后端开发框架,也带有写前端界面的功能.
JSTL 是 Spring 用来写作 HTML 页面的模板语法.
mywarname.war - war 文件是spring 代码打包出的程序,它可以用tomcat网页服务端加载后展示出来.
${pageContext.request.contextPath}
是JSTL的一个语句,可以用来获得应用的被加载路径 - 一般是 "mywarname.war" 的 mywarname部分.
当把spring生成的WAR包放到 tomcat 网页服务器的webapp目录时, tomcat将拆开war, 把内里的页面布置到和 war 包的名称相同的路径. 而java程序常常需要相对 war包名称来定位路径,所以经常会用到 ${pageContext.request.contextPath}
${pageContext.request.contextPath}
有几种用法,常见的2种是
- 设置
<base id="base" href="${pageContext.request.contextPath}">
,修改页面的相对路径的基本前缀. - 设置一个spring变量
$(base)
,<#assign base=request.contextPath />
,后面在需要的时候把${base}
拼接到href的URI上
这两个主意都很机灵,正确的用法很多例子,可以搜索关键词查询,下面我们继续讨论 href 的复杂性
- JSTL 中base用绝对路径(/)带上
${pageContext.request.contextPath}
<base id="base" href="/${pageContext.request.contextPath}/">
假设spring war 包名称是 mywarname.war
, 则${pageContext.request.contextPath} 输出的是 /mywarname
, 产生的HTML内将是
<base id="base" href="//mywarname/">
到这儿,事情开始异常的混乱起来,
值 | 位置 |
---|---|
<a href="pictures/picture.png"> | 相对路径 |
http://localhost/mywebsite/index.html | 当前路径 |
* <base id="base" href="//mywarname/"> | base是一个域名 |
将去访问捏造出的域名
//mywarname/pictures/picture.png
12. JSTL 中Base用相对路径(/)带上${pageContext.request.contextPath}
<base id="base" href="./${pageContext.request.contextPath}/">
产生的HTML内将是
<base id="base" href=".//mywarname/">
这样避免了产生新的域名,但 相对路径+相对路径 的结果,往往和设置这个base的初衷相反,
值 | 位置 |
---|---|
<a href="pictures/picture.png"> | 相对路径 |
http://localhost/mywebsite/index.html | 当前路径 |
* <base id="base" href=".//mywarname/"> | base是一个相对路径 |
将访问
http://localhost/mywebsite/mywarname/pictures/picture.png
你是要去哪里!
- 我们既不加绝对路径,也不用相对路径,
<base id="base" href="${pageContext.request.contextPath}">
产生的HTML内将是
<base id="base" href="/mywebsite">
值 | 位置 |
---|---|
<a href="pictures/picture.png"> | 相对路径 |
http://localhost/mywebsite/index.html | 当前路径 |
* <base id="base" href="/mywebsite"> | base是一个绝对路径,但不是带后缀(/)的完整目录 |
这产生了 10. 的情况,错误的导致所有 a href 变成了以域名为根目录的绝对路径
- tomcat 还支持把war部署到根目录 - 只要将 mywarname.war 命名为 ROOT.war
假设 mywarname.war 包含一个默认页面 index.html, 于是产生URL
同时当我们在用${pageContext.request.contextPath}设置 base href 时还指定了一个更深的一个子目录,例如
<base id="base" href="${pageContext.request.contextPath}/anotherSub/">
产生的HTML内将是
<base id="base" href="//anotherSub/">
值 | 位置 |
---|---|
<a href="pictures/picture.png"> | 相对路径 |
http://localhost/mywebsite/index.html | 当前路径 |
* <base id="base" href="//anotherSub/"> | base是一个域名 |
将访问捏造出的域名
//anotherSub/pictures/picture.png
这实在是乱透了.
- 直接在 a href 中使用${pageContext.request.contextPath}时,也会出现类似 11~15 的问题,不在赘述.
在a href 中直接用${pageContext.request.contextPath}
生成 href的好处是, 减少了 base tag 这一层对于URI的干扰, 让生成的URI更符合直觉了.
-
再看一种更复杂的情况, 当我们用nginx做了反向代理
domain1.com/sub -> localhost/mysubsite
在访问时nginx上的网站, nginx 将认识 domain1 而不认识 localhost, 且nginx认识 sub目录而不认识 mysubsite 目录.
同时,若我们在href 中使用的路径是一个绝对路径,
<a href="/mysubsite/static/plugins/toastr/toastr.min.css">
nginx拉回去给浏览器显示的页面上, 上面的路径将生成
domain1.com/mysubsite/static/plugins/toastr/toastr.min.css
当客户端去访问nginx时, nginx并不认识domain1下的mysubsite目录,虽然可以通过 sub_filter 语句在反向代理拉回来的HTML代码中进行路径的替换. 但从此以后 nginx 的配置文件,就要跟着程序代码里写的URI变化而走了. 要在修改程序代码后,记得去同步修改nginx的配置, 这给自己造成了更多的麻烦.
- 所以我们就不要使用
${pageContext.request.contextPath}
-mywarname.war 名称 来访问资源了,写一个只使用 HTML语法设置 href的JSTL,这样解决了问题吗?
我们接下来要面对的情况已经简单多了.
要架设spring+tomcat的网站, 一般教程是让把WAR文件放到 tomcat 的 webapps 目录, 这是 tomcat默认配置开设的虚拟主机的代码目录.
可tomcat没有限制WAR文件必须放到根URI路径下才能访问, 要修改访问 war 包内部资源所使用的URI路径前缀:
-
可以通过设置 server.xml , 在
<HOST>
块内使用<Context>
块指定 war包 所在的目录和它要占用的URI路径前缀, 例如 sub -
或者把 mywarname.war 命名成 sub#mywarname.war, 产生的URL将是
若HTML中像 18. 一样使用了绝对路径的 href ,这个 href 将无法跟着 war 文件在tomcat设置中绷定的URI 前缀变化而变化.
于是为了适应tomcat设置的任意 war前缀路径,我们只能选择
- 或使用
${pageContext.request.contextPath}
获得移动后的路径, - 或使用纯HTML的相对路径指定 href
结论
到此可以做出总结,为避开href的各种坑, 理应:
- 本{程序包}内的网页和资源,只用相对路径去访问.
- 访问绝对路径时理应明确指定一个域名
- 杜绝使用语言或模板库自带的URI定位器(如当前文件的路径)去设置 HTML 的 base tag. HTML 作为一种动态解析的标记语言,字符串可以任意组合,每引入一层字符串的变化都要增加数倍复杂度.