OSChina 的 URL 设计和详细的实现

红薯 发布于 2012/11/01 10:52
阅读 14K+
收藏 370

OSChina 是用 Java 开发的,以前用 Java 开发的 Web 项目,URL 一般都是 xxx.jsp 或者是 xxx.do 之类的,后者是因为用了 Struts 框架的缘故。后来形如 /xxxx/xxxx 这样的 RESTful 风格 URL 很流行。而 OSChina 也是采用 RESTful 风格的这种 URL,看起来比较专业,呵呵。

在 Java 里要进行自定义 URL 的处理,一般用 Servlet 或者 Filter 来处理,而 Servlet 主要用于处理有固定前缀和后缀的 URL 地址,例如 /action/xxxx 和 xxxx.do 之类的请求。但一般 Servlet 不适合处理 /* 这样的请求,因为系统中总会有某些 URL 是需要特殊处理,例如静态文件就不希望走相同的逻辑,因为它没有“链”的结构,接收了请求就必须处理,不能再返回其他 Servlet 。而 Filter 则不同,它是一种责任链的设计模式,可以将请求传递到链中的下一个处理者。

关于 Servlet 和 Filter 还可阅读我之前的一片文章:初学 Java Web 开发,请远离各种框架,从 Servlet 开发

因为二者的不同,OSChina 采用的是 Filter 实现自定义 URL 处理。

先来看 OSChina 的 URL,例如:

http://www.oschina.net/project/tag/355/html5

URL 一般包含这么几部分内容:

部分
对应 HttpServletRequest 方法
示例中的取值
schema
getSchema()
http
hostname
getServerName()
www.oschina.net
port
getServerPort()
80
path
getRequestURI()
/project/tag/355/html5


在这里我们只关心 hostname 和 path 这两部分。

[hostname]

OSChina 上跟应用有关的有多个二级域名,分别是 www.oschina.net、my.oschina.net、m.oschina.net 和 wap.oschina.net 。这几个 hostname 对应了不同的模版路径。这些对应的路径在 web.xml 的 Filter 配置里:

<filter>
	<filter-name>global</filter-name>
	<filter-class>net.oschina.OSChinaFilter</filter-class>
	<init-param>
		<param-name>domain</param-name>
		<param-value>oschina.net</param-value>
	</init-param>
	<init-param>
		<param-name>template-path-prefix</param-name>
		<param-value>/WEB-INF</param-value>
	</init-param>
	<init-param>
		<param-name>ignore</param-name>
		<param-value>/action/,/uploads/,/img/,/css/,/js/,/test/</param-value>
	</init-param>
	<init-param>
		<param-name>ignoreExts</param-name>
		<param-value>ico,jpg,gif,png,bmp,doc,xls,pdf,zip,rar</param-value>
	</init-param>
	<init-param>
		<param-name>default</param-name>
		<param-value>/www</param-value>
	</init-param>
	<init-param>
		<param-name>m</param-name>
		<param-value>/templates/mobile</param-value>
	</init-param>
	<init-param>
		<param-name>wap</param-name>
		<param-value>/wap</param-value>
	</init-param>
	<init-param>
		<param-name>my</param-name>
		<param-value>/templates/home</param-value>
	</init-param>
</filter>

在这里,每个二级域名都对应了一个模版目录,其中 default 是表示其他没有配置的主机则使用该模版目录。有了这个东西,当需要开发一种新的客户端界面的时候,就不会跟原有的页面冲突,而且 URI 还可以保持一致,如:

http://www.oschina.net/news/34327/be-nice-to-programmers
http://m.oschina.net/news/34327/be-nice-to-programmers

这是同一篇新闻在不同的终端下的版本,前者是PC上,后者是智能手机上。

具体实现的代码请看 URLMappingFilter.java

[path]

接下来才是对 URL 的解析,还是前面那个新闻的 URL 地址,我们只关注最后面一部分,那就是 /news/34327/be-nice-to-programmers 。这个字符串可以通过 request.getRequestURI() 方法来获取得到。

获取到的 Path 信息用 / 字符将它拆分成字符串数组 uri_parts,对应的值:

uri_parts[0] = 'news'
uri_parts[1] = '34327'
uri_parts[2] = 'be-nice-to-programmers'

假设当前访问的是 www.oschina.net ,对应存放模版文件的路径是 /www,那么 URLMappingFilter 会按照如下顺序来查找这个 URI 对应的模版文件:

1. /www/news/34327/be-nice-to-programmers/index.vm
2. /www/news/34327/be-nice-to-programmers.vm
3. /www/news/34327/index.vm
4. /www/news/34327.vm
5. /www/news/index.vm
6. /www/news.vm
7. /www/index.vm

大家应该能看明白上面的顺序,等于是目录的递归。对应新闻的 URL  在上述路径中的第 5 步找到了对应的 index.vm 文件,然后 URLMappingFilter 就请请求 forward 给这个 index.vm 文件,同时把后面的两个值 34327 和 be-nice-to-programmers 通过参数的方式传递给 index.vm,最终的调用请求变成

index.vm?p1=34327&p2=be-nice-to-programmers

这个过程是在服务器端实现的,浏览器并不知情。

有人会问这么遍历路径性能会不会受影响,当然会,但是微乎其微,而且 OSChina 中的路径最多也就是三级,另外我们还将这个结果缓存起来,因此不会有什么影响。

假设你打开浏览器随便访问 OSChina 的任何一个地址,例如是

http://www.oschina.net/a/b/c/d/e/f/g

其实调用的是

index.vm?p1=a&p2=b&p3=c&p4=d&p5=e&p6=f&p7=g

其对应关系非常清晰,这也是 OSChina 内部的约定,不做任何配置。

[静态文件]

我们在开发的时候,生产环境中是由 Nginx 直接处理静态文件,因此静态文件的请求不会送到后端的应用来。但是在进行开发过程中没必要按照 Nginx,只需 Tomcat 即可。导致的问题是所有的静态文件也都会经过这个 URLMappingFilter,因此我们必须在过滤器中对这些文件进行识别,不予处理。

前面的配置中有 ignore 和 ignoreExts 两项,这两项配置分别对应了要忽略的 URI 的前缀和后缀。

Filter 在执行的过程中先要判断当前请求的 URI 是否符合 ignore 和 ignoreExts 中定义的内容,如果符合则将请求返回到过滤器链中进入下一步处理。

主要就这么些内容,有不明白的请多读几遍,实在不明白咱再讨论。哈:)

完整实现请看 URLMappingFilter.java

加载中
0
zeroot
zeroot
安卓客户端看blog ,代码都是没有规律的排序的 O(∩_∩)O
qijingq
qijingq
回复 @红薯 : 吼吼,要不改成.exe以前这么玩的。
红薯
红薯
请问这个跟本文有何关系?
0
丁加砙
丁加砙
简单易懂。
0
LarryKoo
LarryKoo
支持OSC自爆
0
懵懂一时
懵懂一时
学习 学习!
0
ellan
ellan
学习了
0
卜库塔
卜库塔
支持,可惜偶不搞java
0
bear13
bear13
我觉得如果要像这样生成二次请求,那第一次请求的时候参数应该不要明确显示出来,至少不要那么容易辨别,而且第一次请求也太长.可以对第一次请求进行加密,也只有一级.如这样: http://www.oschina.net/d6e3n8d0g9a0
JollyRoger
JollyRoger
回复 @红薯 : 他的意思是说把path给进行编码了吧
红薯
红薯
不明白你的意思哦
0
兔bug
兔bug
很清晰!
返回顶部
顶部