json 基本原理

转载

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")


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章