转载
JSON传值法则
- 只能使用String来传递,用LONG,BOOLEAN,INT等数据类型解析会有偏差,切记!!!!
基本设计思路
- 接口采用类RPC的风格,让客户端只有一个服务器入口地址,便于维护
- 服务端通过统一入口进来,根据请求的命令名载入不同的命令实现模块,完成命令处理和返回。
- 支持chunk encoding
HTTP请求的写法
- 请求支持POST方法。
- Content-Type必须设置为application/x-www-form-urlencoded
- 请求数据格式json=<json数据>,数据是UTF-8编码的
实际上就是标准的HTTP FORM POST写法。
下面是一个简单的例子:
POST /r/index.php HTTP/1.1 Host: www.gypsii.com.cn Connection: keep-alive Content-type: application/x-www-form-urlencoded Content-Length: 349 json={......}
标准写法:
POST /r/index.php HTTP/1.1 Host: www.gypsii.com.cn Connection: keep-alive Content-type: application/x-www-form-urlencoded Content-Length: 349 key1=value1&key2=value2
通用数据报文格式
- 所有的键值,请求的命令都用小写,单词之间用"_"来分割。例如:place_detail
- cmd命令由"a-z","A-Z","_",其他字符为非法字符,服务器不接受。这样做是为了避免HACK行为。
REQUEST
最外层数据报文如下:
{ "cmd":<module_sub_module_..._command>, "id": <request id, optional>, "sid": <session ID, optional>, "cc": <cache timeout, optional>, "headers" <client headers>, "data": <interface specific data structure, optional> } *cmd: 必填项, 请求命令。 *id: 可选项,请求ID,如果请求中包含ID,则返回的RESPONSE中也会包含这个ID。这个是给客户端更灵活的REQ/RSP处理。 *sid: 可选项,服务器SESSION ID。成功登录后,服务器会返回一个session ID,此后的请求都应该加上这个session ID. *cc: 可选项,浏览器缓存时间设置,单位秒。如果不填,则服务器会在HTTP RESPONSE HEADER中加入默认的缓存设置,现在是1分钟。如果设置为0则表示浏览器不缓存。 *headers:必填项,客户端信息,用来生成XMLRPC的请求头。有关XMLRPC的请求头信息,参见[[打包格式和升级UA规范|User Agent规范]] *data:请求的数据体,可选项 headers的结构: { "ua":<user agent>, "lang": <client language locale, en-us/zh-chs, etc>, } 例子: { "cmd": "places_detail", "id": "93dkkakxiu3kaaidi3", "headers" {"ua":"J2ME/2.0.0.0 (customerid=UNI_01, variant=UNI_01_Java_en-us_01, devicetype=s40_nontouch)", "lang":"en-us"}, "data": {"id":1111111111} }
注意:"module_sub_module_..._command"用来对服务器上的请求进行分类。逻辑如下:通过分隔符"_"将模块,子模块,和命令分开。最后一个是命令,前面的都是模块名称。在服务器上脚本文件的存放目录和文件名就按照这个约定来存放。
例如: "cmd":"places_detail"表示在服务器的JSON根目录下有如下目录和文件来处理该命令/hb/req/places/detail.php
RESPONSE
最外层的数据报文如下:
{ "rsp": <1=SUCCESS/0=FAILUARE>, "cmd":<cmd in request> "data": <inteface specific data structure> "id": <corresponding request id, optional> "msg": <message will be prompted at client, optional>, } *id: 对应的请求ID,可选项,如果请求中包含ID,则返回的RESPONSE中也会包含这个ID。这个是给客户端更灵活的REQ/RSP处理 *cmd:对应的请求命令 *data:该对象必须为一个JSON对象表达。如果是数组,则必须为:"data":{"array":[...]} *msg: 用来在客户端显示的消息,如果该字段不存在,或者为内容为空("" or "null"),则不显示 例如: { "rsp": 1, "data": {"name": "...", "creation_time": "...", "thumbnail", "..."} }
支持带进度显示的文件上传
由于上传进度条是由服务器处理的,为了支持负载均衡,服务器上传服务器将会是单独的服务器和地址,并共同COOKIE的处理来保证获取进度条的正确性对于文件上传请求和进度条请求,有如下特殊处理:
- 上传服务器的地址是单独的;
- 客户端程序启动后,异步先向该服务器发送一个进度条的测试请求,如果HTTP RESPONSE的头里面有Set-Cookie,则在之后上传文件和请求进度的请求中将之前得到的cookie中的内容复制到HTTP REQUEST头中的Cookie字段,这样负载均衡器才能将上传文件请求和进度条请求发送到同一台WEB服务器上;
- 如果测试请求的RESPONSE中没有Set-Cookie,则不能向服务器发送进度条请求,否则返回的结果很可能是错的。
- 如果是CMWAP情况下,也不能向服务器发送进度条请求,因为图片是首先上传到运营商的代理WAP网关,然后网关发送到我们的服务器,这样,进度条请求的返回是不准确的。大部分上传时间都耗在从客户端到WAP网关那里,但是由于服务器还没有收到图片,所以进度条请求返回的总是0.
模块代码的写法
预置变量
每个JSON请求处理模块被调用时,下面的变量已经可以使用
- $gypsii_user_id:当前用户USER_ID,如果没有找到,则为;
- $gypsii_token:当前用户的XMLRPC TOKEN,如果没有找到,则为;
- $json:该变量为一个数组,表示客户端传入的JSON对象。
- $json_rsp:该变量为一个数组,执行框架会最后将这个数组转为JSON格式输出。在第一次访问该变量时,该变量已经具有了一个默认元素"rsp"=>"1"
- $client_headers: 该变量为一个数组,表示通过客户端传过来的headers生成的XMLRPC请求头。在后续调用XMLRPC请求时会用到。
- $media: 如果上传的是一个附带二进制的请求,则该变量为二维数组,保存需要上传的二进制文件在服务器的路径。
- type: MIME type image/jpeg, etc
- file: 上传文件在服务器的绝对路径,框架会在本次请求结束时会删除掉上传的临时文件。
如何返回自定义错误
在/includes/config.inc.php中有一个环境变量GYPSII_PRODUCTION,当设置为FALSE,表示为开发模式,否则为生产模式。
在程序中如果需要产生一个自定义错误,则调用如下:
output_json_error(你的资源串定义);
框架会自动捕获错误,并返回一个JSON response到客户端。
当GYPSII_PRODUCTION=FALSE时,会返回详细的错误信息,例如产生错误的文件名,行号等等。
当GYPSII_PRODUCTION=TRUE是,则只返回提示消息
注意:模块代码内不能调用exit()
response的输出是框架通过ob buffer最后统一发给客户端的。所以如果中途退出,框架是不知道的,所以不会有任何内容返回给客户端,客户端就会获得一个空白页面。
如果模块内部要要终止程序的执行,只要调用output_json_error("<错误提示信息>"),就会终止当前模块的执行并返回一个包含该错误提示信息的JSON RESPONSE到客户端。
如何定义自定义返回提示消息或者错误
如果需要返回自定义信息或者错误,要考虑到多语言化境。 /hb/req/res/<locale>/lang_res_req.inc.php是用来存放资源串的地方。由于JSON的资源串不会很多,因此就用一个文件来定义了。
获取DEFAULTTOKEN
如果需要没登录前用一些需要TOKEN的接口,可以在JSON的data中加入"defaulttoken":"true"来获取默认的TOKEN
例如 {
"cmd":<module_sub_module_..._command>, "id": <request id, optional>, "sid": <session ID, optional>, "cc": <cache timeout, optional>, "headers" <client headers>, "data":{"defaulttoken":"true",.....} <interface specific data structure, optional>
}
如何上传附带二进制内容的数据
为了支持二进制内容上传进度显示,上传的格式有特殊规定,参见add place
样例代码
[样例代码请点击这里 /backbone/trunk/source/hb/r/test/foo.php]
样例模块写法
==Place== 这里说明该模块是干什么的 ===子模块=== 如果有子模块,这里说明该子模块是干什么的 ===detail=== place detail方法,用来获取一个place的详情 ====输入报文说明==== 详细说明 *有哪些参数,干什么的 *哪些参数是可选的 ====输出报文说明==== 详细说明 *有哪些参数,干什么的 *哪些参数是可选的
解析JSON对象样例
由请求得到的JSON对象字符串如下 {"rsp":1, "data":{"token":"5d0e12ca502cc1bd4a356b986d70235e929adc3d", "DISPLAY_NAME":"Krelian", "LONGITUDE":"121.4325389", "STATUS":"呃~~~~~", "USER_ID":"1511237", "URL":"http://www.gypsii.com.cn/software/J2ME/gypsii/gypsii~non_touch~2.1.0.0~zh-chs.jad", "THUMBNAIL_URL":"http://www.gypsii.com.cn/icache/attachments/v237/E98CF9005FAF11DEB076B7CC7F68675D/account/icon_128.png?t=1256872626", "LATITUDE":"31.1904683125", "SIZE":"596067", "FROM_GYPSII":"TRUE", "REL_DATE":"2009-11-11", "VERSION":"2.1.0.0"} } 解析代码如下: ==信息头解析== String response = (JSONObject) json.getString("rsp"); JSON对象get能获取不同的数据类型,可以直接转变为所需数据,如json.getBoolean、json.getDouble... ==信息数据解析== 把data转换为JSONObject JSONObject userinfo = (JSONObject)json.get("data"); 取值 userinfo.getString("DISPLAY_NAME") userinfo.getBoolean("FROM_GYPSII") userinfo.getDouble("LATITUDE") userinfo.getString("THUMBNAIL_URL") ... ==数据为数组的格式解析== 获取类似LIST数据时返回的是一个JSON数组,需要转变为JSON数组进行解析 数据格式如下: "data":{"array": [{"CREATION_TIME":"1258698303","ID":"54043195530598589","DISTANCE":"0","THUMBNAIL_URL":"http://www.gypsii.com.cn/icache/attachm ents/v937/A1A6A888D59911DEB42CDB0BC5DC35EC/2152637/5519462_thumb_48.jpg","NAME":"郝茨","USER_NAME":"杨静"}, {"CREATION_TIME":"1258698278","ID":"54043195530598574","DISTANCE":"0","THUMBNAIL_URL":"http://www.gypsii.com.cn/icache/attachme nts/v277/ED38631AD59C11DEA3A7A453A9542C65/2152622/5519432_thumb_48.jpg","NAME":"nokla7210","USER_NAME":"大白"}, {"CREATION_TIME":"1258698112","ID":"54043195530598559","DISTANCE":"0","THUMBNAIL_URL":"http://www.gypsii.com.cn/icache/attachme nts/v237/DDA6726ED59911DE8584D4473DED2034/2152607/5519402_thumb_48.jpg","NAME":"韩添秀","USER_NAME":"爱"}, {"CREATION_TIME":"1258698087","ID":"54043195530598544","DISTANCE":"0","THUMBNAIL_URL":"http://www.gypsii.com.cn/icache/attachme nts/v872/27629780D58011DE8610D27AABCD66FA/2152592/5519372_thumb_48.jpg","NAME":"luoguzhi77","USER_NAME":"美丽的"}, {"CREATION_TIME":"1258698027","ID":"54043195530598529","DISTANCE":"0","THUMBNAIL_URL":"http://www.gypsii.com.cn/icache/attachme nts/v122/3EDAA192D57F11DEB6D5910E9F68860B/2152577/5519342_thumb_48.jpg","NAME":"yuduanchan","USER_NAME":"fj"}, ..... 解析方式为 JSONArray placelist = (JSONArray)json.getJSONObject("data").getJSONArray("array"); 数据长度为placelist.length() 获取数据为 placelist.getJSONObject(index).getString("ID")