论坛首页 Web前端技术论坛

让Ajax框架Buffalo支持JSON协议而非仅仅自定义的XML协议[JS前端及单元测试完成]

浏览 7921 次
精华帖 (2) :: 良好帖 (5) :: 新手帖 (0) :: 隐藏帖 (15)
作者 正文
   发表时间:2010-01-14   最后修改:2010-02-08

Buffalo默认实现了一套很棒的自定义XML协议(具体请参考拙作《Ajax框架Buffalo深度研究》)。

然而在崇尚“标准”的当下,很多人对这种“非标准”的自定义的协议并不感冒,哪怕它再好。

在这种情况下,本人也是本着对Buffalo的继续研究、提升自我的态度,尝试基于org.json的“正宗”JSON参考实现RI,让Buffalo完全支持JSON标准协议。

 

本来打算自己默默研究,等完全成型之后再发布出来,然而自《Ajax框架Buffalo深度研究》发布后,在社区所凝聚的人气之旺,让我有些震惊(或者说惊喜),继而灵机一动:何不利用社区的强大平台,在线开展此项工作呢?

不能保证每天甚至每周都有让人惊喜的进展,仅希望通过这种方式,鞭策自己的同时,也获得更多社区高手的批评斧正,快速提升,少走弯路。

 

接下来的工作,欢迎各位踊跃拍砖,谢谢!

 

 

2010-02-08,状态简述:

1)前端js已经编写完成:充分结合XML协议和JSON协议,完成了代码的重构

2)单元测试已经完成,并提供了完整的参考XML协议的js单一测试用例(同样基于script.aculo.us 的unittest.js)

3)升级prototype.js到最新的1.6.1(其实buffalo对prototype.js依赖不大)

接下来的工作:

1、完成前后端集成测试,并完善demo

2、适时开展RC

 

2010-01-25,状态简述:

1)后端代码重构完成,并已纳入buffalo SVN, 建立了新的branch,位于branches/json/;

2)接下来工作的重点是:a.着手编写前台js; c.并结合demo完成集成测试。

   发表时间:2010-01-14  

Buffalo原有的架构,就这个扩展的角度而言,基本还是合理到位的,核心部分均通过简约的接口定义,与具体实现进行了分离,内部也合理的运用了较好的设计模式实践。所有这些,均为实现JSON协议的扩展和改造打下良好的基础。

考虑到原有XML协议已经非常成熟稳定,整体实现基于JSON协议的扩展思路是这样的:

1、  改造原有与XML协议耦合过高的代码;

2、  实现基于JSONio读写接口;

3、重新编写/扩展客户端序列化、反序列化机制。

 

0 请登录后投票
   发表时间:2010-01-14   最后修改:2010-01-14

首当其冲的是,得搞定入口问题。如何确保双协议的同时支持是关键?

  

首先定义一个全局变量:

Buffalo.DefaultProtocol="JSON";//XML or JSON

  

 

协议的选择应该考虑基于每次的remoteCall,而不是全部要么这样要么那样。

此时需要扩展Buffalo.Call,新增参数protocol,并新增两个方法:

initialize: function(methodname){
		……
		this.protocol = Buffalo.DefaultProtocol;
	},

	setProtocol : function(protocol) {
		if (protocol!=null && (protocol=='XML' || protocol=='JSON')) {
			this.protocol = protocol;
		}
	},

	getProtocol : function() {
		return this.protocol;
	},

 

 

 

然后在正式发出后台请求的地方(Buffalo._remoteCall),通过RequestHeader传递这个协议指令:

try {
			this.transport.open("POST", url, this.async);
			this.transport.setRequestHeader("X-Buffalo-Version", Buffalo.VERSION);
			this.transport.setRequestHeader("X-Buffalo-Protocol", buffaloCall.getProtocol());
			this.transport.setRequestHeader("Content-Type", "text/xml;charset=utf-8");
			this.transport.send(sendingXML);//this.transport.send(buffaloCall.xml());
		} catch (e) {
			this.events.onError(this.transport);
			this.events["onLoading"](false);
			return;
		}

 

 

这样,这个指令就可以从后端的入口ServletApplicationServlet)获得。

0 请登录后投票
   发表时间:2010-01-14  

当然了,Buffalo.Call还需要增加json方法,类似于xml,用以产生json的请求。

为了方便,这里先写死了,先把simpleService.divide走通再说:

	// parse the call to json format
	// TODO: now just for test
	json: function(){
                	this._objects = [];
		var jsonstr = "{'buffalo-call':[{'method':divide},{'double':1},{'double':2}]}";
		return jsonstr; 
	},

 

 

这回轮到修订后端的入口Servlet,即ApplicationServlet.java

定义一个类全局的变量:

private static final String PROTOCOL_ATTR = "X-Buffalo-Protocol";

 

doRequest中获得协议名称:

//get the buffalo protocol
String buffaloProtocol = (String) request.getHeader(PROTOCOL_ATTR);
LOG.debug("using protocol: " + buffaloProtocol);

 

 

 

0 请登录后投票
   发表时间:2010-01-14  
我很好奇的看到有8名高手投了隐藏贴,如果大家已经有好的实现,不妨发给我学习参考,谢谢!
0 请登录后投票
   发表时间:2010-01-15   最后修改:2010-01-15

      经昨天晚上的分析,上述改动不应在ApplicationServlet做,而应该在AbstractRequestWorker.java。即上述在ApplicationServlet的修订无效。

 

      此时要改造的是AbstractRequestWorker.java,即把原先打算在ApplicationServlet的改动,调整在此类做:

      定义一个类全局的变量:

private static final String PROTOCOL_ATTR = "X-Buffalo-Protocol";

新增getProtocol的方法:

/**

 * Get the protocol

 * @return

 */

protected String getProtocol(){

    //get the buffalo protocol from request header ‘X-Buffalo-Protocol

    return (String) RequestContext.getContext().getHttpRequest().getHeader(PROTOCOL_ATTR);

}

在这个抽象类做有什么好处呢?能确保所有Worker都能共享此变量。

 

拿到以后怎么用呢?目前并没有现成的办法。

后面应该需要较大的改造力度了。

 

 

 

0 请登录后投票
   发表时间:2010-01-15   最后修改:2010-01-15

改造:BuffaloProtocal.java

 

经进一步分析发现,BuffaloWorker调用了BuffaloInvoker实例的invoke方法实现输入流的解析(unmarshal)和输出结果对象的序列化(marshal),而BuffaloInvoker所使用的是BuffaloProtocal的实例。

再看看BuffaloProtocal实例的代码就知道,这里存在大量耦合XML协议的部分。

解耦在所难免。

此类改造由此产生了两个目标:

1、  应该要能区别所用的协议;

2、  实例将根据不同的协议初始化对应的StreamReaderStreamWriter

为此,首先增加了一个类的私有变量protocol,用以表达所用协议(这个变量后续需要重构,谁知道后续还会冒出什么协议呢,对吧):

 

 

 

private String protocol = "XML";

 

废弃原有获取单例的方式并增加新的需要明确协议的方式:

	/**
	 * get the instance for default xml protocol
	 * @return
	 * @deprecated use getInstance(String protocol) instead
	 */
	public static BuffaloProtocal getInstance() {
		return getInstance("XML");
	}
	/**
	 * get the right instance for the protocol
	 * @param protocol
	 * @return
	 */
	public static BuffaloProtocal getInstance(String p){
		if (instance == null) {
			instance = new BuffaloProtocal();			
		}
		instance.protocol = p;
		return instance;
	}

 

 

 

 

注意:我感觉上面这样写会有线程安全问题,不知道各位怎么看?

 

改变原有的marshalunmarshal方法中直接绑定XML SteamReaderSteamWriter的做法,通过if…else来判断(先这样,看以后再重构):

public void marshall(Object value, Writer writer) {
		StreamWriter steamWriter;
		if(protocol.equalsIgnoreCase("JSON")){
			steamWriter = new JsonStreamWriter(writer);
		}else{
			steamWriter = new FastStreamWriter(writer);
		}
		marshallingStrategy.marshal(value, converterLookup, steamWriter);
	}
	
	public BuffaloCall unmarshall(Reader reader) {
		StreamReader steamReader;
		if(protocol.equalsIgnoreCase("JSON")){
			steamReader = new JsonStreamReader(reader);
		}else{
			steamReader = new FastStreamReader(reader);
		}
		return marshallingStrategy.unmarshal(steamReader, converterLookup);
	}
	
	public BuffaloCall unmarshall(InputStream inputStream) {
		StreamReader steamReader;
		if(protocol.equalsIgnoreCase("JSON")){
			steamReader = new JsonInputStreamReader(inputStream);
		}else{
			steamReader = new FastInputStreamReader(inputStream);
		}
		return marshallingStrategy.unmarshal(steamReader, converterLookup);
	}

 

 

 

注意了,这里我先定义了三个类,分别为:

JsonStreamReader——实现io/SteamReader接口,用以解析JSON输入流;

JsonInputStreamReader——这个得再确认,原先XML协议下的说法是Opera下有编码问题才做的,先照葫芦画瓢改造;

JsonStreamWriter——实现io/SteamWriter接口,用以序列化执行结果并输出JSON

上述三个类将是本次扩展实现以支持JSON协议的重中之重。

 

0 请登录后投票
   发表时间:2010-01-15  

改造:BuffaloInvoker.java

 

根据BuffaloProtocal.java的改造而做出的适应性改造。

首先增加了一个类的私有变量protocol,用以表达所用协议:

private String protocol = "XML";

 

废弃原有获取单例的方式并增加新的需要明确协议的方式:

	/**
	 * get the instance for default xml protocol
	 * @return
	 * @deprecated use getInstance(String protocol) instead
	 */
	public static BuffaloInvoker getInstance() {
		return getInstance("XML");
	}
	
	/**
	 * get the right instance for the protocol
	 * @param protocol
	 * @return
	 */
	public static BuffaloInvoker getInstance(String p) {
		if (instance == null) {
			instance = new BuffaloInvoker();			
		}
		instance.protocol = p;
		return instance;
	}

 

三处BuffaloProtocal.getInstance()的地方,改为:BuffaloProtocal.getInstance(protocol)

 

 

0 请登录后投票
   发表时间:2010-01-15  

改造:BuffaloWorker.java

 

根据BuffaloInvoker的调整而微调:

BuffaloInvoker.getInstance()

改为:

BuffaloInvoker.getInstance(getProtocol())

如下:

//get the right BuffaloInvoker instance, and invoke it
//BuffaloInvoker.getInstance().invoke(service, inputStream, new OutputStreamWriter(response.getOutputStream(), OUTPUT_ENCODING));
BuffaloInvoker.getInstance(getProtocol()).invoke(service, inputStream, new OutputStreamWriter(response.getOutputStream(), OUTPUT_ENCODING));

 

至此,我个人感觉结构性的代码调整已经完成。

下面所做的,就是我们刚才说到的三个重头戏。

但涉及到协议的代码,将非常细节,要求精通Buffalo的XML协议细节,并要求对JSON有较好的把握,所以整体难度也会更大写,从进度上看可能需要点时间了。

0 请登录后投票
   发表时间:2010-01-16   最后修改:2010-01-16

通过进一步学习JSON发现,正如Michael说的“有些需要特殊解决的问题,在JSON中仍然存在,特别是类型的处理。你仍然不得不在JSON中加上类似于type的特殊属性并在后端进行处理”,我决定中庸一些,不寻求绝对精简的JSON,而是要求所有的值,都给我老老实实带上Type

格式举例:

{'buffalo-call':[{'method':divide},{'double':1},{'double':2}]}

 

 

然而,在编写协议解析的时候,出现了第一个疑问:这个协议需要一个字符一个字符来解析吗,何不直接转化为JSONObject再处理,岂不是更方便?

这个问题同样适用于XML:何不直接读取inputStream后,转化为标准的DOM对象后再处理?

 

难道还有其他什么考虑吗?

0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics