//jsonClient RPC客户端 import JSON; import web.socket.client; namespace web.socket; class jsonClient{ ctor(){ this.rpc = ..web.socket.client(); this.rpc.version = "2.0"; this.rpc.varargs = true; this.rpc._translateMessage = function(msg){ if( msg.type == 1 ){ if( owner.beforeJsonParse ) { msg.data = owner.beforeJsonParse(msg.data); if(!msg.data) return; } var data,err = ..JSON.tryParse(msg.data); if( type(data) != "table"){ // Chrome DevTools Protocol 没有 jsonrpc 字段 if( this.rpc.onError ) this.rpc.onError(..string.concat( err," 接收到的数据格式错误:",msg.data)); return; } if(#data){ for(i=1;#data ) this.rpc._response(data[i]); } else { this.rpc._response(data); } } }; this.rpc.sendJson = function(data){ var jsonData = ..JSON.stringify(data); if( owner.afterJsonStringify ) { jsonData = owner.afterJsonStringify(jsonData); if(!jsonData) return; }; return this.rpc.send(jsonData); } this.rpc._response = function(repData){ if( repData.method ){ var event = owner.eventCallback[repData.method]; if(event) { var result,err; if( owner.varargs ){ result,err = event(..table.unpackArgs(repData.params) ); } else { result,err = event(repData.params); } if(repData.id) { owner.sendJson( { @{ _defined = {"result"}; _type="object"}; error = err; result = result; id = repData.id; } ) } } } elseif(repData.id) { var cb = owner.endCallback[repData.id]; if(cb){ this.rpc.endCallback[repData.id] = null; if( cb.end ){ cb.end( repData.result,repData.error ) } cb.done = true; }; } else { if(repData.error){ this.rpc.onError(..string.concat("Server Error:",repData.error.message," #",repData.error.code),repData.error); return; } if( this.rpc.onError ) this.rpc.onError("接收到的数据不符合 JSON RPC 协议规则"); owner.sendJson( { error = { code = -32600; message = "Invalid Request - The JSON sent is not a valid Request object." }; }) } }; this.rpc.endCallback ={}; this.rpc.eventCallback ={}; this.rpc.beginTrans = function(){ owner.transaction = {}; }; this.rpc.commitTrans = function(){ if(#owner.transaction){ if(! owner.sendJson( owner.transaction ) ){ if(this.rpc.onError){ this.rpc.onError(..afterJsonStringify(owner.transaction) + '\n发送失败,请先调用 connect 函数连接 JSON RPC 服务端'); } return; } } owner.transaction = null; }; this.rpc.rollbackTrans = function(){ owner.transaction = null; }; this.rpc.xcall = function(method,...){ var cb = {}; cb.method = method; var id = ..table.push(owner.endCallback,cb); var data = { id = id; jsonrpc = owner.version; method = method; } if( owner.varargs ){ data.params = [...] } else { data.params = ...; } if( owner[["beforeRequest"]] ){ data = owner.beforeRequest(data); }; if( owner.transaction ){ ..table.push(owner.transaction,data); return cb; } if( owner.sendJson(data) ) return cb; if(this.rpc.onError){ var err = '发送失败,请先调用 connect 函数连接 JSON RPC 服务端:\n'+ ..JSON.stringify(data); if(this.rpc.onError) this.rpc.onError(err); ..win.setTimeout( function(){ if( cb.end ) cb.end(,{ code = -32603; message = '发送失败,请先调用 connect 函数连接 JSON RPC 服务端' }); this.rpc.endCallback[id] = null; } ) return cb; } } } connect = function(url){ return this.rpc.connect(url); }; waitForConnected = function(hwnd,timeout){ ..win.delay(1000); return ..win.wait( function(){ if( this.rpc.readyState > 1 ) return false; if( this.rpc.readyState == 1 ) return true; },hwnd,timeout,1000 ); }; on = function(method,callback){ if( _socketEvent[method] ){ this.rpc[_socketEvent[method]] = callback; return; } this.rpc.eventCallback[method] = callback; } @_meta; } namespace jsonClient{ _meta = { _get = function(k){ if( type(k) == type.string ){ if( owner[["method"]] ){ k = owner[["method"]] + "." + k; } owner[[k]] = { rpc = owner[["rpc"]]; method = k; @_meta; } return owner[[k]]; } } _call = function( ... ){ return owner.rpc.xcall( owner.method,...) ; } } _socketEvent = { open = "onOpen"; close = "onClose"; error = "onError"; message = "onMessage"; fragmen = "onFragment"; } } /**intellisense() web.socket.jsonClient = WebSocket / JSON-RPC 2.0 单线程异步客户端 web.socket.jsonClient() = !stdwebsocketjsonClient. end intellisense**/ /**intellisense(!stdwebsocketjsonClient) rpc = WebSocket客户端对象\n此对象的成员谨慎改动\n!stdwebsocketjsonClientrpc. ? = 远程对象名或远程方法名字,\n!stdWebSocketJsonClientObject. on(method,proc) = @.on("__/*需要监听的Rpc通知事件名字*/",function(param){\n \n}) on("open",proc) = @.on("open",function(){\n __/*已打开连接*/ \n} on("close",proc) = @.on("close",function(e){\n __/*连接被关闭\ne.code为错误代码e.reason为错误原因*/\n}) on("error",proc) = @.on("error",function(errMessage,rpcError){\n __/*发生错误,errMessage 为错误信息。\n如果服务端按 jsonrpc 协议返回 error 对象,\n则 rpcError 有值,包含 messge,code 字段。*/\n}) on("message",proc) = @.on("message",function(msg){\n __/*收到服务端数据\nmsg.type 为 1 时 msg.data 为文本,\n否则 msg.data 为字节串(buffer 类型)*/\n}) on("fragment",proc) = @.on("fragment",function(msg){\n __/*收到分片数据\n第一个数据包使用msg.type指明类型,参考WebSocket协议规范\n后续数据包msg.type为0,最后一个数据包msg.fin为1\n\n如果不指定这个回调函数,则自动并接分片数据后触发onMessage事件*/ \n}) connect("ws://__") = 重新连接到WebSocket服务端\n参数指定WebSocket服务端网址,例如 "ws://localhost:7511"\n如果不指定参数,则获取上次调用此函数指定的网址参数,\n如果之前也没有指定网址则抛出异常 end intellisense**/ /**intellisense(!stdWebSocketJsonClientObject) ? = 远程对象名或远程方法名字,\n作为函数调用时返回一个调用对象,\n通过指定返回调用对象的 end 属性定义调用结束回调函数,例如:\nret.end=function(result,err){\n \n}\n回调参数 result 为调用返回值,err 为错误信息,\n如果调和成功,err参数为null\n!stdWebSocketJsonClientObject. rpc = WebSocket客户端对象\n此对象的成员谨慎改动\n!stdwebsocketjsonClientrpc. end = @.end = function(result,err){ __/*可通过远程函数调用返回对象的 end 属性指定一个调用完成触发的回调函数。\n回调参数 result 为远程调用返回值,err 为错误对象,\n调用成功 err 参数为null*/ } done = 通过远程函数调用返回对象的 done 属性判断调用是否完成。\n注意出错时则 done 可能不会设为 true,应在 rpc.onError 回调中处理这种情况。 end intellisense**/ /**intellisense(!stdwebsocketjsonClientrpc) varargs = 默认值为true,\n值为true时将不定个数的参数放入数组发送给服务端\n值为false时直接将单个参数发送给服务端\n\nJSON-RPC 2.0一个会制造混乱的地方:\n如果params是一个数组,并没有规定是展开为一个参数,还是作为一个数组参数。\n\n目前aardio的RPC服务端会负责展开数组作为多个参数,\n但客户端需要在这里手动设置 version = 值为JSON-RPC协议版本:"2.0"\n不应该修改这个值 xcall(method,param) = @.xcall("Page.navigate",{\n url = "网址"\n}).end = function(result,err){\n __/*调用JSON-RPC服务端的指定方法\n服务器应答后回调此函数返回值的成员函数:end函数\nend函数有2个回调参数,参数@1为result,参数@2为err\n失败err参数非空*/\n} beforeRequest(请求数据) = @.beforeRequest = function(reqData){ __/*此回调事件在发送请求前触发\nreqData.params是即将发送的参数*/ return reqData; } beforeJsonParse = @.beforeJsonParse = function(jsonData){ __/*可以在这里解密服务端数据,返回null中止本次调用*/ return jsonData; } afterJsonStringify = @.afterJsonStringify = function(jsonData){ __/*可以在这里加密客户端JSON,返回null中止本次调用*/ return jsonData; } beginTrans() = 开始批量调用\n之后的所有RPC调用不提交服务器,\n直到调用commitTrans函数 commitTrans() = 完成批量调用并提交到服务器 rollbackTrans() = 撤消尚未提交的批量调用 socket = 异步套接字对象\n在关闭连接状态下此属性的值为null\n应由对象自动打开或删除套接字对象,调用者不可改动此属性的值\n!stdtcpaclient. connect("ws://__") = 重新连接到WebSocket服务端\n参数指定WebSocket服务端网址,例如 "ws://localhost:7511"\n如果不指定参数,则获取上次调用此函数指定的网址参数,\n如果之前也没有指定网址则抛出异常 waitForConnected(.(关联窗口句柄,超时) = 等待连接到WebSocket服务端\n所有参数可选,超时以毫秒为单位,\n\n连接成功返回true,失败返回false或null url = 上次成功连接的网址\n也可以用于指定下次连接的默认网址 close() = 关闭连接\n可选增加2个参数指定发送给服务器的关闭帧附加数据:\n参数@1为数值类型的错误代码,参数@2为字符串类型错误描述 headers = 其他HTTP请求头\n值可以是文本或数组、或键值对组成的表\n请求时会调用 web.joinHeaders()函数拼接并转换HTTP头\n该函数支持的类型和格式这个属性都可以支持 responseHeaders = 服务端响应的HTTP头\n这是一个表对象,键名都已转为小写 readyState = 连接状态,\n0为等待连接,1为已连接并准备就绪,2为正在关闭,3为已关闭\n只有成功通过WebSocket协议握手以后readyState才会被置为1\n这与socket.readyState连接成功就会置为1是不同的 isClosed() = 套接字是否已关闭 isConnected() = 套接字是否已连接并准备就绪(已与服务器握手成功) heartbeatInterval = 客户端主动发送心跳间隔,默认为30秒,\n设为-1时禁用客户端心跳,注意某些服务端收到心跳包会报错,这时建议关掉心跳,\n此属性值修改以后,只能在下次调用connect函数才会生效 heartbeatData = 单向心跳发送的数据,默认为空数据\n这个值修改以后,只能在下次调用connect函数才会生效 heartbeatType = 单向心跳发送的的帧类型,\n默认为0xA,也就是单向心跳 Pong 帧\n这个值修改以后,只能在下次调用connect函数才会生效 originUrl = 浏览器启动WebSocket客户端的网址\n一些WebSocket服务器根据这个判断是不是允许连接,\n所以有时候设置这个很重要\n默认使用WebSocket网址,并把 前面的ws://改为http:// userAgent = 客户端应用程序代理头\n默认为"Mozilla/5.0" protocol = 应用程序支持的协议列表,默认为"chat" end intellisense**/