(转)clientX 异步客户端,支持SOCKS5
By
admin
at 2018-12-06 • 0人收藏 • 1245人看过
整理自: aar群 感谢 ҉k҉o҉m҉(znkee)提供
以下代码未经测试!
//clientX 异步客户端,支持SOCKS5 import crypt; import crypt.bin; import inet.url; import web; import wsock.tcp.asynClient; namespace web.socket; class clientX{ ctor(){ this.readyState = 0; this.secKey = getSecKey(); this.heartbeatInterval = 30; this.heartbeatData = ""; this.heartbeatType = 0xA; this.userAgent = "Mozilla/5.0"; ..table.gc(this,"close"); }; close = function(code, reason){ if(this.socket){ if( ( this.readyState !=3 ) && ( this.readyState !=2 ) ){ if( code && reason ){ this.sendData({ WORD code = ..raw.swap(code,"WORD"); BYTE reason[] = reason; },8); } else{ this.sendData("",8); }; this.readyState = 2; } this.socket.shutdown(); } }; _upgradeRequest = function(){ var host = this.uri.host; if( this.uri.port!=80 ) host = host +":"+ this.uri.port; if(!this.originUrl ){ this.originUrl = "http://" + host +"/"; } var headers = ..web.joinHeaders({ ["Connection"] = "Upgrade"; ["Host"] = host; ["User-Agent"] = this.userAgent; ["Pragma"] = "no-cache"; ["Accept"] = "*/*;"; ["Upgrade"] = "websocket"; ["Connection"] = "Upgrade"; ["Origin"] = this.originUrl; ["Sec-WebSocket-Version"] = "13"; ["Sec-WebSocket-Protocol"] = this.protocol : "chat"; ["Sec-WebSocket-Key"] = this.secKey; ["Sec-WebSocket-Origin"] = this.url; ["Cache-Control"] = "no-cache"; ["Accept-Language"] = "zh-CN,zh;q=0.9"; ["Sec-WebSocket-Extensions"] = "permessage-deflate; client_max_window_bits"; },this.headers) var reqPath = this.url : "/"; if( this.uri.extraInfo ) reqPath = reqPath + this.uri.extraInfo; var ret = this.socket.write('GET '+ reqPath +' HTTP/1.1\r\n'+ headers +'\r\n') if(!ret){ if( this.onError ) this.onError( 'Handshake failure'); this.close(); return false; } return true; } connect = function(url,proxy){ if(proxy){ var tmp=..string.split(proxy,':') if(#tmp==2){ this.proxy = { host = tmp[1]; port = tonumber(tmp[2]) }; this.connectStep=1; } } if(!url) url = this.url; if(!url) error("请指定连接网址,例如 ws://localhost:7511"); if( this.isConnected() ) { this.close(); ..win.delay(100); }; this.readyState = 0; this.socket = ..wsock.tcp.asynClient(); this.socket.bufferSize = 1024*10; this.webSocketCloseCode = null; this.webSocketCloseReason = null; this.uri = ..inet.url.split(url); this.port = this.port:80; if( ..string.cmp(this.uri.scheme,"ws") !=0 ){ error("错误的URL协议:" + this.uri.scheme,2); } this.url = url; this.socket.onConnect = function(err){ if(err){ if( this.onError ) this.onError( ..wsock.err.lasterr(err) ); this.close(); return; }; var ret; if(this.proxy){ var tab = { _struct_aligned = 1; BYTE ver=5; BYTE num=1; BYTE method=0; } var ret = this.socket.write(tab) if(!ret){ if( this.onError ) this.onError( 'SOCKS5 认证失败 1'); this.close(); return; } }else { if(!this._upgradeRequest()){ return ; } } } if(this.proxy){ this.socket.onRead = this._onSOCKS5Data; }else { this.socket.onRead = this._onReadHttpHeader; } this.socket.onClose = function(err){ if( this.socket == owner ){ this.readyState = 2; if( this.onClose ) { if(err){ this.onClose( code = 1006; reason = ..wsock.err.lasterr(err); ); } else { this.onClose( code = this.webSocketCloseCode : 1000; reason = this.webSocketCloseReason ); } } if(( this.socket == owner )&&(this.readyState == 2)){ this.webSocketCloseCode = null; this.webSocketCloseReason = null; this.readyState = 3; this.socket = null; } } /* 这里不能用this.socket.close(), 因为这个套接字关闭事件被触发时,可能已经创建新的this.socket了 */ owner.close(); } if(this.proxy){ return this.socket.connect(this.proxy.host, this.proxy.port); }else { return this.socket.connect(this.uri.host, this.uri.port:80); } }; isClosed = function(){ if( this.readyState == 3 ) return true; if( !this.hSocket ) return true; }; isConnected = function(){ return ( this.readyState == 1 ) && ( owner.hSocket ); }; waitForConnected = function(hwnd,timeout){ ..win.delay(1000); return ..win.wait( function(){ if( this.readyState > 1 ) return false; if( this.readyState == 1 ) return true; },hwnd,timeout,1000 ); }; _beginTranslateMessage = function(msg){ if( this.onMessage ) { this.onMessage(msg); }; if( this._translateMessage ) { this._translateMessage(msg); }; }; _onReadMessage = function(err){ if(err){ if( this.onError ) this.onError( ..wsock.err.lasterr(err) ); this.close(); return; }; var msg = this._beginRecvMessage(); if(!msg){ return; }; //control frames if( msg.type & 2#1000 ){ if( msg.type == 8 ){//close if(#msg.data>=2){ this.webSocketCloseCode = ..raw.swap( ..raw.convert(msg.data,{WORD code}).code ); this.webSocketCloseReason = ..raw.tostring(msg.data,3); } else { this.webSocketCloseCode = 1005; this.webSocketCloseReason = null; } this.close(); } elseif( msg.type == 9){//ping this.sendData(msg.data,10); } elseif( msg.type == 10){//pong } return; } if( msg.fin ){ if( msg.type ){ if( msg.type == 1 ) msg.data = ..raw.tostring(msg.data); this._beginTranslateMessage(msg); } else { if( this.onFragment ){ this.onFragment(msg); } else { ..table.push(this.cacheFragment.data,msg.data); this.cacheFragment.length = this.cacheFragment.length + #(msg.data); if( this.cacheFragment.type == 1 ) this.cacheFragment.data = ..string.join(this.cacheFragment.data); else { var len = 0; var buffer = ..raw.buffer(this.cacheFragment.length); var data = this.cacheFragment.data; for(i=1;#data;1){ ..raw.concat(buffer,data[i],len) len = len + #data[i]; } this.cacheFragment.data = buffer; } this._beginTranslateMessage(this.cacheFragment); } } } else { if( msg.type ){ if( this.onFragment ){ this.onFragment(msg); } else { this.cacheFragment = msg; this.cacheFragment.data = {msg.data} this.cacheFragment.length = #(msg.data); } } else { if( this.onFragment ){ this.onFragment(msg); } else { ..table.push(this.cacheFragment.data,msg.data); this.cacheFragment.length = this.cacheFragment.length + #(msg.data); } } } }; _onSOCKS5Data = function(err){ if(err){ if( this.onError ) this.onError( ..wsock.err.lasterr(err) ); this.close(); return; }; select(this.connectStep) { case 1 { this.connectStep++ var tab = this.socket.read({ _struct_aligned = 1; BYTE ver; BYTE method; }) //清空缓存 this.socket.readAll() if(tab.ver==5 and tab.method==0){ //如果是域名 var host = this.uri.host var port = this.uri.port if(..string.match(host,"\a")){ tab = { _struct_aligned = 1; BYTE ver=5; BYTE cmd=1; BYTE resverd=0; BYTE type=3; BYTE len=#host; BYTE domain[]=host; WORD port=..raw.swap(port,"WORD"); } }else { //ip tab = { _struct_aligned = 1; BYTE ver=5; BYTE cmd=1; BYTE resverd=0; BYTE type=1; INT ip=..raw.swap(..wsock.htonl(..wsock.inet_addr(host))); WORD port=..raw.swap(port,"WORD"); } } var ret = this.socket.write(tab) if(!ret){ if( this.onError ) this.onError( 'SOCKS5 认证失败 2'); this.close(); } } } case 2 { var tab = this.socket.read({ _struct_aligned = 1; BYTE ver; BYTE code; }) //清空缓存 this.socket.readAll() if(tab.code==0){ //重新绑定onRead this.socket.onRead = this._onReadHttpHeader; //发送协议升级 this._upgradeRequest() }else { if( this.onError ) this.onError( 'SOCKS5 认证失败 3'); } } } } _onReadHttpHeader = function(err){ if(err){ if( this.onError ) this.onError( ..wsock.err.lasterr(err) ); this.close(); return; }; var responseHead = this.socket.readTo('\r\n\r\n');//HTTP头以两个回车换行结束 if(!responseHead){ return; }; var httpHeaders = ..string.split(responseHead,'<\r\n>'); var statusLine = httpHeaders[1] ? ..string.splitEx(httpHeaders[1],"\s+",3); if( ..string.startWith(statusLine[1],"HTTP/",true) ){ this.status = statusLine[2]; if( this.status == "101" ){ ..table.shift(httpHeaders,1); this.responseHeaders = {}; for(i=#httpHeaders;1;-1){ var h = ..string.splitEx(httpHeaders[i],"\:\s*",2); h[1] = ..string.trim(h[1]); h[2] = ..string.trim(h[2]); this.responseHeaders[ ..string.lower(h[1])] = h[2]; } if( this.responseHeaders["sec-websocket-accept"] != getSecAccept(this.secKey) ){ if( this.onError ) this.onError( 'Incorrect "Sec-WebSocket-Accept" header value'); this.close(); return; } this.readyState = 1;//连接成功 if( this.onOpen ) this.onOpen(); this.socket.onRead = this._onReadMessage; if( this.heartbeatInterval > 0 ){ var d,t = this.heartbeatData,this.heartbeatType; this.socket.heartbeatTimerId = this.socket._form.addtimer( this.heartbeatInterval * 1000,function(hwnd,msg,id,tick){ if(!this.sendData(d,t)){//单向心跳 if( this.onError ) this.onError( "Heartbeat failed" ); this.socket.shutdown(); } } ) } } else { if( this.onError ) this.onError( 'Unexpected response code:' + this.status); this.close();; } } else { if( this.onError ) this.onError( 'Invalid status line'); this.close();; } } send = function(data){ var t = type(data); if( t = type.string ) return this.sendData(data,1); if( t = type.buffer ) return this.sendData(data,2); if( t[["_struct"]] ) return this.sendData(..raw.buffer(data),2); } sendData = function(data,opcode = 1,fin=1,mask=1,rsv1 = 0,rsv2 = 0,rsv3 = 0){ if(this.readyState != 1) { if( this.onError ) this.onError( 'Failed to execute "send/sendData"'); return; }; if( data[["_struct"]] ) data = ..raw.buffer(data); var len = #data; var buf = ..raw.buffer( len + 14); buf[1] = (fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | opcode; var w = 2; if (len <= 125) { buf[2] = (mask << 7) | len; } elseif(len < (1 << 16) ) { buf[2] = (mask << 7) | 126; ..raw.convert({WORD v = ..raw.swap(len,"WORD"); },buf,2); w = 4; } else { buf[2] = (mask << 7) | 127; ..raw.convert({LONG v=..math.size64(len).swap(); },buf,2); w = 10; } if(mask){ var k = ..string.random(4); ..raw.concat(buf,k,w); w = w + 4; for(i=1;len;1) buf[w+i] = data[i] ^ k[ i% 4 : 4 ]; } else { ..raw.concat(buf,data,w); } if(!this.socket.writeBuffer(buf,w + len)){ if( this.onError ) this.onError( ..wsock.err.lasterr() ); this.socket.shutdown(); return false; } return true; }; _endRecvMessage4 = function(){ var len = this.recvMessage.dataSize; if(len){ var buf = ..raw.buffer(len); if( ! this.socket.readBuffer(buf) ){ return; } var maskKey = this.recvMessage.maskKey; if( maskKey ){ for(i=1;len;1) buf[i] = buf[i] ^ maskKey[ i% 4 : 4 ]; } this.recvMessage.data = buf; } else { this.recvMessage.data = ""; } var msg = this.recvMessage; this.recvMessage = null; return msg; }; _recvMessageMaskKey3 = function(){ var msg = this.recvMessage; //上次的消息还没有接收完整,继续尝试 if( msg.maskKey ) return this._endRecvMessage4(); if( msg.mask ){ msg.maskKey = this.socket.read(4); if(!msg.maskKey) return; } return this._endRecvMessage4(); } _recvMessageDataSize2 = function(){ var msg = this.recvMessage; //上次的消息还没有接收完整,继续尝试 if( msg.dataSize!==null ) return this._recvMessageMaskKey3(); var len = msg.len; if (len == 126) { len = this.socket.read({WORD v}); if(!len) return; msg.dataSize = ..raw.swap(len.v,"WORD"); } elseif(len == 127) { len = this.socket.read({LONG size64 = ..math.size64() }); if(!len) return; msg.dataSize = tonumber( len.size64.swap() ); } else { msg.dataSize = len; } return this._recvMessageMaskKey3(); } _beginRecvMessage = function(){ if(!this.socket) return; //上次的消息还没有接收完整,继续尝试 if( this.recvMessage ) return this._recvMessageDataSize2(); var msg = {}; var h = this.socket.read(2); if(!h) { return; //异止套接字读取数据不完整会自动退回缓冲区 } var h1,h2 = h[1],h[2]; this.recvMessage = { type = h1 & 2#1111; fin = (h1 >> 7) & 1; rsv1 = (h1 >> 6) & 1; rsv2 = (h1 >> 5) & 1; rsv3 = (h1 >> 4) & 1; mask = (h2 >> 7) & 1; len = h2 & 2#1111111; } return this._recvMessageDataSize2(); } } namespace clientX{ import console sha1 = function(data){ var cr = ..crypt(); var hash = cr.createHash( 0x8004/*_CALG_SHA1*/,data ); var data = hash.getValue(); hash.destroy(); cr.release(); return ..crypt.bin.encodeBase64(data); } getSecAccept = function(data){ return sha1(..string.trim(data)+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); } getSecKey = function(){ return ..crypt.bin.encodeBase64(..string.random(16) ); } } /**intellisense() web.socket.clientX = 支持单线程异步的WebSocket客户端\n可直接在界面线程中使用,支持SOCKS5代理,不会阻塞界面,不需要创建多线程\n支持服务端心跳(Ping/Pong帧),客户端单向心跳(Pong帧)机制,\n可调用close函数断线,并可调用connect函数实现重新连接服务器 web.socket.clientX() = !stdwebsocketclientX. web.socket.clientX.sha1(__) = 使用sha1算法取哈希值,并使用Base64编码为普通文本 web.socket.clientX.getSecKey() = 获取WebSocket客户端密钥 web.socket.clientX.getSecAccept(__) = 获取WebSocket客户端配对密钥,\n参数指定服务端HTTP头中sec-websocket-accept返回的值 end intellisense**/ /**intellisense(!stdwebsocketclientX) socket = 异步套接字对象\n在关闭连接状态下此属性的值为null\n应由对象自动打开或删除套接字对象,调用者不可改动此属性的值\n!stdtcpaclient. connect("ws://__","ip:port") = 重新连接到WebSocket服务端\n参数指定WebSocket服务端网址,例如 "ws://localhost:7511"\n如果不指定参数,则获取上次调用此函数指定的网址参数,\n第二个参数是SOCKS5代理\n如果之前也没有指定网址则抛出异常 waitForConnected(.(关联窗口句柄,超时) = 等待连接到WebSocket服务端\n所有参数可选,超时以毫秒为单位,\n\n连接成功返回true,失败返回false或null url = 上次成功连接的网址\n也可以用于指定下次连接的默认网址 close() = 关闭连接\n可选增加2个参数指定发送给服务器的关闭帧附加数据:\n参数@1为数值类型的错误代码,参数@2为字符串类型错误描述 send(__) = 发送数据,支持字符串或缓冲区(buffer)、结构体\n字符串作为UTF8文本类型发送,其他以二进制类型发送,\n成功返回true sendData(.(data,opcode,fin,mask,rsv1,rsv2,rsv3) = 发送WebSocket数据包\n参数@1支持支持字符串或缓冲区(buffer)、结构体\n除参数@1以外,所以参数可选\n一般应当调用send函数,而不是调用sendData函数\n\n如果一定要使用这个函数,请阅读此函数源码,以及WebSocket协议相关说明 onOpen = @.onOpen = function(){ ??.send("已连接到WebSocket服务"); } onClose = @.onClose = function(e){ __/*连接被关闭\ne.code为错误代码e.reason为错误原因*/ } onError = @.onError = function(err){ __/*发生错误,err为错误信息*/ } onMessage = @.onMessage = function(msg){ __/*收到服务端数据\nmsg.type为1时msg.data为文本,\n否则msg.data为字节数组(buffer类型)*/ } onFragment = @.onFragment = function(msg){ __/*收到分片数据\n第一个数据包使用msg.type指明类型,参考WebSocket协议规范\n后续数据包msg.type为0,最后一个数据包msg.fin为1\n\n如果不指定这个回调函数,则自动并接分片数据后触发onMessage事件*/ } _translateMessage = 此回调函数的参数与onMessage相同,\n如果定义了这个回调函数,\n那么此函数将在调用onMessage以后被调用,\n这个函数提供了一个机会用于自动处理服务器消息\n,为其他需要扩展web.socket.client功能的库所预留,\n一旦定义将不能修改 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() = 套接字是否已连接并准备就绪(已与服务器握手成功) secKey = 连接密钥,不可改动 heartbeatInterval = 客户端主动发送单向心跳(默认发送Pong空帧)间隔,默认为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**/
登录后方可回帖