??xml version="1.0" encoding="utf-8" standalone="yes"?> 关于跨域名问题还是问题么Q这斚w的解军_践非常多Q今天我旧话重提把我所知道的通过几个应用场景来分别ȝ一?/p>
先说明一点:我说的某某域名在您的控制下的意思是q个域名下的|页由您来负责开发内部的JavaScript 场景?/span>Q将www.yyy.com的页面用iframe嵌入到www.xxx.com的中Q两个域名都在您的控制下Q如何在iframe内外q行一定的数据交流 q里的实现基于如下技术要点: q种实现的一些分析: 场景?/span>Q将www.yyy.com的页面用iframe嵌入到www.xxx.com的中Q只有被嵌入的yyy.com在您的控制下Q如何在iframe内外q行一定的交流 场景?/span>Q您是内容发布商Q如何改造接口,让其他域名下的页面可以从览器端出发获得您的数据 q样只要query.php的输出是可执行的javaScript脚本Q比如:djsCallBackQ{jsondata}Q? 如果您是数据提供者,您可以要求数据烦取者在查询参数中提供回调函数名Q比如query.php?callback=myDataHandler&key=…? 场景?/span>Q通过后端E序语言Qؓ了跨域名而做各自的后台数据抓取{化服务,比如php curlQYAHOO CHINA NCP是用这U方案?/p>
场景?/span>Q通过flash proxyQ因为flash的跨域调用可以通过crossdomain.xml和security.allowdomain(’*')文g实现Q而js又可以和flashq行通信Q所以js完全可以借用flash 实现js跨域通信?/p>
W五U场景,处理很方便,也很实用?/p>
W六U场景,需要一定的flash基础哈,作用当然非常强大?/p>
场景一Q将bbs.xxx.com的页面用iframe嵌入到www.xxx.com的中Q如何在iframe内外使用js通信
一U域名都是xxx.com q个域名一定是在您的控制下Q所以你只要在两个页面中同时升域名卛_
在父H口和iframe内部分别加上js语句Qdocument.domain=”xxx.com”;
之后2个页面就{于在同一域名下,通过window.parent oIframe.contentDocument可以相互访问,q行无障的JS通信
在新、淘宝等很多面都能扑ֈq样的语句。不qdocument.domain不可以随便指定,只能向上升Q从bbs.xxx.com升到yyy.com肯定会出?/p>
你可以通过怺改变hash值的方式来进行一些数据的通信
1、父H口通过改变子窗口的src中的hash值把一部分信息传入Q如果src只有hash部分改变Q那么子H口是不会重新蝲入的?br />
2、子H口可以重写父窗口的location.hrefQ但是注意这里子H口无法d而只能重写location.href所以要求前提是您控制两个域名,知道当前父窗口的location.href是什么ƈ写在子窗口内Q这样通过parent.location.href = “已知的父H口的href”+”#”+hash。这LH口只有hash改变也不会重载?br />
3、上面两步分别做C两个H口之间的无h数据通知Q那么下面的来说如何感知数据变化。标准中没有相关规定Q所以当前的L览器遇到location.hash变化都不会触发Q何javaScript事gQ也是说您要自己写监听函数来监视loaction.hash的值的变化。做法是通过setTimeout或者setInterval来写一个监听函数每20-100ms查看一下hash是否变化Q如果变化了驱动jsҎ新的数据做想做的事情?/p>
1、信息通道是双向的Q当然会兼容单向Q如果只是父H口向子H口通知数据Q只需要子H口写hash监听Q反之亦然?br />
2、局限性也是颇大,因ؓq种通信的前提是双方知道Ҏ的location.href。如果父H口带有动态的location.search也就是查询参敎ͼ那么子窗口的处理上就比较困难Q需要把父窗口的location.search作ؓ传递信息的一部分告知子窗口?br />
3、另外的困扰会有览器带l你QIE之外的浏览器遇到hash的改变会记录历史Q这样你在处理前q后退的时候会非常头疼
真实场景Qgoogle adsence的一个需求,你希望google发现您的面不能匚w出相x非常好的按点击付费q告Ӟ你希望google的广告iframe能够隐藏?br />
google的广告iframe在google域下昄不能把自己隐藏掉Q那么怎么办呢Q?br />
1、google会提供给你一个html面
2、您这个页面放|在您的域名下,q告诉google它的位置
3、当google发现没有很好的广告时Q会子H口的loaction重定向到您的那个面下,q样您的面因ؓ同域名就可以讉K爉面来隐藏自己?br />
是不是很巧的ҎQ?/p>
我们知道ajax的xmlHttpRequest()说到底是一个无hh服务器数据的辅助工具Q但是xmlHttpRequestq不能跨域名h数据Q在某些情况下成了极大的限制?br />
但是我们如果通过其他方式完成无刷新请求数据不也可以么Q我们用DomҎ操作动态JS脚本h来做qg事?br />
//创徏一个脚本节?br />
var oScript = document.createElement(’script’);
//指定脚本src src可以指向L域名
//注意src不再指向静态jsQ而是带着查询参数指向一个动态脚本广播服务?br />
oScript.src = “http://yyy.com/query.php?”+yourQueryString;
//如果指定了charset 同时q可以解决xmlHttpRequest另一大困?q问题
//oScript.charset = “utf-8″;
//通过Dom操作把这个新的节点加入到文档当中
document.getElementsByTagName(”head”)[0].appendChild(oScript);
当他从服务器q回后就会自动执?你可以方便的用json方式来做数据传递了?br />
要注意,您的脚本h最好带上时间戳Q避免浏览器~存造成取回数据实时性下降?/p>
q样您就可以Ҏ参数来提供给他myDataHandler({jsondata}),q样不同的数据烦取者都会得到自定义的正的异步回调?/p>
ȝȝQ?br />
W一U场景,相应的处理办法有q非常好的效果,可以说完全解决了问题?br />
W二U场景,相应的处理办法具有一定的跨域数据交流功效Q具有相当大的局限,q不适合在复杂业务流E中应用Q实际上我也实也没看到q基于此的大规模应用?br />
W三U场景,相应的处理办法比较y妙,虽然redirect之后׃q你什么事了,但如果你是google一样面向众多域名的内容提供商,也是个不错的解决思\?br />
W四U场景,相应的处理办法非常强大,ҎAjax可以看到Q跨域名没问题,无刷新没问题Q本w又是异步的QJSON比xml快的多,同时解决q问题Q只是请求都是Get方式的,不能做Post方式的请求。多一U武器自然可以从定w择了?/p>
在此我测试了如下pȝ:gmail,163邮箱,126邮箱,tom邮箱
我在览器禁用cookie的情况下,登陆上述4邮箱!
Gmail:
正确输入用户名密码后,pȝl出如下提示:
163邮箱:
正确输入用户名密码后,pȝl出如下提示:让我搞不清楚是用户名密码错误q是其它错误!
126邮箱
正确输入用户名密码后,pȝl出如下提示:让我搞不清楚是用户名密码错误q是其它错误!
tom邮箱
正确输入用户名密码后,pȝl出如下提示:居然提示我是"非法h"
从上面测试中感受C同的用户体验.对大多数做web开发的人来?判断览器是否支持Cookieq不是什么难?隄是对l节的处?
也许我们与优U的品差距最大的是对l节的处?
? 我测试用的是IE6.0
览和修改Web늚文档对象模型(DOM).通过多种技术方式定位、选定Web上的特定元?止或激zIE讄.查看HTML对象的类名?
ID,以及cM链接路径、tab序、快捷键{细?描绘表格、单元格、图片或选定标签的轮?昄囄象素、大、\径、替代文字等.x重定义浏览器
H口大小?00x600或自定义大小.清空览器缓存和cookie,被清除项可从所有对象或l定域中选择.直接讉K兌W3C规范参考、IE开发组
blog或其他来?Q显C计时标尺,帮助寚w对象. ....
安装完HttpWatch后,׃在浏览器中找CQ?br />
?/span>用介l:http://www.cnblogs.com/mayingbao/archive/2007/11/30/978530.html ?/span>QFiddler2是一?u>|络调试代理Q用来监本地计?
机和Internet之间所有的HTTP通讯。可以监所有的HTTP通讯Q设|断点,q且可以修改到进入到本地计算机的数据和从本地计算机出ȝ数据
Q就是可以伪造数据)。Fiddler包含一个JScript .NET 事g脚本子系l(event-based scripting
subsystemQ,可以使用M一U?Net语言扩展。该软g是免费的Q支持多U浏览器Q包括Internet ExplorerQMozilla
FirefoxQOpera和其它一些浏览器?/p>
从Fiddler官方|站上可以看见原版的英文介绍Q?a title="http://www.fiddler2.com/fiddler2/" target="_blank">http://www.fiddler2.com/fiddler2/ 使用介绍Q?/span>http://blog.csdn.net/lihongzhai/archive/2009/09/14/4551035.aspx NUnit是一?u>单元试框架Q专门针对于。NET来写?其实在前面有JUnit(Java)QCPPUnit(C++)Q他们都是xUnit的一员。最初它是从JUnit而来。现在的版本?.2.接下来我所用的都是Zq个版本?/p>
下蝲地址Q?/span>http://www.nunit.org/index.php 使用介绍Q?/span>http://www.uml.org.cn/net/200702273.asp 介:PowerDesigner是Sybase公司的CASE工具集,使用它可以方便地对管理信息系l进行分析设计,它几乎包括了数据库模型设?/u>的全q程?br />
利用PowerDesigner可以制作数据程图、概忉|据模型、物理数据模型,可以生成多种客户端开发工L应用E序Q还可ؓ数据仓库制作l构模型Q也能对团队设计模型q行控制?br />
主要功能QDataArchitectQ这是一个强大的数据库设计工P使用DataArchitect
可利用实?关系图ؓ一个信息系l。创?概念数据模型"QCDMQConceptual Data ModelQ。ƈ且可ҎCDM
产生Z某一特定数据库管理系l(例如QSybase System 11Q的"物理数据模型"-PDM(Physical Data
Model)。还可优化PDMQ生ؓ特定DBMS 创徏数据库的SQL 语句q可以文件Ş式存储以便在其他时刻q行q些SQL
语句创徏数据库。另外,DataArchitectq可Ҏ已存在的数据库反向生?br />
PDMQCDM 及创建数据库的SQL脚本?br />
ProcessAnalystQ这部分用于创徏功能模型和数据流图,创徏"处理层次关系"?br />
AppModelerQؓ客户/服务器应用程序创建应用模型?br />
ODBC AdministratorQ此部分用来理pȝ的各U数据源?br />
下蝲地址Q?/span>http://www.baidu.com/s?tn=sitehao123&bs=PowerDesigner&f=3&wd=powerdesigner%CF%C2%D4%D8&oq=powerdesigner&rsp=0 Q? 使用介绍Q?http://www.cnblogs.com/yxonline/archive/2007/04.html 介:Visual Studio内置的ILDASM成ؓ最?u>挖掘E序集的上佳工具?
但自从Reflector出现后,ILDASM相Ş见拙。因为,Reflector能提供更多的E序集信息。Reflector可以?NETE序集中?
中间语言反编译成C#或者Visual Basic代码。除了能IL转换为C#或Visual
Basic以外QReflectorq能够提供程序集中类及其成员的概要信息、提供查看程序集中IL的能力以及提供对W三Ҏ件的支持?img alt="" src="http://www.cnblogs.com/gaoweipeng/admin/file:///C:/DOCUME%7E1/ADMINI%7E1/LOCALS%7E1/Temp/moz-screenshot.png" /> 下蝲地址Q?/Files/gaoweipeng/Reflector.rar 使用介绍及其他相关下载:http://www.cnblogs.com/zzy2740/archive/2005/09/20/240216.html 介: 1st JavaScript Editor 是一Ƒּ大的JavaScript开发、校验和调试工具Q?
它简单易用,不论你是初学者或者是专业的开发h士,都可以轻松上手!同时它又是完的Ajax (Asynchronous JavaScript
and XML),CSS, HTML, DOM DHTML开发工P它提供了单而且实用的功能:丰富的代码编辑功?JavaScript,
HTML, CSS, VBScript, PHP ,ASP(Net))Q语法高亮,内置预览功能Q提供了完整的HTML 标记, HTML 属?
HTML 事g, JavaScript 事g和JavaScript 函数{完整的代码库,同时有着贴心的代码自动补全功能?br />
官网及下载地址Q?span style="font-size: 10.5pt; font-family: 'Times New Roman';">http://yaldex.com/ ?/span>Q用Reglator可以方便的验?u>正则表达式的执行l果Q带有智能感知功能。帮助文档是一个很好的学习正则的入门教E,也可以当作参考手册。基本上全了,同时q行学习和实c还有生成c# or vb.net代码{附加功能?cM的Y件现在很多:RegexBuddy QRegexTester。等 下蝲地址Q?/span>http://sourceforge.net/projects/regulator/ 使用介绍Q?/span>http://www.ctochina.net/topic/ShowArticle/112.html 结Q上面的开发工具都是我qx喜欢用的Q希望通过此文的介l,Ҏ有用过的朋友带来帮助。也希望园子中的朋友?strong>U一U自己qx常用的开发工?/strong>Q分享些更实用,方便的开发工PHttpWatch
?/span>Q强大的|页数据分析工具。集成在Internet Explorer工具栏。包括网|要。Cookies理。缓存管理。消息头发?接受。字W查询。POST 数据和目录管理功能。报告输出?/p>
ȝ面:
下蝲地址Q?/span>http://www.crsky.com/soft/3455.html
Fiddler2
下蝲地址Q?/span>http://www.fiddler2.com/fiddler2/
NUnit
PowerDesigner
Reflector
1st JavaScript Editor
Regulator
]]>
首先Q请?a >http://www.andykhan.com/jexcelapi/index.html下蝲java excel apiQ主上同时有比较详l的介绍。最新版本ؓ2.4.3Q同时也可以刎ͼhttp://www.andykhan.com/jexcelapi/jexcelapi_2_4_3.tar.gz下蝲到该最新版的APIQ由于该目是开源的Q所以下载的文g中已l包含了源代码,同样的,文g中也有javadocQ大家在开发中可以参考javadoc?/p>
下蝲完毕后,我们需要把文g中的jxl.jar加入C的开发classpath中?br />
下图是现在要生的excel截图Q?br />
http://blog.csdn.net/beming/gallery/image/3437.aspx
代码如下Q?/p>
File excel = new File("d:/aming.xls");
if(!excel.exists()){
excel.createNewFile();
}
WritableWorkbook wwb = Workbook.createWorkbook(excel);
WritableSheet ws = wwb.createSheet("testexcel",0);
Label lable = null;
//对中文的支持非常?br />
lable = new Label(0,0,"我的中国?);
ws.addCell(lable);
//可以定义模板格式化你的cell
WritableFont
wf = new
WritableFont(WritableFont.ARIAL,10,WritableFont.NO_BOLD,false,UnderlineStyle.NO_UNDERLINE,
Colour.BLACK);
WritableCellFormat wcf = new WritableCellFormat(wf);
wcf.setBackground(Colour.WHITE);
lable = new Label(0,1,"fdsl",wcf);
ws.addCell(lable);
wf = new WritableFont(WritableFont.TIMES,18,WritableFont.BOLD,true);
wcf = new WritableCellFormat(wf);
lable = new Label(0,2,"aming",wcf);
ws.addCell(lable);
//cell的类型同样可以定义ؓ数字cd
Number nb = new Number(0,3,21.4321321);
ws.addCell(nb);
//支持格式化你的数字串
NumberFormat nf = new NumberFormat("#.###");
wcf = new WritableCellFormat(nf);
nb = new Number(0,4,21.43254354354354,wcf);
ws.addCell(nb);
//cell的类型可以ؓbooleancd
Boolean bl = new Boolean(0,5,true);
ws.addCell(bl);
//cell的类型同样可以ؓ日期Q时?br />
DateTime dt = new DateTime(0,6,new Date());
ws.addCell(dt);
//q且可以很好格式化你的日期格?br />
DateFormat df = new DateFormat("MM dd yyyy hh:mm:ss");
wcf = new WritableCellFormat(df);
dt = new DateTime(0,7,new Date(),wcf);
ws.addCell(dt);
//开始写文g?br />
wwb.write();
wwb.close();
上面的下载地址无法打开.
下蝲请到:http://prdownloads.sourceforge.net/jexcelapi
5.2 分配JSP初始化参?
lJSP面提供初始化参数在三个斚w不同于给servlet提供初始化参数?
1Q用jsp-file而不是servlet-class。因此,WEB-INF/web.xml文g的servlet元素如下所C:
2) 几乎L分配一个明的URL模式。对servletQ一般相应地使用以http://host/webAppPrefix/servlet/ 开始的~省URL。只需CQ用注册名而不是原名称卛_。这对于JSP面在技术上也是合法的。例如,在上面给出的例子中,可用URL http://host/webAppPrefix/servlet/PageName 讉KRealPage.jsp的对初始化参数具有访问权的版本。但在用于JSP面Ӟ许多用户g不喜Ƣ应用常规的servlet的URL。此外,如果 JSP面位于服务器ؓ其提供了目录清单的目录中Q如Q一个既没有index.html也没有index.jsp文g的目录)Q则用户可能会连接到? JSP面Q单dQ从而意外地ȀzL初始化的面。因此,好的办法是用url-patternQ?.3节)JSP面的原URL与注册的 servlet名相兌。这P客户机可使用JSP面的普通名Uͼ但仍然激zd制的版本。例如,l定来自目1的servlet定义Q可使用下面? servlet-mapping定义Q?/p>
3QJSP用jspInit而不是init。自动从JSP面建立的servlet或许已经使用了intiҎ。因此,使用JSP声明提供一个initҎ是不合法的,必须制定jspInitҎ?
?
了说明初始化JSP面的过E,E序清单5-9l出了一个名为InitPage.jsp的JSP面Q它包含一个jspInitҎ且放|于
deployDemo Web应用层次l构的顶层。一般,http://host/deployDemo/InitPage.jsp
形式的URL激zL面的不h初始化参数访问权的版本,从而将对firstName和emailAddress变量昄null。但是,
web.xml文gQ程序清?-10Q分配了一个注册名Q然后将该注册名与URL模式/InitPage.jsp相关联?
E序清单5-9 InitPage.jsp
E序清单5-10 web.xmlQ说明JSP面的init参数的摘录)
5.3 提供应用范围内的初始化参?
一般,对单个地servlet或JSP面分配初始化参数。指定的servlet?
JSP面利用ServletConfig的getInitParameterҎdq些参数。但是,在某些情形下Q希望提供可׃Q意servlet?
JSP面借助ServletContext的getInitParameterҎd的系l范围内的初始化参数?
可利用context-param元素声明q些pȝ范围内的初始化倹{context-param元素应该包含param-name、param-value以及可选的description子元素,如下所C:
<context-param>
<param-name>support-email</param-name>
<param-value>blackhole@mycompany.com</param-value>
</context-param>
?
回忆一下,Z保证可移植性,web.xml内的元素必须以正的ơ序声明。但q里应该注意Qcontext-param元素必须出现L与文档有关的?
素(icon、display-name或descriptionQ之后及filter、filter-mapping、listener?
servlet元素之前?
5.4 在服务器启动时装载servlet
假如servlet或JSP面有一个要花很长时间执行的
init
QservletQ或jspInitQJSPQ方法。例如,假如init或jspInitҎ从某个数据库或ResourceBundle查找产量。这U?
情况下,在第一个客hh时装载servlet的缺省行为将对第一个客h产生较长旉的gq。因此,可利用servlet的load-on-
startup元素规定服务器在W一ơ启动时装蝲servlet。下面是一个例子?/p>
可以为此元素体提供一个整数而不是用一个空的load-on-startup。想法是服务器应该在装蝲较大数目的servlet或JSP面之前 装蝲较少数目的servlet或JSP面。例如,下面的servlet(攄在Web应用的WEB-INF目录下的web.xml文g中的web- app元素内)指C服务器首先装蝲和初始化SearchServletQ然后装载和初始化由位于Web应用的result目录中的index.jsp? 件生的 servlet?/p>
6 声明qo?/span>
servlet版本2.3引入了过滤器的概c虽然所有支持servlet API版本2.3的服务器都支持过滤器Q但Z使用与过滤器有关的元素,必须在web.xml中用版?.3的DTD?
q?
滤器可截取和修改q入一个servlet或JSP面的请求或从一个servlet或JSP面发出的相应。在执行一个servlet或JSP面之前Q?
必须执行W一个相关的qo器的doFilterҎ。在该过滤器对其FilterChain对象调用doFilterӞ执行链中的下一个过滤器。如果没
有其他过滤器Qservlet或JSP面被执行。过滤器h对到来的ServletRequest对象的全部访问权Q因此,它们可以查看客户机名、查?
到来的cookie{。ؓ了访问servlet或JSP面的输出,qo器可响应对象包裹在一个替w对象(stand-in
objectQ中Q比方说把输出篏加到一个缓冲区。在调用FilterChain对象的doFilterҎ之后Q过滤器可检查缓冲区Q如有必要,对?
q行修改Q然后传送到客户机?
例如Q程序清?-11帝国难以了一个简单的qo器,只要讉K相关的servlet或JSP面Q它截取请求ƈ在标准输Z打印一个报告(开发过E中在桌面系l上q行Ӟ大多数服务器都可以用这个过滤器Q?
E序清单5-11 ReportFilter.java
一旦徏立了一个过滤器Q可以在web.xml中利用filter元素以及filter-nameQQ意名Uͼ、file-classQ完全限定的c?
名)和(可选的Qinit-params子元素声明它。请注意Q元素在web.xml的web-app元素中出现的ơ序不是L的;允许服务器(但不是必
需的)强制所需的次序,q且实际中有些服务器也是q样做的。但q里要注意,所有filter元素必须出现在Q意filter-mapping元素之前Q?
filter-mapping元素又必d现在所有servlet或servlet-mapping元素之前?
例如Q给定上q的ReportFilterc,可在web.xml中作Z面的filter声明。它把名UReporter与实际的cReportFilterQ位于moreservletsE序包中Q相兌?/p>
一旦命名了一个过滤器Q可利用filter-mapping元素把它与一个或多个servlet或JSP面相关联。关于此工作有两种选择?
?
先,可用filter-name和servlet-name子元素把此过滤器与一个特定的servlet名(此servlet名必ȝ后在相同?
web.xml文g中用servlet元素声明Q关联。例如,下面的程序片断指C系l只要利用一个定制的URL讉K名ؓSomeServletName
的servlet或JSP面Q就q行名ؓReporter的过滤器?/p>
其次Q可利用filter-name和url-pattern子元素将qo器与一lservlet、JSP面或静态内容相兌。例如,盔R的程序片D|C系l只要访问Web应用中的LURLQ就q行名ؓReporter的过滤器?/p>
例如Q程序清?-12l出了将ReportFilterqo器与名ؓPageName的servlet相关联的web.xml文g的一部分。名?
PageName依次又与一个名为TestPage.jsp的JSP面以及以模式http:
//host/webAppPrefix/UrlTest2/
开头的URL相关联。TestPage.jsp的源代码已经JSP面命名的谈论在前面??分配名称和定制的URL"中给出。事实上Q程序清?-
12中的servlet和servlet-name从该节原封不动地拿q来的。给定这些web.xml,可看C面的标准输出形式的调试报告(换行?
ZҎ阅读Q?
audit.irs.gov tried to access
http://mycompany.com/deployDemo/UrlTest2/business/tax-plan.html
on Tue Dec 25 13:12:29 EDT 2001.
E序清单5-12 Web.xmlQ说明filter用法的摘录)
7 指定Ƣ迎?假如用户提供了一个像http: //host/webAppPrefix/directoryName/
q样的包含一个目录名但没有包含文件名的URLQ会发生什么事情呢Q用戯得到一个目录表Q一个错误?q是标准文g的内容?如果得到标准文g内容Q是
index.html、index.jsp、default.html、default.htm或别的什么东西呢Q?
Welcome-file-list
元素及其辅助的welcome-file元素解决了这个模p的问题。例如,下面的web.xmlҎ出,如果一个URLl出一个目录名但未l出文g名,?
务器应该首先试用index.jspQ然后再试用index.html。如果两者都没有扑ֈQ则l果有赖于所用的服务器(如一个目录列表)?
虽然许多服务器缺省遵循这U行为,但不一定必这栗因此,明确C用welcom-file-list保证可移植性是一U良好的习惯?
8
指定处理错误的页?
现在我了解到Q你在开发servlet和JSP面时从不会犯错误,而且你的所有页面是那样的清晎ͼ一般的E序员都不会被它们的搞糊涂。但是,是hM?
错误的,用户可能会提供不合规定的参数Q用不正确的URL或者不能提供必需的表单字D倹{除此之外,其它开发h员可能不那么l心Q他们应该有些工h?
服自q不?error-page元素是用来克服q些问题的。它有两个可能的子元素,分别是:error-code和exception-
type。第一个子元素error-code指出在给定的HTTP错误代码出现时用的URL。第二个子元素excpetion-type指出在出现某?
l定的Java异常但未捕捉到时使用的URL。error-code和exception-type都利用location元素指出相应的URL。此
URL必须?开始。location所指出的位|处的页面可通过查找HttpServletRequest对象的两个专门的属性来讉K关于错误的信息,
q两个属性分别是Qjavax.servlet.error.status_code和javax.servlet.error.message?
可回忆一下,在web.xml内以正确的次序声明web-app的子元素很重要。这里只要记住,error-page出现在web.xml文g的末N
q,servlet、servlet-name和welcome-file-list之后卛_?
8.1 error-code元素
Z更好C解error-code元素的|可考虑一下如果不正确地输入文件名Q大多数站点会作Z么反映。这样做一般会出现一?04错误信息Q它?
CZ能找到该文gQ但几乎没提供更多有用的信息。另一斚wQ可以试一下在www.microsoft.com、www.ibm.com
处或者特别是在www.bea.com
处输出未知的文g名。这是会得出有用的消息,q些消息提供可选择的位|,以便查找感兴的面。提供这h用的错误面对于Web应用来说是很有h值得?
事实上rm-error-page子元素)。由form-login-pagel出的HTML表单必须h一个j_security_check?
ACTION属性、一个名为j_username的用户名文本字段以及一个名为j_password的口令字Dc?
例如Q程序清?-19指示服务器用基于表单的验证。Web应用的顶层目录中的一个名为login.jsp的页面将攉用户名和口oQƈ且失败的登陆?
q同目录中名ؓlogin-error.jsp的页面报告?E序清单5-19 web.xmlQ说明login-config的摘录)
9.2 限制对Web资源的访?现在Q可以指C服务器使用何种验证Ҏ了?了不P"你说道,"除非我能指定一个来收到保护? URLQ否则没有多大用处?没错。指些URLq说明他们应该得CU保护正是security-constriaint元素的用途。此元素? web.xml中应该出现在login-config的紧前面。它包含是个可能的子元素Q分别是Qweb-resource-collection? auth-constraint、user-data-constraint和display-name。下面各节对它们进行介l?l web-resource-collection 此元素确定应该保护的资源。所有security-constraint元素都必d含至一个web-resource-collectionV此? 素由一个给ZQ意标识名U的web-resource-name元素、一个确定应该保护的URL的url-pattern元素、一个指出此保护所适用? HTTP命oQGET、POST{,~省为所有方法)的http-method元素和一个提供资料的可选description元素l成。例如,下面? Web-resource-collection(在security-constratint元素内)指出Web应用的proprietary目录? 所有文档应该受C护?重要的是应该注意刎ͼurl-pattern仅适用于直接访问这些资源的客户机。特别是Q它不适合于通过MVC体系l构利用 RequestDispatcher来访问的面Q或者不适合于利用类似jsp:forward的手D|讉K的页面。这U不匀U如果利用得当的话很有好 处。例如,servlet可利用MVC体系l构查找数据Q把它放到bean中,发送请求到从bean中提取数据的JSP面q显C它。我们希望保证决不直 接访问受保护的JSP面Q而只是通过建立该页面将使用的bean的servlet来访问它。url-pattern和auth-contraint元素 可通过声明不允怓Q何用L接访问JSP面来提供这U保证。但是,q种不匀U的行ؓ可能让开发h员放松警惕,使他们偶然对应受保护的资源提供不受限制的 讉K?l auth-constraint 管web-resource-collention元素质出了哪些URL应该受到保护Q但是auth-constraint元素却指出哪些用户应该具? 受保护资源的讉K权。此元素应该包含一个或多个标识h讉K权限的用L别role- name元素Q以及包含(可选)一个描q角色的description元素。例如,下面web.xml中的security-constraint元素? 门规定只有指定ؓAdministrator或Big KahunaQ或两者)的用户具有指定资源的讉K权? 重要的是认识刎ͼ到此为止Q这个过E的可移植部分结束了。服务器怎样定哪些用户处于M角色以及它怎样存放用户的口令,完全有赖于具体的pȝ? 例如QTomcat使用install_dir/conf/tomcat-users.xml用户名与角色名和口令相兌Q正如下面例子中所C,它指? 用户joeQ口令bigshotQ和janeQ口令enajQ属于administrator和kahuna角色?l user-data-constraint q个可选的元素指出在访问相兌源时使用M传输层保护。它必须包含一个transport-guarantee子元素(合法gؓNONE? INTEGRAL或CONFIDENTIALQ,q且可选地包含一个description元素。transport-guarantee为NONE值将 Ҏ用的通讯协议不加限制。INTEGRALDC数据必M一U防止截取它的h阅读它的方式传送。虽然原理上Qƈ且在未来的HTTP版本中)Q在 INTEGRAL和CONFIDENTIAL之间可能会有差别Q但在当前实践中Q他们都只是单地要求用SSL。例如,下面指示服务器只允许对相兌源做 HTTPSq接Q?l display-name security-constraint的这个很用的子元素给予可能由GUI工具使用的安全约束项一个名U?9.3 分配角色? q今为止Q讨论已l集中到完全由容器(服务器)处理的安全问题之上了。但servlet以及JSP面也能够处理它们自q安全问题? 例如Q容器可能允许用户从bigwig或bigcheese角色讉K一个显CZh员额外紧贴的面Q但只允许bigwig用户修改此页面的参数。完成这 U更l致的控制的一U常见方法是调用HttpServletRequset的isUserInRoleҎQƈ据此修改讉K?Servlet? security-role-ref子元素提供出现在服务器专用口令文件中的安全角色名的一个别名。例如,假如~写了一个调? request.isUserInRoleQ?boss"Q的servletQ但后来该servlet被用在了一个其口o文g调用角色manager而不 是boss的服务器中。下面的E序D该servlet能够使用q两个名UC的Q何一个? 也可以在web-app内利用security-role元素提供出现在role-name元素中的所有安全角色的一个全局列表。分别地生命角色佉KU? IDEҎ处理安全信息?10 控制会话时 如果某个会话在一定的旉内未被访问,服务器可把它扔掉以节U内存。可利用HttpSession的setMaxInactiveIntervalҎ? 接设|个别会话对象的时倹{如果不采用q种ҎQ则~省的超时值由具体的服务器军_。但可利用session-config和session- timeout元素来给Z个适用于所有服务器的明的时倹{超时值的单位为分钟,因此Q下面的例子讄~省会话时gؓ三个时Q?80分钟Q?/p>
11 Web应用的文档化 来多的开发环境开始提供servlet和JSP的直接支持。例子有Borland Jbuilder Enterprise Edition、Macromedia UltraDev、Allaire JRun StudioQ写此文Ӟ已被Macromedia收购Q以及IBM VisuaAge for Java{? 大量的web.xml元素不仅是ؓ服务器设计的Q而且q是为可视开发环境设计的。它们包括icon、display-name和discription {? 可回忆一下,在web.xml内以适当地次序声明web-app子元素很重要。不q,q里只要Cicon、display-name? description是web.xml的web-app元素内的前三个合法元素即可?l icon icon元素指出GUI工具可用来代表Web应用的一个和两个囑փ文g。可利用small-icon元素指定一q?6 x 16的GIF或JPEG囑փQ用large-icon元素指定一q?2 x 32的图像。下面D一个例子: l display-name display-name元素提供GUI工具可能会用来标记此Web应用的一个名U。下面是个例子?l description description元素提供解释性文本,如下所C: 12 兌文g与MIMEcd 服务器一般都h一U让Web站点理员将文g扩展名与媒体相关联的Ҏ。例如,会自动l予名ؓmom.jpg的文件一个image/jpeg? MIME cd。但是,假如你的Web应用h几个不寻常的文gQ你希望保证它们在发送到客户机时分配为某UMIMEcd。mime-mapping元素Q具? extension和mime-type子元素)可提供这U保证。例如,下面的代码指C服务器application/x-fubar的MIMEcd? 配给所有以.fool尾的文件? 或许Q你的Web应用希望重蝲QoverrideQ标准的映射。例如,下面的代码将告诉服务器在发送到客户机时指定.ps文g作ؓU文? Qtext/plainQ而不是作为PostScriptQapplication/postscriptQ?/p>
13 定位TLD JSP
taglib元素h一个必要的uri属性,它给Z个TLDQTag Library
DescriptorQ文件相对于Web应用的根的位|。TLD文g的实际名U在发布新的标签库版本时可能会改变,但我们希望避免更Ҏ有现有JSP?
面。此外,可能q希望用保持taglib元素的简l性的一个简短的uri。这是部v描述W文件的taglib元素z场的所在了。Taglib包含?
个子元素Qtaglib-uri和taglib-location。taglib-uri元素应该与用于JSP
taglib元素的uri属性的东西相匹配。Taglib-location元素l出TLD文g的实际位|。例如,假如你将文gchart-tags-
1.3beta.tld攑֜WebApp/WEB-INF/tlds中。现在,假如web.xml在web-app元素内包含下列内宏V?
l出q个说明后,JSP面可通过下面的简化Ş式用标{ֺ?14 指定应用事g监听E序
应用事g监听器程序是建立或修改servlet环境或会话对象时通知的类。它们是servlet规范的版?.3中的新内宏V这里只单地说明用来?
Web应用注册一个监听程序的web.xml的用法?
注册一个监听程序涉及在web.xml的web-app元素内放|一个listener元素。在listener元素内,listener-class?
素列出监听程序的完整的限定类名,如下所C: <listener>
虽然listener元素的结构很单,但请不要忘记Q必L地l出web-app元素内的子元素的ơ序。listener元素位于所有的
servlet
元素之前以及所有filter-mapping元素之后。此外,因ؓ应用生存期监听程序是serlvet规范?.3版本中的新内容,所以必M?
web.xml DTD?.3版本Q而不?.2版本?
例如Q程序清?-20l出一个名为ContextReporter的简单的监听E序Q只要Web应用的Servlet-Context建立Q如装蝲
Web应用Q或消除Q如服务器关闭)Ӟ它就在标准输Z昄一条消息。程序清?-21l出此监听程序注册所需要的web.xml文g的一部分?
E序清单5-20 ContextReporterjava E序清单5-21 web.xmlQ声明一个监听程序的摘录Q?15 J2EE元素
本节描述用作J2EE环境l成部分的Web应用的web.xml元素。这里将提供一个简明的介绍Q详l内容可以参?
http://java.sun.com/j2ee/j2ee-1_3-fr-spec.pdf的Java 2 Plantform
Enterprise Edition版本1.3规范的第5章?l distributable distributable
元素指出QWeb应用是以q样的方式编E的Q即Q支持集的服务器可安全地在多个服务器上分布Web应用。例如,一个可分布的应用必d使用
Serializable对象作ؓ其HttpSession对象的属性,而且必须避免用实例变量(字段Q来实现持箋性。distributable元素?
接出现在discription元素之后Qƈ且不包含子元素或数据Q它只是一个如下的标志?l resource-env-ref resource
-env-ref元素声明一个与某个资源有关的管理对象。此元素׃个可选的description元素、一个resource-env-ref-
name元素Q一个相对于java:comp/env环境的JNDI名)以及一个resource-env-type元素Q指定资源类型的完全限定?
c)Q如下所C: l env-entry env
-entry元素声明Web应用的环境项。它׃个可选的description元素、一个env-entry-name元素Q一个相对于java:
comp/env环境JNDI名)、一个env-entry-value元素Q项|以及一个env-entry-type元素Qjava.langE序
包中一个类型的完全限定cdQjava.lang.Boolean、java.lang.String{)l成。下面是一个例子: l ejb-ref
ejb
-ref元素声明对一个EJB的主目录的应用。它׃个可选的description元素、一个ejb-ref-name元素Q相对于java:
comp/env的EJB应用Q、一个ejb-ref-type元素Qbean的类型,Entity或SessionQ、一个home元素Qbean的主
目录接口的完全限定名Q、一个remote元素Qbean的远E接口的完全限定名)以及一个可选的ejb-link元素Q当前bean链接的另一?
bean的名Uͼl成?l ejb-local-ref
ejb-local-ref元素声明一个EJB的本C目录的引用。除了用local-home代替home外,此元素具有与ejb-ref元素相同的属
性ƈ以相同的方式使用?/p>
URL模式q可以包含通配W。例如,下面的小E序指示服务器发送所有以Web应用的URL前缀开始,?.aspl束的请求到名ؓBashMS的servlet?/p>
3.3 命名JSP面
因ؓJSP面要{换成sevletQ自然希望就像命名servlet一样命名JSP面。毕竟,JSP
面可能会从初始化参数、安全设|或定制的URL中受益,正如普通的serlvet那样。虽然JSP面的后台实际上是servletq句话是正确的,?
存在一个关键的猜疑Q即Q你不知道JSP面的实际类名(因ؓpȝ自己挑选这个名字)。因此,Z命名JSP面Q可jsp-file元素替换?
servlet-calss元素Q如下所C:
命名JSP面的原因与命名servlet的原因完全相同:即ؓ了提供一个与定制讄Q如Q初始化参数和安全设|)一起用的名称Qƈ且,以便能更
Ҏz?
JSP面的URLQ比方说Q以便多个URL通过相同面得以处理Q或者从URL中去?jsp扩展名)。但是,在设|初始化参数Ӟ应该注意QJSP?
面是利用jspInitҎQ而不是initҎd初始化参数的?
例如Q程序清?-3l出一个名为TestPage.jsp的简单JSP
面Q它的工作只是打印出用来Ȁzd的URL的本地部分。TestPage.jsp攄在deployDemo应用的顶层。程序清?-4l出了用来分?
一个注册名PageNameQ然后将此注册名与http://host/webAppPrefix/UrlTest2/anything
形式的URL相关联的web.xml文gQ即QdeployDemo/WEB-INF/web.xmlQ的一部分?
E序清单5-3 TestPage.jsp
E序清单5-4 web.xmlQ说明JSP命名的摘录Q?/p>
4 止Ȁzdservlet
对servlet或JSP面建立定制URL的一个原因是Q这样做可以注册?
initQservletQ或jspInitQJSP面Q方法中d得初始化参数。但是,初始化参数只在是利用定制URL模式或注册名讉K
servlet或JSP面时可以用,用缺省URL http://host/webAppPrefix/servlet/ServletName
讉K时不能用。因此,你可能会希望关闭~省URLQ这样就不会有h意外地调用初始化servlet了。这个过E有时称为禁止激zdservletQ因?
多数服务器具有一个用~省的servlet URL注册的标准servletQƈȀzȝ省的URL应用的实际servlet?
有两U禁止此~省URL的主要方法:
l 在每个Web应用中重新映?servlet/模式?
l 全局关闭Ȁzdservlet?
?
要的是应该注意到Q虽焉新映每个Web应用中的/servlet/模式比彻底禁止激zservlet所做的工作更多Q但重新映射可以用一U完全可UL
的方式来完成。相反,全局止Ȁzdservlet完全是针对具体机器的Q事实上有的服务器(如ServletExecQ没有这L选择。下面的讨论Ҏ
个Web应用重新映射/servlet/ URL模式的策略。后面提供在Tomcat中全局止Ȁzdservlet的详l内宏V?
4.1 重新映射/servlet/URL模式
?
一个特定的Web应用中禁止以http://host/webAppPrefix/servlet/
开始的URL的处理非常简单。所需做的事情是建立一个错误消息servletQƈ使用前一节讨论的url-pattern元素所有匹配请求{向该
servlet。只要简单地使用Q?
<url-pattern>/servlet/*</url-pattern>
作ؓservlet-mapping元素中的模式卛_?
例如Q程序清?-5l出了将SorryServlet servletQ程序清?-6Q与所有以http://host/webAppPrefix/servlet/ 开头的URL相关联的部v描述W文件的一部分?
E序清单5-5 web.xmlQ说明JSP命名的摘录Q?/p>
E序清单5-6 SorryServlet.java
4.2 全局止ȀzdQTomcat
Tomcat 4中用来关闭缺省URL的方法与Tomcat 3中所用的很不相同。下面介l这两种ҎQ?
1Q禁止激zdQ?Tomcat 4
Tomcat
4用与前面相同的方法关闭激zdservletQ即利用web.xml中的url-mapping元素q行关闭。不同之处在于Tomcat使用了放?
install_dir/conf中的一个服务器专用的全局web.xml文gQ而前面用的是存攑֜每个Web应用的WEB-INF目录中的标准
web.xml文g?
因此Qؓ了在Tomcat 4中关闭激zdservletQ只需在install_dir/conf/web.xml中简单地注释?servlet/* URL映射即可,如下所C:
再次提醒Q应该注意这个项是位于存攑֜install_dir/conf的Tomcat专用的web.xml文g中的Q此文g不是存放在每个Web应用的WEB-INF目录中的标准web.xml?
2Q禁止激zdQTomcat3
在Apache
Tomcat的版?中,通过在install_dir/conf/server.xml中注释出InvokerInterceptor全局止~省
servlet URL。例如,下面是禁止用缺省servlet URL的server.xml文g的一部分?/p>
5 初始化和预装载servlet与JSP面
q里讨论控制servlet和JSP面的启动行为的Ҏ。特别是Q说明了怎样分配初始化参C及怎样更改服务器生存期中装载servlet和JSP面的时刅R?
5.1 分配servlet初始化参?
?
用init-param元素向servlet提供初始化参敎ͼinit-param元素hparam-name和param-value子元素。例如,
在下面的例子中,如果initServlet
servlet是利用它的注册名QInitTestQ访问的Q它能够从其方法中调用getServletConfig().
getInitParameter("param1")获得"Value
1"Q调用getServletConfig().getInitParameter("param2")获得"2"?/p>
在涉及初始化参数Ӟ有几炚w要注意:
l q回倹{GetInitParameter的返回值L一个String。因此,在前一个例子中Q可对param2使用Integer.parseInt获得一个int?
l JSP中的初始化。JSP面使用jspInit而不是init。JSP面q需要用jsp-file元素代替servlet-class?
l
~省URL。初始化参数只在通过它们的注册名或与它们注册名相关的定制URL模式讉KServlet时可以用。因此,在这个例子中Qparam1?
param2初始化参数将能够在用URL http://host/webAppPrefix/servlet/InitTest
时可用,但在使用URL http://host/webAppPrefix/servlet/myPackage.InitServlet
时不能用?
例如Q程序清?-7l出一个名为InitServlet的简单servletQ它使用initҎ讄firstName和emailAddress字段。程序清?-8l出分配名称InitTestlservlet的web.xml文g?
E序清单5-7 InitServlet.java
E序清单5-8 web.xmlQ说明初始化参数的摘录)
转自http://elevenet.javaeye.com/blog/67862
q篇文章q是不错的,有些老,有些地址q是直接译的。不是很准确?/strong>
关键? J2EE
该方法的实现原理很简单,是用户讉KWebpȝ时记录每个用L信息Q然后进行对照,q根据设定的{略(比如Q?U钟h面10?判断用户是否属于恶意h?/p>
我们的入侉|程序应该放到所有Java WebE序的执行前Q也卌发现用户是恶意刷新就不再l箋执行Java Web中的其它部分内容Q否则就会失M意义。这需要以插g的方式把入R的E序|入Java Web应用中,使得每次用户讉KJava WebQ都先要到这个入侉|程序中报一ơ到Q符合规则才能放行?/p>
Java Web应用大致分ؓ两种Q一U纯JSP(+Java Bean)方式Q一U是Z框架(如Struts、EasyJWeb{?的。第一U方式的Java Web可以通过Java Servlet中的Filter接口实现Q也卛_C个Filter接口Q在其doFilterҎ中插入入侉|程序,然后再web.xml中作单的 配置卛_。在Z框架的Web应用中,׃所有应用都有一个入口,因此可以把入侉|的E序直接插入框架入口引擎中,使框架本w支持入侉|功能。当? 也可以通过实现Filter接口来实现?/p>
在EasyJWeb框架中,已经|入了简单入侉|的E序Q因此,q里我们以EasyJWeb框架Z,介绍具体的实现方法及源码Q完整的代码可以在EasyJWeb源码中找到?/p>
在基于EasyJWeb的Java Web应用??a >http://www.easyjf.com/bbs/
)Q默认情况下你只要连l刷新页面次数过多,即会弹出如下的错误: EasyJWeb框架友情提示!:-):
您对面的刷新太?L?0U后再刷新页面!
详细h?a target="_blank">http://www.easyjf.com
二、用戯问信息记录UserConnect.javac?/strong>
q个cL一个简单的Java BeanQ主要代表用L信息Q包括用户名、IP、第一ơ访问时间、最后登录时间、登录次数、用L态等。全?br />
代码如下Q?br />
package com.easyjf.web;
import java.util.Date;
/**
*
*
Title:用户验证信息
Description:记录用户d信息,判断用户d情况
Copyright: Copyright (c) 2006
Company: www.easyjf.com
>public void setFirstFailureTime(Date firstFailureTime) {
this.firstFailureTime = firstFailureTime;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
三、监控线EUserConnectManage.javac?/strong>
q是入R的核心部分Q主要实现具体的入R、记录、判断用户信息、在U用Lh{功能,q提供其它应用程序用本lg的调用接口?br />
package com.easyjf.web;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
/**
*
*
Title:用户入R信?/p>
*
Description:用于判断用户h情况查,默认?0U钟之内q箋q接10ơؓ时
Copyright: Copyright (c) 2006
Company: www.easyjf.com
>public static void reset(String ip,String userName)//重置用户信息
{
Date now=new Date();
String key=ip+":"+userName;
UserConnect auth=(UserConnect)users.get(key);
if(auth==null)//把用户当前的讉K信息加入到users容器?br />
{
auth=new UserConnect();
auth.setIp(ip);
auth.setUserName(userName);
auth.setFailureTimes(0);
auth.setFirstFailureTime(now);
users.put(key,auth);
}
else
{
auth.setFailureTimes(0);
auth.setFirstFailureTime(now);
}
}
public static void remove(String ip,String userName)//删除用户在容器中的记?br />
{
String key=ip+":"+userName;
users.remove(key);
}
public static void clear()//清空容器中内?br />
{
if(!users.isEmpty())users.clear();
}
public static long getMaxFailureInterval() {
return maxFailureInterval;
}
public static void setMaxFailureInterval(long maxFailureInterval) {
UserConnectManage.maxFailureInterval = maxFailureInterval;
}
public static int getMaxFailureTimes() {
return maxFailureTimes;
}
public static void setMaxFailureTimes(int maxFailureTimes) {
UserConnectManage.maxFailureTimes = maxFailureTimes;
}
public static int getMaxOnlineUser() {
return maxOnlineUser;
}
public static void setMaxOnlineUser(int maxOnlineUser) {
UserConnectManage.maxOnlineUser = maxOnlineUser;
}
public static long getWaitInterval() {
return waitInterval;
}
public static void setWaitInterval(long waitInterval) {
UserConnectManage.waitInterval = waitInterval;
}
四、调用接?/strong>
在需要进入R判断的地方Q直接用UserConnectManagecM的checkLoginValidateҎ卛_。如EasyJWeb的核心Servlet
com.easyjf.web.ActionServlet中调用UserConnectManage的代码:
if(!UserConnectManage.checkLoginValidate(request.getRemoteAddr(),"guest"))
{
info(request,response,new Exception("您对面的刷新太?L?+UserConnectManage.getWaitInterval()/1000+"U?br />
后再h面Q?));
return;
}
五、ȝ
当然Q这里提供的Ҏ只是一个简单的实现CZQ由于上面的用户信息是直接保存在内存中,若ƈ发用户很大的时候的代码的占用,可以考虑引入数据库来记录?
L讉K信息Q当然相应的执行效率肯定用降低。上面介l的实现中,入R判断的{略也只有用戯问次数及旉间隔两个元素Q您q可以根据你的实现情况增
加其它的元素?br />
׃水^有限Q该设计上有N不合理或者需要改q的地方Q恳请大家指正!
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=657199
览器安全模型规定,XMLHttpRequest、框ӞframeQ等只能在一个域中通信。从安全角度考虑Q这个规定很合理Q但是,也确实给分布式(面向服务、搭等{本周提到的概念QWeb开发带来了ȝ?/p>
Z实现跨域通信Q通常的解x案有3U:
本地代理Q?/strong> FlashQ?/strong> Script标签Q?/strong> 也就是说Q客L可以使用一个用于命名jsonp的查询参数来军_可以获取的数据。最单的情况下,如果jsonp参数为空Q则q回的数据就是被括在圆括号中的JSON?/p>
下面Q我们就?a title="http://del.icio.us/help/json" target="_blank">del.icio.us的JSON APIZQ来说明JSONP的原理。该API有一?#8220;script tag”变量Q即Q可以将下面的URL作ؓscript标签的src属性|用以加蝲del.icio.usq个API提供的数据。——译者注Q如下所C: http://del.icio.us/feeds/json/bob/mochikit+interpreter: 如果用JSONP的方式来表示Q那么与此具有相同语义的URL应该是这LQ?/p>
http://del.icio.us/feeds/json/bob/mochikit+interpreter? 单纯看这个URLg没有什么,但我们可以要求服务器在数据有效时l出通知。因此,我可以编写一个用于跟t数据的系l: Ҏ以上假设Q用于获取数据的URL应该如下所C: 可见Q由于用圆括号括住了返回的数据Q这q当于把一个JSONPh转化成了一ơ函数调用,或者得C一个纯_的JSON直接量。服务器所要配合做的,是在JSON数据的开头添加一段文本Q即回调函数的名U。——译者注QƈJSON数据攑֜括号中! 当然Q接下来最好是使用Mochikit、Dojo{框架来抽象JSONPQ从而让自己省去动手~写DOM以插入script标签的麻烦?/p>
没错QJSONP只是解决了标准化的问题。假如远E主机想通过script标签向页面中注入恶意代码Q而不是返回JSON数据Q那么页面安全可能会
随时受到威胁。不q,一旦实CJSONPQ那么对开发h员来说肯定是一件省时省力的大好事,在此基础上各U一般化的抽象、教E及文档也会应运而生的?/p>
注:~写?JSONP ?Bob Ippolito 在一名?“Remote JSON - JSONP” 的文章中提出。但许多支持?JSONP 技术实现跨域通信的厂商没有称其ؓ JSONP。例如,雅虎公司qq种技术ؓ “JSON with callbacks”。另外,原文发表?005q?2?日?/p>
大概是说通过script的src来实现跨域,通过l编码的json在url中传递数据? @vampire 你的理解是对的,看来我的译你能看明白,呵呵。用JSONP技术时Q一般是由JS在客L面中动态插入script标签Q将其src属性设|?
为带参数的URL。当面加蝲Ӟ前述URL会将参数通过GETh发送到相应服务器端应用E序Q由URL表示Q,服务器根据参数返回数据——注意,q个
数据格式是JSONQƈ且(注意Q被包含在一个函数调用中Q例如:callback({json_data})。这P在客L面中存在预定义?
callback(data)函数的定义时Q服务器q回的函数调用就会立x行,由客L的函数对数据q行操作?/p>
实际的例子有很多Q如Yahoo Search、Google Base、Flickr Search、Amazon
Search{等。要学习和用这些站Ҏ供的支持JSONP的APIQ一般要注意两点Q一是有的站点要求注册密钥(使用时必L到参CQ,二是要注?
参数的用方法。例如,有的API要求使用预定义的回调函数Q而有的API则允怋用者自己定义回调函数?/p>
下面是向Flickr SearchhJSONP响应的URLQ其中用了预定义的回调函数jsonFlickrApiQ参C不必l出Q: http://api.flickr.com/services/rest/ 下面q个URL发送到Google BaseQ它允许开发h员用自己定义的回调函数Q?/p>
http://www.google.com/base/feeds/snippets 把前面的URL攑ֈ览器地址栏中Q回车,卛_看到l果?/p>
需要一些硬件设施(没有服务器的客户端无法运行)Qƈ且带宽和潜伏旉也要加倍(q程服务器-代理服务器-客户端)?/p>
q程L中需要部|一个crossdomain.xml文gQ而且QFlash作ؓ一门专有技术,其前途尚不明朗;换句话说Q开发h员很可能要学习一U目标不定的编E语a?/p>
无法切知道内容是否有效Q没有标准的实现ҎQ又可能被认为是一U?#8220;安全风险”?/p>
在此Q我使用一U新技术,也是一U独立于标准的方法,即通过script标签来跨域获取数据,名ؓJSON with PaddingQ或者就?strong>JSONP。JSONP的原理很单,但需要服务器端给予相应配合。大致来_JSONP的实现思\是在客L~程时作好用JSON数据的准备,然后再通过圆括号将q些数据括v来以创徏一条有效的JavaScript语句Q可能是一ơ有效的函数调用Q?
jsonp=if(typeof(Delicious)%3D%3D%27undefined%27)
Delicious%3D%7B%7D%3BDelicious.posts%3D
http://del.icio.us/feeds/json/bob/mochikit+interpreter?jsonp=delicious_callbacks%5B12345%5D
留言(9)
是不是说面上的js生带有不同参数的jsonpQ通过src传递给服务端,服务端根据该jsonpq回相应数据Q有点不太明白,能否提供一个实例?谢谢
?method=flickr.photos.search
&api_key=85618ad7d326d8ef93c6bee9ed32706f
&per_page=5&format=json&text=china
?q=jquery
&alt=json-in-script
&callback=customCallback
]]>
Z么会q样QHTTP虽不是什么新事物Q但Z使用它的方式却五花八门。其中有些做法符合Web设计者的初衷Q但许多q如此。要Z的HTTP 应用Q无论是面向人类、还是计机、或同时面向q两者用的Q应用REST原则Q意味着你要恰好反过来:量“正确?#8221;使用WebQ或者说按符合REST 的方式用WebQ倘若你不喜欢用对或错来评判的话)。对许多人来_q的是一U崭新的方式Ҏ?/p>
我经常在文章里作同样的声明:REST、Web和HTTP是不同的事物QREST可以用多U不同技术来实现Q而HTTP只是一U恰好符合REST? 构风格的具体架构。所以,其实我应该小心区?#8220;REST”?#8220;REST式HTTP”q两个概늚。但我没有这么做Q在本文剩余部分Q我们姑且认为它们是? 同的事物?/p>
跟Q何新的方式方法一P发掘一些共同的模式是有益的。在本系列的W一?a >W二? 文章中,我已l讲qC一些基——比如集合资源的概念、将计算l果转换源本w、以及用聚合QsyndicationQ来模仿事g。后l文章将q一步讲 q这些及其他模式。不q在本文中,我想主要说说反模式(anti-patternsQ——即那些力求W合REST式HTTP、但未能成功而造成问题的典? 做法?/p>
首先我们来看看我发掘了哪些反模式Q?/p>
下面我们来逐个详细说明?
在许多h看来QREST仅仅意味着用HTTP暴露一些应用功能。HTTP GET是最重要的基本操作(operation Q(严格地讲Q用“动词QverbQ?#8221;?#8220;ҎQmethodQ?#8221;q样的术语比较好Q。GETҎ应当用于获取由URI标识的资源的一个表C? QrepresentationQ,而许多(即便谈不上所有)现有的HTTP库和服务器编EAPI不是URI视ؓ一U资源标识符Qresource identifierQ,而是之视ؓ一U传递参数的便利手段。这D了以下这UURIs的出玎ͼ
http://example.com/some-api?method=deleteCustomer&id=1234
实际上,你无法根据构成URI的字W获知关于给定系l的“REST性(RESTfulnessQ?#8221;的Q何信息,不过对于上面那个URIQ我们可以判 断该 GET操作不是“安全的(safeQ?#8221;——也是_调用者很可能要ؓl果Q删除一个客P负责Q尽规范里说在q种情况下用GETҎ是错误的?/p>
q种做法唯一有利的方面在于它~程hҎQ而且在浏览器中调试也单——你只要把URI_脓到浏览器地址栏里、然后调整一?#8220;参数”p了。这U反模式主要存在以下问题Q?
注意Q符合这一反模式的APIs没准最l?a title="Web Things, by Mark Baker » » Accidentally RESTful">yW合REST原则。这里有个例子:
http://example.com/some-api?method=findCustomer&id=1234
q个URI是标识操作及其参数呢Q还是标识一个资源呢Q两U情况都有可能:它可以是一个完全合法的、可加入书签的URIQ对它做GET操作也许?#8220; 安全? ”Q它也许会根据Accept报头q回不同的格式,q支持复杂的~存机制。在很多情况下,q将是偶然的。APIl常在刚开始时采用q种方式来暴露一?#8220;? ”接口Q但当开发者要增添“?#8221;功能时就有问题了Q因Z无法通过对上qURI做PUT操作来更C个客户——开发者得构造另一个URIQ?/p>
q一反模式跟前一个颇为相|只不q这里用的是POSTҎ而已。POST除了携带一个URIQ还携带一个实体主体(entity bodyQ。一个典型的场景是:单个URI作ؓPOSTh的目标、通过发送不同的消息来表达不同的意图。实际上QSOAP 1.1 Web服务是q样做的Q它把HTTP当作一U?#8220;传输协议”来用。服务器ҎSOAP消息Q可能还包括一些WS-Addressing SOAP报头Q决定做什么?/p>
可能有h认ؓ“全部采用POST”?#8220;全部采用GET”存在的问题完全一P只是它更隄一些,而且不能利用~存Q甚臌偶尔的机会都没有Q,且无法支持书{。事实上Q它q不是违反了哪条REST原则Q而是Ҏ忽视了REST原则?
即你按各个动词的原本意图来使用它们Q你仍可以轻易禁止缓存机制。最单的做法是在你的HTTP响应里增加这样一个报_
Cache-control: no-cache
q样可以止~存机制发挥作用。当Ӟq也许正是你惌做的Q然而通常q只是你的Web框架规定的一个缺省设|。不q,寚w效的~存与再验证
Qcaching and re-validationQ的支持Q是采用REST式HTTP的诸多关键优点之一。Sam
Ruby表示Q在判断是否W合REST原则时的一个关键问题就?#8220;你支持ETag?/a>”Q?
QETag是HTTP
1.1里引入的一U机Ӟ它允许客L通过加密的校验和来验证一个被~存的表C是否仍然有效)。要生成正确的报_最单的做法是把这个Q务交l一?#8220;
知道”怎样做的基础设施——例如通过在Web服务器(比如Apache HTTPDQ的目录里生成一个文件?/p>
当然Q这也要涉及到客L一方:你在Z个REST式服务实现程序客LӞ你应充分利用现有的缓存机Ӟ以免每次都重新获取表C。例如,服务器也
许已l发Z息:初次q回的表C在600U内都可被认为是“新的”Q比方说因ؓ后端pȝ?0分钟才轮询一ơ)。这L话,短时间内重复h同一信息完
全没必要了。在客户端设|一个代理缓存(比如SquidQ也许比自行构徏相应逻辑更好?/p>
HTTP的缓存机制强大而复杂;Mark Nottingham的《缓存指南(Cache TutorialQ?/a>是一个很好的指南? 你要是发明自q报头、格式或协议Q那׃定程度上破坏了自描述性约束。极端地Ԍ所有没有被某个标准化组l官Ҏ准化的东襉Kq反此约束,因而可
被认为符合本反模式。在实践中,你应努力做到可能遵循标准,q懂?#8220;某些协定可能只在一个较的领域Q比方说Q你的服务和客户端是专门针对它开发的Q中
适用” 的道理?/p>
自从“四hl(Gang of FourQ?#8221;出版?a title="Design Patterns - Wikipedia, the free encyclopedia">书籍忽视响应代码
ȝ
但愿本文能有助于你在开始首个REST目旉免落入这些常见的陷阱?
非常感谢Javier Botana和Burkhard NeppertҎ文初E的反馈?
Stefan Tilkov是InfoQ SOAC的首席编辑,以及位于德国/瑞士?a >innoQ公司的合伙h、首席顾问和主要的REST狂热M者?/p>
大部分对REST的介l是以其正式的定义和背景作ؓ开场的。但q儿且先按下不表Q我先提Z个简单扼要的定义QREST定义了应该如何正地使用 Q这和大多数人的实际使用方式有很大不同)Web标准Q例如HTTP和URI。如果你在设计应用程序时能坚持REST原则Q那预C着你将会得C个? 了优质Web架构Q这让你受益)的系l。MQ五条关键原则列丑֦下:
下面让我们进一步审视这些原则?/p>
在这里我使用?#8220;事物”来代替更正式准确的术?#8220;资源”Q因Z条如此简单的原则Q不应该被没在术语当中。思考一下h们构建的pȝQ通常会找C pd值得被标识的关键抽象。每个事物都应该是可标识的,都应该拥有一个明昄ID——在Web中,代表ID的统一概念是:URI。URI构成了一个全局? 名空_使用URI标识你的关键资源意味着它们获得了一个唯一、全局的ID?
对事物用一致的命名规则Qnaming schemeQ最主要的好处就是你不需要提q规则——而是依靠某个已被定义Q在全球范围中几乎完运行,q且能被l大多数人所理解的规则。想一下你 构徏的上一个应用(假设它不是采用RESTful方式构徏的)中的L一个高U对象(high-level objectQ,那就很有可能看到许多从用唯一标识中受益的用例。比如,如果你的应用中包含一个对֮的抽象,那么我可以相当肯定,用户会希望将一个指 向某个顾客的链接Q能通过电子邮g发送到同事那里Q或者加入到览器的书签中,甚至写到U怸。更透彻地讲Q如果在一个类gAmazon.com的在U商 城中Q没有用唯一的IDQ一个URIQ标识它的每一件商品,可想而知q将是多么可怕的业务决策?/p>
当面对这个原则时Q许多h惊讶于这是否意味着需要直接向外界暴露数据库记录(或者数据库记录IDQ——自从多q以来面向对象的实践告诫我们Q要持 久化的信息作为实现细节隐藏v来之后,哪怕是刚有Ҏ法都怼使h惊恐。但是这条原则与隐藏实现l节两者之间ƈ没有M冲突Q通常Q值得被URI标识的事 物——资源——要比数据库记录抽象的多。例如,一个定单资源可以由定单V地址以及许多其它斚wQ可能不希望作ؓ单独标识的资源暴露出来)l成。标识所? 值得标识的事物,领会q个观念可以q一步引g创造出在传l的应用E序设计中不常见的资源:一个流E或者流E步骤、一ơ销售、一ơ谈判、一份报仯求—? q都是应该被标识的事物的CZ。同Pq也会导致创建比非RESTful设计更多的持久化实体?
下面是一些你可能惛_的URI的例子:
http://example.com/customers/1234
http://example.com/orders/2007/10/776654
http://example.com/products/4554
http://example.com/processes/salary-increase-234
正如我选择了创Z于阅ȝURI——这是个有用的观点,管不是RESTful设计所必须的——应该能十分Ҏ地推出URI的含义:它们明显地标识着单一“数据?#8221;。但是再往下看Q?/p>
http://example.com/orders/2007/11
http://example.com/products?color=green
首先Q这两个URI看v来与之前的稍有不同——毕竟,它们不是对一件事物的标识Q而是对一cM物集合的标识Q假定第一个URI标识了所有在2007q?1月䆾提交的定单,W二个则是绿颜色产品的集合)。但是这些集合自w也是事物(资源Q,也应该被标识?/p>
注意Q用唯一、全局l一的命名规则的好处Q既适用于浏览器中的Web应用Q也适用于机ҎQmachine-to-machineQm2mQ通信?/p>
来对W一个原则做下ȝQ用URI标识所有值得标识的事物,特别是应用中提供的所?#8220;高”资源Q无些资源代表单一数据V数据项集合、虚拟亦或实际的对象q是计算l果{?/p>
接下来要讨论的原则有一个有点o人害怕的正式描述Q?#8220;媒体被当作应用状态引擎(Hypermedia as the engine of application stateQ?#8221;Q有时简写ؓHATEOAS。(严格地说Q这不是我说的。)q个描述的核心是媒?/strong>概念Q换句话_?strong>链接的思想。链接是我们在HTML中常见的概念Q但是它的用处绝不局限于此(用于Z|络览Q。考虑一下下面这个虚构的XML片段Q?
23
如果你观察文档中product和customer的链接,可以很Ҏ地想象到Q应用程序(已经索过文档Q如?#8220;跟随”链接索更多的信息。当Ӟ如果使用一个遵守专用命名规范的?#8220;id”属性作为链接,也是可行的—?strong>但是仅限于应用环境之?/strong>。用URI表示链接的优雅之处在于,链接可以指向׃同应用、不同服务器甚至位于另一个大陆上的不同公司提供的资源——因为URI命名规范是全球标准,构成Web的所有资源都可以互联互通?/p>
媒体原则还有一个更重要的方面——应?#8220;状?#8221;。简而言之,实际上服务器端(如果你愿意,也可以叫服务提供者)为客LQ服务消费者)提供一l链 接,使客L能通过链接应用从一个状态改变ؓ另一个状态。稍后我们会在另一文章中探究q个斚w的媄响;目前Q只需要记住:链接是构成动态应用的非常? 效的方式?/p>
Ҏ原则ȝ如下QQ何可能的情况下,使用链接指引可以被标识的事物Q资源)。也正是链接造就了现在的Web?/p>
在前两个原则的讨Z暗含着一个假设:接收URI的应用程序可以通过URI明确?strong>?/strong>一些有意义的事情。如果你在公共汽车上看到一个URIQ你可以它输入览器的地址栏中q回车——但是你的浏览器如何知道需要对q个URI做些什么呢Q?/p>
它知道如何去处理URI的原因在于所有的资源都支持同L接口Q一套同LҎQ只要你乐意Q也可以UCؓ操作Q集合。在HTTP中这被叫做动? QverbQ,除了两个大家熟知的(GET和POSTQ之外,标准Ҏ集合中还包含PUT、DELETE、HEAD和OPTIONS。这些方法的含义q同 行ؓ许诺都一起定义在HTTP规范之中。如果你是一名OO开发h员,可以想象到RESTful HTTPҎ中的所有资源都l承自类gq样的一个类Q采用类Java、C#的伪语法描述Q请注意关键的方法)Q?/p>
class Resource {
Resource(URI u);
Response get();
Response post(Request r);
Response put(Request r);
Response delete();
}
׃所有资源用了同样的接口,你可以依此用GETҎ索一?strong>表述QrepresentationQ——也
是对资源的描述。因范中定义了GET的语义,所以可以肯定当你调用它的时候不需要对后果负责——这是Z么可?#8220;安全”地调用此Ҏ。GETҎ
支持非常高效、成熟的~存Q所以在很多情况下,你甚至不需要向服务器发送请求。还可以肯定的是QGETҎhq等?/strong>[?
注:指多个相同请求返回相同的l果]——如果你发送了一个GETh没有得到l果Q你可能不知道原因是h未能到达目的圎ͼq是响应在反馈的途中丢失了?
q等性保证了你可以简单地再发送一ơ请求解决问题。幂{性同样适用于PUTQ基本的含义?#8220;更新资源数据Q如果资源不存在的话Q则Ҏ此URI创徏一个新
的资?#8221;Q和DELETEQ你完全可以一遍又一遍地操作它,直到得出l论——删除不存在的东西没有Q何问题)Ҏ。POSTҎQ通常表示“创徏一个新?
?#8221;Q也能被用于调用?font color="#000000">?/font>q程Q因而它既不安全也不hq等性?/p>
如果你采用RESTful的方式暴露应用功能(如果你乐意,也可以称为服务功能)Q?strong>那这条原则和它的U束同样也适用于你
来看下面q个单的采购Ҏ例子Q?
可以看到Q例子中定义了两个服务程序(没有包含M实现l节Q。这些服务程序的接口都是Z完成dQ正是我们讨论的 OrderManagement和CustomerManagement服务Q而定制的。如果客LE序试图使用q些服务Q那它必针对这些特定接口进? ~码——不可能在这些接口定义之前,使用客户E序L目的地和接口协作。这些接口定义了服务E序的应用协议(application protocolQ?/p>
在RESTful HTTP方式中,你将通过l成HTTP应用协议的通用接口讉K服务E序。你可能会想出像q样的方式:
可以看到Q服务程序中的特定操作被映射成ؓ标准的HTTPҎ——ؓ了消除歧义,我创Z一l全新的资源?#8220;q是骗h的把?#8221;Q我听见你叫L? 不,q不是欺骗。标识一个顾客的URI上的GETҎ正好相当于getCustomerDetails操作。有人用三角形Ş象化地说明了q一点:
把三个顶Ҏ象ؓ你可以调节的按钮。可以看到在W一U方法中Q你拥有许多操作Q许多种cȝ数据以及固定数量?#8220;实例”Q本质上和你拥有的服务程序数 量一_。在W二U方法中Q你拥有固定数量的操作,许多U类的数据和许多调用固定Ҏ的对象。它的意义在于,证明了通过q两U方式,你基本上可以表示M 你喜Ƣ的事情?/p>
Z么用标准方法如此重要?从根本上_它你的应用成ؓWeb的一部分——应用程序ؓWeb变成Internet上最成功的应用所做的贡献Q与 它添加到Web中的资源数量成比例。采用RESTful方式Q一个应用可能会向Web中添加数以百万计的客户URIQ如果采用CORBA技术ƈl持应用? 原有设计方式Q那它的贡献大抵只是一?#8220;端点QendpointQ?#8221;——就好比一个非常小的门Q仅仅允许有钥匙的hq入其中的资源域?/p>
l一接口也得所有理解HTTP应用协议的组件能与你的应用交互。通用客户E序Qgeneric clientQ就是从中受益的lg的例子,例如curl、wget、代理、缓存、HTTP服务器、网兌有Google、Yahoo!、MSN{等?
ȝ如下Qؓ使客LE序能与你的资源怺协作Q资源应该正地实现默认的应用协议(HTTPQ,也就是用标准的GET、PUT、POST和DELETEҎ?/p>
到目前ؓ止我们一直忽略了一个稍微复杂的问题Q客L序如何知道该怎样处理索到的数据,比如作ؓGET或者POSTh的结果?原因是,HTTP 采取的方式是允许数据处理和操作调用之间关pdȝ。换句话_如果客户E序知道如何处理一U特定的数据格式Q那可以与所有提供这U表q格式的资源? 互。让我们再用一个例子来阐明q个观点。利用HTTP内容协商Qcontent negotiationQ,客户E序可以h一U特定格式的表述Q?/p>
GET /customers/1234 HTTP/1.1
Host: example.com
Accept: application/vnd.mycompany.customer+xml
h的结果可能是一些由公司专有的XML格式表述的客户信息。假讑֮L序发送另外一个不同的hQ就如下面这P
GET /customers/1234 HTTP/1.1
Host: example.com
Accept: text/x-vcard
l果则可能是VCard格式的客户地址。(在这里我没有展示响应的内容,在其HTTP Content-type头中应该包含着关于数据cd的元数据。)q说明ؓ什么理想的情况下,资源表述应该采用标准格式——如果客L序对HTTP应用? 议和一l数据格式都有所“了解”Q那么它可以用一U有意义的方?strong>与世界上L一个RESTful HTTP应用交互? 不幸的是Q我们不可能拿到所有东西的标准格式Q但是,或许我们可以惛_在公司或者一些合作伙伴中使用标准格式来营造一个小环境。当然以上情况不仅适用于从 服务器端到客L的数据,反之既然——倘若从客L传来的数据符合应用协议,那么服务器端可以用特定的格式处理数据Q而不d心客L的类型?
在实践中Q资源多重表q还有着其它重要的好处:如果你ؓ你的资源提供HTML和XML两种表述方式Q那q些资源不仅可以被你的应用所用,q可以被L标准Web览器所用——也是_你的应用信息可以被所有会使用Web的h获取到?/p>
资源多重表述q有另外一U用方式:你可以将应用的Web UIU_到Web API中——毕竟,API的设计通常是由UI可以提供的功能驱动的Q而UI也是通过API执行动作的。将q两个Q务合二ؓ一带来了o人惊讶的好处Q这使得 使用者和调用E序都能得到更好的Web接口?/p>
ȝQ针对不同的需求提供资源多重表q?/p>
无状态通信是我要讲到的最后一个原则。首先,需要着重强调的是,虽然REST包含无状态性(statelessnessQ的观念Q但qƈ不是说暴露功能的应用不能有状态—?br /> ? 实上Q在大部分情况下q会D整个做法没有M用处。REST要求状态要么被攑օ资源状态中Q要么保存在客户端上。或者换句话_服务器端不能保持除了? ơ请求之外的QQ何与光信的客L的通信状态。这样做的最直接的理由就是可伸羃性—? 如果服务器需要保持客L状态,那么大量的客L交互会严重媄响服务器的内存可用空_footprintQ。(注意Q要做到无状态通信往往需要需要一? 重新设计——不能简单地一些session状态绑~在URI上,然后宣U这个应用是RESTful。)
但除此以外,其它斚w可能昑־更ؓ重要Q无状态约束服务器的变化对客L是不可见的,因ؓ在两ơ连l的h中,客户端ƈ不依赖于同一台服务器。一 个客L从某台服务器上收C份包含链接的文档Q当它要做一些处理时Q这台服务器宕掉了,可能是硬盘坏掉而被拿去修理Q可能是软g需要升U重启——如果这 个客L讉K了从q台服务器接收的链接Q它不会察觉到后台的服务器已l改变了?/p>
我承认:以上我所说的REST不是真正的RESTQ而且我可能有点过多地热衷于简单化。但因ؓ我想有一个与众不同的开场,所以没有在一开始就介绍其正式的定义和背景。现在就让我们稍微简要地介绍一下这斚w的内宏V?/p>
首先Q先前我q没有明地区分HTTP、RESTful HTTP和REST。要理解q些不同斚w之间的关p,我们要先来看看REST的历双Ӏ?/p>
Roy T. Fielding在他?a title="博士学位论文" id="ewd-">博士学位论文Q实际上你应该访问这个链接——至对于一学术论文来_它是相当易读的。此论文已被译?a title="中文" id="nev8">中文Q? 中定义了术语REST。Roy曾是许多基本Web协议的主要设计者,其中包括HTTP和URIsQƈ且他在论文中对这些协议提Z很多x。(q篇论文? 誉ؓ“REST圣经”Q这是恰当的——毕竟,是作者发明了q个术语Q所以在定义上,他写的Q何内定w被认为是权威的。)在论文中QRoy首先定义一U方? 论来谈论架构风格——高U、抽象的模式Q来表达架构Ҏ背后的核心理c每一个架构风格由一pd?strong>U束QconstraintsQ定义Ş成。架构风格的例子包括“没有风格”Q根本没有Q何约束)、管道和qo器(pipe and filterQ、客L/服务器、分布式对象以及——你猜到它了——REST?/p>
如果对你来说q些听v来都太抽象了Q那对了——REST在本质上是一个可以被许多不同技术实现的高层ơ的风格Q而且可以被实例化——通过为它的抽 象特性赋上不同的倹{比如,REST中包含资源和l一接口的概念——也是_所有资源都应该对这些相同的Ҏ作出反应。但是RESTq没有说明是哪些? 法,或者有多少Ҏ?/p>
REST风格的一?#8220;化n”便是HTTPQ以及一套相关的一套标准,比如URIQ,或者稍微抽象一些:Web架构自n。接着上面的例子,HTTP? 用HTTP动词作ؓRESTl一接口?#8220;实例”。由于Fielding是在Web已经Q或者至是大部分)“完善”了之后才定义的REST风格Q有人可? 会争Z者是不是100%的匹配。但是无论如何,整体上来说Web、HTTP和URI仅仅是REST风格的一个主要实现。不q,׃Roy FieldingxREST论文的作者,又对Web架构设计有过p的媄响,两者相g在情理之中?/p>
最后,我在前面一ơ又一ơ地使用着术语“RESTful HTTP”Q原因很单:许多使用HTTP的应用因Z些理由ƈ没有遵@REST原则Q有Z说用HTTP而不遵@REST原则q同于滥用HTTP? 当然q听h有点狂热——事实上q反RESTU束的原因通常是,仅仅因ؓ每个U束带来的设计权衡可能不适合于一些特D情c但通常Q违背RESTU束的原 因可归咎于对其好处认知的~Z。来看一个明昄反面案例Q用HTTP GET调用cM于删除对象的操作Q这q反了REST的安全约束和一般性常识(客户E序不应为此负责Q服务器端开发h员大概不是有意而ؓ之)。但在随后的? 章中Q我会提及更多这h那样的对HTTP的滥用?/p>
本文试图对RESTQWeb架构Q背后的概念提供快速的介绍。RESTful HTTP暴露功能的方式与RPC、分布式对象以及Web Services是不相同的;要真正理解这些不同是需要一些心态的转变。不你构徏的应用是仅仅x露Web UIq是xAPI变成Web的一份子Q了解下REST的原则还是有好处的?/p>
Stefan Tilkov是InfoQ SOAC的首席编辑,q且是位于d国和瑞士?/strong>innoQ公司的共同创始h、首席顾问和REST狂热分子首领?/strong>
译者简介:苑永凯,软g设计师,毕业于山东大学。主要关注领域ؓJava EE企业应用、Java EE中间件技术以及敏捷开发方法实践,微有心得。他的Blog?a >http://blog.csdn.net/ai92Q您也可以通过yuanyk[AT]gmail.com与他联系。参与InfoQ中文站内容徏设,请邮件至china-editorial[at]infoq.com?
作? 骆古?/strong> 发布? 2007q??7?下午8?8?
一
U思维方式影响了Y件行业的发展。REST软g架构是当今世界上最成功的互联网的超媒体分布式系l。它让h们真正理解我们的|络协议HTTP本来面貌。它
正在成ؓ|络服务的主技术,同时也正在改变互联网的网lY件开发的全新思维方式。AJAX技术和Rails框架把REST软g架构思想真正地在实际中很
好表现出来。今天微软也已经应用RESTq且提出把我们现有的|络变成Z?a >语义|?/a>Q这U网l将会得搜索更加智能化?
REST软g架构是由Roy Thomas Fielding博士?000q首ơ提出的。他为我们描l了开发基于互联网的网lY件的蓝图。REST软g架构是一个抽象的概念Q是一Uؓ了实现这一互联|的媒体分布式pȝ?strong>行动指南。利用Q何的技术都可以实现q种理念。而实现这一软g架构最著名的就是HTTP协议。通常我们把REST也写作ؓREST/HTTPQ在实际中往往把REST理解为基于HTTP的REST软g架构Q或者更q一步把REST和HTTP看作为等同的概念?
今天QHTTP是互联网上应用最q泛的计机协议。HTTP不是一个简单的q蝲数据的协议,而是一个具有丰富内늚|络软g? 协议。它不仅仅能够对于互联网资源q行唯一定位Q而且q能告诉我们对于该资源进行怎样q作。这也是REST软g架构当中最重要的两个理c而REST软g 架构理念是真正理解HTTP协议而Ş成的。有了REST软g架构理念出现Q才使得软g业避免了对HTTP协议的片面理解。只有正的理论指导Q才能避免在 软g开发的实际工作q程中少走弯路?
REST软g架构之所以是一个超媒体pȝQ是因ؓ它可以把|络上所有资源进行唯一的定位,不管你的文g是图片、文件Wordq是视频文gQ也不管? 的文件是txt文g格式、xml文g格式q是其它文本文g格式。它利用支持HTTP的TCP/IP协议来确定互联网上的资源?
REST软g架构遵@了CRUD原则Q该原则告诉我们对于资源Q包括网l资源)只需要四U行为:创徏QCreateQ、获取(ReadQ、更? QUpdateQ和销毁(DELETEQ就可以完成对其操作和处理了。其实世界万物都是遵循这一规律Q生、变、见、灭。所以计机世界也不例外。这个原? 是源自于我们对于数据库表的数据操作:insertQ生Q、selectQ见Q、updateQ变Q和deleteQ灭Q,所以有时候CRUD也写作ؓ RUDIQ其中的I是insert。这四个操作是一U原子操作,即一U无法再分的操作Q通过它们可以构造复杂的操作q程Q正如数学上四则q算是数字的最 基本的运一栗?
管在Java语言世界中网l服务目前是以SOAP技术ؓ主,但是REST是是网l服务的另一选择Qƈ且是真正意义上的|络服务。基于REST? 想的|络服务不久的将来也会成为是|络服务的主技术。REST不仅仅把HTTP作ؓ自己的数据运输协议,而且也作为直接进行数据处理的工具。而当前的|? l服务技术都需要用其它手D|完成数据处理工作Q它们完全独立于HTTP协议来进行的Q这样增加了大量的复杂Y件架构设计工作。REST的思想充分利用 了现有的HTTP技术的|络能力。在德国电视C曄出现q一个这L五十万欧元智力题Q如何实现网l服务才能充分利用现有的HTTP协议Q该问题l出? 四个{案Q去问微软;WSDL2.0/SOAP1.2QWS-TransferQ根本没有。这个问题告诉我们HTTPq不是一个简单的数据传来传去的协 议,而是一个聪明的会表现自q协议Q这也许是REST = Representational State Transfer的真正含义?
实际上目前很多大公司已经采用了REST技术作为网l服务,如Google、Amazon{。在Java语言中重要的两个以SOAP技术开始的|络服务框架XFire和Axis也把REST作ؓ自己的另一U选择。它们的新的目分别?a >Apache CXF ?a >Axis2 。Java语言也制定关于REST|络服务规范QJAX-RS: Java API for RESTful Web Services (JSR 311)。相信还会出现更多与REST相关的激动h心的信息?
管AJAX技术的出现才不Cq时_但是AJAX技术遵循了REST的一些重要原则。AJAX技术充分利用了HTTP来获取网l资源ƈ且实C HTTP没有的对于异步数据进行传输的功能。AJAX技术还使得软g更好地实现分布性功能,在一个企业内只要一个h下蝲了AJAX引擎Q其它企业内部的? 员,可以共享该资源了。AJAX技术遵守REST准则的应用程序中单和可~的架构Q凡是采用AJAX技术的面z而又丰富Q一个页面表C丰富? 彩的形态?
AJAX技术还使用了一U不同于XML格式的JSON文g格式Q这个意义在哪里呢?在REST软g架构下我们不能对于XML文gq行序列化处理,q? L序员必须要用自qXMLl定框架。而以序列化的JavaScript对象为基的JSON已经获得了广泛认可,它被认ؓ能以q比XML更好的方? 来序列化和传输简单数据结构,而且它更z。这对REST是一个极大A献和补充?
当前的网l应用Y件还q背了REST?#8220;无状态服务器”U束。REST服务器只知道自己的状态。REST不关心客L的状态,客户端的状态自己来? 理,q是AJAX技术的应用之地。通过AJAX技术,可以发挥有状态网l客h的优ѝ而REST的服务器兛_的是从所有网l客L发送到服务器操作的? 序。这样得互联网q样一个巨大的|络得到有序的管理?
Ruby on Rails框架Q简URails或者Rails框架Q是一个基于Ruby语言的越来越行的网l应用Y件开发框架。它提供了关于REST最好的支持Q也? 当今应用REST最成功的一个Y件开发框架。Rails框架Q从版本1.2.xP成ؓ了第一个引入REST作ؓ核心思想的主网lY件开发框架。在 Rails框架的充分利用了REST软g架构之后Qh们更加坚信REST的重要性和必要性。Rails利用REST软g架构思想对网l服务也提供了一的 支持。从最直观的角度看待RESTQ它是网l服务最理想的手D,但是Rails框架把REST带到了网l应用Y件开发框架。这是一ơ飞跃,让REST的? 想从|络服务的应用提升到了网l应用Y件开发。利用REST思想的simply_restful插g已经成ؓ了Rails框架的核心内宏V?
我们把现有基于SOAP的网l服务和ZREST/HTTP|络服务作个比喻Q前者是一U传l的寄信方式Q而后者是C|络的电子邮件方式。要是是 寄信和电子邮仉有病毒存在的话,传统的寄信被送到Ҏ很危险Q而电子邮件是开发的Q电子邮件供应商比如Google为我们检查了电子邮g是否有病毒? q里q不是说明SOAP|络服务消息包含义病毒,而是说明HTTP是无法处理SOAP信息包究竟好不好Q需要额外的软g工具解决q一问题Q包括防火墙也用 不上和管不了?
REST/HTTP|络服务的信息包可以被防火墙理解和控制。你可以按照操作和链接进行过滤信息包Q如你可以规定从外部来的只能dQGET操作Q? 自己服务器的资源。这样对于系l管理员而言使得软g理更ؓ单。REST的安全性还可以利用传输安全协议SSL/TLS、基本和摘要式认证(Basic und Digest AuthenticationQ。除了这些REST自n的安全性功能外Q还可以利用像基于信息的Web Services SecurityQJSR 155Q作为REST不错的补充?
l过多年的开发、教学和~写不唐H的JavaScriptQ?我发C下面的一些准则。我希望它们可以帮助你对“Z么这栯计和执行JavaScript比较?#8221;有一点理解。这些规则曾l帮助我更快C付品,q且产品的质量更高,也更Ҏl护?/p>
1.不要做Q何假?/strong>
QJavaScript是一个不可靠的助手)
可能不唐H的JavaScript 的最重要的一个特性就是——你要停止Q何假设:
在开始设计你的脚本之前,要考虑的第一件事情就是检查一下你要ؓ其编写脚本的HTML代码Q看看有什么东西可以帮助你辑ֈ目的?/p>
2.扑և钩子和节点关p?/strong>QHTML是脚本的基石Q?/p>
?
开始编写脚本之前,要先看一下你要ؓ之编写JavaScript的HTML。如果HTML是未l组l的或者未知的Q那么你几乎不可能有一个好的脚本编写方
案——很可能׃出现下面的情况:要么是会用JavaScript创徏太多标记Q要么就是应用太依赖于JavaScript?/p>
在HTML中有一些东襉K要考虑Q那是钩子和节点关pR?/p>
<1>.HTML 钩子 HTML
最初的和最重要的钩子就是IDQ而且ID可以通过最快的DOMҎ——getElementById
讉K到。如果在一个有效的HTML文档中所有的ID都是独一无二的话Q在IE中关于name ?ID
有一个bugQ不q有些好的类库解决了q个问题Q,使用ID是安全可靠的,q且易于试?/p>
其他一些钩子就是是HTML元素和CSS
c,HTML元素可以通过getElementsByTagNameҎ讉KQ而在多数览器中都还不能通过原生的DOMҎ来访问CSScR不q,有很
多外部类库提供了可以讉KCSScdQ类g getElementsByClassNameQ?的方法?/p>
<2>.HTML 节点关系 关于HTML的另外比较有意思的一点就是标C间的关系Q思考下面的问题Q?/p>
遍历DOM很耗资源而且速度很慢Q这是Z么要量使用览器中已经在用的技术来做这件事情?/p>
3.把遍历交l专家来?/strong>QCSSQ更快地遍历DOMQ?/p>
?
关DOM的脚本和使用Ҏ或属性(getElementsByTagName, nextSibling, previousSibling,
parentNode以及其它Q来遍历DOMgqh了很多hQ这点很有意思。而有的是,我们其实早已l通过另外一U技术—?CSS
——做了这些事情?/p>
CSS 是这样一U技术,它用CSS选择器,通过遍历DOM来访问目标元素ƈ改变它们的视觉属性。一D复杂的使用DOM的JavaScript可以用一个CSS选择器取代: var n = document.getElementById('nav'); /* 下面的代码与上面功能一?*/ #nav a{ q是一个可以好好利用的很强大的技巧。你可以通过动态ؓDOM中高层的元素dclass 或者更改元素ID来实现这一炏V如果你使用DOM为文档的bodyd了一个CSSc,那么设计师就很可以容易地定义文档的静态版本和动态版本?/p>
JavaScript: var dynamicClass = 'js'; CSS: /* 静态版?*/ #nav { /* 动态版?*/ body.js #nav { 4.理解览器和用户Q在既有的用模式上创徏你所需要的东西Q?/p>
?
唐突的JavaScript
中很重要的一部分是理解览器是如何工作的(其是浏览器是如何崩溃的Q以及用h望的是什么。不考虑览器你也可以很ҎC用JavaScript
创徏一个完全不同的界面。拖拽界面,折叠区域Q滚动条和滑动块都可以用JavaScript创徏Q但是这个问题ƈ不是个简单的技术问题,你需要思考下?
的问题: 最
后一个问题其实不是问题,因ؓ如果需要你可以用DOM来凭I创建HTML。关于这点的一个例子就?#8220;打印”链接Q由于浏览器没有提供一个非
JavaScript的打印文档功能,所以你需要用DOM来创c链接。同样地Q一个实C展开和收~内Ҏ块的、可以点ȝ标题栏也属于q种情况?
标题栏不能被键盘Ȁz,但是链接可以。所以ؓ了创Z个可以点ȝ标题栏你需要用JavaScript链接加入进去,然后所有用键盘的用户可以收
~和展开内容模块了?/p>
解决q类问题的极好的资源是设计模式库。至于要知道览器中的哪些东西是独立于输入设备的Q那p靠经验的U篏了。首先你要理解的是事g处理机制?/p>
5.理解事gQ事件处理会引v改变Q?/p>
?
件处理是走向不唐H的JavaScript的第二步。重点不是让所有的东西都变得可以拖拽、可以点L者ؓ它们d内联处理Q而是理解事g处理是一个可?
完全分离出来的东ѝ我们已l将HTML,CSS和JavaScript分离开来,但是在事件处理的分离斚w却没有走得很q?/p>
事g处理器会监听发生在文档中元素上的变化Q如果有事g发生Q处理器׃扑ֈ一个很奇妙的对象(一般会是一个名为e的参敎ͼQ这个对象会告诉元素发生了什么以及可以用它做什么?/p>
?
于大多数事g处理来说Q真正有的是它不止发生在你惌讉K的元素上Q还会在DOM中较高层U的所有元素上发生Q但是ƈ不是所有的事g都是q?
Pfocus和blur事g是例外)。D例来_利用q个Ҏ你可以Z个导航列表只d一个事件处理器Qƈ且用事件处理器的方法来获取真正触发事g
的元素。这U技术叫做事件委托,它有几点好处Q?/p>
需
要记住的另一件事是,在事件向父元素传播的时候你可以停止它而且你可以覆写掉HTML元素Q比如链接)的缺省行为。不q,有时候这q不是个好主意,因ؓ?
览器赋予HTML元素那些行ؓ是有原因的。D个例子,链接可能会指向页面内的某个目标,不去修改它们能确保用户可以将面当前的脚本状态也加入书签?/p>
6.Z人着?/strong>Q命名空_作用域和模式Q?/p>
你的代码几乎从来不会是文档中的唯一的脚本代码。所以保证你的代码里没有其它脚本可以覆盖的全局函数或者全局变量显得尤为重要。有一些可用的模式可以来避免这个问题,最基础的一点就是要使用 var 关键字来初始化所有的变量。假设我们编写了下面的脚本: var nav = document.getElementById('nav'); 上面的代码中包含了一个叫做nav的全局变量和名字分别ؓ init,show ?reset 的三个函数。这些函数都可以讉K到navq个变量q且可以通过函数名互相访问: var nav = document.getElementById('nav'); 你可以将代码装C个对象中来避免上面的那种全局式编码,q样可以将函数变成对象中的ҎQ将全局变量变成对象中的属性?你需要?#8220;名字+冒号”的方式来定义Ҏ和属性,q且需要在每个属性或Ҏ后面加上逗号作ؓ分割W?/p>
var myScript = { 所有的Ҏ和属性都可以通过使用“cd+Ҏ作符”的方式从外部和内部访问到?/p>
var myScript = { q种模式的缺点就是,你每ơ从一个方法中讉K其它Ҏ或属性都必须在前面加上对象的名字Q而且对象中的所有东襉K是可以从外部讉K的。如果你只是惌部分代码可以被文档中的其他脚本访问,可以考虑下面的模块(moduleQ模式: var myScript = function(){ }, ?
可以使用和前面的代码同样的方式访问返回的公有的属性和ҎQ在本示例中可以q么讉KQmyScript.public() ?
myScript.foo
。但是这里还有一点让得不舒服Q当你想要从外部或者从内部的一个私有方法中讉K公有Ҏ的时候,q是要写一个冗长的名字Q对象的名字可以非常长)。ؓ
了避免这一点,你需要将它们定义为私有的q且在return语句中只q回一个别名: var myScript = function(){ } //只返回指向那些你惌讉K的私有方法和属性的指针 q就保证了代码风g致性,q且你可以用短一点的别名来访问其中的Ҏ或属性?/p>
如果你不惛_外部暴露M的方法或属性,你可以将所有的代码装C个匿名方法中Qƈ在它的定义结束后立刻执行它: (function(){ 对于那些只执行一ơƈ且对其它函数没有依赖的代码模块来_q种模式非常好?/p>
通过遵@上面的那些规则,你的代码更好Cؓ用户工作Q也可以使你的代码在机器上更好地q行q与其他开发者的代码和睦相处。不q,q有一个群体需要考虑到?/p>
7.为接手的开发者考虑Qɾl护更加ҎQ?/p>
使你的脚本真正地unobtrusive的最后一步是在编写完代码之后仔细查一遍,q且要照ֈ一旦脚本上U之后要接手你的代码的开发者。考虑下面的问题: 最重要的一ҎQ要认识到文档中的HTML和CSS代码相对于JavaScript来说更有可能被改变(因ؓ它们负责视觉效果Q。所以不要在脚本代码中包含Q何可以让l端用户看到的class和IDQ而是要将它们分离出来攑ֈ一个保存配|信息的对象中?/p>
myscript = function(){ q样l护者就知道d里修改这些属性,而不需要改动其他代码?/p>
更多信息 以上是我发现的七条准则。如果你惌了解更多与上面所探讨的主题相关的东西Q可以看看下面的链接Q?/p>
if(n){
var as = n.getElementsByTagName('a');
if(as.length > 0){
for(var i=0;as[i];i++){
as[i].style.color = ‘#369′;
as[i].style.textDecoration = ‘none’;
}
}
}
color:#369;
text-decoration:none;
}
var b = document.body;
b.className = b.className ? b.className + ' js' : 'js';
....
}
....
}
function init(){
// do stuff
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
function init(){
show();
if(nav.className === 'show'){
reset();
}
// do stuff
}
function show(){
var c = nav.className;
// do stuff
}
function reset(){
// do stuff
}
nav:document.getElementById('nav'),
init:function(){
// do stuff
},
show:function(){
// do stuff
},
reset:function(){
// do stuff
}
}
nav:document.getElementById('nav'),
init:function(){
myScript.show();
if(myScript.nav.className === 'show'){
myScript.reset();
}
// do stuff
},
show:function(){
var c = myScript.nav.className;
// do stuff
},
reset:function(){
// do stuff
}
}
//q些都是U有Ҏ和属?
var nav = document.getElementById('nav');
function init(){
// do stuff
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
//公有的方法和属性被使用对象语法包装在return 语句里面
return {
public:function(){
foo:'bar'
}
}();
// q些都是U有Ҏ和属?
var nav = document.getElementById('nav');
function init(){
// do stuff
}
function show(){
// do stuff
// do stuff
}
function reset(){
// do stuff
}
var foo = 'bar';
function public(){
return {
public:public,
foo:foo
}
}();
// these are all private methods and properties
var nav = document.getElementById('nav');
function init(){
// do stuff
show(); // q里不需要类名前~
}
function show(){
// do stuff
}
function reset(){
// do stuff
}
})();
var config = {
navigationID:'nav',
visibleClass:'show'
};
var nav = document.getElementById(config.navigationID);
function init(){
show();
if(nav.className === config.visibleClass){
reset();
};
// do stuff
};
function show(){
var c = nav.className;
// do stuff
};
function reset(){
// do stuff
};
}();
作?陈金z?/strong> 发布?2009q??0?上午4??
Web领域的经验在q去十多q的不断的用和锤炼中,整个 开发领域的技术、理c缺陷已l趋于成熟。JavaEE Stack, .NET Stack, Ruby On Rails{框架代表了目前q个技术领域的所有经验积累。这h们在开始一个新的项目的时候,只需要选择对应语言的最佛_践,基本上不会犯大的错误。例 如,如果使用Java开发一个新的Web应用Q那么基本上Spring/Guice+Hibernate/iBatis/+Struts /SpringMVCq种架构是不会生重大的架构问题的;如果使用RoR那么你已l在使用最佛_践了Q系l的分层Q领域层Q数据库层,服务层,表现层等 {;Z保证pȝ的可扩展性,服务器端应当是无状态架构,{等。总而言之,web开发领域,它丰富的U篏使得开发者逐渐更多的_֊投入到应用本w?/p>
来看富客LQ或者富互联|应用。在我看来,今天的RichClient与RIA已经没有分别Q只要代表着丰富界面元素和丰富用户体验,需要与服务器进?交互的应用都可以UCؓRichClient或者RIAQ虽然感觉上RichClient?#8220;企业?#8221;一些(服务器往往在企业内部)QRIA?#8220;个h?#8221;一 些(服务器往往处于公网Q。从最的层面来说Q我现在正在使用的离U模式的GoogleDoc是一个RichClient应用──虽然它没有那?RichQ采用和microsoft office一样土的界? 我现在正在听音乐的Last.fm客户端显然是一个非常典型的RIA──它所有的个h喜好信息、音乐全都来自远在美国的服务器。本地的q个界面Q只是提?攉个h和音乐信息,以及控制音乐的播攑֒停止Q目前拥?150万玩家的兽世界Q则是一个挣钱最多的Q最“?#8221;的客LQ?0多G的客L包含了电?品质的广阔场景,华丽的魔法效果和极其复杂的hZ互?/p>
如今的用户需求已l达C一个新的高度,那些灰色的,Ҏ正正的界面已l逐渐不能够满_L需求。从我们工作的客L来,他们除了?#8220;完成功能”有着?本的期待外,对于应用做?#8220;?#8221;Q也抱有极大的热情。我工作的上一个项目是一个CRMpȝQ它是基?NET Framework 3.5的一个RichClient应用。它的主H口是一个带着U色渐变背景的无ҎH口Q还有请专业工制作的图标,点击某一个菜单还有华丽的二菜单?动效果。我们在q个目中获得了很多Q有些值得借鉴Q有些仍然值得反思。我仍然记得我们在项目的不同阶段Q做一个技术决定是如此的彷徨和忐忑Q因为在当时 的RichClient企业开发领域,几乎没有M丰富的经验可以借鉴Q我们重新发明了一些轮子,然后又推dQ我们偏MUI框架l我们提供的各种便利 而自己实现种U基Ҏ,只是因ؓ他们偏离了我们所倡导的测试性的原则。在写下本文的时候,我尝试搜索了一下,仍然没有比较深入的实跉|文章来介绍企业?境下RichClient开发。大多数的书Q如Swing、JavaFX?NET WPF开发等{,偏向于小规模Ҏ介l,而在大规模的企业应用中,q些的技巧对于架构决{往往帮助很小?/p>
我的工作l历应当是和大多数开始进行RichClient开发的开发者类|有着丰富的Web开发的l验之后开始进行RichClient开发。加?ThoughtWorks之后参加了多个不同的RichClient目的开发工作,使用/试q的语言包括Java Swing, Flex/Adobe Air, .NET WinForm/.NET WPF. 对于不同q_之间的种U有些体会。在q里我将q些实践和原则ȝ如下。例子很可能q时Q毕竟华丽的界面框架层出不穷Q但原则应当通用的。用和遵@q些?则将会帮助你犯错误──臛_比我们过ȝ的错误要。如果你拥有一定的web开发经验,那么q篇文章你读h会很亲切?/p>
q些原则/实践往往不是孤立的,我尝试将他们之间用图的方式关联v来,帮助你在使用的过E中q行选择。例如,你遵循了“一切皆异步”的原则,那么很可能你 需要进?#8220;U程理”?#8220;事g理”Q如果你需要引?#8220;~存与本地存?#8221;Q那?#8220;数据交互模式”你也需要进行考虑。希望这张图能够帮助读者理解不同原则之间的联系?/p>
下面列出的这些原则或者实跉|有严格意义上的区分。按照上面的图,我推荐是Q一旦你考虑C某一个实践,那么与它直接兌的实践你最好也要实现。它会得你的架构更全面Q经得v用户功能的需求和交互的需求?/p>
Z让这些实跉|加通用Q我采用伪代码书写。相信读者能够{化成相应的语a──Java, C#, ActionScript或者其他。这些实践ƈ非与某一U语a相关。在某些特定的例子中Q我会采用特定语aQ但大多数都是伪代码描述的?/p>
所有耗时的操作都应当异步q行。这是第一条、也是最重要的原则,q背了这条原则将会导致你的应用完全不可用?/p>
考虑q样的一个功能:点击一?更新股票信息"按钮Q系l会从股市场(W三方应用)获得最新的股票信息Qƈ信息更新到ȝ面。丝毫不考虑用户体验的写法:
void updateStockDataButton_clicked() { stockData = stockDataService.getLatest(); // 从远E获取股信? updateUI(stockData); // q个Ҏ会更新界? }
那么Q当用户点击updateStockDataButton
的时候,会有什么反应?难说。如果是一个无限带宽、无限计资源的世界Q这D代码直观又?懂,而且工作的非常好Q它会从W三方股系l读到股数据,q且更新到界面上。可惜不是。这D代码在现实世界工作的时候,当用L击这个按钮,整个界面?ȝ──知道那种感觉吗?是点完q个按钮Q界面不动了Q如果你在用Windows, 然后试拽住H口到处UdQ你会发现这个窗口经q的地方都是白的。你的客户不会理解你的程序实际上在很努力的从股票市场获得数据Q他们只会很愤怒的_q?个东西把我的机器弄死了!他们的思\被打断了。于是他们不再用你的程序,你们的合作没了。你没钱了。你的狗也跑了?/p>
出现界面ȝ的原因是Q耗时操作d了UIU程。UIU程一般负责着渲染界面Q响应用户交互,如果q个U程被阻塞,它将无法响应所有的用户交互hQ甚?包括拖拽H口q样单的操作。所有的界面框架Q无论是Java/.NET/ActionScript/JavaScript, 都只有一个UIU程Q这个估计永q都不会变?/p>
用户看到的应用通常与程序员大相径庭。用户对应用的期待别分别是Q能用、可用、好用、好看。而我观察到的大多数程序员停留在第一阶段Q能用?#8220;一切皆异步”q个原则说来单,做v来也不会很难。把上面的代码稍作改动,如下Q?/p>
void updateStockDataButton_clicked() { runInAnotherThread( function () { stockData = stockDataService.getLatest(); // 从远E获取股信? updateUI(stockData); // q个Ҏ在UIU程更新界面 } }
注意加粗部分?code>runInAnotherThread是跟语言q_特定的。对?net C#Q可以是一?code>Dispatcher+delegate或?code>ThreadPool.QueueUserWorkItemQ对于JavaQ可以干脆是一?code>Runable。对于AJAX, 可以?code>XMLHttpRequest或者把q个计算扔到一?code>IFrame中;对于ActionScript, g没有什么好的方法,把获取数据的部分交给XML.load
然后通过事g回调的方式来q行界面h吧?/p>
耗时操作一般两U来源生:|络带来的gq以及大规模q算。两者对应的异步实现方式有所不同。前者往往可以通过特定语言、^台的获取数据的方式来q行异步Q特别是~Z多线E特性的动态语a。例如典型的AJAX方式Q?/p>
xhr = new XmlHttpRequest() xhr.send("POST", '/stockData/MSFT', function() { doSomethingWith(xhr.responseText); // 只有当数据返回的时候,才会调用 })
大规模运带来的耗时在Java/C#{支持多U程的语a环境中很Ҏ实现Q而对于JavaScript/ActionScript{很难,折衷的方式是 复杂运gq到服务器端q行Q或者将复杂q算拆解成若q个耗时较少的小q算Q例如ActionScript的伪多线E实现方式?/p>
“一切皆异步”q个原则说来ҎQ但要在企业应用中以一U一致的方式q行实现很难。上例中runInAnotherThread
的方式貌似简单,也可能出 现在各种GUI框架的介l中Q但l不是一个稍兯模的RichClient应当采用的方式。它很难作ؓ一U编E范式被遵@Q你l不会希望看到在你的代码?所有用到异步的地方?code>new Runnable(){...}。这样带来的问题不仅仅是异步被不被管理的到处乱扔Q还带来了测试的复杂性。ؓ了解册些只有在臛_有点规模?RichClient中才出现的问题,你最好也实现?#8220;4 U程理”Q见下篇Q,能够实现“3 事g理”Q见下篇Q更好。终极方式是这些抽象到应用的基框架中,使得所有的开发h员以一U一致的方式q行~程?/p>
视图q个概念在WEB开发中几乎被忽略。这里所说的视图是指面、页面块{界面元素。在WEB开发中Q视囄生命周期很短Q在q入面的时候创建,在离开面的时候销毁。一不小心页面被弄糟了,或者不能按照预期的渲染了,点下h按钮Q整个世界一片清净?/p>
WEB下的视图D也是如此自然。基于超链接的方式,每点Mơ,p够打开一个新的页面,旧的面被浏览器销毁,新的面诞生。(q里不考虑AJAX或者其他JavaScriptҎQ?/p>
如果把这U想法带入到RichClient开发,后果会很p糕。每当点L钮或者进行其他操作需要导航到新的H口Q你不加M限制的创建新H口或者新的视 图。然而CPU不是无限的。创Z个新的视N常是很耗CPU和内存的。系l响应会变慢。用户会抱怨,拒绝付钱Q于是因为饥饿,你的狗再ơ离开了你?/p>
每次新创图生的严重后果q不仅仅是非功能性的Q还包括功能性的~失。如果你用过SkypeQ当你在l张三通话的时候,再次点击张三q且q行通话Q你 会发现刚刚的通话界面会弹出来Q而不是开启新H口。在我们的一个项目中Q有一个功能:点击软g界面上的电话Lp开启一个新H口Qƈ直接q到桌上的电?拨号通话。可以想象,如果每次都会弹出新的H口QY件的逻辑是根本错误的?/p>
如何解决q个问题Q最单的方式是将所有已知的视图全都保存到本地的一个缓存中Q我们命名ؓViewFactory
Q当需要进行获取某个视囄时候,直接?code>ViewFactory拿到Q如果没有创建,那么创徏Qƈ攑ֈCache中:
class ViewFactory { cache = {} View getView(Object key) { if cache.contains(key) { return cache[key] } cache[key] = createView(key) return cache[key] } }
需要注意的是,ViewFactory
?code>key的选择。对于简单的应用Q?code>key可以q脆是某个单独H口的类名。例如整个系l中往往只有一个配|窗口,?么key是q个cdQ对于需要复用的H口Q往往需要根据其业务主键来创建相应的视图。例如代码中只有一?code>UserDetailWindow, 需要用来展CZ同用L信息。当需要同时显CZ个以上的用户信息的时候,用同一个窗口实例显然不寏V这时?code>key的选择可以是类?用户ID?/p>
上面的方案ƈ没有解决D的问题。导航需要解决的问题有两个,如何D以及如何在导航时传递数据。这时候不得不慕WEB的解x式。我要访?code>ID?code>1的用户信息,只需要访问类gusers/1
的页面就好;需要访问搜索结果第5,只需要访?code>/search?q=someword&page=5好。这?code>/search是视图,q=someword
?code>page=5是传递的数据。目前我q没有发CQ何一本书来讲q如何进行视囑֯航。我们的方式是实C?code>Navigatorcȝ来导航,Navigator
依赖于前面提到的ViewFactory
Q?/p>
class Navigator { Navigator(ViewFactory viewFactory) { this.viewFactory = viewFactory; } void goTo(Object viewKey) { this.viewFactory.getView(viewKey).show() } }
Q这个类看v来跟ViewFactory
没什么大的差别,但他们逻辑上是完全不同Qƈ且下面的扩展中会增强Q?/p>
q样是可以解决问题的。如果要在不同的视图之间传递数据,只需要对Navigator.goTo
ҎE加扩展Q多d一个参数就能够传递参C。例如,在用户列表窗口点ȝ户名Q发送一条消息ƈ打开聊天H口Q可以写为:
void messageButton_clicked() { Navigator.goTo("ChatWindow#userId", "聊天消息") }
然而这U方式ƈ不完。当你发现大量的数据在窗口之间交互的时候,q种主动权交给调用Ҏ制的方式Q会l状态同步带来不麻烦;如果你用了本地存储Q它过存储层直接与服务器交互的方式也会带来不少的不便之处。更好的方式是?#8220;3 事g理”Q见下篇Q。当Ӟ如果H口之间D不存在数据传递,ZNavigator
的方式仍然简单ƈ且可用?/p>
相关阅读Q?/strong>
[ ThoughtWorks实践集锦Q?Q] 我和敏捷团队的五个约?/a>?/p>
[ ThoughtWorks实践集锦Q?Q] 如何在敏捷开发中做好数据q移?/p>
作者介l?/strong>Q陈金洲QBuffalo AJAX中文问题 Framework作者,ThoughtWorks咨询师,现居北京。目前的工作主要集中在RichClient开发,同时一直对Web可用性进行观察,q对其实C持兴?/p>
lInfoQ中文站投E或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也Ƣ迎大家加入?a target="_blank">InfoQ中文站用戯论组
作?陈金z?/strong> 发布?2009q??1?下午10??
事g理应当是整个RichClient/RIA开发中的最难以把握的部分。这部分控制的好Q你的程序用h如行云水Q用L思维不会被打断。Q何一 个做RichClient开发的E序员,可以对其他方面毫无所知,但这部分应当非常熟悉。事件是RichClient的核心,?#8220;一切皆异步”的终极实现。前面所说的例子Q实际上可以被抽象ؓ事gQ例如第一个,获取股票数据Q从事g的观点看Q应该是Q?/p>
看v来相当复杂。然而这样去考虑的时候,你可以将执行计算与界面展现清晰的分开。界面只需要响应事Ӟq算可以在另外的地方 悄悄的进行,q当d完成或者失败的是时候报告相应的事g。从l验看来Q往往同样的数据会在不同的地方q行不同的展C,例如skype在通话的时候这个h 的头像会昄为占U,而具体的通话H口中又是另外不同的展现QMSN的个人签名在好友列表H口中显CZؓ一个点d以编辑控Ӟ而同时在聊天H口昄Z?不能点击只能看的标签。这是RichClient的特性,你永q不知道同一份数据会以什么Ş式来展现Q更要命的是Q当数据在一个地Ҏ新的时候,其他所?能展现的地方都需要同时做相应的更新。如果我们仍然以W一部分的例子,单采?code>runInAnoterThread是完全不能解册个问题的?/p>
我们曄犯过一些很严重的错误,D最l即侉K构都U重难返。无视事件的抽象带来的媄响是架构U别的,修补无于事?/p>
事g的实现方式可以有很多U。对于没有事件支持的语言Q接口或者干脆某一个约束的Ҏ可以。有事g支持的语a能够享受到好处,但仍然是语法U别的,Ҏ 是一L。观察者模式在q里很好用。仍然以股票ZQ被观察的对象就是获取股数据对?code>StockDataRetriverQ观察的是StockWindow
Q?/p>
StockDataRetriver { observers: [] retrieve() { try { theData = ...// 从远E获取数? observers.each {|o| o.stockDataReady(theData)} // 触发数据获取成功事g } catch { observers.each { |o| o.stockDataFailed() } // 触发事g获取p|事g } } } StockDataRetriver.observers.add(StockWindow) // StockWindow加入到观察者队? StockWindow { stockDataReady(theData) { showDataInUIThread(); // 在UIU程昄数据 } stockDataFailed() { showErrorInUIThread(); // 在UIU程昄错误 } }
你会发现代码变得单。UI与计之间的耦合被事件解开Qƈ且区分UIU程与运线E之间也变得Ҏ。当试以事件的视角去观察整个应用程序的时候,你会更关注于用户与界面之间的交互?/p>
让我们l抽象。如果把“获取股票数据”q个按钮点击Q让StockDataRetriver
去获取数据当作事件来处理Q应该怎么写呢Q将按钮作ؓ被观?者,StockDataRetriver
作ؓ观察者显然不好,好不Ҏ分开的耦合又黏在一赗引入一个中间的Events
看v来不错:
Events { listeners: {} register(eventId, listener) { listeners[eventId].add(listener) } broadcast(eventId) { listeners[eventId].observers.each{|o| o.doSomething(); } } }
Events
中维护了一?code>listeners的列表,它是一个简单的Hashl构Qkey?code>eventIdQvalue?code>observer的列表;它提供了两个ҎQ用来注册事件监听以及通知事g产生。对于上面的案例Q可以先注册StockDataRetriver
Z个观察者,观察start_retrive_stock_data
事gQ?/p>
Events.register('start_retrive_stock_data', StockDataRetriever)
当点?#8220;获取股票数据”按钮的时候,可以是这P
Events.broadcast('start_retrive_stock_data')
你会发现StockDataRetriver
能够老老实实的开始获取数据了?/p>
需要注意的是,q所有事件定义ؓ全局事g是一个好的实c在更大规模的系l中Q将事gq行有效整理和分U是有好处的。在强类型的语言Q如 Java/C#Q中Q抽象出强类型的EventId
Q能够帮助理解系l和q行~程Q避免到处进行强制类型{换。例如,StockEvent
Q?/p>
StockDataLoadedEvent { StockData theData; StockDataLoadedEvent(StockData theData); } Event.broadcast(new StockDataLoadedEvent(loadedData))
q个事g的监听者能够不加类型{换的获得StockData
数据。上面的例子是不支持事g的语aQC#语言支持自定义强cd的事Ӟ用v来要自然一些:
delegate void StockDataLoaded(StockData theData)
事g理原则我相信ƈ不难理解。然而困隄是具体实现。对一个新的UI框架不熟悉的时候,我们l常?#8220;代码的优?#8221;?#8220;界面提供的特?#8221;之间徘徊。实现这 L一个事件架构需要在目一开始就E具雏ŞQƈ且所有的事g都有良好的命名和理。避免在命名、用事件的时候的随意性,对于让代码可诅R应用稳定有?常大的意义。一个好的事件管理、通知机制是一个良好RichClient应用的根本基。一般说来,你正在用的~程q_如Swing/WinForm /WPF/Flex{能够提供良好的事g响应机制Q即监听事g、onXXX{,但一般没有统一的事件的监听和管理机制。对于架构师Q对于要使用的编E^?对于q些的原生支持要了熟于心Q在~写q样的事件架构的时候也能兼这些语a、^台提供给你的支持?/p>
采用了事件的事g后,你不得不同时实践“U程理”Q因Z件一般来说意味着耗时的操作放到别的地方完成,当完成的时候进行事仉知。简单的模式下,你可以在所有需要进行异步运的地方Q将q算攑ֈ另外一个线E,?code>ThreadPool.QueueUserWorkItem, 在运完成的时候通知事g。但从资源的角度考虑Q将q些U程资源有效的管理也是很重要的,?#8220;U程理”部分有详l的阐述。另外,如果能将你的应用转变?数据驱动的,你需要关?#8220;~存以及本地存储”?/p>
在WEB开发几乎无需考虑U程Q所有的面渲染由浏览器完成Q浏览器会异步的q行文字和图片的渲染。我们只需要写界面和JavaScript好。如果你认同“一切皆异步”Q你一定得考虑U程理?/p>
毫无理的线E处理是q样的:凡是需要进行异步调用的地方Q都新v一个线E来q行q算Q例如前面提到的runInThread
的实现。这U方式如果托在 ?#8220;事g理”之下Q问题不大,只会l测试带来一些麻烦:你不得不wait一D|间来定是否耗时操作完成。这U方式很山寨Q也无法实现更高U功能。更?的的方式是将q些U程资源q行l筹理?/p>
U程的管理的核心功能是用来统一化所有的耗时操作Q最单的TaskExecutor
如下Q?/p>
TaskExecutor { void pendTask(task) { //task: 耗时操作d runInThread { task.run(); // q行d } } } RetrieveStockDataTask extends Task { void run() { theData = ... // 直接获取q程数据Q不用在另外U程中执? Events.broadcast(new StockDataLoadedEvent(theData)) // q播事g } }
需要进行这个操作的时候,只需要执行类g下面的代码:
TaskExecutor.pendTask(new RetrieveStockDataTask())
好处很明显。通过引入TaskExecutor
Q所有线E管理放在同一个地方,耗时操作不需要自行维护线E的生命周期。你可以?code>TaskExecutor中灵zd义线E策略实C些有的效果Q如暂停执行Q监控Q务状늭Q如果你愿意Qؓ了更好的q行调试跟踪Q你甚至可以所有的d以同步的方式执行?/p>
耗时d的定义与执行被分开Q得在d内部能够按照正常的方式进行编码。测试也很容易写了?/p>
不同的语aq_会提供不同的U程理能力?NET2.0提供?code>BackgroundWorker, 提供了一序列对多U程调用的封装,事g如开始调用,调用Q跨U程q回|报告q算q度{等。它内部也实C对线E的调度处理。在你要开始实现类似的TaskExecutorӞ参考一下它的API设计会有参考h倹{Java 6提供的Executor也不错?/p>
一个完善的TaskExecutor
可以包含如下功能Q?/p>
Task
的定义:一个通用的Q务定义。最单的是run()
Q复杂的可以加上生命周期的管理:start()
?code>end()?code>success()?code>fail()..取决于要控制到多么细致的_度?
pendTask
Q将d攑օq算U程?
reportStatus
Q报告运状?
写这L一个线E管理的不难。最单的实现是每当pendTask
的时候新开U程Q当q算l束的时候报告状态。或者用像BackgroundWorker
或?code>Executorq样的高UAPI。对于像ActionScript/JavaScriptq样的,只能用伪U程Q?或者干脆将无法拆解的Q务扔到服务器端完成?/p>
Ua的B/Sl构Q浏览器不持有Q何数据,包括基本不变的界面和实际展现的数据。RichClient的一大进步是界面部分本地持有,与服务器只作数据通讯Q从而降低数据流量。像《魔兽世界?0多G的超大型客户端,在普通的拨号|络都可以顺畅的游戏?/p>
~存与本地存储之间的差别在于Q前者是在线模式下,一D|间不变的数据~存Q最的与服务器q行交互Q更快的响应客户Q后者是在离U模式下Q应用仍然能 够完成某些功能。一般来_凡是需要类g“查看XXX历史”功能的,需?#8220;点击列表查看详细信息”的,都会存在本地存储的必要,无论q个功能是否需要向 用户开放?/p>
无论是缓存还是本地存储,最需要处理的问题如何处理本地数据与服务器数据之间的更新机制。当新数据来的时候,当旧数据更新的时候,当数据被删除的时候,{?{。一般来_引入q个实践Q最好也实现Z数据变化?#8220;事g理”。如果能够实?#8220;客户?服务器数据交互模?#8221;那就更完了?/p>
我们犯过q样一个错误。系l启动的时候,当前用L联系人列表读取出来,攑ֈ内存中。当用户双击q个联系人的时候,弹出q个联系人的详细信息H口。由?没有本地存储Q由于采用了Navigator方式的导航,于是很自然的采用?code>Navigator.goTo('ContactDetailWindow', theContactInfo)。由于列表页面一般是不变的,因此昄出来的永q是那䆾旧的数据。后来有了编辑联pMh信息的功能,ZL昄更新的数 据,我们调用更改ؓNavigator.goTo('ContactDetailWindow', 'contactId')
Q然后在ContactDetailWindow
中按?code>contactId把联pMh信息重新d一ơ。远在南非的用户抱怨慢。还 好我没养狗,没有狗离开我。后来我们慢慢的实现了本地存储,所有的数据d都从q个地方获得。当数据需要更新的时候,直接更新q个本地存储?/p>
本地存储会在Ҏ上媄响RichClientE序的架构。除非本C保存M信息Q否则本地存储一定需要优先考虑。某些编E^台需要你在本地存储界面和?据,如Google Gears的本地存储,|于Adobe Air的AJAX应用{,某些~程q_只需要存储数据,因ؓ界面完全是本地绘制的Q如Java/JavaFX/WinForm/WPF{。缓存界面与~存 数据在实C差别很大?/p>
本地存储的存储机制最好是采用某一U基于文件的关系数据库,如SQLite、H2QHypersonicSQLQ、Firebird{。一旦确定要采用本地存储Q就从成熟的数据库中选择一个,而不要尝试着自己写基于文件的某种~存机制。你会发现到最后你实现了一个山寨版的数据库?/p>
在没有考虑本地存储之前Q与q端的数据访问是直接q接的:
我们上面的例子说明,一旦考虑使用本地存储Q就不能直接讉Kq程服务器,那么需要一个中间的数据层:
数据层的主要职责是维护本地存储与q程服务器之间的数据同步Qƈ提供与应用相关的数据~存、更新机制。数据更新机制有两种Q一U是ProxyQ代理)模式Q一U是自动同步模式?/p>
代理模式比较Ҏ理解。每当需要访问数据的时候,请求发送到q个代理。这个代理会查本地是否可用,如果可用Q如~存处于有效期,那么直接从本地读取数 据,否则它会真正去访问远端服务器Q获取数据,更新~存q返回数据。这U手工处理同步的方式单ƈ且容易控制。当应用处于ȝ模式的时候仍然可以工作的?好?/p>
自动同步模式下,客户端变成都针对本地数据层。有一个健壮的自动同步机制与服务器的保持长q接Q保证数据一直都是更新的。这U方式在应用需要完全本地可q行的时候工作的非常好。如果设计得好,自动同步方式健壮的话Q这U方式会l编E带来极大的便利?/p>
说到同步Q很多h会考虑数据库自带的自动同步机制。我完全不推荐数据库自带的机制。他们的设计初衷本n是ؓ了数据库备䆾Q以及可扩展?QScalabilityQ的考虑。在应用层面Q数据库的同步机制往往不知道具体应用需要进行哪些数据的同步Q同步周期等{。更致命的是Q这U机制或多或 会要求客户端与服务器端具备cM的数据库表结构,q就q样的设计会l客L的缓存表设计带来很大的局限。另外,它对客户?服务器连接也存在一定的局?性,例如需要开攄定端口,特定服务{等。对于纯_的Internet应用Q这U方式更是完全不可行的,你根本不知道q程数据库的l构Q例?Flickr, Google Docs.
当本地存?自动同步机制?#8220;事g理”都实现的时候,应用会是一U全新的架构Q基于数据驱动的事gl构。对于所有本地数据的增删攚w定义ZӞ关?q些数据的视N注册为响应的观察者,d数据的变化于展现隔R界面永q只是被动的响应数据的变化,在我看来Q这是最极致的方式?/p>
限于幅Q这文章ƈ没有很深入的讨论每一U原?实践。同时还有一些在RichClient中需要考虑的东西我们ƈ没有讨论Q?/p>
感谢我的同事周小强、付莹在我写作过E中提供的无U的和帮助。小强推荐了介绍Google Gears架构的链接,让我能够写作“本地存储”部分有了更深的体会?/p>
q篇文章是我q两q来在RichClient工作、网l游戏、WebGame众多思考的一个集合。我试qJavaFX/WPF/AdobAir 以及相关的文章,然而大多数的例子都是从华丽的界面入手,没有实践相关的内宏V有意思的反而是《大型多人在U游戏开发》这本书Q给了我在企?RichClient开发很多启发。我们曾l犯了很多错误,也获得了许多l验Q以后我们应当能做得更好?/p>
相关阅读Q?/strong>
[ ThoughtWorks实践集锦Q?Q] 我和敏捷团队的五个约?/a>?/p>
[ ThoughtWorks实践集锦Q?Q] 如何在敏捷开发中做好数据q移?/p>
[ ThoughtWorks实践集锦Q?Q] RichClient/RIA原则与实践(上)?/p>
作者介l?/strong>Q陈金洲QBuffalo AJAX中文问题 Framework作者,ThoughtWorks咨询师,现居北京。目前的工作主要集中在RichClient开发,同时一直对Web可用性进行观察,q对其实C持兴?/p>
lInfoQ中文站投E或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也Ƣ迎大家加入?a target="_blank">InfoQ中文站用戯论组
Ajax的跨域访问问题是现有的Ajax开发h员比较常遇到的问题?br />
IE对于跨域讉K的处理是Q弹告框Q提醒用戗如果用户将该网站纳入可信Q|站Q或者调低安全别,那么q个问题IE׃会在提醒你?nbsp;
FireFox{其它非微Y的浏览器遇到跨域讉KQ则解决Ҏl一是拒l访问?br />
?
QIE是主浏览器Q只要它能正怋用就好了。此a差已QIE虽然能够处理Q但是是有前提的Q要么用户不厌其烦地在页面弹告框之后点击是(点击?
׃执行该Ajax调用了)Q要么用户将该网站纳入可信Q站点。这两种做法Q在企业理pȝ的应用中倒是比较常见Q因为系l管理员可以以行政手D保证用?
的行为。但是对于互联网上的|站或者门户开发,q种做法则不行?br />
最q遇Cq个问题Q需要在跨域讉Kl束之后完成使主H口出现一些特效,搜烦了一些资料,通过不断试以及在不同浏览器中进行兼Ҏ测试,扑ֈ了几个可行的ҎQ?br />
1?
Web代理的方式。即用户讉KA|站时所产生的对B|站的跨域访问请求均提交到A|站的指定页面,p面代替用户面完成交互Q从而返回合适的l果。此
Ҏ可以解决现阶D|能够惛_的多数跨域访问问题,但要求A|站提供Web代理的支持,因此A|站与B|站之间必须是紧密协作的Q且每次交互q程QA|站
的服务器负担增加Q且无法代用户保存session状态?br />
2、on-Demand方式。MYMSN的门户就用的q种方式Q不q?
MYMSN中不涉及跨域讉K问题。动态控制script标记的生成,通过修改script标记的src属性完成对跨域面的调用。此Ҏ存在的缺?
是,script的src属性完成该调用旉取的方式时get方式Q如果请求时传递的字符串过大时Q可能会无法正常q行。不q此Ҏ非常适合聚合c门户
用?br />
3、iframe方式。查看过醒来在javaeye上的一关于跨域访问的帖子Q他提到自己已经用iframe的方式解?
了跨域访问问题。数据提交跟获取Q采用iframeq种方式的确可以了,但由于父H口与子H口之间不能交互Q跨域访问的情况下,q种交互被拒l)Q因此无
法完成对父窗口效果的影响?br />
4、用h地{储方式:IE本n依附于windowsq_的特性ؓ我们提供了一U基?
iframeQ利用内存来“l行”的方案,即两个window之间可以在客L通过windows剪脓板的方式q行数据传输Q只需要在接受数据的一方设|?
Intervalq行轮询Q获得结果后清除Interval卛_。FF的^台独立性决定了它不支持剪脓板这U方式,而以往版本的FF中存在的插g漏洞又被
fixed了,所以FF无法通过内存来完成暗渡陈仓。而由于文件操作FF也没有提供支持(无法通过Cookie跨域完成数据传递)Q致使这U技巧性的方式
只能在IE中用?br />
5、我自己用于解决q类问题的方式:l合了前面几U方式,在访问A|站Ӟ先请求B|站完成数据处理Q再?
据返回的标识来获得所需的结果。这U方法的~点也很明显QB|站的负载增大了。优点,对session也实C保持Q同时A|站与B|站面间的交互能力
增强了。最重要的一点,q种Ҏ满了我的全部需?
中秋好?br /> 怎么把我的链接去掉了呢?
l我做个链接可以吗?Q?
非常感谢Q!
我的EMAILQhardcometure@163.com